Compare commits

...

10 Commits

46 changed files with 1923 additions and 298 deletions

View File

@ -66,14 +66,14 @@ $(KERNEL_ELF): $(KERNEL_OBJS) $(ARCH_DIR)/kernel/linker.ld | dirs
$(OVMF_VARS): | dirs $(OVMF_VARS): | dirs
cp $(OVMF_VARS_TEMPLATE) $@ cp $(OVMF_VARS_TEMPLATE) $@
run: $(BOOT_EFI) $(OVMF_VARS) run: $(BOOT_EFI) $(KERNEL_ELF) $(OVMF_VARS)
qemu-system-x86_64 \ qemu-system-x86_64 \
-drive if=pflash,format=raw,readonly=on,file=$(OVMF_CODE) \ -drive if=pflash,format=raw,readonly=on,file=$(OVMF_CODE) \
-drive if=pflash,format=raw,file=$(OVMF_VARS) \ -drive if=pflash,format=raw,file=$(OVMF_VARS) \
-drive format=raw,file=fat:rw:$(BUILD_DIR)/image \ -drive format=raw,file=fat:rw:$(BUILD_DIR)/image \
-serial stdio -serial stdio
run-headless: $(BOOT_EFI) $(OVMF_VARS) run-headless: $(BOOT_EFI) $(KERNEL_ELF) $(OVMF_VARS)
rm -f $(DEBUG_LOG) $(SERIAL_LOG) rm -f $(DEBUG_LOG) $(SERIAL_LOG)
qemu-system-x86_64 \ qemu-system-x86_64 \
-display none \ -display none \

View File

@ -2,6 +2,7 @@ BOOT_SRCS := \
$(ARCH_DIR)/boot/main.c \ $(ARCH_DIR)/boot/main.c \
$(ARCH_DIR)/boot/file.c \ $(ARCH_DIR)/boot/file.c \
$(ARCH_DIR)/boot/elf_loader.c \ $(ARCH_DIR)/boot/elf_loader.c \
$(ARCH_DIR)/boot/framebuffer.c \
$(ARCH_DIR)/boot/memory_map.c \ $(ARCH_DIR)/boot/memory_map.c \
$(ARCH_DIR)/boot/support.c $(ARCH_DIR)/boot/support.c

View File

@ -0,0 +1,35 @@
#include <tianole/boot_info.h>
#include "efi.h"
#include "framebuffer.h"
efi_status boot_capture_framebuffer(
efi_system_table_t *system_table, boot_info_t *boot_info)
{
efi_graphics_output_protocol_t *gop;
efi_graphics_output_mode_information_t *info;
efi_guid_t gop_guid = efi_graphics_output_protocol_guid();
efi_status status;
if (system_table == 0 || boot_info == 0) {
return EFI_INVALID_PARAMETER;
}
status = system_table->boot_services->locate_protocol(
&gop_guid, 0, (void **)&gop);
if (status != EFI_SUCCESS || gop == 0 || gop->mode == 0 ||
gop->mode->info == 0) {
return status;
}
info = gop->mode->info;
boot_info->framebuffer_base = gop->mode->frame_buffer_base;
boot_info->framebuffer_size = gop->mode->frame_buffer_size;
boot_info->framebuffer_width = info->horizontal_resolution;
boot_info->framebuffer_height = info->vertical_resolution;
boot_info->framebuffer_pixels_per_scan_line =
info->pixels_per_scan_line;
boot_info->framebuffer_pixel_format = info->pixel_format;
return EFI_SUCCESS;
}

View File

@ -0,0 +1,19 @@
#ifndef X86_BOOT_FRAMEBUFFER_H
#define X86_BOOT_FRAMEBUFFER_H
#include <tianole/boot_info.h>
#include "efi.h"
/**
* boot_capture_framebuffer() - Copy GOP mode data into boot_info.
* @system_table: UEFI system table used to locate GOP.
* @boot_info: Kernel handoff structure updated on success.
*
* Failure is non-fatal for boot. The kernel keeps serial/debug-port logging if
* the firmware exposes no supported graphics output protocol.
*/
efi_status boot_capture_framebuffer(
efi_system_table_t *system_table, boot_info_t *boot_info);
#endif

View File

@ -4,6 +4,7 @@
#include "efi.h" #include "efi.h"
#include "elf_loader.h" #include "elf_loader.h"
#include "file.h" #include "file.h"
#include "framebuffer.h"
#include "memory_map.h" #include "memory_map.h"
static efi_char16_t boot_banner_text[] = u"Tianole x86 bootloader.\r\n"; static efi_char16_t boot_banner_text[] = u"Tianole x86 bootloader.\r\n";
@ -24,6 +25,7 @@ efi_status EFIAPI efi_main(
system_table->con_out->output_string( system_table->con_out->output_string(
system_table->con_out, boot_banner_text); system_table->con_out, boot_banner_text);
boot_debug_log_puts("Tianole x86 bootloader loaded.\n"); boot_debug_log_puts("Tianole x86 bootloader loaded.\n");
(void)boot_capture_framebuffer(system_table, &boot_info);
status = boot_read_file(image_handle, status = boot_read_file(image_handle,
system_table, system_table,

View File

@ -29,6 +29,9 @@ typedef uint64_t efi_physical_address_t;
#define EFI_LOADED_IMAGE_PROTOCOL_GUID_A 0x5b1b31a1 #define EFI_LOADED_IMAGE_PROTOCOL_GUID_A 0x5b1b31a1
#define EFI_LOADED_IMAGE_PROTOCOL_GUID_B 0x9562 #define EFI_LOADED_IMAGE_PROTOCOL_GUID_B 0x9562
#define EFI_LOADED_IMAGE_PROTOCOL_GUID_C 0x11d2 #define EFI_LOADED_IMAGE_PROTOCOL_GUID_C 0x11d2
#define EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID_A 0x9042a9de
#define EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID_B 0x23dc
#define EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID_C 0x4a38
#if defined(__x86_64__) #if defined(__x86_64__)
#define EFIAPI __attribute__((ms_abi)) #define EFIAPI __attribute__((ms_abi))
@ -58,6 +61,7 @@ typedef struct efi_simple_file_system_protocol
efi_simple_file_system_protocol_t; efi_simple_file_system_protocol_t;
typedef struct efi_file_protocol efi_file_protocol_t; typedef struct efi_file_protocol efi_file_protocol_t;
typedef struct efi_loaded_image_protocol efi_loaded_image_protocol_t; typedef struct efi_loaded_image_protocol efi_loaded_image_protocol_t;
typedef struct efi_graphics_output_protocol efi_graphics_output_protocol_t;
typedef struct efi_system_table efi_system_table_t; typedef struct efi_system_table efi_system_table_t;
struct efi_simple_text_output_protocol { struct efi_simple_text_output_protocol {
@ -122,6 +126,31 @@ struct efi_loaded_image_protocol {
void *unload; void *unload;
}; };
typedef struct {
uint32_t version;
uint32_t horizontal_resolution;
uint32_t vertical_resolution;
uint32_t pixel_format;
uint32_t pixel_information[4];
uint32_t pixels_per_scan_line;
} efi_graphics_output_mode_information_t;
typedef struct {
uint32_t max_mode;
uint32_t mode;
efi_graphics_output_mode_information_t *info;
efi_uintn_t size_of_info;
efi_physical_address_t frame_buffer_base;
efi_uintn_t frame_buffer_size;
} efi_graphics_output_protocol_mode_t;
struct efi_graphics_output_protocol {
void *query_mode;
void *set_mode;
void *blt;
efi_graphics_output_protocol_mode_t *mode;
};
struct efi_boot_services { struct efi_boot_services {
efi_table_header_t hdr; efi_table_header_t hdr;
void *raise_tpl; void *raise_tpl;
@ -164,6 +193,15 @@ struct efi_boot_services {
void *get_next_monotonic_count; void *get_next_monotonic_count;
void(EFIAPI *stall)(efi_uintn_t microseconds); void(EFIAPI *stall)(efi_uintn_t microseconds);
void *set_watchdog_timer; void *set_watchdog_timer;
void *connect_controller;
void *disconnect_controller;
void *open_protocol;
void *close_protocol;
void *open_protocol_information;
void *protocols_per_handle;
void *locate_handle_buffer;
efi_status(EFIAPI *locate_protocol)(
efi_guid_t *protocol, void *registration, void **interface);
}; };
struct efi_system_table { struct efi_system_table {
@ -212,4 +250,14 @@ static inline efi_guid_t efi_loaded_image_protocol_guid(void)
}; };
} }
static inline efi_guid_t efi_graphics_output_protocol_guid(void)
{
return (efi_guid_t){
.data1 = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID_A,
.data2 = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID_B,
.data3 = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID_C,
.data4 = {0x96, 0xfb, 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a},
};
}
#endif #endif

View File

@ -3,6 +3,8 @@ ARCH_KERNEL_SRCS := \
$(ARCH_DIR)/kernel/gdt.c \ $(ARCH_DIR)/kernel/gdt.c \
$(ARCH_DIR)/kernel/idt.c \ $(ARCH_DIR)/kernel/idt.c \
$(ARCH_DIR)/kernel/irq.c \ $(ARCH_DIR)/kernel/irq.c \
$(ARCH_DIR)/kernel/screen.c \
$(ARCH_DIR)/kernel/screen_font.c \
$(ARCH_DIR)/kernel/traps.c $(ARCH_DIR)/kernel/traps.c
ARCH_KERNEL_OBJS := \ ARCH_KERNEL_OBJS := \

View File

@ -4,6 +4,8 @@
#include <tianole/arch.h> #include <tianole/arch.h>
#include "screen.h"
#define X86_QEMU_DEBUG_PORT 0xe9 #define X86_QEMU_DEBUG_PORT 0xe9
#define X86_COM1_BASE 0x3f8 #define X86_COM1_BASE 0x3f8
@ -37,7 +39,7 @@ static void serial_putc(char ch)
outb(X86_COM1_BASE + X86_COM_DATA, (uint8_t)ch); outb(X86_COM1_BASE + X86_COM_DATA, (uint8_t)ch);
} }
void arch_early_log_init(void) void arch_early_log_init(const boot_info_t *boot_info)
{ {
outb(X86_COM1_BASE + X86_COM_INTERRUPT_ENABLE, 0x00); outb(X86_COM1_BASE + X86_COM_INTERRUPT_ENABLE, 0x00);
outb(X86_COM1_BASE + X86_COM_LINE_CONTROL, X86_COM_LCR_DLAB); outb(X86_COM1_BASE + X86_COM_LINE_CONTROL, X86_COM_LCR_DLAB);
@ -46,12 +48,14 @@ void arch_early_log_init(void)
outb(X86_COM1_BASE + X86_COM_LINE_CONTROL, X86_COM_LCR_8N1); outb(X86_COM1_BASE + X86_COM_LINE_CONTROL, X86_COM_LCR_8N1);
outb(X86_COM1_BASE + X86_COM_FIFO_CONTROL, 0xc7); outb(X86_COM1_BASE + X86_COM_FIFO_CONTROL, 0xc7);
outb(X86_COM1_BASE + X86_COM_MODEM_CONTROL, 0x0b); outb(X86_COM1_BASE + X86_COM_MODEM_CONTROL, 0x0b);
screen_console_init(boot_info);
} }
void arch_early_log_putc(char ch) void arch_early_log_putc(char ch)
{ {
debug_port_putc(ch); debug_port_putc(ch);
serial_putc(ch); serial_putc(ch);
screen_console_putc(ch);
} }
void arch_halt_forever(void) void arch_halt_forever(void)

View File

@ -5,6 +5,7 @@
#include <tianole/arch.h> #include <tianole/arch.h>
#include <tianole/early_log.h> #include <tianole/early_log.h>
#include <tianole/errno.h>
#include <tianole/irq.h> #include <tianole/irq.h>
#include <tianole/timer.h> #include <tianole/timer.h>
@ -102,14 +103,14 @@ int irq_register(uint8_t irq, irq_handler_t handler, void *data)
uint64_t flags; uint64_t flags;
if (irq >= IRQ_COUNT || handler == 0) { if (irq >= IRQ_COUNT || handler == 0) {
return -1; return -EINVAL;
} }
flags = arch_irq_save(); flags = arch_irq_save();
if (irq_actions[irq].handler != 0) { if (irq_actions[irq].handler != 0) {
arch_irq_restore(flags); arch_irq_restore(flags);
return -1; return -EBUSY;
} }
irq_actions[irq].handler = handler; irq_actions[irq].handler = handler;

210
arch/x86/kernel/screen.c Normal file
View File

@ -0,0 +1,210 @@
#include <stdint.h>
#include <tianole/boot_info.h>
#include "screen.h"
#include "screen_font.h"
#define GLYPH_SCALE 1u
#define CELL_WIDTH 6u
#define CELL_HEIGHT 9u
#define PIXEL_FORMAT_RGB 0u
#define PIXEL_FORMAT_BGR 1u
#define ASCII_CR 13
#define ASCII_LF 10
static volatile uint32_t *framebuffer;
static uint32_t screen_width;
static uint32_t screen_height;
static uint32_t pixels_per_scan_line;
static uint32_t pixel_format;
static uint32_t cursor_x;
static uint32_t cursor_y;
static uint32_t columns;
static uint32_t rows;
static uint32_t pixel_color(uint8_t red, uint8_t green, uint8_t blue)
{
if (pixel_format == PIXEL_FORMAT_RGB) {
return red | ((uint32_t)green << 8) | ((uint32_t)blue << 16);
}
return blue | ((uint32_t)green << 8) | ((uint32_t)red << 16);
}
static void draw_pixel(uint32_t x, uint32_t y, uint32_t color)
{
if (x >= screen_width || y >= screen_height) {
return;
}
framebuffer[y * pixels_per_scan_line + x] = color;
}
static void clear_cell(uint32_t column, uint32_t row)
{
uint32_t x;
uint32_t y;
uint32_t start_x = column * CELL_WIDTH;
uint32_t start_y = row * CELL_HEIGHT;
uint32_t background = pixel_color(0, 0, 0);
for (y = 0; y < CELL_HEIGHT; y++) {
for (x = 0; x < CELL_WIDTH; x++) {
draw_pixel(start_x + x, start_y + y, background);
}
}
}
static void draw_char(char ch, uint32_t column, uint32_t row)
{
const uint8_t *glyph = screen_font_glyph(ch);
uint32_t foreground = pixel_color(220, 220, 220);
uint32_t glyph_row;
uint32_t glyph_col;
uint32_t scale_x;
uint32_t scale_y;
uint32_t start_x = column * CELL_WIDTH;
uint32_t start_y = row * CELL_HEIGHT + 1;
clear_cell(column, row);
for (glyph_row = 0; glyph_row < SCREEN_FONT_HEIGHT; glyph_row++) {
for (glyph_col = 0; glyph_col < SCREEN_FONT_WIDTH;
glyph_col++) {
if ((glyph[glyph_row] &
(1u << (SCREEN_FONT_WIDTH - 1 -
glyph_col))) == 0) {
continue;
}
for (scale_y = 0; scale_y < GLYPH_SCALE; scale_y++) {
for (scale_x = 0; scale_x < GLYPH_SCALE;
scale_x++) {
draw_pixel(start_x +
glyph_col *
GLYPH_SCALE +
scale_x,
start_y +
glyph_row *
GLYPH_SCALE +
scale_y,
foreground);
}
}
}
}
}
static void clear_screen(void)
{
uint32_t x;
uint32_t y;
uint32_t background = pixel_color(0, 0, 0);
for (y = 0; y < screen_height; y++) {
for (x = 0; x < screen_width; x++) {
draw_pixel(x, y, background);
}
}
}
static void clear_text_row(uint32_t row)
{
uint32_t column;
for (column = 0; column < columns; column++) {
clear_cell(column, row);
}
}
static void scroll_up(void)
{
uint32_t x;
uint32_t y;
for (y = CELL_HEIGHT; y < rows * CELL_HEIGHT; y++) {
for (x = 0; x < columns * CELL_WIDTH; x++) {
framebuffer[(y - CELL_HEIGHT) * pixels_per_scan_line +
x] = framebuffer[y * pixels_per_scan_line + x];
}
}
clear_text_row(rows - 1);
}
static void newline(void)
{
cursor_x = 0;
if (cursor_y + 1 < rows) {
cursor_y++;
return;
}
scroll_up();
cursor_y = rows - 1;
}
/**
* screen_console_init() - Attach early logging to the GOP framebuffer.
* @boot_info: Boot handoff data captured before ExitBootServices().
*
* The console is intentionally minimal. It validates that the bootloader found
* a linear 32-bit RGB/BGR framebuffer, records the current mode, and clears the
* visible surface so subsequent early_log output is readable in the QEMU
* window.
*/
void screen_console_init(const boot_info_t *boot_info)
{
if (boot_info == 0 || boot_info->framebuffer_base == 0 ||
boot_info->framebuffer_width == 0 ||
boot_info->framebuffer_height == 0 ||
boot_info->framebuffer_pixels_per_scan_line == 0 ||
boot_info->framebuffer_pixel_format > PIXEL_FORMAT_BGR) {
return;
}
framebuffer =
(volatile uint32_t *)(uintptr_t)boot_info->framebuffer_base;
screen_width = boot_info->framebuffer_width;
screen_height = boot_info->framebuffer_height;
pixels_per_scan_line = boot_info->framebuffer_pixels_per_scan_line;
pixel_format = boot_info->framebuffer_pixel_format;
columns = screen_width / CELL_WIDTH;
rows = screen_height / CELL_HEIGHT;
cursor_x = 0;
cursor_y = 0;
clear_screen();
}
/**
* screen_console_putc() - Mirror a log byte to the early framebuffer console.
* @ch: Byte emitted by early_log.
*
* Characters are drawn into fixed-size cells. Newlines advance the cursor; when
* the last row is exhausted the framebuffer text area scrolls up one row so
* real boot smoke tests keep useful context on screen.
*/
void screen_console_putc(char ch)
{
if (framebuffer == 0 || rows == 0 || columns == 0) {
return;
}
if (ch == ASCII_CR) {
return;
}
if (ch == ASCII_LF) {
newline();
return;
}
draw_char(ch, cursor_x, cursor_y);
cursor_x++;
if (cursor_x >= columns) {
newline();
}
}

18
arch/x86/kernel/screen.h Normal file
View File

@ -0,0 +1,18 @@
#ifndef X86_KERNEL_SCREEN_H
#define X86_KERNEL_SCREEN_H
#include <tianole/boot_info.h>
/**
* screen_console_init() - Bind early log output to a boot framebuffer.
* @boot_info: Boot handoff data with framebuffer mode information.
*/
void screen_console_init(const boot_info_t *boot_info);
/**
* screen_console_putc() - Draw one early log byte on the boot framebuffer.
* @ch: Character emitted by early_log.
*/
void screen_console_putc(char ch);
#endif

View File

@ -0,0 +1,81 @@
#include <stdint.h>
#include "screen_font.h"
struct glyph {
char ch;
uint8_t rows[SCREEN_FONT_HEIGHT];
};
static const struct glyph font[] = {
{' ', {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
{'!', {0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x04}},
{'.', {0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x0c}},
{',', {0x00, 0x00, 0x00, 0x00, 0x0c, 0x04, 0x08}},
{':', {0x00, 0x0c, 0x0c, 0x00, 0x0c, 0x0c, 0x00}},
{'-', {0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00}},
{'_', {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f}},
{'=', {0x00, 0x00, 0x1f, 0x00, 0x1f, 0x00, 0x00}},
{'/', {0x01, 0x02, 0x04, 0x04, 0x08, 0x10, 0x10}},
{'0', {0x0e, 0x11, 0x13, 0x15, 0x19, 0x11, 0x0e}},
{'1', {0x04, 0x0c, 0x04, 0x04, 0x04, 0x04, 0x0e}},
{'2', {0x0e, 0x11, 0x01, 0x02, 0x04, 0x08, 0x1f}},
{'3', {0x1e, 0x01, 0x01, 0x0e, 0x01, 0x01, 0x1e}},
{'4', {0x02, 0x06, 0x0a, 0x12, 0x1f, 0x02, 0x02}},
{'5', {0x1f, 0x10, 0x10, 0x1e, 0x01, 0x01, 0x1e}},
{'6', {0x0e, 0x10, 0x10, 0x1e, 0x11, 0x11, 0x0e}},
{'7', {0x1f, 0x01, 0x02, 0x04, 0x08, 0x08, 0x08}},
{'8', {0x0e, 0x11, 0x11, 0x0e, 0x11, 0x11, 0x0e}},
{'9', {0x0e, 0x11, 0x11, 0x0f, 0x01, 0x01, 0x0e}},
{'A', {0x0e, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11}},
{'B', {0x1e, 0x11, 0x11, 0x1e, 0x11, 0x11, 0x1e}},
{'C', {0x0e, 0x11, 0x10, 0x10, 0x10, 0x11, 0x0e}},
{'D', {0x1e, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1e}},
{'E', {0x1f, 0x10, 0x10, 0x1e, 0x10, 0x10, 0x1f}},
{'F', {0x1f, 0x10, 0x10, 0x1e, 0x10, 0x10, 0x10}},
{'G', {0x0e, 0x11, 0x10, 0x17, 0x11, 0x11, 0x0f}},
{'H', {0x11, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11}},
{'I', {0x0e, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0e}},
{'J', {0x07, 0x02, 0x02, 0x02, 0x12, 0x12, 0x0c}},
{'K', {0x11, 0x12, 0x14, 0x18, 0x14, 0x12, 0x11}},
{'L', {0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1f}},
{'M', {0x11, 0x1b, 0x15, 0x15, 0x11, 0x11, 0x11}},
{'N', {0x11, 0x19, 0x15, 0x13, 0x11, 0x11, 0x11}},
{'O', {0x0e, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e}},
{'P', {0x1e, 0x11, 0x11, 0x1e, 0x10, 0x10, 0x10}},
{'Q', {0x0e, 0x11, 0x11, 0x11, 0x15, 0x12, 0x0d}},
{'R', {0x1e, 0x11, 0x11, 0x1e, 0x14, 0x12, 0x11}},
{'S', {0x0f, 0x10, 0x10, 0x0e, 0x01, 0x01, 0x1e}},
{'T', {0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04}},
{'U', {0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e}},
{'V', {0x11, 0x11, 0x11, 0x11, 0x0a, 0x0a, 0x04}},
{'W', {0x11, 0x11, 0x11, 0x15, 0x15, 0x1b, 0x11}},
{'X', {0x11, 0x11, 0x0a, 0x04, 0x0a, 0x11, 0x11}},
{'Y', {0x11, 0x11, 0x0a, 0x04, 0x04, 0x04, 0x04}},
{'Z', {0x1f, 0x01, 0x02, 0x04, 0x08, 0x10, 0x1f}},
{'?', {0x0e, 0x11, 0x01, 0x02, 0x04, 0x00, 0x04}},
};
/**
* screen_font_glyph() - Return a tiny bitmap glyph for early console text.
* @ch: ASCII character requested by the early framebuffer console.
*
* The boot console keeps the font deliberately small: lowercase letters are
* folded to uppercase, and unsupported characters fall back to question mark.
*/
const uint8_t *screen_font_glyph(char ch)
{
uint32_t index;
if (ch >= 97 && ch <= 122) {
ch = (char)(ch - 97 + 65);
}
for (index = 0; index < sizeof(font) / sizeof(font[0]); index++) {
if (font[index].ch == ch) {
return font[index].rows;
}
}
return font[sizeof(font) / sizeof(font[0]) - 1].rows;
}

View File

@ -0,0 +1,11 @@
#ifndef X86_KERNEL_SCREEN_FONT_H
#define X86_KERNEL_SCREEN_FONT_H
#include <stdint.h>
#define SCREEN_FONT_WIDTH 5u
#define SCREEN_FONT_HEIGHT 7u
const uint8_t *screen_font_glyph(char ch);
#endif

View File

@ -2,6 +2,7 @@
#include <tianole/arch.h> #include <tianole/arch.h>
#include <tianole/early_log.h> #include <tianole/early_log.h>
#include <tianole/errno.h>
#include <tianole/mm.h> #include <tianole/mm.h>
#include "page_table.h" #include "page_table.h"
@ -172,7 +173,7 @@ static int ensure_next_table(uint64_t *table, uint64_t index, uint64_t **next)
page = alloc_page(); page = alloc_page();
if (page == 0) { if (page == 0) {
return -1; return -ENOMEM;
} }
clear_page(page); clear_page(page);
@ -193,24 +194,34 @@ static int page_entry(virt_addr_t virt, int create, uint64_t **entry)
uint64_t pt_index = table_index(virt, 12); uint64_t pt_index = table_index(virt, 12);
if (create != 0) { if (create != 0) {
if (ensure_next_table(pml4, pml4_index, &pdpt) != 0 || int ret = ensure_next_table(pml4, pml4_index, &pdpt);
ensure_next_table(pdpt, pdpt_index, &pd) != 0 ||
ensure_next_table(pd, pd_index, &pt) != 0) { if (ret != 0) {
return -1; return ret;
}
ret = ensure_next_table(pdpt, pdpt_index, &pd);
if (ret != 0) {
return ret;
}
ret = ensure_next_table(pd, pd_index, &pt);
if (ret != 0) {
return ret;
} }
} else { } else {
if ((pml4[pml4_index] & PAGE_PRESENT) == 0) { if ((pml4[pml4_index] & PAGE_PRESENT) == 0) {
return -1; return -ENOENT;
} }
pdpt = entry_table(pml4[pml4_index]); pdpt = entry_table(pml4[pml4_index]);
if ((pdpt[pdpt_index] & PAGE_PRESENT) == 0) { if ((pdpt[pdpt_index] & PAGE_PRESENT) == 0) {
return -1; return -ENOENT;
} }
pd = entry_table(pdpt[pdpt_index]); pd = entry_table(pdpt[pdpt_index]);
if ((pd[pd_index] & PAGE_PRESENT) == 0) { if ((pd[pd_index] & PAGE_PRESENT) == 0) {
return -1; return -ENOENT;
} }
pt = entry_table(pd[pd_index]); pt = entry_table(pd[pd_index]);
} }
@ -222,17 +233,19 @@ static int page_entry(virt_addr_t virt, int create, uint64_t **entry)
int map_page(virt_addr_t virt, phys_addr_t phys, uint64_t flags) int map_page(virt_addr_t virt, phys_addr_t phys, uint64_t flags)
{ {
uint64_t *entry; uint64_t *entry;
int ret;
if ((virt & (PAGE_SIZE - 1)) != 0 || (phys & (PAGE_SIZE - 1)) != 0) { if ((virt & (PAGE_SIZE - 1)) != 0 || (phys & (PAGE_SIZE - 1)) != 0) {
return -1; return -EINVAL;
} }
if (page_entry(virt, 1, &entry) != 0) { ret = page_entry(virt, 1, &entry);
return -1; if (ret != 0) {
return ret;
} }
if ((*entry & PAGE_PRESENT) != 0) { if ((*entry & PAGE_PRESENT) != 0) {
return -1; return -EEXIST;
} }
*entry = (phys & PAGE_MASK) | flags | PAGE_PRESENT; *entry = (phys & PAGE_MASK) | flags | PAGE_PRESENT;
@ -243,13 +256,15 @@ int map_page(virt_addr_t virt, phys_addr_t phys, uint64_t flags)
int unmap_page(virt_addr_t virt) int unmap_page(virt_addr_t virt)
{ {
uint64_t *entry; uint64_t *entry;
int ret;
if ((virt & (PAGE_SIZE - 1)) != 0) { if ((virt & (PAGE_SIZE - 1)) != 0) {
return -1; return -EINVAL;
} }
if (page_entry(virt, 0, &entry) != 0 || (*entry & PAGE_PRESENT) == 0) { ret = page_entry(virt, 0, &entry);
return -1; if (ret != 0 || (*entry & PAGE_PRESENT) == 0) {
return -ENOENT;
} }
*entry = 0; *entry = 0;
@ -260,10 +275,15 @@ int unmap_page(virt_addr_t virt)
int virt_to_phys(virt_addr_t virt, phys_addr_t *phys) int virt_to_phys(virt_addr_t virt, phys_addr_t *phys)
{ {
uint64_t *entry; uint64_t *entry;
int ret;
if (phys == 0 || page_entry(virt, 0, &entry) != 0 || if (phys == 0) {
(*entry & PAGE_PRESENT) == 0) { return -EINVAL;
return -1; }
ret = page_entry(virt, 0, &entry);
if (ret != 0 || (*entry & PAGE_PRESENT) == 0) {
return -ENOENT;
} }
*phys = (*entry & PAGE_MASK) | (virt & (PAGE_SIZE - 1)); *phys = (*entry & PAGE_MASK) | (virt & (PAGE_SIZE - 1));

View File

@ -44,6 +44,14 @@
- 不写重复描述简单代码行为的注释。 - 不写重复描述简单代码行为的注释。
- 如果代码依赖硬件手册、启动协议、调用顺序或特殊寄存器状态,应写明约束。 - 如果代码依赖硬件手册、启动协议、调用顺序或特殊寄存器状态,应写明约束。
- 如果一个实现是临时简化,应写清楚后续替换边界,而不是写成永久接口。 - 如果一个实现是临时简化,应写清楚后续替换边界,而不是写成永久接口。
- 公共头文件 `include/tianole/*.h` 中的函数声明必须使用 Linux kernel-doc 风格块注释。
- 公共结构体、枚举、typedef 和长期宏常量也必须使用 kernel-doc 风格块注释。
- 公共函数注释至少说明函数职责、关键参数、返回值或副作用;不要只复述函数名。
- 公共类型注释要说明字段含义、所有权、生命周期或并发约束。
- 公共宏注释要说明它代表的协议常量、标志位或长期 ABI 含义。
- 公共函数声明和前一个声明/注释块之间必须留一个空行,避免 API 挤在一起。
- 私有 `static` 小函数不要求每个都写函数头注释;只有逻辑、数据流、锁语义或硬件约束不明显时才写。
- 多行注释要控制行宽,优先写职责、数据流和调用约束,而不是描述每一行代码。
## C 格式 ## C 格式
@ -67,7 +75,10 @@
- 可独立执行的检查放在 `scripts/checks/` - 可独立执行的检查放在 `scripts/checks/`
- 多个检查共享的函数放在 `scripts/lib/` - 多个检查共享的函数放在 `scripts/lib/`
- 不把所有检查逻辑持续堆进 `scripts/check.sh` - 不把所有检查逻辑持续堆进 `scripts/check.sh`
- 项目结构规则放在 `scripts/tools/check_structure.py`,由 `scripts/checks/structure.sh` 调用;优先用宿主 Linux/LLVM 工具实现底层检查,把 Tianole 自己的目录和 include 约束固化在轻量 Python 工具里。 - 项目结构规则由 `scripts/tools/check_structure.py` 编排,具体规则拆到
`scripts/tools/structure_checks/`;不要把 Python 工具堆成单个超大文件。
- 优先用宿主 Linux/LLVM 工具实现底层检查,把 Tianole 自己的目录和 include
约束固化在轻量 Python 工具里。
- 启动阶段内核自测集中放在 `kernel/selftest/`,不要散落在具体实现目录里。 - 启动阶段内核自测集中放在 `kernel/selftest/`,不要散落在具体实现目录里。
## 验证 ## 验证
@ -75,3 +86,20 @@
- 新增或大改 C/H 文件后运行 `clang-format` - 新增或大改 C/H 文件后运行 `clang-format`
- 提交前至少运行 `scripts/check.sh` - 提交前至少运行 `scripts/check.sh`
- 如果只是文档修改,至少运行 `git diff --check` - 如果只是文档修改,至少运行 `git diff --check`
## 错误码
- 可恢复的内核内部错误优先返回 Linux 风格的负 errno例如
`-EINVAL`、`-ENOMEM`、`-ENOENT`、`-EBUSY`、`-EEXIST`、`-ETIMEDOUT`。
- 不要用裸 `-1` 表达多个失败原因;调用者需要能区分输入错误、资源耗尽、
对象已存在和超时。
- 不要直接返回 `-22` 这类负数字面量;使用 `-EINVAL` 这类符号化 errno
结构检查会拒绝裸负数返回。
- errno 常量放在 `include/tianole/errno.h`,只增加当前内核实际使用的值。
## 调度状态
- 线程状态转换必须通过 `kernel/sched/sched.h` 中的 helper 完成。
- 不要在调度实现或其他子系统里直接写 `thread->state = ...`;结构检查会拒绝
这种写法。
- 新增状态或转换规则时,必须同步更新状态转换 helper 和 scheduler selftest。

View File

@ -79,6 +79,7 @@
- kernel early log 已拆成通用前端和 x86 backend。 - kernel early log 已拆成通用前端和 x86 backend。
- x86 backend 已同时写 QEMU debug port 和 COM1。 - x86 backend 已同时写 QEMU debug port 和 COM1。
- x86 backend 已可在 UEFI GOP framebuffer 上显示 early logQEMU 图形窗口可直接观察启动日志。
- `panic()` 已接入 early log 和 `arch_halt_forever()` - `panic()` 已接入 early log 和 `arch_halt_forever()`
- `scripts/check.sh` 已验证 `build/debug.log``build/serial.log` 中的关键启动行。 - `scripts/check.sh` 已验证 `build/debug.log``build/serial.log` 中的关键启动行。

View File

@ -152,9 +152,11 @@
- 已把 `kernel_thread_create()` 中的线程 id 分配和 run queue 入队纳入 interrupt-safe lock 保护。 - 已把 `kernel_thread_create()` 中的线程 id 分配和 run queue 入队纳入 interrupt-safe lock 保护。
- 已建立 `sched_irq_exit()`timer IRQ 只设置 `need_resched`trap 的 IRQ 返回边界统一消费调度请求。 - 已建立 `sched_irq_exit()`timer IRQ 只设置 `need_resched`trap 的 IRQ 返回边界统一消费调度请求。
- 已建立最小 DEAD 线程回收路径,调度前会释放非当前 DEAD 线程的内核栈和线程对象。 - 已建立最小 DEAD 线程回收路径,调度前会释放非当前 DEAD 线程的内核栈和线程对象。
- 已建立统一 `kernel_thread_exit()`/`sched_thread_exit()`,线程入口返回和显式退出都会进入明确退出路径,再由调度安全边界回收非当前 DEAD 线程。
- 已在调度私有头中加入 thread state helper调度核心、线程退出和 wait queue 路径不再直接散写主要状态转换。 - 已在调度私有头中加入 thread state helper调度核心、线程退出和 wait queue 路径不再直接散写主要状态转换。
- 已提供 `wait_queue_lock_irqsave()` / `wait_queue_unlock_irqrestore()` 和 locked wakeup 接口,条件修改与 wakeup 可以收敛在同一 wait queue 锁边界内。
- 已把调度代码按职责拆分为 `core.c`、`thread.c`、`wait.c`、`idle.c` 和私有 `sched.h`,并把当前阶段自测/演示线程移到 `kernel/selftest/sched.c` - 已把调度代码按职责拆分为 `core.c`、`thread.c`、`wait.c`、`idle.c` 和私有 `sched.h`,并把当前阶段自测/演示线程移到 `kernel/selftest/sched.c`
- `scripts/check.sh` 已验证 `timer initialized`、`timer tick=1/2/3`、`scheduler initialized`、`kernel thread selftest ok`、timer 驱动线程轮转、`sched_sleep()`、wait queue wakeup、条件等待和超时等待 - `scripts/check.sh` 已验证 `timer initialized`、`timer tick=1/2/3`、`scheduler initialized`、`kernel thread selftest ok`、timer 驱动线程轮转、`sched_sleep()`、wait queue wakeup、条件等待、超时等待、线程返回退出、显式退出和 DEAD 线程回收
后续扩展: 后续扩展:
@ -166,8 +168,8 @@
### B. wait queue 锁语义 ### B. wait queue 锁语义
- 为 wait queue 增加内部锁或要求调用方持有指定锁,并在接口命名中体现约束 - 已为 wait queue 增加内部锁,并提供显式 locked wakeup 接口;后续继续扩大调用方按条件锁规则更新条件的覆盖面
- 继续把条件所属数据的修改规则文档化;当前 wait queue 内部锁已经覆盖条件检查、等待入队和 wakeup 队列修改 - 继续把条件所属数据的修改规则文档化;当前 wait queue 内部锁已经覆盖条件检查、等待入队、wakeup 队列修改,以及 demo 中的条件修改 + wakeup
- 区分 `wake_one`、`wake_all`、timeout wakeup 和条件 wakeup 的状态处理。 - 区分 `wake_one`、`wake_all`、timeout wakeup 和条件 wakeup 的状态处理。
- 检查 wakeup 是否可能唤醒 DEAD、RUNNING 或未入队线程。 - 检查 wakeup 是否可能唤醒 DEAD、RUNNING 或未入队线程。
@ -181,7 +183,7 @@
### D. thread lifecycle ### D. thread lifecycle
- 为线程退出增加更完整的生命周期状态、引用规则和最终释放约束。 - 为线程退出增加更完整的生命周期状态、引用规则和最终释放约束。
- 补充“当前线程不能释放自身内核栈”的文档和自测 - 继续补充“当前线程不能释放自身内核栈”的更严格断言和未来 join/wait 语义
- 为未来 `kthread_stop()`、join/wait 和进程退出保留接口空间。 - 为未来 `kthread_stop()`、join/wait 和进程退出保留接口空间。
- 回收路径需要覆盖等待队列残留、run queue 残留和 timer sleep 残留。 - 回收路径需要覆盖等待队列残留、run queue 残留和 timer sleep 残留。

View File

@ -3,13 +3,66 @@
#include <stdint.h> #include <stdint.h>
void arch_early_log_init(void); #include <tianole/boot_info.h>
/**
* arch_early_log_init() - Initialize architecture early log devices.
* @boot_info: Bootloader handoff data, or NULL for backend-only setup.
*
* Provides the backend setup used by the generic early log front end.
*/
void arch_early_log_init(const boot_info_t *boot_info);
/**
* arch_early_log_putc() - Emit one character through arch early log devices.
* @ch: Character to write.
*
* This keeps port I/O and other architecture details out of generic logging.
*/
void arch_early_log_putc(char ch); void arch_early_log_putc(char ch);
/**
* arch_halt_forever() - Stop execution permanently.
*
* Used by panic and unrecoverable paths after diagnostics have been emitted.
*/
void arch_halt_forever(void) __attribute__((noreturn)); void arch_halt_forever(void) __attribute__((noreturn));
/**
* arch_irq_save() - Save interrupt state and disable maskable interrupts.
*
* Return: Architecture flags needed by arch_irq_restore().
*/
uint64_t arch_irq_save(void); uint64_t arch_irq_save(void);
/**
* arch_irq_restore() - Restore a previously saved interrupt state.
* @flags: Value returned by arch_irq_save().
*
* This is the backend for interrupt-safe spinlocks on the current CPU.
*/
void arch_irq_restore(uint64_t flags); void arch_irq_restore(uint64_t flags);
/**
* arch_page_table_uses_page() - Check whether a page backs page tables.
* @page: Physical page base address.
*
* Return: Non-zero if the architecture page-table layer owns the page.
*/
int arch_page_table_uses_page(uint64_t page); int arch_page_table_uses_page(uint64_t page);
/**
* arch_traps_init() - Initialize architecture trap and IRQ entry tables.
*
* Sets up descriptor tables and entry points before devices enable IRQs.
*/
void arch_traps_init(void); void arch_traps_init(void);
/**
* arch_timer_init() - Initialize the architecture timer backend.
*
* Connects the hardware timer to the generic timer and scheduler path.
*/
void arch_timer_init(void); void arch_timer_init(void);
#endif #endif

View File

@ -3,8 +3,26 @@
#include <stdint.h> #include <stdint.h>
/**
* BOOT_MEMORY_TYPE_CONVENTIONAL - Firmware memory type for usable RAM.
*
* Mirrors the UEFI conventional memory type used by the bootloader handoff.
* The kernel converts this into its own long-term memory model during MM init.
*/
#define BOOT_MEMORY_TYPE_CONVENTIONAL 7u #define BOOT_MEMORY_TYPE_CONVENTIONAL 7u
/**
* typedef boot_memory_descriptor_t - Boot memory map descriptor.
* @type: Firmware memory type.
* @pad: Reserved padding used to keep the handoff layout stable.
* @physical_start: Physical base address of this region.
* @virtual_start: Firmware virtual address field, currently preserved only.
* @number_of_pages: Region size in 4 KiB pages.
* @attribute: Firmware attributes for the region.
*
* Describes one memory map entry copied from firmware-owned data into the
* Tianole boot handoff format.
*/
typedef struct { typedef struct {
uint32_t type; uint32_t type;
uint32_t pad; uint32_t pad;
@ -14,10 +32,25 @@ typedef struct {
uint64_t attribute; uint64_t attribute;
} boot_memory_descriptor_t; } boot_memory_descriptor_t;
/* /**
* Boot-time handoff data owned by Tianole rather than by a specific * typedef boot_info_t - Bootloader-to-kernel handoff data.
* firmware or architecture API. Fields can grow over time while the * @version: Handoff structure version.
* kernel entry stays stable. * @boot_flags: Flags describing the firmware handoff state.
* @memory_map: Physical address of boot_memory_descriptor_t entries.
* @memory_map_size: Total memory map size in bytes.
* @memory_map_key: Firmware key captured before ExitBootServices().
* @memory_descriptor_size: Size of each memory descriptor entry.
* @memory_descriptor_version: Firmware descriptor version.
* @reserved0: Reserved field for alignment and future expansion.
* @framebuffer_base: Physical base address of the boot framebuffer.
* @framebuffer_size: Framebuffer size in bytes.
* @framebuffer_width: Visible framebuffer width in pixels.
* @framebuffer_height: Visible framebuffer height in pixels.
* @framebuffer_pixels_per_scan_line: Physical pixels per scan line.
* @framebuffer_pixel_format: Bootloader-provided pixel format identifier.
*
* Boot-time handoff data owned by Tianole rather than by a specific firmware
* or architecture API. Fields can grow while the kernel entry stays stable.
*/ */
typedef struct { typedef struct {
uint32_t version; uint32_t version;
@ -28,9 +61,22 @@ typedef struct {
uint64_t memory_descriptor_size; uint64_t memory_descriptor_size;
uint32_t memory_descriptor_version; uint32_t memory_descriptor_version;
uint32_t reserved0; uint32_t reserved0;
uint64_t framebuffer_base;
uint64_t framebuffer_size;
uint32_t framebuffer_width;
uint32_t framebuffer_height;
uint32_t framebuffer_pixels_per_scan_line;
uint32_t framebuffer_pixel_format;
} boot_info_t; } boot_info_t;
#define BOOT_INFO_VERSION 1u /**
* BOOT_INFO_VERSION - Current boot_info_t layout version.
*/
#define BOOT_INFO_VERSION 2u
/**
* BOOT_FLAG_SERVICES_ACTIVE - Firmware boot services were active at handoff.
*/
#define BOOT_FLAG_SERVICES_ACTIVE (1u << 0) #define BOOT_FLAG_SERVICES_ACTIVE (1u << 0)
#endif #endif

View File

@ -3,11 +3,56 @@
#include <stdint.h> #include <stdint.h>
void early_log_init(void); #include <tianole/boot_info.h>
/**
* early_log_init() - Initialize early logging backends.
* @boot_info: Bootloader handoff data used by optional display backends.
*
* Sets up the architecture-provided early output path. This must be usable
* before heap, scheduler, VFS or normal drivers exist.
*/
void early_log_init(const boot_info_t *boot_info);
/**
* early_log_putc() - Write one character to every early log backend.
* @ch: Character to emit.
*
* This is the byte-level output path used by the string and number helpers.
*/
void early_log_putc(char ch); void early_log_putc(char ch);
/**
* early_log_puts() - Write a NUL-terminated string to early logs.
* @text: String to emit. A NULL pointer is ignored by callers only if they
* explicitly check before calling.
*
* The function does not allocate memory and is safe for early boot diagnostics.
*/
void early_log_puts(const char *text); void early_log_puts(const char *text);
/**
* early_log_u64_hex() - Write a 64-bit value in hexadecimal form.
* @value: Value to print.
*
* Used by trap, memory and boot diagnostics where libc formatting is absent.
*/
void early_log_u64_hex(uint64_t value); void early_log_u64_hex(uint64_t value);
/**
* early_log_u64_decimal() - Write a 64-bit value in decimal form.
* @value: Value to print.
*
* Used for counters and boot statistics without depending on printf support.
*/
void early_log_u64_decimal(uint64_t value); void early_log_u64_decimal(uint64_t value);
/**
* panic() - Print a fatal error and stop the machine.
* @message: Panic reason to include in the early log.
*
* This path must work even when normal kernel services are not initialized.
*/
void panic(const char *message) __attribute__((noreturn)); void panic(const char *message) __attribute__((noreturn));
#endif #endif

View File

@ -3,16 +3,61 @@
#include <stdint.h> #include <stdint.h>
/**
* ELF_MAGIC - ELF file magic value in little-endian word form.
*/
#define ELF_MAGIC 0x464c457fu #define ELF_MAGIC 0x464c457fu
/**
* ELFCLASS64 - ELF identification value for 64-bit objects.
*/
#define ELFCLASS64 2 #define ELFCLASS64 2
/**
* ELFDATA2LSB - ELF identification value for little-endian objects.
*/
#define ELFDATA2LSB 1 #define ELFDATA2LSB 1
/**
* EV_CURRENT - Current ELF object format version.
*/
#define EV_CURRENT 1 #define EV_CURRENT 1
/**
* ET_EXEC - ELF object type for executable files.
*/
#define ET_EXEC 2 #define ET_EXEC 2
/**
* EM_X86_64 - ELF machine type for x86_64.
*/
#define EM_X86_64 62 #define EM_X86_64 62
/**
* PT_LOAD - ELF program header type for loadable segments.
*/
#define PT_LOAD 1 #define PT_LOAD 1
/**
* typedef elf64_ehdr_t - ELF64 file header.
* @ident: ELF identification bytes.
* @type: Object file type.
* @machine: Target machine architecture.
* @version: ELF object version.
* @entry: Entry point virtual address.
* @phoff: Program header table file offset.
* @shoff: Section header table file offset.
* @flags: Processor-specific flags.
* @ehsize: ELF header size.
* @phentsize: Size of one program header entry.
* @phnum: Number of program header entries.
* @shentsize: Size of one section header entry.
* @shnum: Number of section header entries.
* @shstrndx: Section name string table index.
*
* Used by the bootloader and future ELF loader to validate and locate image
* segments without depending on host libc headers.
*/
typedef struct { typedef struct {
unsigned char ident[16]; unsigned char ident[16];
uint16_t type; uint16_t type;
@ -30,6 +75,20 @@ typedef struct {
uint16_t shstrndx; uint16_t shstrndx;
} elf64_ehdr_t; } elf64_ehdr_t;
/**
* typedef elf64_phdr_t - ELF64 program header.
* @type: Segment type.
* @flags: Segment permissions.
* @offset: Segment file offset.
* @vaddr: Segment virtual address.
* @paddr: Segment physical address, if meaningful for the image.
* @filesz: Number of bytes present in the file.
* @memsz: Number of bytes required in memory.
* @align: Segment alignment requirement.
*
* Describes one loadable or metadata segment consumed by boot and future
* user-mode ELF loading paths.
*/
typedef struct { typedef struct {
uint32_t type; uint32_t type;
uint32_t flags; uint32_t flags;

34
include/tianole/errno.h Normal file
View File

@ -0,0 +1,34 @@
#ifndef TIANOLE_ERRNO_H
#define TIANOLE_ERRNO_H
/**
* EINVAL - Invalid argument.
*/
#define EINVAL 22
/**
* ENOENT - No such entry or mapping.
*/
#define ENOENT 2
/**
* ENOMEM - Out of memory.
*/
#define ENOMEM 12
/**
* EBUSY - Resource is already busy.
*/
#define EBUSY 16
/**
* EEXIST - Object already exists.
*/
#define EEXIST 17
/**
* ETIMEDOUT - Operation timed out.
*/
#define ETIMEDOUT 110
#endif

View File

@ -3,8 +3,21 @@
#include <stdint.h> #include <stdint.h>
/**
* typedef irq_handler_t - External IRQ dispatch callback.
* @irq: IRQ line that triggered the callback.
* @data: Opaque pointer supplied at registration time.
*/
typedef void (*irq_handler_t)(uint8_t irq, void *data); typedef void (*irq_handler_t)(uint8_t irq, void *data);
/**
* irq_register() - Register a handler for an external IRQ line.
* @irq: IRQ line number in the generic IRQ namespace.
* @handler: Function called when the IRQ is dispatched.
* @data: Opaque handler data passed back during dispatch.
*
* Return: 0 on success, -EINVAL for bad input or -EBUSY if occupied.
*/
int irq_register(uint8_t irq, irq_handler_t handler, void *data); int irq_register(uint8_t irq, irq_handler_t handler, void *data);
#endif #endif

View File

@ -3,6 +3,12 @@
#include <tianole/boot_info.h> #include <tianole/boot_info.h>
/**
* kernel_report_boot_state() - Log the boot handoff state.
* @boot_info: Firmware-independent boot data passed to the kernel.
*
* Prints early diagnostics used to verify the bootloader-to-kernel contract.
*/
void kernel_report_boot_state(const boot_info_t *boot_info); void kernel_report_boot_state(const boot_info_t *boot_info);
#endif #endif

View File

@ -6,24 +6,118 @@
#include <tianole/boot_info.h> #include <tianole/boot_info.h>
/**
* PAGE_SIZE - Base page size used by the current memory manager.
*/
#define PAGE_SIZE 4096u #define PAGE_SIZE 4096u
/**
* typedef phys_addr_t - Physical address value.
*
* Represents addresses in physical memory, not directly dereferenceable by C.
*/
typedef uint64_t phys_addr_t; typedef uint64_t phys_addr_t;
/**
* typedef virt_addr_t - Virtual address value.
*
* Represents addresses in the active virtual address space.
*/
typedef uint64_t virt_addr_t; typedef uint64_t virt_addr_t;
/**
* PAGE_PRESENT - Mapping flag that marks a page table entry present.
*/
#define PAGE_PRESENT (1ull << 0) #define PAGE_PRESENT (1ull << 0)
/**
* PAGE_WRITABLE - Mapping flag that allows writes through a page mapping.
*/
#define PAGE_WRITABLE (1ull << 1) #define PAGE_WRITABLE (1ull << 1)
/**
* PAGE_NO_EXECUTE - Mapping flag that blocks instruction fetches.
*/
#define PAGE_NO_EXECUTE (1ull << 63) #define PAGE_NO_EXECUTE (1ull << 63)
/**
* mm_init() - Initialize the generic memory manager.
* @boot_info: Bootloader-provided memory map and handoff data.
*
* Converts boot memory information into Tianole-owned page allocator state.
*/
void mm_init(const boot_info_t *boot_info); void mm_init(const boot_info_t *boot_info);
/**
* alloc_page() - Allocate one physical page.
*
* Return: Physical page base address, or 0 when no page is available.
*/
phys_addr_t alloc_page(void); phys_addr_t alloc_page(void);
/**
* free_page() - Return one physical page to the allocator.
* @page: Physical page base address previously returned by alloc_page().
*
* The page must not still be mapped or owned by another subsystem.
*/
void free_page(phys_addr_t page); void free_page(phys_addr_t page);
/**
* kmalloc() - Allocate small kernel heap memory.
* @size: Number of bytes requested.
*
* Return: Kernel virtual pointer, or NULL when allocation fails.
*/
void *kmalloc(size_t size); void *kmalloc(size_t size);
/**
* kfree() - Free memory allocated by kmalloc().
* @ptr: Pointer returned by kmalloc(), or NULL.
*
* Releases the allocation back to the kernel heap.
*/
void kfree(void *ptr); void kfree(void *ptr);
/**
* map_page() - Map one virtual page to one physical page.
* @virt: Virtual page address.
* @phys: Physical page address.
* @flags: Generic page flags such as PAGE_WRITABLE.
*
* Return: 0 on success, -EINVAL, -ENOMEM or -EEXIST on failure.
*/
int map_page(virt_addr_t virt, phys_addr_t phys, uint64_t flags); int map_page(virt_addr_t virt, phys_addr_t phys, uint64_t flags);
/**
* unmap_page() - Remove one virtual page mapping.
* @virt: Virtual page address to unmap.
*
* Return: 0 on success, -EINVAL or -ENOENT on failure.
*/
int unmap_page(virt_addr_t virt); int unmap_page(virt_addr_t virt);
/**
* virt_to_phys() - Resolve a virtual address to a physical address.
* @virt: Virtual address to query.
* @phys: Output storage for the resolved physical address.
*
* Return: 0 when a mapping exists, -EINVAL or -ENOENT otherwise.
*/
int virt_to_phys(virt_addr_t virt, phys_addr_t *phys); int virt_to_phys(virt_addr_t virt, phys_addr_t *phys);
/**
* heap_init() - Initialize the kernel heap.
*
* Sets up kmalloc()/kfree() after page allocation and page tables are ready.
*/
void heap_init(void); void heap_init(void);
/**
* page_table_selftest() - Run boot-time page table checks.
*
* Verifies map, unmap and address translation behavior during early boot.
*/
void page_table_selftest(void); void page_table_selftest(void);
#endif #endif

View File

@ -6,9 +6,31 @@
#include <tianole/spinlock.h> #include <tianole/spinlock.h>
/**
* typedef kernel_thread_entry_t - Kernel thread entry function.
* @arg: Opaque argument supplied at thread creation.
*
* The function runs in kernel thread context. Returning from it is equivalent
* to calling kernel_thread_exit().
*/
typedef void (*kernel_thread_entry_t)(void *arg); typedef void (*kernel_thread_entry_t)(void *arg);
/**
* typedef wait_condition_t - Wait queue condition predicate.
* @arg: Opaque predicate argument supplied by the caller.
*
* Return: Non-zero when the condition is satisfied, zero to keep waiting.
*/
typedef int (*wait_condition_t)(void *arg); typedef int (*wait_condition_t)(void *arg);
/**
* enum thread_state - Scheduler-visible thread lifecycle state.
* @THREAD_READY: Thread is runnable and may be selected by the scheduler.
* @THREAD_RUNNING: Thread is currently executing on the CPU.
* @THREAD_SLEEPING: Thread is blocked until a timer deadline.
* @THREAD_WAITING: Thread is blocked on a wait queue.
* @THREAD_DEAD: Thread has exited and is waiting for safe reclamation.
*/
enum thread_state { enum thread_state {
THREAD_READY, THREAD_READY,
THREAD_RUNNING, THREAD_RUNNING,
@ -17,12 +39,41 @@ enum thread_state {
THREAD_DEAD, THREAD_DEAD,
}; };
/**
* struct wait_queue - Queue of threads blocked on an event or condition.
* @lock: Interrupt-safe lock protecting queue links and wakeup state.
* @head: Oldest waiting thread.
* @tail: Newest waiting thread.
*
* The queue owns only wait links. A condition protected by this queue must be
* updated under wait_queue_lock_irqsave(), followed by a locked wakeup before
* releasing the lock; otherwise the caller must document its own lock rule.
*/
struct wait_queue { struct wait_queue {
struct spinlock lock; struct spinlock lock;
struct thread *head; struct thread *head;
struct thread *tail; struct thread *tail;
}; };
/**
* struct thread - Kernel scheduler thread object.
* @id: Scheduler-assigned thread identifier.
* @state: Current scheduler state.
* @stack_pointer: Saved context stack pointer for context switching.
* @entry: Thread entry function.
* @arg: Entry function argument.
* @stack_base: Allocated kernel stack base.
* @stack_top: Aligned initial stack top.
* @stack_size: Kernel stack size in bytes.
* @wake_tick: Timer tick deadline for sleeping threads.
* @next: Run queue link owned by the scheduler.
* @wait_next: Wait queue link owned by wait queue code.
* @wait_queue: Wait queue currently owning @wait_next, or NULL.
* @name: Diagnostic thread name.
*
* This structure is public only as an early-stage compromise. Long term, most
* fields should move behind scheduler-private helpers or an opaque handle.
*/
struct thread { struct thread {
uint64_t id; uint64_t id;
enum thread_state state; enum thread_state state;
@ -35,26 +86,172 @@ struct thread {
uint64_t wake_tick; uint64_t wake_tick;
struct thread *next; struct thread *next;
struct thread *wait_next; struct thread *wait_next;
struct wait_queue *wait_queue;
char name[32]; char name[32];
}; };
/**
* sched_init() - Initialize scheduler core state.
*
* Creates scheduler data structures and runs scheduler selftests before
* threads are started.
*/
void sched_init(void); void sched_init(void);
/**
* kernel_thread_create() - Create a kernel thread.
* @name: Human-readable thread name used by diagnostics.
* @entry: Thread entry point.
* @arg: Opaque argument passed to @entry.
*
* Return: Thread object on success, or NULL when allocation fails.
*/
struct thread *kernel_thread_create( struct thread *kernel_thread_create(
const char *name, kernel_thread_entry_t entry, void *arg); const char *name, kernel_thread_entry_t entry, void *arg);
/**
* kernel_thread_exit() - Terminate the current kernel thread.
*
* Marks the current thread dead and yields forever until the scheduler
* switches away and later reclaims it from a safe context.
*/
void kernel_thread_exit(void) __attribute__((noreturn));
/**
* sched_start() - Start scheduler demo threads and enter scheduling.
*
* Creates the idle thread and current boot-stage scheduler demonstration.
*/
void sched_start(void) __attribute__((noreturn)); void sched_start(void) __attribute__((noreturn));
/**
* sched_tick() - Notify the scheduler about a timer tick.
* @tick: Current generic timer tick.
*
* Wakes sleeping threads whose deadlines expired and requests rescheduling.
*/
void sched_tick(uint64_t tick); void sched_tick(uint64_t tick);
/**
* sched_irq_exit() - Consume pending reschedule work after IRQ handling.
*
* Keeps timer IRQ handling short by moving the actual scheduling decision to
* a common interrupt-exit boundary.
*/
void sched_irq_exit(void); void sched_irq_exit(void);
/**
* sched_yield() - Yield the CPU to another runnable kernel thread.
*
* Performs cooperative scheduling and context switching when another runnable
* thread is available.
*/
void sched_yield(void); void sched_yield(void);
/**
* sched_sleep() - Sleep the current thread for a number of ticks.
* @ticks: Number of timer ticks to sleep.
*
* Marks the current thread sleeping and yields until the timer path wakes it.
*/
void sched_sleep(uint64_t ticks); void sched_sleep(uint64_t ticks);
/**
* wait_queue_init() - Initialize a wait queue.
* @queue: Wait queue storage owned by the caller.
*
* Resets queue links and the internal interrupt-safe lock.
*/
void wait_queue_init(struct wait_queue *queue); void wait_queue_init(struct wait_queue *queue);
/**
* wait_queue_lock_irqsave() - Lock a wait queue and save interrupt state.
* @queue: Wait queue whose condition or waiter links will be updated.
* @flags: Storage for the previous interrupt state.
*
* Callers may use this lock to protect the condition paired with
* wait_queue_wait(). In that model, update the condition and call a locked
* wakeup helper before unlocking to avoid lost wakeups.
*/
void wait_queue_lock_irqsave(struct wait_queue *queue, uint64_t *flags);
/**
* wait_queue_unlock_irqrestore() - Unlock a wait queue and restore interrupts.
* @queue: Wait queue previously locked by wait_queue_lock_irqsave().
* @flags: Interrupt state returned by wait_queue_lock_irqsave().
*/
void wait_queue_unlock_irqrestore(struct wait_queue *queue, uint64_t flags);
/**
* wait_queue_wake_one_locked() - Wake one waiter while holding queue lock.
* @queue: Locked wait queue containing waiting threads.
*
* Use when the wakeup condition is modified under the wait queue lock.
*/
void wait_queue_wake_one_locked(struct wait_queue *queue);
/**
* wait_queue_wake_all_locked() - Wake all waiters while holding queue lock.
* @queue: Locked wait queue containing waiting threads.
*
* Use when the wakeup condition is modified under the wait queue lock.
*/
void wait_queue_wake_all_locked(struct wait_queue *queue);
/**
* wait_queue_sleep() - Sleep until another thread wakes the queue.
* @queue: Queue to sleep on.
*
* Enqueues the current thread, marks it waiting and yields until wakeup.
*/
void wait_queue_sleep(struct wait_queue *queue); void wait_queue_sleep(struct wait_queue *queue);
/**
* wait_queue_wait() - Sleep until a condition becomes true.
* @queue: Queue used for wakeups.
* @condition: Predicate checked before and after sleeping.
* @arg: Opaque predicate argument.
*
* The predicate is evaluated while the queue lock is held. If the waited-on
* condition uses this queue for synchronization, writers must hold the same
* lock, update the condition, and then call wait_queue_wake_*_locked().
*
* Return: 0 when the condition is true, -EINVAL on invalid input.
*/
int wait_queue_wait( int wait_queue_wait(
struct wait_queue *queue, wait_condition_t condition, void *arg); struct wait_queue *queue, wait_condition_t condition, void *arg);
/**
* wait_queue_wait_timeout() - Sleep until a condition or timeout.
* @queue: Queue used for wakeups.
* @condition: Predicate checked before and after sleeping.
* @arg: Opaque predicate argument.
* @ticks: Maximum number of timer ticks to wait.
*
* The predicate is evaluated while the queue lock is held. The same condition
* locking rule as wait_queue_wait() applies.
*
* Return: 0 when the condition is true, -EINVAL or -ETIMEDOUT otherwise.
*/
int wait_queue_wait_timeout(struct wait_queue *queue, int wait_queue_wait_timeout(struct wait_queue *queue,
wait_condition_t condition, wait_condition_t condition,
void *arg, void *arg,
uint64_t ticks); uint64_t ticks);
/**
* wait_queue_wake_one() - Wake one thread from a wait queue.
* @queue: Queue containing waiting threads.
*
* Removes the oldest waiter and marks it ready if it is still waitable.
*/
void wait_queue_wake_one(struct wait_queue *queue); void wait_queue_wake_one(struct wait_queue *queue);
/**
* wait_queue_wake_all() - Wake every thread from a wait queue.
* @queue: Queue containing waiting threads.
*
* Drains the queue and marks every waitable thread ready.
*/
void wait_queue_wake_all(struct wait_queue *queue); void wait_queue_wake_all(struct wait_queue *queue);
#endif #endif

View File

@ -3,16 +3,42 @@
#include <stdint.h> #include <stdint.h>
/**
* struct spinlock - Early interrupt-safe lock.
* @locked: Non-zero while the lock is held.
*
* Current implementation is single-CPU and panics on contention. The API is
* shaped so it can later grow toward a real SMP spinlock.
*/
struct spinlock { struct spinlock {
int locked; int locked;
}; };
/**
* SPINLOCK_INITIALIZER - Static initializer for struct spinlock.
*/
#define SPINLOCK_INITIALIZER \ #define SPINLOCK_INITIALIZER \
{ \ { \
0 \ 0 \
} }
/**
* spin_lock_irqsave() - Acquire a single-CPU interrupt-safe spinlock.
* @lock: Lock to acquire.
* @flags: Storage for the previous interrupt state.
*
* Disables interrupts before taking the lock so IRQ and thread context can
* share early kernel data structures.
*/
void spin_lock_irqsave(struct spinlock *lock, uint64_t *flags); void spin_lock_irqsave(struct spinlock *lock, uint64_t *flags);
/**
* spin_unlock_irqrestore() - Release an interrupt-safe spinlock.
* @lock: Lock to release.
* @flags: Interrupt state returned by spin_lock_irqsave().
*
* Restores the caller's original interrupt state after releasing the lock.
*/
void spin_unlock_irqrestore(struct spinlock *lock, uint64_t flags); void spin_unlock_irqrestore(struct spinlock *lock, uint64_t flags);
#endif #endif

View File

@ -3,7 +3,18 @@
#include <stdint.h> #include <stdint.h>
/**
* timer_tick() - Advance generic timer state by one hardware tick.
*
* Called by the architecture timer IRQ handler before scheduler tick handling.
*/
void timer_tick(void); void timer_tick(void);
/**
* timer_ticks() - Read the generic monotonic tick counter.
*
* Return: Number of timer ticks observed since timer initialization.
*/
uint64_t timer_ticks(void); uint64_t timer_ticks(void);
#endif #endif

View File

@ -5,20 +5,20 @@
static int early_log_ready; static int early_log_ready;
void early_log_init(void) void early_log_init(const boot_info_t *boot_info)
{ {
if (early_log_ready != 0) { if (early_log_ready != 0) {
return; return;
} }
arch_early_log_init(); arch_early_log_init(boot_info);
early_log_ready = 1; early_log_ready = 1;
} }
void early_log_putc(char ch) void early_log_putc(char ch)
{ {
if (early_log_ready == 0) { if (early_log_ready == 0) {
early_log_init(); early_log_init(0);
} }
arch_early_log_putc(ch); arch_early_log_putc(ch);

View File

@ -8,7 +8,7 @@
void kernel_main(const boot_info_t *boot_info) void kernel_main(const boot_info_t *boot_info)
{ {
early_log_init(); early_log_init(boot_info);
early_log_puts("kernel_main entered\n"); early_log_puts("kernel_main entered\n");
arch_traps_init(); arch_traps_init();
kernel_report_boot_state(boot_info); kernel_report_boot_state(boot_info);

View File

@ -1,3 +1,4 @@
#include <tianole/errno.h>
#include <tianole/sched.h> #include <tianole/sched.h>
#include "sched.h" #include "sched.h"
@ -15,7 +16,7 @@ int sched_idle_create(void)
{ {
idle_thread = kernel_thread_create("idle", idle_thread_entry, 0); idle_thread = kernel_thread_create("idle", idle_thread_entry, 0);
if (idle_thread == 0) { if (idle_thread == 0) {
return -1; return -ENOMEM;
} }
return 0; return 0;

View File

@ -3,6 +3,7 @@
#include <stdint.h> #include <stdint.h>
#include <tianole/early_log.h>
#include <tianole/sched.h> #include <tianole/sched.h>
#include <tianole/spinlock.h> #include <tianole/spinlock.h>
@ -42,38 +43,88 @@ static inline int thread_is_dead(const struct thread *thread)
return thread != 0 && thread->state == THREAD_DEAD; return thread != 0 && thread->state == THREAD_DEAD;
} }
static inline void thread_set_ready(struct thread *thread) static inline int thread_state_transition_is_valid(
enum thread_state from, enum thread_state to)
{
if (from == to) {
return 1;
}
switch (from) {
case THREAD_READY:
return to == THREAD_RUNNING;
case THREAD_RUNNING:
return to == THREAD_READY || to == THREAD_SLEEPING ||
to == THREAD_WAITING || to == THREAD_DEAD;
case THREAD_SLEEPING:
case THREAD_WAITING:
return to == THREAD_READY || to == THREAD_DEAD;
case THREAD_DEAD:
return 0;
default:
return 0;
}
}
static inline void thread_validate_state_transition(
struct thread *thread, enum thread_state state)
{
if (thread == 0) {
panic("null thread state transition");
}
if (!thread_state_transition_is_valid(thread->state, state)) {
panic("invalid thread state transition");
}
}
static inline void thread_set_state(
struct thread *thread, enum thread_state state)
{
thread_validate_state_transition(thread, state);
thread->state = state;
}
static inline void thread_init_ready(struct thread *thread)
{ {
thread->wake_tick = 0; thread->wake_tick = 0;
thread->state = THREAD_READY; thread->state = THREAD_READY;
} }
static inline void thread_set_ready(struct thread *thread)
{
thread_set_state(thread, THREAD_READY);
thread->wake_tick = 0;
}
static inline void thread_set_running(struct thread *thread) static inline void thread_set_running(struct thread *thread)
{ {
thread->state = THREAD_RUNNING; thread_set_state(thread, THREAD_RUNNING);
} }
static inline void thread_set_sleeping( static inline void thread_set_sleeping(
struct thread *thread, uint64_t wake_tick) struct thread *thread, uint64_t wake_tick)
{ {
thread_validate_state_transition(thread, THREAD_SLEEPING);
thread->wake_tick = wake_tick; thread->wake_tick = wake_tick;
thread->state = THREAD_SLEEPING; thread->state = THREAD_SLEEPING;
} }
static inline void thread_set_waiting(struct thread *thread) static inline void thread_set_waiting(struct thread *thread)
{ {
thread_set_state(thread, THREAD_WAITING);
thread->wake_tick = 0; thread->wake_tick = 0;
thread->state = THREAD_WAITING;
} }
static inline void thread_set_dead(struct thread *thread) static inline void thread_set_dead(struct thread *thread)
{ {
thread_set_state(thread, THREAD_DEAD);
thread->wake_tick = 0; thread->wake_tick = 0;
thread->state = THREAD_DEAD;
} }
void enqueue_thread(struct thread *thread); void enqueue_thread(struct thread *thread);
void sched_reap_dead_threads(void); void sched_reap_dead_threads(void);
void sched_thread_exit(void) __attribute__((noreturn));
void sched_selftest(void); void sched_selftest(void);
int sched_idle_create(void); int sched_idle_create(void);
void sched_demo_start(void) __attribute__((noreturn)); void sched_demo_start(void) __attribute__((noreturn));

View File

@ -77,7 +77,7 @@ struct thread *kernel_thread_create(
stack_top = (uintptr_t)thread->stack_base + KERNEL_STACK_SIZE; stack_top = (uintptr_t)thread->stack_base + KERNEL_STACK_SIZE;
thread_set_ready(thread); thread_init_ready(thread);
thread->entry = entry; thread->entry = entry;
thread->arg = arg; thread->arg = arg;
thread->stack_top = align_down_uintptr(stack_top, STACK_ALIGNMENT); thread->stack_top = align_down_uintptr(stack_top, STACK_ALIGNMENT);
@ -86,6 +86,7 @@ struct thread *kernel_thread_create(
thread->wake_tick = 0; thread->wake_tick = 0;
thread->next = 0; thread->next = 0;
thread->wait_next = 0; thread->wait_next = 0;
thread->wait_queue = 0;
copy_thread_name(thread->name, sizeof(thread->name), name); copy_thread_name(thread->name, sizeof(thread->name), name);
spin_lock_irqsave(&scheduler_lock, &flags); spin_lock_irqsave(&scheduler_lock, &flags);
@ -98,6 +99,13 @@ struct thread *kernel_thread_create(
static void release_thread(struct thread *thread) static void release_thread(struct thread *thread)
{ {
if (thread->wait_queue != 0) {
panic("reaping thread still on wait queue");
}
early_log_puts("thread reaped ");
early_log_puts(thread->name);
early_log_puts("\n");
kfree(thread->stack_base); kfree(thread->stack_base);
kfree(thread); kfree(thread);
} }
@ -106,7 +114,10 @@ void sched_reap_dead_threads(void)
{ {
struct thread *prev = 0; struct thread *prev = 0;
struct thread *thread = run_queue_head; struct thread *thread = run_queue_head;
struct thread *reap_list = 0;
uint64_t flags;
spin_lock_irqsave(&scheduler_lock, &flags);
while (thread != 0) { while (thread != 0) {
struct thread *next = thread->next; struct thread *next = thread->next;
@ -121,13 +132,41 @@ void sched_reap_dead_threads(void)
run_queue_tail = prev; run_queue_tail = prev;
} }
release_thread(thread); thread->next = reap_list;
reap_list = thread;
} else { } else {
prev = thread; prev = thread;
} }
thread = next; thread = next;
} }
spin_unlock_irqrestore(&scheduler_lock, flags);
while (reap_list != 0) {
struct thread *next = reap_list->next;
reap_list->next = 0;
release_thread(reap_list);
reap_list = next;
}
}
void sched_thread_exit(void)
{
if (current_thread == 0) {
panic("thread exit without current thread");
}
thread_set_dead(current_thread);
for (;;) {
sched_yield();
}
}
void kernel_thread_exit(void)
{
sched_thread_exit();
} }
static void thread_trampoline(void) static void thread_trampoline(void)
@ -139,9 +178,5 @@ static void thread_trampoline(void)
} }
thread->entry(thread->arg); thread->entry(thread->arg);
thread_set_dead(thread); kernel_thread_exit();
for (;;) {
sched_yield();
}
} }

View File

@ -1,3 +1,4 @@
#include <tianole/errno.h>
#include <tianole/sched.h> #include <tianole/sched.h>
#include <tianole/timer.h> #include <tianole/timer.h>
@ -17,7 +18,12 @@ void wait_queue_init(struct wait_queue *queue)
static void wait_queue_enqueue_locked( static void wait_queue_enqueue_locked(
struct wait_queue *queue, struct thread *thread) struct wait_queue *queue, struct thread *thread)
{ {
if (thread->wait_queue != 0) {
panic("thread already queued on wait queue");
}
thread->wait_next = 0; thread->wait_next = 0;
thread->wait_queue = queue;
if (queue->tail != 0) { if (queue->tail != 0) {
queue->tail->wait_next = thread; queue->tail->wait_next = thread;
@ -28,14 +34,36 @@ static void wait_queue_enqueue_locked(
queue->tail = thread; queue->tail = thread;
} }
static void wait_queue_remove_locked( static struct thread *wait_queue_remove_head_locked(struct wait_queue *queue)
{
struct thread *thread = queue->head;
if (thread == 0) {
return 0;
}
queue->head = thread->wait_next;
if (queue->head == 0) {
queue->tail = 0;
}
thread->wait_next = 0;
thread->wait_queue = 0;
return thread;
}
static int wait_queue_remove_locked(
struct wait_queue *queue, struct thread *target) struct wait_queue *queue, struct thread *target)
{ {
struct thread *prev = 0; struct thread *prev = 0;
struct thread *thread; struct thread *thread;
if (queue == 0 || target == 0) { if (queue == 0 || target == 0) {
return; return 0;
}
if (target->wait_queue != queue) {
return 0;
} }
for (thread = queue->head; thread != 0; thread = thread->wait_next) { for (thread = queue->head; thread != 0; thread = thread->wait_next) {
@ -51,17 +79,70 @@ static void wait_queue_remove_locked(
} }
thread->wait_next = 0; thread->wait_next = 0;
return; thread->wait_queue = 0;
return 1;
} }
prev = thread; prev = thread;
} }
panic("wait queue membership is inconsistent");
} }
static void wait_queue_mark_ready_locked(struct thread *thread) static void wait_queue_mark_ready_locked(struct thread *thread)
{ {
if (thread_is_waiting(thread) || thread_is_sleeping(thread)) { if (thread == 0) {
return;
}
if (!thread_is_waiting(thread) && !thread_is_sleeping(thread)) {
panic("wait queue wakeup found non-waiting thread");
}
thread_set_ready(thread); thread_set_ready(thread);
}
void wait_queue_lock_irqsave(struct wait_queue *queue, uint64_t *flags)
{
if (queue == 0 || flags == 0) {
return;
}
spin_lock_irqsave(&queue->lock, flags);
}
void wait_queue_unlock_irqrestore(struct wait_queue *queue, uint64_t flags)
{
if (queue == 0) {
return;
}
spin_unlock_irqrestore(&queue->lock, flags);
}
void wait_queue_wake_one_locked(struct wait_queue *queue)
{
struct thread *thread;
if (queue == 0) {
return;
}
thread = wait_queue_remove_head_locked(queue);
wait_queue_mark_ready_locked(thread);
}
void wait_queue_wake_all_locked(struct wait_queue *queue)
{
struct thread *thread;
if (queue == 0) {
return;
}
while (queue->head != 0) {
thread = wait_queue_remove_head_locked(queue);
wait_queue_mark_ready_locked(thread);
} }
} }
@ -96,7 +177,7 @@ int wait_queue_wait(
uint64_t flags; uint64_t flags;
if (queue == 0 || condition == 0 || current_thread == 0) { if (queue == 0 || condition == 0 || current_thread == 0) {
return -1; return -EINVAL;
} }
for (;;) { for (;;) {
@ -127,7 +208,7 @@ int wait_queue_wait_timeout(struct wait_queue *queue,
uint64_t flags; uint64_t flags;
if (queue == 0 || condition == 0 || current_thread == 0) { if (queue == 0 || condition == 0 || current_thread == 0) {
return -1; return -EINVAL;
} }
if (condition(arg) != 0) { if (condition(arg) != 0) {
@ -135,7 +216,7 @@ int wait_queue_wait_timeout(struct wait_queue *queue,
} }
if (ticks == 0) { if (ticks == 0) {
return -1; return -ETIMEDOUT;
} }
deadline = timer_ticks() + ticks; deadline = timer_ticks() + ticks;
@ -152,7 +233,7 @@ int wait_queue_wait_timeout(struct wait_queue *queue,
if (now >= deadline) { if (now >= deadline) {
current_thread->wake_tick = 0; current_thread->wake_tick = 0;
spin_unlock_irqrestore(&queue->lock, flags); spin_unlock_irqrestore(&queue->lock, flags);
return -1; return -ETIMEDOUT;
} }
thread_set_sleeping(current_thread, deadline); thread_set_sleeping(current_thread, deadline);
@ -169,7 +250,6 @@ int wait_queue_wait_timeout(struct wait_queue *queue,
void wait_queue_wake_one(struct wait_queue *queue) void wait_queue_wake_one(struct wait_queue *queue)
{ {
struct thread *thread;
uint64_t flags; uint64_t flags;
if (queue == 0) { if (queue == 0) {
@ -177,25 +257,12 @@ void wait_queue_wake_one(struct wait_queue *queue)
} }
spin_lock_irqsave(&queue->lock, &flags); spin_lock_irqsave(&queue->lock, &flags);
if (queue->head == 0) { wait_queue_wake_one_locked(queue);
spin_unlock_irqrestore(&queue->lock, flags);
return;
}
thread = queue->head;
queue->head = thread->wait_next;
if (queue->head == 0) {
queue->tail = 0;
}
thread->wait_next = 0;
wait_queue_mark_ready_locked(thread);
spin_unlock_irqrestore(&queue->lock, flags); spin_unlock_irqrestore(&queue->lock, flags);
} }
void wait_queue_wake_all(struct wait_queue *queue) void wait_queue_wake_all(struct wait_queue *queue)
{ {
struct thread *thread;
uint64_t flags; uint64_t flags;
if (queue == 0) { if (queue == 0) {
@ -203,15 +270,6 @@ void wait_queue_wake_all(struct wait_queue *queue)
} }
spin_lock_irqsave(&queue->lock, &flags); spin_lock_irqsave(&queue->lock, &flags);
while (queue->head != 0) { wait_queue_wake_all_locked(queue);
thread = queue->head;
queue->head = thread->wait_next;
if (queue->head == 0) {
queue->tail = 0;
}
thread->wait_next = 0;
wait_queue_mark_ready_locked(thread);
}
spin_unlock_irqrestore(&queue->lock, flags); spin_unlock_irqrestore(&queue->lock, flags);
} }

View File

@ -1,6 +1,7 @@
#include <stdint.h> #include <stdint.h>
#include <tianole/early_log.h> #include <tianole/early_log.h>
#include <tianole/errno.h>
#include <tianole/mm.h> #include <tianole/mm.h>
#include "arch/x86/mm/page_table.h" #include "arch/x86/mm/page_table.h"
@ -20,10 +21,26 @@ void page_table_selftest(void)
page_tables_init(); page_tables_init();
if (map_page(TEST_VIRTUAL_PAGE + 1, page, PAGE_WRITABLE) != -EINVAL) {
panic("page table selftest unaligned map errno failed");
}
if (virt_to_phys(TEST_VIRTUAL_PAGE, 0) != -EINVAL) {
panic("page table selftest null output errno failed");
}
if (virt_to_phys(TEST_VIRTUAL_PAGE, &resolved) != -ENOENT) {
panic("page table selftest missing resolve errno failed");
}
if (map_page(TEST_VIRTUAL_PAGE, page, PAGE_WRITABLE) != 0) { if (map_page(TEST_VIRTUAL_PAGE, page, PAGE_WRITABLE) != 0) {
panic("page table selftest map failed"); panic("page table selftest map failed");
} }
if (map_page(TEST_VIRTUAL_PAGE, page, PAGE_WRITABLE) != -EEXIST) {
panic("page table selftest duplicate map errno failed");
}
if (virt_to_phys(TEST_VIRTUAL_PAGE, &resolved) != 0 || if (virt_to_phys(TEST_VIRTUAL_PAGE, &resolved) != 0 ||
resolved != page) { resolved != page) {
panic("page table selftest resolve failed"); panic("page table selftest resolve failed");
@ -38,6 +55,10 @@ void page_table_selftest(void)
panic("page table selftest unmap failed"); panic("page table selftest unmap failed");
} }
if (unmap_page(TEST_VIRTUAL_PAGE) != -ENOENT) {
panic("page table selftest missing unmap errno failed");
}
free_page(page); free_page(page);
early_log_puts("page table selftest ok\n"); early_log_puts("page table selftest ok\n");
} }

View File

@ -1,6 +1,7 @@
#include <stdint.h> #include <stdint.h>
#include <tianole/early_log.h> #include <tianole/early_log.h>
#include <tianole/errno.h>
#include <tianole/sched.h> #include <tianole/sched.h>
#include <tianole/spinlock.h> #include <tianole/spinlock.h>
@ -13,15 +14,53 @@ static void thread_selftest_entry(void *arg)
(void)arg; (void)arg;
} }
static int selftest_condition_false(void *arg)
{
(void)arg;
return 0;
}
static void assert_thread_transition(
enum thread_state from, enum thread_state to, int expected)
{
if (thread_state_transition_is_valid(from, to) != expected) {
panic("thread state transition selftest failed");
}
}
static void sched_state_machine_selftest(void)
{
assert_thread_transition(THREAD_READY, THREAD_RUNNING, 1);
assert_thread_transition(THREAD_RUNNING, THREAD_READY, 1);
assert_thread_transition(THREAD_RUNNING, THREAD_SLEEPING, 1);
assert_thread_transition(THREAD_RUNNING, THREAD_WAITING, 1);
assert_thread_transition(THREAD_RUNNING, THREAD_DEAD, 1);
assert_thread_transition(THREAD_SLEEPING, THREAD_READY, 1);
assert_thread_transition(THREAD_WAITING, THREAD_READY, 1);
assert_thread_transition(THREAD_SLEEPING, THREAD_DEAD, 1);
assert_thread_transition(THREAD_WAITING, THREAD_DEAD, 1);
assert_thread_transition(THREAD_READY, THREAD_SLEEPING, 0);
assert_thread_transition(THREAD_READY, THREAD_WAITING, 0);
assert_thread_transition(THREAD_READY, THREAD_DEAD, 0);
assert_thread_transition(THREAD_SLEEPING, THREAD_RUNNING, 0);
assert_thread_transition(THREAD_WAITING, THREAD_RUNNING, 0);
assert_thread_transition(THREAD_DEAD, THREAD_READY, 0);
assert_thread_transition(THREAD_DEAD, THREAD_RUNNING, 0);
}
void sched_selftest(void) void sched_selftest(void)
{ {
struct spinlock test_lock; struct spinlock test_lock;
struct wait_queue test_wait_queue;
struct thread *first = struct thread *first =
kernel_thread_create("worker-a", thread_selftest_entry, 0); kernel_thread_create("worker-a", thread_selftest_entry, 0);
struct thread *second = struct thread *second =
kernel_thread_create("worker-b", thread_selftest_entry, 0); kernel_thread_create("worker-b", thread_selftest_entry, 0);
uint64_t flags; uint64_t flags;
sched_state_machine_selftest();
test_lock.locked = 0; test_lock.locked = 0;
if (first == 0 || second == 0 || first == second) { if (first == 0 || second == 0 || first == second) {
@ -33,6 +72,10 @@ void sched_selftest(void)
panic("kernel thread selftest state failed"); panic("kernel thread selftest state failed");
} }
if (first->wait_queue != 0 || second->wait_queue != 0) {
panic("kernel thread selftest wait queue ownership failed");
}
if ((first->stack_top & (STACK_ALIGNMENT - 1)) != 0 || if ((first->stack_top & (STACK_ALIGNMENT - 1)) != 0 ||
(second->stack_top & (STACK_ALIGNMENT - 1)) != 0) { (second->stack_top & (STACK_ALIGNMENT - 1)) != 0) {
panic("kernel thread selftest stack alignment failed"); panic("kernel thread selftest stack alignment failed");
@ -46,6 +89,15 @@ void sched_selftest(void)
spin_lock_irqsave(&test_lock, &flags); spin_lock_irqsave(&test_lock, &flags);
spin_unlock_irqrestore(&test_lock, flags); spin_unlock_irqrestore(&test_lock, flags);
wait_queue_init(&test_wait_queue);
if (wait_queue_wait(0, selftest_condition_false, 0) != -EINVAL) {
panic("wait queue selftest null queue errno failed");
}
if (wait_queue_wait(&test_wait_queue, 0, 0) != -EINVAL) {
panic("wait queue selftest null condition errno failed");
}
early_log_puts("kernel thread selftest ok\n"); early_log_puts("kernel thread selftest ok\n");
} }
@ -110,13 +162,17 @@ static void condition_wait_demo_waiter(void *arg)
static void condition_wait_demo_waker(void *arg) static void condition_wait_demo_waker(void *arg)
{ {
uint64_t flags;
(void)arg; (void)arg;
early_log_puts("condition waker sleeping\n"); early_log_puts("condition waker sleeping\n");
sched_sleep(5); sched_sleep(5);
wait_queue_lock_irqsave(&condition_wait_queue, &flags);
condition_ready = 1; condition_ready = 1;
early_log_puts("condition waker wake_all\n"); early_log_puts("condition waker wake_all\n");
wait_queue_wake_all(&condition_wait_queue); wait_queue_wake_all_locked(&condition_wait_queue);
wait_queue_unlock_irqrestore(&condition_wait_queue, flags);
} }
static void timeout_wait_demo_waiter(void *arg) static void timeout_wait_demo_waiter(void *arg)
@ -126,41 +182,75 @@ static void timeout_wait_demo_waiter(void *arg)
(void)arg; (void)arg;
early_log_puts("timeout waiter sleeping\n"); early_log_puts("timeout waiter sleeping\n");
if (wait_queue_wait_timeout(
&timeout_wait_queue, condition_is_ready, &never_ready, 0) !=
-ETIMEDOUT) {
panic("timeout wait zero tick errno failed");
}
if (wait_queue_wait_timeout( if (wait_queue_wait_timeout(
&timeout_wait_queue, condition_is_ready, &never_ready, 3) != &timeout_wait_queue, condition_is_ready, &never_ready, 3) !=
-1) { -ETIMEDOUT) {
panic("timeout wait did not time out"); panic("timeout wait did not time out");
} }
early_log_puts("timeout waiter timed out\n"); early_log_puts("timeout waiter timed out\n");
} }
static void return_exit_demo_thread(void *arg)
{
(void)arg;
early_log_puts("return exit thread returning\n");
}
static void explicit_exit_demo_thread(void *arg)
{
(void)arg;
early_log_puts("explicit exit thread exiting\n");
kernel_thread_exit();
}
void sched_demo_start(void) void sched_demo_start(void)
{ {
struct thread *first = kernel_thread_create( struct thread *first;
"round-robin-a", scheduler_demo_entry, (void *)(uintptr_t)1); struct thread *second;
struct thread *second = kernel_thread_create( struct thread *waiter;
"round-robin-b", scheduler_demo_entry, (void *)(uintptr_t)2); struct thread *waker;
struct thread *waiter = struct thread *condition_waiter;
kernel_thread_create("waiter", wait_queue_demo_waiter, 0); struct thread *condition_waker;
struct thread *waker = struct thread *timeout_waiter;
kernel_thread_create("waker", wait_queue_demo_waker, 0); struct thread *return_exit;
struct thread *condition_waiter = kernel_thread_create( struct thread *explicit_exit;
"condition-waiter", condition_wait_demo_waiter, 0);
struct thread *condition_waker = kernel_thread_create(
"condition-waker", condition_wait_demo_waker, 0);
struct thread *timeout_waiter = kernel_thread_create(
"timeout-waiter", timeout_wait_demo_waiter, 0);
if (first == 0 || second == 0 || waiter == 0 || waker == 0 ||
condition_waiter == 0 || condition_waker == 0 ||
timeout_waiter == 0) {
panic("scheduler demo thread creation failed");
}
wait_queue_init(&demo_wait_queue); wait_queue_init(&demo_wait_queue);
wait_queue_init(&condition_wait_queue); wait_queue_init(&condition_wait_queue);
wait_queue_init(&timeout_wait_queue); wait_queue_init(&timeout_wait_queue);
condition_ready = 0; condition_ready = 0;
first = kernel_thread_create(
"round-robin-a", scheduler_demo_entry, (void *)(uintptr_t)1);
second = kernel_thread_create(
"round-robin-b", scheduler_demo_entry, (void *)(uintptr_t)2);
waiter = kernel_thread_create("waiter", wait_queue_demo_waiter, 0);
waker = kernel_thread_create("waker", wait_queue_demo_waker, 0);
condition_waiter = kernel_thread_create(
"condition-waiter", condition_wait_demo_waiter, 0);
condition_waker = kernel_thread_create(
"condition-waker", condition_wait_demo_waker, 0);
timeout_waiter = kernel_thread_create(
"timeout-waiter", timeout_wait_demo_waiter, 0);
return_exit =
kernel_thread_create("return-exit", return_exit_demo_thread, 0);
explicit_exit = kernel_thread_create(
"explicit-exit", explicit_exit_demo_thread, 0);
if (first == 0 || second == 0 || waiter == 0 || waker == 0 ||
condition_waiter == 0 || condition_waker == 0 ||
timeout_waiter == 0 || return_exit == 0 || explicit_exit == 0) {
panic("scheduler demo thread creation failed");
}
early_log_puts("scheduler starting\n"); early_log_puts("scheduler starting\n");
sched_yield(); sched_yield();

View File

@ -2,6 +2,7 @@
#include <stdint.h> #include <stdint.h>
#include <tianole/early_log.h> #include <tianole/early_log.h>
#include <tianole/errno.h>
#include <tianole/mm.h> #include <tianole/mm.h>
#define HEAP_BASE 0xffffff2000000000ull #define HEAP_BASE 0xffffff2000000000ull
@ -85,12 +86,16 @@ static int map_heap_range(virt_addr_t start, size_t bytes)
for (current = start; current < end; current += PAGE_SIZE) { for (current = start; current < end; current += PAGE_SIZE) {
phys_addr_t page = alloc_page(); phys_addr_t page = alloc_page();
int ret;
if (page == 0 || if (page == 0) {
map_page(current, return -ENOMEM;
page, }
PAGE_WRITABLE | PAGE_NO_EXECUTE) != 0) {
return -1; ret = map_page(current, page, PAGE_WRITABLE | PAGE_NO_EXECUTE);
if (ret != 0) {
free_page(page);
return ret;
} }
} }
@ -102,9 +107,11 @@ static int heap_extend(size_t min_size)
size_t bytes = size_t bytes =
align_up_size(min_size + sizeof(struct heap_block), PAGE_SIZE); align_up_size(min_size + sizeof(struct heap_block), PAGE_SIZE);
struct heap_block *block = (struct heap_block *)(uintptr_t)heap_end; struct heap_block *block = (struct heap_block *)(uintptr_t)heap_end;
int ret;
if (map_heap_range(heap_end, bytes) != 0) { ret = map_heap_range(heap_end, bytes);
return -1; if (ret != 0) {
return ret;
} }
heap_end += bytes; heap_end += bytes;

View File

@ -34,6 +34,12 @@ check_lines build/debug.log \
"condition waiter sleeping" \ "condition waiter sleeping" \
"condition waker sleeping" \ "condition waker sleeping" \
"timeout waiter sleeping" \ "timeout waiter sleeping" \
"return exit thread returning" \
"explicit exit thread exiting" \
"thread reaped worker-a" \
"thread reaped worker-b" \
"thread reaped return-exit" \
"thread reaped explicit-exit" \
"preempt thread 1 step=2" \ "preempt thread 1 step=2" \
"preempt thread 2 step=2" \ "preempt thread 2 step=2" \
"timeout waiter timed out" \ "timeout waiter timed out" \
@ -71,6 +77,12 @@ check_lines build/serial.log \
"condition waiter sleeping" \ "condition waiter sleeping" \
"condition waker sleeping" \ "condition waker sleeping" \
"timeout waiter sleeping" \ "timeout waiter sleeping" \
"return exit thread returning" \
"explicit exit thread exiting" \
"thread reaped worker-a" \
"thread reaped worker-b" \
"thread reaped return-exit" \
"thread reaped explicit-exit" \
"preempt thread 1 step=2" \ "preempt thread 1 step=2" \
"preempt thread 2 step=2" \ "preempt thread 2 step=2" \
"timeout waiter timed out" \ "timeout waiter timed out" \

2
scripts/tools/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
__pycache__/
*.py[cod]

View File

@ -1,206 +1,47 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import argparse import argparse
import re
import subprocess
import sys import sys
from pathlib import Path
from structure_checks.common import (
SOURCE_ROOTS = ("arch", "include", "kernel", "mm") all_files,
C_SOURCE_ROOTS = ("arch", "kernel", "mm") c_files,
MAKEFILE_SOURCE_VARS = ( changed_files,
"ARCH_KERNEL_SRCS", public_headers,
"ARCH_MM_SRCS", repo_root,
"BOOT_SRCS", source_files,
"KERNEL_SRCS", )
"MM_SRCS", from structure_checks.makefiles import check_makefile_source_lists
from structure_checks.public_headers import check_public_header_docs
from structure_checks.source_rules import (
check_no_bare_negative_errno,
check_no_relative_parent_includes,
check_scheduler_state_writes,
check_selftests_are_centralized,
) )
def repo_root() -> Path: def parse_args() -> argparse.Namespace:
return Path(__file__).resolve().parents[2]
def run_git(root: Path, args: list[str]) -> list[str]:
result = subprocess.run(
["git", *args],
cwd=root,
check=True,
text=True,
stdout=subprocess.PIPE,
)
return [line for line in result.stdout.splitlines() if line]
def changed_files(root: Path) -> set[Path]:
files = set()
for path in run_git(root, ["diff", "--name-only", "--diff-filter=ACMR"]):
files.add(Path(path))
for path in run_git(
root, ["diff", "--cached", "--name-only", "--diff-filter=ACMR"]
):
files.add(Path(path))
for path in run_git(root, ["ls-files", "--others", "--exclude-standard"]):
files.add(Path(path))
return files
def all_files(root: Path) -> set[Path]:
files = set()
for base in SOURCE_ROOTS:
root_dir = root / base
if not root_dir.exists():
continue
for path in root_dir.rglob("*"):
if path.is_file():
files.add(path.relative_to(root))
for path in (root / "scripts").rglob("*"):
if path.is_file():
files.add(path.relative_to(root))
return files
def source_files(files: set[Path]) -> list[Path]:
return sorted(path for path in files if path.suffix in (".c", ".h"))
def c_files(files: set[Path]) -> list[Path]:
return sorted(
path
for path in files
if path.suffix == ".c" and path.parts and path.parts[0] in C_SOURCE_ROOTS
)
def read_text(root: Path, path: Path) -> str:
return (root / path).read_text(encoding="utf-8")
def check_no_relative_parent_includes(root: Path, files: list[Path]) -> list[str]:
errors = []
include_re = re.compile(r'^\s*#include\s+[<"]\.\./')
for path in files:
for line_no, line in enumerate(read_text(root, path).splitlines(), 1):
if include_re.search(line):
errors.append(
f"{path}:{line_no}: do not use ../ includes; "
"promote shared headers or add a private include root"
)
return errors
def check_selftests_are_centralized(files: list[Path]) -> list[str]:
errors = []
for path in files:
if "selftest" not in path.name:
continue
if len(path.parts) < 2 or path.parts[:2] != ("kernel", "selftest"):
errors.append(f"{path}: kernel selftests belong under kernel/selftest/")
return errors
def expand_make_var_token(token: str, makefile: Path) -> str:
return token.replace("$(ARCH_DIR)", "arch/x86").replace(
"$(BUILD_DIR)", "build"
)
def parse_makefile_sources(root: Path, makefile: Path) -> set[Path]:
text = read_text(root, makefile)
lines = text.splitlines()
sources = set()
index = 0
while index < len(lines):
line = lines[index]
match = re.match(r"^([A-Z0-9_]+)\s*:=", line)
if match == None or match.group(1) not in MAKEFILE_SOURCE_VARS:
index += 1
continue
remainder = line.split(":=", 1)[1].strip()
while True:
continued = remainder.endswith("\\")
remainder = remainder[:-1].strip() if continued else remainder
if remainder:
for token in remainder.split():
if token.endswith(".c"):
sources.add(Path(expand_make_var_token(token, makefile)))
if not continued:
break
index += 1
if index >= len(lines):
break
remainder = lines[index].strip()
index += 1
return sources
def makefile_sources(root: Path) -> set[Path]:
sources = set()
for makefile in sorted(root.rglob("Makefile")):
if "build" in makefile.parts:
continue
sources.update(parse_makefile_sources(root, makefile.relative_to(root)))
return sources
def check_makefile_source_lists(root: Path, files: set[Path], all_mode: bool) -> list[str]:
errors = []
listed = makefile_sources(root)
existing = set(c_files(all_files(root)))
for path in sorted(listed):
if not (root / path).exists():
errors.append(f"{path}: listed in Makefile but file does not exist")
if all_mode:
for path in sorted(existing - listed):
errors.append(f"{path}: C source is not listed in a Makefile source list")
else:
for path in sorted(c_files(files)):
if path not in listed:
errors.append(
f"{path}: changed C source is not listed in a Makefile source list"
)
return errors
def main() -> int:
parser = argparse.ArgumentParser(description="Check Tianole structure rules") parser = argparse.ArgumentParser(description="Check Tianole structure rules")
mode = parser.add_mutually_exclusive_group() mode = parser.add_mutually_exclusive_group()
mode.add_argument("--all", action="store_true", help="check the whole tree") mode.add_argument("--all", action="store_true", help="check the whole tree")
mode.add_argument( mode.add_argument(
"--changed", action="store_true", help="check changed and untracked files" "--changed", action="store_true", help="check changed and untracked files"
) )
args = parser.parse_args() return parser.parse_args()
def main() -> int:
args = parse_args()
root = repo_root() root = repo_root()
all_mode = not args.changed all_mode = not args.changed
files = all_files(root) if all_mode else changed_files(root) files = all_files(root) if all_mode else changed_files(root)
errors = [] errors = []
errors.extend(check_no_relative_parent_includes(root, source_files(files))) sources = source_files(files)
errors.extend(check_no_relative_parent_includes(root, sources))
errors.extend(check_no_bare_negative_errno(root, sources))
errors.extend(check_scheduler_state_writes(root, sources))
errors.extend(check_public_header_docs(root, public_headers(files)))
errors.extend(check_selftests_are_centralized(c_files(files))) errors.extend(check_selftests_are_centralized(c_files(files)))
errors.extend(check_makefile_source_lists(root, files, all_mode)) errors.extend(check_makefile_source_lists(root, files, all_mode))

View File

@ -0,0 +1 @@
"""Structure checks for Tianole source layout."""

View File

@ -0,0 +1,83 @@
import subprocess
from pathlib import Path
SOURCE_ROOTS = ("arch", "include", "kernel", "mm")
C_SOURCE_ROOTS = ("arch", "kernel", "mm")
def repo_root() -> Path:
return Path(__file__).resolve().parents[3]
def run_git(root: Path, args: list[str]) -> list[str]:
result = subprocess.run(
["git", *args],
cwd=root,
check=True,
text=True,
stdout=subprocess.PIPE,
)
return [line for line in result.stdout.splitlines() if line]
def changed_files(root: Path) -> set[Path]:
files = set()
for path in run_git(root, ["diff", "--name-only", "--diff-filter=ACMR"]):
files.add(Path(path))
for path in run_git(
root, ["diff", "--cached", "--name-only", "--diff-filter=ACMR"]
):
files.add(Path(path))
for path in run_git(root, ["ls-files", "--others", "--exclude-standard"]):
files.add(Path(path))
return files
def all_files(root: Path) -> set[Path]:
files = set()
for base in SOURCE_ROOTS:
root_dir = root / base
if not root_dir.exists():
continue
for path in root_dir.rglob("*"):
if path.is_file():
files.add(path.relative_to(root))
for path in (root / "scripts").rglob("*"):
if path.is_file():
files.add(path.relative_to(root))
return files
def source_files(files: set[Path]) -> list[Path]:
return sorted(path for path in files if path.suffix in (".c", ".h"))
def public_headers(files: set[Path]) -> list[Path]:
return sorted(
path
for path in files
if path.suffix == ".h"
and len(path.parts) == 3
and path.parts[:2] == ("include", "tianole")
)
def c_files(files: set[Path]) -> list[Path]:
return sorted(
path
for path in files
if path.suffix == ".c" and path.parts and path.parts[0] in C_SOURCE_ROOTS
)
def read_text(root: Path, path: Path) -> str:
return (root / path).read_text(encoding="utf-8")

View File

@ -0,0 +1,87 @@
import re
from pathlib import Path
from .common import all_files, c_files, read_text
MAKEFILE_SOURCE_VARS = (
"ARCH_KERNEL_SRCS",
"ARCH_MM_SRCS",
"BOOT_SRCS",
"KERNEL_SRCS",
"MM_SRCS",
)
def expand_make_var_token(token: str, makefile: Path) -> str:
return token.replace("$(ARCH_DIR)", "arch/x86").replace(
"$(BUILD_DIR)", "build"
)
def parse_makefile_sources(root: Path, makefile: Path) -> set[Path]:
text = read_text(root, makefile)
lines = text.splitlines()
sources = set()
index = 0
while index < len(lines):
line = lines[index]
match = re.match(r"^([A-Z0-9_]+)\s*:=", line)
if match == None or match.group(1) not in MAKEFILE_SOURCE_VARS:
index += 1
continue
remainder = line.split(":=", 1)[1].strip()
while True:
continued = remainder.endswith("\\")
remainder = remainder[:-1].strip() if continued else remainder
if remainder:
for token in remainder.split():
if token.endswith(".c"):
sources.add(Path(expand_make_var_token(token, makefile)))
if not continued:
break
index += 1
if index >= len(lines):
break
remainder = lines[index].strip()
index += 1
return sources
def makefile_sources(root: Path) -> set[Path]:
sources = set()
for makefile in sorted(root.rglob("Makefile")):
if "build" in makefile.parts:
continue
sources.update(parse_makefile_sources(root, makefile.relative_to(root)))
return sources
def check_makefile_source_lists(root: Path, files: set[Path], all_mode: bool) -> list[str]:
errors = []
listed = makefile_sources(root)
existing = set(c_files(all_files(root)))
for path in sorted(listed):
if not (root / path).exists():
errors.append(f"{path}: listed in Makefile but file does not exist")
if all_mode:
for path in sorted(existing - listed):
errors.append(f"{path}: C source is not listed in a Makefile source list")
else:
for path in sorted(c_files(files)):
if path not in listed:
errors.append(
f"{path}: changed C source is not listed in a Makefile source list"
)
return errors

View File

@ -0,0 +1,89 @@
def is_function_declaration_start(line: str) -> bool:
stripped = line.strip()
if stripped == "" or stripped.startswith("#"):
return False
if stripped.startswith(("typedef ", "struct ", "enum ", "union ")):
return False
if "(" not in stripped or stripped.startswith("*"):
return False
if "(*" in stripped or stripped.endswith("{"):
return False
return True
def collect_function_declarations(lines: list[str]) -> list[tuple[int, int, str]]:
decls = []
index = 0
while index < len(lines):
if not is_function_declaration_start(lines[index]):
index += 1
continue
start = index
text = lines[index].strip()
while ";" not in lines[index]:
index += 1
if index >= len(lines):
break
text += " " + lines[index].strip()
if index < len(lines):
decls.append((start, index, text))
index += 1
return decls
def is_public_type_start(line: str) -> bool:
stripped = line.strip()
if stripped.startswith(("struct ", "enum ", "union ")):
return stripped.endswith("{") or "{" in stripped
if not stripped.startswith("typedef "):
return False
return "(" not in stripped
def collect_public_type_declarations(lines: list[str]) -> list[tuple[int, int, str]]:
decls = []
index = 0
while index < len(lines):
if not is_public_type_start(lines[index]):
index += 1
continue
start = index
text = lines[index].strip()
while ";" not in lines[index]:
index += 1
if index >= len(lines):
break
text += " " + lines[index].strip()
if index < len(lines):
decls.append((start, index, text))
index += 1
return decls
def symbol_name_from_type_declaration(text: str) -> str:
if text.startswith("typedef "):
return text.rsplit("}", 1)[-1].strip().rstrip(";").split()[-1]
parts = text.split()
if len(parts) >= 2:
return parts[1]
return "<unknown>"

View File

@ -0,0 +1,114 @@
from pathlib import Path
from .common import read_text
from .public_header_decls import (
collect_function_declarations,
collect_public_type_declarations,
symbol_name_from_type_declaration,
)
def is_documented_public_define(lines: list[str], index: int, guard: str | None) -> bool:
stripped = lines[index].strip()
if not stripped.startswith("#define "):
return False
parts = stripped.split()
if len(parts) < 2:
return False
name = parts[1].split("(", 1)[0]
if name == guard:
return False
return True
def header_guard_name(lines: list[str]) -> str | None:
for line in lines:
stripped = line.strip()
if stripped.startswith("#define "):
parts = stripped.split()
if len(parts) >= 2:
return parts[1]
return None
def has_kernel_doc_before(lines: list[str], start: int) -> bool:
index = start - 1
while index >= 0 and lines[index].strip() == "":
index -= 1
if index < 0 or lines[index].strip() != "*/":
return False
while index >= 0:
if lines[index].strip().startswith("/**"):
return True
index -= 1
return False
def has_blank_line_before_doc(lines: list[str], start: int) -> bool:
index = start - 1
while index >= 0 and lines[index].strip() == "":
index -= 1
if index < 0 or lines[index].strip() != "*/":
return True
while index >= 0 and not lines[index].strip().startswith("/**"):
index -= 1
if index <= 0:
return True
return lines[index - 1].strip() == ""
def check_kernel_doc_block(
errors: list[str], path: Path, lines: list[str], start: int, kind: str, name: str
) -> None:
line_no = start + 1
if not has_kernel_doc_before(lines, start):
errors.append(
f"{path}:{line_no}: public {kind} '{name}' needs "
"a kernel-doc comment immediately before it"
)
return
if not has_blank_line_before_doc(lines, start):
errors.append(
f"{path}:{line_no}: public {kind} '{name}' "
"comment block must be separated by a blank line"
)
def check_public_header_docs(root: Path, files: list[Path]) -> list[str]:
errors = []
for path in files:
lines = read_text(root, path).splitlines()
for start, _end, text in collect_function_declarations(lines):
name = text.split("(", 1)[0].split()[-1].lstrip("*")
check_kernel_doc_block(errors, path, lines, start, "function", name)
for start, _end, text in collect_public_type_declarations(lines):
name = symbol_name_from_type_declaration(text)
check_kernel_doc_block(errors, path, lines, start, "type", name)
guard = header_guard_name(lines)
for index, line in enumerate(lines):
if not is_documented_public_define(lines, index, guard):
continue
name = line.strip().split()[1].split("(", 1)[0]
check_kernel_doc_block(errors, path, lines, index, "macro", name)
return errors

View File

@ -0,0 +1,66 @@
import re
from pathlib import Path
from .common import read_text
def check_no_relative_parent_includes(root: Path, files: list[Path]) -> list[str]:
errors = []
include_re = re.compile(r'^\s*#include\s+[<"]\.\./')
for path in files:
for line_no, line in enumerate(read_text(root, path).splitlines(), 1):
if include_re.search(line):
errors.append(
f"{path}:{line_no}: do not use ../ includes; "
"promote shared headers or add a private include root"
)
return errors
def check_no_bare_negative_errno(root: Path, files: list[Path]) -> list[str]:
errors = []
return_re = re.compile(r"\breturn\s+-[0-9]+\s*;")
for path in files:
for line_no, line in enumerate(read_text(root, path).splitlines(), 1):
if return_re.search(line):
errors.append(
f"{path}:{line_no}: return a symbolic negative errno "
"such as -EINVAL instead of a bare negative number"
)
return errors
def check_scheduler_state_writes(root: Path, files: list[Path]) -> list[str]:
errors = []
state_write_re = re.compile(r"->state\s*=")
allowed_path = Path("kernel/sched/sched.h")
for path in files:
if path == allowed_path:
continue
for line_no, line in enumerate(read_text(root, path).splitlines(), 1):
if state_write_re.search(line):
errors.append(
f"{path}:{line_no}: thread state writes must use "
"kernel/sched/sched.h helpers"
)
return errors
def check_selftests_are_centralized(files: list[Path]) -> list[str]:
errors = []
for path in files:
if "selftest" not in path.name:
continue
if len(path.parts) < 2 or path.parts[:2] != ("kernel", "selftest"):
errors.append(f"{path}: kernel selftests belong under kernel/selftest/")
return errors