diff --git a/Makefile b/Makefile index 3738e20..cd676c9 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,7 @@ LD := lld-link BUILD_DIR := build KERNEL_ELF := $(BUILD_DIR)/image/kernel.elf DEBUG_LOG := $(BUILD_DIR)/debug.log +SERIAL_LOG := $(BUILD_DIR)/serial.log ifeq ($(ARCH),x86_64) ARCH_MAKEFILE := arch/x86/Makefile @@ -28,7 +29,7 @@ all: $(BOOT_EFI) $(KERNEL_ELF) dirs: mkdir -p $(BUILD_DIR)/arch/boot $(BUILD_DIR)/arch/kernel $(BUILD_DIR)/kernel $(EFI_DIR) -$(BUILD_DIR)/arch/boot/%.obj: $(ARCH_DIR)/boot/%.c $(ARCH_DIR)/include/efi.h $(ARCH_DIR)/include/tianole/early_log.h include/tianole/boot_info.h include/tianole/elf.h | dirs +$(BUILD_DIR)/arch/boot/%.obj: $(ARCH_DIR)/boot/%.c $(ARCH_DIR)/include/efi.h $(ARCH_DIR)/boot/debug_log.h include/tianole/boot_info.h include/tianole/elf.h | dirs $(CC) $(CFLAGS) -c $< -o $@ $(BOOT_EFI): $(BOOT_OBJS) | dirs @@ -37,7 +38,10 @@ $(BOOT_EFI): $(BOOT_OBJS) | dirs $(BUILD_DIR)/arch/kernel/entry.o: $(ARCH_DIR)/kernel/entry.S | dirs $(CC) $(KERNEL_ASFLAGS) -c $< -o $@ -$(BUILD_DIR)/kernel/%.o: kernel/%.c include/tianole/boot_info.h include/tianole/kernel_init.h $(ARCH_DIR)/include/tianole/early_log.h | dirs +$(BUILD_DIR)/arch/kernel/%.o: $(ARCH_DIR)/kernel/%.c include/tianole/arch.h | dirs + $(CC) $(KERNEL_CFLAGS) -c $< -o $@ + +$(BUILD_DIR)/kernel/%.o: kernel/%.c include/tianole/boot_info.h include/tianole/kernel_init.h include/tianole/arch.h include/tianole/early_log.h | dirs $(CC) $(KERNEL_CFLAGS) -c $< -o $@ $(KERNEL_ELF): $(KERNEL_OBJS) $(ARCH_DIR)/kernel/linker.ld | dirs @@ -50,10 +54,11 @@ run: $(BOOT_EFI) $(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 + -drive format=raw,file=fat:rw:$(BUILD_DIR)/image \ + -serial stdio run-headless: $(BOOT_EFI) $(OVMF_VARS) - rm -f $(DEBUG_LOG) + rm -f $(DEBUG_LOG) $(SERIAL_LOG) qemu-system-x86_64 \ -display none \ -nodefaults \ @@ -62,7 +67,8 @@ run-headless: $(BOOT_EFI) $(OVMF_VARS) -drive if=pflash,format=raw,file=$(OVMF_VARS) \ -drive format=raw,file=fat:rw:$(BUILD_DIR)/image \ -debugcon file:$(DEBUG_LOG) \ - -global isa-debugcon.iobase=0xe9 + -global isa-debugcon.iobase=0xe9 \ + -serial file:$(SERIAL_LOG) clean: rm -rf $(BUILD_DIR) diff --git a/arch/x86/boot/debug_log.h b/arch/x86/boot/debug_log.h new file mode 100644 index 0000000..e7a7c12 --- /dev/null +++ b/arch/x86/boot/debug_log.h @@ -0,0 +1,19 @@ +#ifndef X86_BOOT_DEBUG_LOG_H +#define X86_BOOT_DEBUG_LOG_H + +static inline void boot_debug_log_putc(char ch) +{ + __asm__ volatile("outb %0, $0xe9" : : "a"(ch)); +} + +static inline void boot_debug_log_puts(const char *text) +{ + while (*text != '\0') { + if (*text == '\n') { + boot_debug_log_putc('\r'); + } + boot_debug_log_putc(*text++); + } +} + +#endif diff --git a/arch/x86/boot/main.c b/arch/x86/boot/main.c index 18b95f4..cf1cece 100644 --- a/arch/x86/boot/main.c +++ b/arch/x86/boot/main.c @@ -1,9 +1,9 @@ +#include "debug_log.h" #include "efi.h" #include "elf_loader.h" #include "file.h" #include "memory_map.h" #include "tianole/boot_info.h" -#include "tianole/early_log.h" static efi_char16_t boot_banner_text[] = u"Tianole x86 bootloader.\r\n"; static efi_char16_t kernel_path_text[] = u"\\kernel.elf"; @@ -22,7 +22,7 @@ efi_status EFIAPI efi_main( system_table->con_out->output_string( system_table->con_out, boot_banner_text); - early_log_puts("Tianole x86 bootloader loaded.\n"); + boot_debug_log_puts("Tianole x86 bootloader loaded.\n"); status = boot_read_file(image_handle, system_table, @@ -30,28 +30,28 @@ efi_status EFIAPI efi_main( &kernel_image, &kernel_size); if (status != EFI_SUCCESS) { - early_log_puts("failed: read kernel.elf\n"); + boot_debug_log_puts("failed: read kernel.elf\n"); return status; } status = boot_load_kernel_elf( system_table, kernel_image, kernel_size, &kernel_entry); if (status != EFI_SUCCESS) { - early_log_puts("failed: load kernel image\n"); + boot_debug_log_puts("failed: load kernel image\n"); return status; } status = boot_exit_services_with_latest_memory_map( image_handle, system_table, &boot_info); if (status != EFI_SUCCESS) { - early_log_puts("failed: exit boot services\n"); + boot_debug_log_puts("failed: exit boot services\n"); return status; } - early_log_puts("jumping to kernel entry\n"); + boot_debug_log_puts("jumping to kernel entry\n"); kernel_entry(&boot_info); - early_log_puts("kernel returned unexpectedly\n"); + boot_debug_log_puts("kernel returned unexpectedly\n"); for (;;) { __asm__ volatile("hlt"); } diff --git a/arch/x86/include/tianole/early_log.h b/arch/x86/include/tianole/early_log.h deleted file mode 100644 index 8038823..0000000 --- a/arch/x86/include/tianole/early_log.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef X86_EARLY_LOG_H -#define X86_EARLY_LOG_H - -#include - -static inline void early_log_putc(char ch) -{ - __asm__ volatile("outb %0, $0xe9" : : "a"(ch)); -} - -static inline void early_log_puts(const char *text) -{ - while (*text != '\0') { - if (*text == '\n') { - early_log_putc('\r'); - } - early_log_putc(*text++); - } -} - -static inline void early_log_u64_decimal(uint64_t value) -{ - char digits[20]; - uint32_t index = 0; - - if (value == 0) { - early_log_putc('0'); - return; - } - - while (value != 0) { - digits[index++] = (char)('0' + (value % 10)); - value /= 10; - } - - while (index != 0) { - early_log_putc(digits[--index]); - } -} - -#endif diff --git a/arch/x86/kernel/Makefile b/arch/x86/kernel/Makefile index 40f5feb..5f277d5 100644 --- a/arch/x86/kernel/Makefile +++ b/arch/x86/kernel/Makefile @@ -1,2 +1,6 @@ +ARCH_KERNEL_SRCS := \ + $(ARCH_DIR)/kernel/early_log.c + ARCH_KERNEL_OBJS := \ - $(BUILD_DIR)/arch/kernel/entry.o + $(BUILD_DIR)/arch/kernel/entry.o \ + $(patsubst $(ARCH_DIR)/kernel/%.c,$(BUILD_DIR)/arch/kernel/%.o,$(ARCH_KERNEL_SRCS)) diff --git a/arch/x86/kernel/early_log.c b/arch/x86/kernel/early_log.c new file mode 100644 index 0000000..12d92fa --- /dev/null +++ b/arch/x86/kernel/early_log.c @@ -0,0 +1,75 @@ +#include + +#include "tianole/arch.h" + +#define X86_QEMU_DEBUG_PORT 0xe9 + +#define X86_COM1_BASE 0x3f8 +#define X86_COM_DATA 0 +#define X86_COM_INTERRUPT_ENABLE 1 +#define X86_COM_FIFO_CONTROL 2 +#define X86_COM_LINE_CONTROL 3 +#define X86_COM_MODEM_CONTROL 4 +#define X86_COM_LINE_STATUS 5 + +#define X86_COM_LCR_DLAB 0x80 +#define X86_COM_LCR_8N1 0x03 +#define X86_COM_LSR_THRE 0x20 + +static inline void outb(uint16_t port, uint8_t value) +{ + __asm__ volatile("outb %0, %1" : : "a"(value), "Nd"(port)); +} + +static inline uint8_t inb(uint16_t port) +{ + uint8_t value; + + __asm__ volatile("inb %1, %0" : "=a"(value) : "Nd"(port)); + return value; +} + +static void debug_port_putc(char ch) +{ + outb(X86_QEMU_DEBUG_PORT, (uint8_t)ch); +} + +static void serial_putc(char ch) +{ + uint32_t timeout; + + for (timeout = 0; timeout < 100000; timeout++) { + if ((inb(X86_COM1_BASE + X86_COM_LINE_STATUS) & + X86_COM_LSR_THRE) != 0) { + break; + } + } + + outb(X86_COM1_BASE + X86_COM_DATA, (uint8_t)ch); +} + +void arch_early_log_init(void) +{ + 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_DATA, 0x03); + outb(X86_COM1_BASE + X86_COM_INTERRUPT_ENABLE, 0x00); + 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); +} + +void arch_early_log_putc(char ch) +{ + debug_port_putc(ch); + serial_putc(ch); +} + +void arch_halt_forever(void) +{ + __asm__ volatile("cli"); + + for (;;) { + __asm__ volatile("hlt"); + } +} diff --git a/docs/agents/tasks/01-early-debug.md b/docs/agents/tasks/01-early-debug.md index a17d4d4..100baeb 100644 --- a/docs/agents/tasks/01-early-debug.md +++ b/docs/agents/tasks/01-early-debug.md @@ -37,3 +37,18 @@ - QEMU 日志中能看到 bootloader 和 kernel 输出。 - 串口或 debug log 中能看到 `boot services exited`。 - 人为触发 panic 时能看到 panic 信息并停止。 + +## 当前状态 + +已完成: + +- kernel early log 已拆成通用前端和 x86 backend。 +- x86 backend 已同时写 QEMU debug port 和 COM1。 +- `panic()` 已接入 early log 和 `arch_halt_forever()`。 +- `scripts/check.sh` 已验证 `build/debug.log` 和 `build/serial.log` 中的关键启动行。 + +后续进入异常阶段时继续补齐: + +- oops 格式。 +- 异常栈输出。 +- 内核符号化输出。 diff --git a/docs/roadmap.md b/docs/roadmap.md index 1a610e4..a712b24 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -13,15 +13,16 @@ - 进入 kernel 前调用 `ExitBootServices`。 - `memory map` 通过 `boot_info` 传入 kernel。 - kernel 能统计 memory map 描述符数量和 conventional memory 页数。 -- x86 早期日志接口已集中到 `arch/x86/include/tianole/early_log.h`。 +- kernel early log 已拆成通用前端和 x86 backend。 +- x86 early log backend 同时输出到 QEMU debug port 和 COM1 串口。 +- 最小 `panic()` 已接入 early log 和架构 halt 路径。 - 构建系统已拆成根 Makefile、`arch/x86/Makefile` 和目录 Makefile。 -- `scripts/check.sh` 和 GitHub Actions 已接入。 +- `scripts/check.sh` 已验证启动日志和串口日志,GitHub Actions 已接入。 - `.clang-format` 已用于强制当前 C 代码风格。 还没有完成: -- COM1 串口日志。 -- panic/oops 早期错误路径。 +- oops 早期错误路径。 - GDT/IDT/异常/中断。 - 物理页分配器、虚拟内存和内核堆。 - 调度、进程、文件系统、用户态和 shell。 @@ -30,19 +31,14 @@ 下一步执行: -- `docs/agents/tasks/01-early-debug.md` +- `docs/agents/tasks/02-cpu-interrupts.md` 目标: -- 加入 COM1 串口 backend。 -- 保留当前 QEMU debug port backend。 -- 提供统一 early log 前端。 -- 增加最小 `panic()`。 -- 让启动日志可以通过 firmware 之外的路径稳定输出。 - -这一步完成后进入: - -- `docs/agents/tasks/02-cpu-interrupts.md` +- 建立 x86_64 GDT/TSS/IDT。 +- 建立异常入口和 trap frame。 +- 让未处理异常进入 panic。 +- 为后续 IRQ、timer 和 page fault 做准备。 ## 任务路由 diff --git a/include/tianole/arch.h b/include/tianole/arch.h new file mode 100644 index 0000000..757dad1 --- /dev/null +++ b/include/tianole/arch.h @@ -0,0 +1,8 @@ +#ifndef TIANOLE_ARCH_H +#define TIANOLE_ARCH_H + +void arch_early_log_init(void); +void arch_early_log_putc(char ch); +void arch_halt_forever(void) __attribute__((noreturn)); + +#endif diff --git a/include/tianole/early_log.h b/include/tianole/early_log.h new file mode 100644 index 0000000..59bc5b2 --- /dev/null +++ b/include/tianole/early_log.h @@ -0,0 +1,12 @@ +#ifndef TIANOLE_EARLY_LOG_H +#define TIANOLE_EARLY_LOG_H + +#include + +void early_log_init(void); +void early_log_putc(char ch); +void early_log_puts(const char *text); +void early_log_u64_decimal(uint64_t value); +void panic(const char *message) __attribute__((noreturn)); + +#endif diff --git a/kernel/Makefile b/kernel/Makefile index 32e6638..a567341 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -1,6 +1,7 @@ KERNEL_SRCS := \ kernel/main.c \ - kernel/boot_report.c + kernel/boot_report.c \ + kernel/early_log.c KERNEL_OBJS := \ $(ARCH_KERNEL_OBJS) \ diff --git a/kernel/early_log.c b/kernel/early_log.c new file mode 100644 index 0000000..13d3c32 --- /dev/null +++ b/kernel/early_log.c @@ -0,0 +1,68 @@ +#include + +#include "tianole/arch.h" +#include "tianole/early_log.h" + +static int early_log_ready; + +void early_log_init(void) +{ + if (early_log_ready != 0) { + return; + } + + arch_early_log_init(); + early_log_ready = 1; +} + +void early_log_putc(char ch) +{ + if (early_log_ready == 0) { + early_log_init(); + } + + arch_early_log_putc(ch); +} + +void early_log_puts(const char *text) +{ + if (text == 0) { + return; + } + + while (*text != '\0') { + if (*text == '\n') { + early_log_putc('\r'); + } + early_log_putc(*text++); + } +} + +void early_log_u64_decimal(uint64_t value) +{ + char digits[20]; + uint32_t index = 0; + + if (value == 0) { + early_log_putc('0'); + return; + } + + while (value != 0) { + digits[index++] = (char)('0' + (value % 10)); + value /= 10; + } + + while (index != 0) { + early_log_putc(digits[--index]); + } +} + +void panic(const char *message) +{ + early_log_puts("panic: "); + early_log_puts(message); + early_log_puts("\n"); + + arch_halt_forever(); +} diff --git a/kernel/main.c b/kernel/main.c index e47dbe7..aeadaea 100644 --- a/kernel/main.c +++ b/kernel/main.c @@ -3,6 +3,7 @@ void kernel_main(const boot_info_t *boot_info) { + early_log_init(); early_log_puts("kernel_main entered\n"); kernel_report_boot_state(boot_info); diff --git a/scripts/check.sh b/scripts/check.sh index d56cbb8..e338b92 100755 --- a/scripts/check.sh +++ b/scripts/check.sh @@ -23,10 +23,27 @@ required_lines=( ) for line in "${required_lines[@]}"; do - if ! grep -F "$line" build/debug.log >/dev/null; then + if ! grep -F "$line" build/debug.log >/dev/null; then echo "missing expected boot log line: $line" >&2 echo "--- build/debug.log ---" >&2 cat build/debug.log >&2 || true + exit 1 + fi +done + +serial_required_lines=( + "kernel_main entered" + "boot_info.version ok" + "boot services exited" + "memory map descriptors=" + "conventional memory pages=" +) + +for line in "${serial_required_lines[@]}"; do + if ! grep -F "$line" build/serial.log >/dev/null; then + echo "missing expected serial log line: $line" >&2 + echo "--- build/serial.log ---" >&2 + cat build/serial.log >&2 || true exit 1 fi done