diff --git a/Makefile b/Makefile index 8ee0fbd..7d98627 100644 --- a/Makefile +++ b/Makefile @@ -66,14 +66,14 @@ $(KERNEL_ELF): $(KERNEL_OBJS) $(ARCH_DIR)/kernel/linker.ld | dirs $(OVMF_VARS): | dirs cp $(OVMF_VARS_TEMPLATE) $@ -run: $(BOOT_EFI) $(OVMF_VARS) +run: $(BOOT_EFI) $(KERNEL_ELF) $(OVMF_VARS) qemu-system-x86_64 \ -drive if=pflash,format=raw,readonly=on,file=$(OVMF_CODE) \ -drive if=pflash,format=raw,file=$(OVMF_VARS) \ -drive format=raw,file=fat:rw:$(BUILD_DIR)/image \ -serial stdio -run-headless: $(BOOT_EFI) $(OVMF_VARS) +run-headless: $(BOOT_EFI) $(KERNEL_ELF) $(OVMF_VARS) rm -f $(DEBUG_LOG) $(SERIAL_LOG) qemu-system-x86_64 \ -display none \ diff --git a/arch/x86/boot/Makefile b/arch/x86/boot/Makefile index 2432692..154abcc 100644 --- a/arch/x86/boot/Makefile +++ b/arch/x86/boot/Makefile @@ -2,6 +2,7 @@ BOOT_SRCS := \ $(ARCH_DIR)/boot/main.c \ $(ARCH_DIR)/boot/file.c \ $(ARCH_DIR)/boot/elf_loader.c \ + $(ARCH_DIR)/boot/framebuffer.c \ $(ARCH_DIR)/boot/memory_map.c \ $(ARCH_DIR)/boot/support.c diff --git a/arch/x86/boot/framebuffer.c b/arch/x86/boot/framebuffer.c new file mode 100644 index 0000000..46197d1 --- /dev/null +++ b/arch/x86/boot/framebuffer.c @@ -0,0 +1,35 @@ +#include + +#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; +} diff --git a/arch/x86/boot/framebuffer.h b/arch/x86/boot/framebuffer.h new file mode 100644 index 0000000..eb895fa --- /dev/null +++ b/arch/x86/boot/framebuffer.h @@ -0,0 +1,19 @@ +#ifndef X86_BOOT_FRAMEBUFFER_H +#define X86_BOOT_FRAMEBUFFER_H + +#include + +#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 diff --git a/arch/x86/boot/main.c b/arch/x86/boot/main.c index 5c69f6d..8a37f16 100644 --- a/arch/x86/boot/main.c +++ b/arch/x86/boot/main.c @@ -4,6 +4,7 @@ #include "efi.h" #include "elf_loader.h" #include "file.h" +#include "framebuffer.h" #include "memory_map.h" 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, boot_banner_text); boot_debug_log_puts("Tianole x86 bootloader loaded.\n"); + (void)boot_capture_framebuffer(system_table, &boot_info); status = boot_read_file(image_handle, system_table, diff --git a/arch/x86/include/efi.h b/arch/x86/include/efi.h index 0a3d83a..51d7df4 100644 --- a/arch/x86/include/efi.h +++ b/arch/x86/include/efi.h @@ -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_B 0x9562 #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__) #define EFIAPI __attribute__((ms_abi)) @@ -58,6 +61,7 @@ typedef struct efi_simple_file_system_protocol efi_simple_file_system_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_graphics_output_protocol efi_graphics_output_protocol_t; typedef struct efi_system_table efi_system_table_t; struct efi_simple_text_output_protocol { @@ -122,6 +126,31 @@ struct efi_loaded_image_protocol { 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 { efi_table_header_t hdr; void *raise_tpl; @@ -164,6 +193,15 @@ struct efi_boot_services { void *get_next_monotonic_count; void(EFIAPI *stall)(efi_uintn_t microseconds); 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 { @@ -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 diff --git a/arch/x86/kernel/Makefile b/arch/x86/kernel/Makefile index c34ffe1..416eb5d 100644 --- a/arch/x86/kernel/Makefile +++ b/arch/x86/kernel/Makefile @@ -3,6 +3,8 @@ ARCH_KERNEL_SRCS := \ $(ARCH_DIR)/kernel/gdt.c \ $(ARCH_DIR)/kernel/idt.c \ $(ARCH_DIR)/kernel/irq.c \ + $(ARCH_DIR)/kernel/screen.c \ + $(ARCH_DIR)/kernel/screen_font.c \ $(ARCH_DIR)/kernel/traps.c ARCH_KERNEL_OBJS := \ diff --git a/arch/x86/kernel/early_log.c b/arch/x86/kernel/early_log.c index 8698d89..a61007d 100644 --- a/arch/x86/kernel/early_log.c +++ b/arch/x86/kernel/early_log.c @@ -4,6 +4,8 @@ #include +#include "screen.h" + #define X86_QEMU_DEBUG_PORT 0xe9 #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); } -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_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_FIFO_CONTROL, 0xc7); outb(X86_COM1_BASE + X86_COM_MODEM_CONTROL, 0x0b); + screen_console_init(boot_info); } void arch_early_log_putc(char ch) { debug_port_putc(ch); serial_putc(ch); + screen_console_putc(ch); } void arch_halt_forever(void) diff --git a/arch/x86/kernel/screen.c b/arch/x86/kernel/screen.c new file mode 100644 index 0000000..00731d9 --- /dev/null +++ b/arch/x86/kernel/screen.c @@ -0,0 +1,210 @@ +#include + +#include + +#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(); + } +} diff --git a/arch/x86/kernel/screen.h b/arch/x86/kernel/screen.h new file mode 100644 index 0000000..ba03870 --- /dev/null +++ b/arch/x86/kernel/screen.h @@ -0,0 +1,18 @@ +#ifndef X86_KERNEL_SCREEN_H +#define X86_KERNEL_SCREEN_H + +#include + +/** + * 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 diff --git a/arch/x86/kernel/screen_font.c b/arch/x86/kernel/screen_font.c new file mode 100644 index 0000000..ffb2bab --- /dev/null +++ b/arch/x86/kernel/screen_font.c @@ -0,0 +1,81 @@ +#include + +#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; +} diff --git a/arch/x86/kernel/screen_font.h b/arch/x86/kernel/screen_font.h new file mode 100644 index 0000000..3771ab1 --- /dev/null +++ b/arch/x86/kernel/screen_font.h @@ -0,0 +1,11 @@ +#ifndef X86_KERNEL_SCREEN_FONT_H +#define X86_KERNEL_SCREEN_FONT_H + +#include + +#define SCREEN_FONT_WIDTH 5u +#define SCREEN_FONT_HEIGHT 7u + +const uint8_t *screen_font_glyph(char ch); + +#endif diff --git a/docs/agents/tasks/01-early-debug.md b/docs/agents/tasks/01-early-debug.md index 5030e6d..6ff82b7 100644 --- a/docs/agents/tasks/01-early-debug.md +++ b/docs/agents/tasks/01-early-debug.md @@ -79,6 +79,7 @@ - kernel early log 已拆成通用前端和 x86 backend。 - x86 backend 已同时写 QEMU debug port 和 COM1。 +- x86 backend 已可在 UEFI GOP framebuffer 上显示 early log,QEMU 图形窗口可直接观察启动日志。 - `panic()` 已接入 early log 和 `arch_halt_forever()`。 - `scripts/check.sh` 已验证 `build/debug.log` 和 `build/serial.log` 中的关键启动行。 diff --git a/docs/agents/tasks/04-time-scheduler.md b/docs/agents/tasks/04-time-scheduler.md index 17ae08c..90177f9 100644 --- a/docs/agents/tasks/04-time-scheduler.md +++ b/docs/agents/tasks/04-time-scheduler.md @@ -154,6 +154,7 @@ - 已建立最小 DEAD 线程回收路径,调度前会释放非当前 DEAD 线程的内核栈和线程对象。 - 已建立统一 `kernel_thread_exit()`/`sched_thread_exit()`,线程入口返回和显式退出都会进入明确退出路径,再由调度安全边界回收非当前 DEAD 线程。 - 已在调度私有头中加入 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`。 - `scripts/check.sh` 已验证 `timer initialized`、`timer tick=1/2/3`、`scheduler initialized`、`kernel thread selftest ok`、timer 驱动线程轮转、`sched_sleep()`、wait queue wakeup、条件等待、超时等待、线程返回退出、显式退出和 DEAD 线程回收。 @@ -167,8 +168,8 @@ ### B. wait queue 锁语义 -- 为 wait queue 增加内部锁或要求调用方持有指定锁,并在接口命名中体现约束。 -- 继续把条件所属数据的修改规则文档化;当前 wait queue 内部锁已经覆盖条件检查、等待入队和 wakeup 队列修改。 +- 已为 wait queue 增加内部锁,并提供显式 locked wakeup 接口;后续继续扩大调用方按条件锁规则更新条件的覆盖面。 +- 继续把条件所属数据的修改规则文档化;当前 wait queue 内部锁已经覆盖条件检查、等待入队、wakeup 队列修改,以及 demo 中的条件修改 + wakeup。 - 区分 `wake_one`、`wake_all`、timeout wakeup 和条件 wakeup 的状态处理。 - 检查 wakeup 是否可能唤醒 DEAD、RUNNING 或未入队线程。 diff --git a/include/tianole/arch.h b/include/tianole/arch.h index 2968583..776b95a 100644 --- a/include/tianole/arch.h +++ b/include/tianole/arch.h @@ -3,12 +3,15 @@ #include +#include + /** * 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(void); +void arch_early_log_init(const boot_info_t *boot_info); /** * arch_early_log_putc() - Emit one character through arch early log devices. diff --git a/include/tianole/boot_info.h b/include/tianole/boot_info.h index a8158b4..f685603 100644 --- a/include/tianole/boot_info.h +++ b/include/tianole/boot_info.h @@ -42,6 +42,12 @@ typedef struct { * @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. @@ -55,12 +61,18 @@ typedef struct { uint64_t memory_descriptor_size; uint32_t memory_descriptor_version; 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_VERSION - Current boot_info_t layout version. */ -#define BOOT_INFO_VERSION 1u +#define BOOT_INFO_VERSION 2u /** * BOOT_FLAG_SERVICES_ACTIVE - Firmware boot services were active at handoff. diff --git a/include/tianole/early_log.h b/include/tianole/early_log.h index 03ac396..42c4992 100644 --- a/include/tianole/early_log.h +++ b/include/tianole/early_log.h @@ -3,13 +3,16 @@ #include +#include + /** * 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(void); +void early_log_init(const boot_info_t *boot_info); /** * early_log_putc() - Write one character to every early log backend. diff --git a/include/tianole/sched.h b/include/tianole/sched.h index b96134e..ecf3ce7 100644 --- a/include/tianole/sched.h +++ b/include/tianole/sched.h @@ -45,8 +45,9 @@ enum thread_state { * @head: Oldest waiting thread. * @tail: Newest waiting thread. * - * The queue owns only wait links. The condition being waited on may be owned - * by another subsystem and must have a documented synchronization rule. + * 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 spinlock lock; @@ -163,6 +164,40 @@ void sched_sleep(uint64_t ticks); */ 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. @@ -177,6 +212,10 @@ void wait_queue_sleep(struct wait_queue *queue); * @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( @@ -189,6 +228,9 @@ int wait_queue_wait( * @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, diff --git a/kernel/early_log.c b/kernel/early_log.c index b4edf37..60a890f 100644 --- a/kernel/early_log.c +++ b/kernel/early_log.c @@ -5,20 +5,20 @@ 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) { return; } - arch_early_log_init(); + arch_early_log_init(boot_info); early_log_ready = 1; } void early_log_putc(char ch) { if (early_log_ready == 0) { - early_log_init(); + early_log_init(0); } arch_early_log_putc(ch); diff --git a/kernel/main.c b/kernel/main.c index d0ae4c0..2af18d6 100644 --- a/kernel/main.c +++ b/kernel/main.c @@ -8,7 +8,7 @@ void kernel_main(const boot_info_t *boot_info) { - early_log_init(); + early_log_init(boot_info); early_log_puts("kernel_main entered\n"); arch_traps_init(); kernel_report_boot_state(boot_info); diff --git a/kernel/sched/wait.c b/kernel/sched/wait.c index 8a680c9..a84eb89 100644 --- a/kernel/sched/wait.c +++ b/kernel/sched/wait.c @@ -91,8 +91,58 @@ static int wait_queue_remove_locked( static void wait_queue_mark_ready_locked(struct thread *thread) { - if (thread_is_waiting(thread) || thread_is_sleeping(thread)) { - thread_set_ready(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); +} + +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); } } @@ -200,7 +250,6 @@ int wait_queue_wait_timeout(struct wait_queue *queue, void wait_queue_wake_one(struct wait_queue *queue) { - struct thread *thread; uint64_t flags; if (queue == 0) { @@ -208,14 +257,12 @@ void wait_queue_wake_one(struct wait_queue *queue) } spin_lock_irqsave(&queue->lock, &flags); - thread = wait_queue_remove_head_locked(queue); - wait_queue_mark_ready_locked(thread); + wait_queue_wake_one_locked(queue); spin_unlock_irqrestore(&queue->lock, flags); } void wait_queue_wake_all(struct wait_queue *queue) { - struct thread *thread; uint64_t flags; if (queue == 0) { @@ -223,9 +270,6 @@ void wait_queue_wake_all(struct wait_queue *queue) } spin_lock_irqsave(&queue->lock, &flags); - while (queue->head != 0) { - thread = wait_queue_remove_head_locked(queue); - wait_queue_mark_ready_locked(thread); - } + wait_queue_wake_all_locked(queue); spin_unlock_irqrestore(&queue->lock, flags); } diff --git a/kernel/selftest/sched.c b/kernel/selftest/sched.c index 20eb019..674483a 100644 --- a/kernel/selftest/sched.c +++ b/kernel/selftest/sched.c @@ -162,13 +162,17 @@ static void condition_wait_demo_waiter(void *arg) static void condition_wait_demo_waker(void *arg) { + uint64_t flags; + (void)arg; early_log_puts("condition waker sleeping\n"); sched_sleep(5); + wait_queue_lock_irqsave(&condition_wait_queue, &flags); condition_ready = 1; 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) @@ -209,23 +213,36 @@ static void explicit_exit_demo_thread(void *arg) void sched_demo_start(void) { - struct thread *first = kernel_thread_create( + struct thread *first; + struct thread *second; + struct thread *waiter; + struct thread *waker; + struct thread *condition_waiter; + struct thread *condition_waker; + struct thread *timeout_waiter; + struct thread *return_exit; + struct thread *explicit_exit; + + wait_queue_init(&demo_wait_queue); + wait_queue_init(&condition_wait_queue); + wait_queue_init(&timeout_wait_queue); + condition_ready = 0; + + first = kernel_thread_create( "round-robin-a", scheduler_demo_entry, (void *)(uintptr_t)1); - struct thread *second = kernel_thread_create( + second = kernel_thread_create( "round-robin-b", scheduler_demo_entry, (void *)(uintptr_t)2); - struct thread *waiter = - kernel_thread_create("waiter", wait_queue_demo_waiter, 0); - struct thread *waker = - kernel_thread_create("waker", wait_queue_demo_waker, 0); - struct thread *condition_waiter = kernel_thread_create( + 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); - struct thread *condition_waker = kernel_thread_create( + condition_waker = kernel_thread_create( "condition-waker", condition_wait_demo_waker, 0); - struct thread *timeout_waiter = kernel_thread_create( + timeout_waiter = kernel_thread_create( "timeout-waiter", timeout_wait_demo_waiter, 0); - struct thread *return_exit = + return_exit = kernel_thread_create("return-exit", return_exit_demo_thread, 0); - struct thread *explicit_exit = kernel_thread_create( + explicit_exit = kernel_thread_create( "explicit-exit", explicit_exit_demo_thread, 0); if (first == 0 || second == 0 || waiter == 0 || waker == 0 || @@ -234,10 +251,6 @@ void sched_demo_start(void) panic("scheduler demo thread creation failed"); } - wait_queue_init(&demo_wait_queue); - wait_queue_init(&condition_wait_queue); - wait_queue_init(&timeout_wait_queue); - condition_ready = 0; early_log_puts("scheduler starting\n"); sched_yield();