feat(kernel): add early serial log backend
This commit is contained in:
parent
dbbae27de7
commit
9a8c9f14ef
16
Makefile
16
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)
|
||||
|
||||
19
arch/x86/boot/debug_log.h
Normal file
19
arch/x86/boot/debug_log.h
Normal file
@ -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
|
||||
@ -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");
|
||||
}
|
||||
|
||||
@ -1,41 +0,0 @@
|
||||
#ifndef X86_EARLY_LOG_H
|
||||
#define X86_EARLY_LOG_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
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
|
||||
@ -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))
|
||||
|
||||
75
arch/x86/kernel/early_log.c
Normal file
75
arch/x86/kernel/early_log.c
Normal file
@ -0,0 +1,75 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#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");
|
||||
}
|
||||
}
|
||||
@ -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 格式。
|
||||
- 异常栈输出。
|
||||
- 内核符号化输出。
|
||||
|
||||
@ -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 做准备。
|
||||
|
||||
## 任务路由
|
||||
|
||||
|
||||
8
include/tianole/arch.h
Normal file
8
include/tianole/arch.h
Normal file
@ -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
|
||||
12
include/tianole/early_log.h
Normal file
12
include/tianole/early_log.h
Normal file
@ -0,0 +1,12 @@
|
||||
#ifndef TIANOLE_EARLY_LOG_H
|
||||
#define TIANOLE_EARLY_LOG_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
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
|
||||
@ -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) \
|
||||
|
||||
68
kernel/early_log.c
Normal file
68
kernel/early_log.c
Normal file
@ -0,0 +1,68 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#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();
|
||||
}
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -31,4 +31,21 @@ for line in "${required_lines[@]}"; do
|
||||
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
|
||||
|
||||
cat build/debug.log
|
||||
|
||||
Loading…
Reference in New Issue
Block a user