/* Nadir kernel entry point: first C code to run. * * Called once from the stage-2 entry stub after the CPU is in 64-bit long * mode with a valid stack, zeroed BSS and identity-mapped memory. Runs at * Ring 0 (CPL 0) with IF=0 on entry under the stage-2 GDT (CS 0x18); * `-mno-red-zone` keeps IRQ pushes off the red zone so interrupt frames * are safe. Installs the exception IDT first so from then on a fault * prints diagnostics instead of triple-faulting. `sti` runs exactly once * after every device is programmed; the main loop keeps IF=1 and sleeps * in `hlt` (which wakes on each IRQ) — never `cli`-parks, or timer ticks * and keystrokes would stop. It never returns; on return the caller halts. * * Every line goes through klog`[ 0.000000] subsystem: message`, * time from pit_ticks()/PIT_HZ). * Serial COM1 mirrors the same text without the timestamp prefix. */ #include #include "console.h" #include "idt.h" #include "keyboard.h" #include "klog.h" #include "kmalloc.h" #include "pic.h" #include "pit.h" #include "pmm.h" #include "serial.h" /* A value that only comes out right if the CPU truly executes 64-bit * arithmetic: every nibble survives the shifts above 32 bits. */ #define PROOF_VALUE 0x0123456789ABCDEFULL /* Mirror one echoed keystroke to COM1 when the UART looks present. */ static void serial_echo(char c, int ok) { char tmp[2]; if (ok == 0) { return; } if (c == '\b') { /* Erase on a serial terminal: back, blank, back. */ serial_puts("\b \b"); } else if (c == '\n') { serial_puts("\r\n"); } else { tmp[0] = c; tmp[1] = '\0'; serial_puts(tmp); } } void kmain(void) { int serial_present; uint64_t last_uptime; console_clear(); /* Boot banner: keeps the "64-bit" needle the CI boot checker greps * for (see .github/scripts/check_boot.py). */ klog("nadir", "Hello World from Nadir kernel (64-bit long mode, Ring 0)"); /* WHY this order: pic_remap must precede idt_install_irqs so IRQ * gates land on valid remapped vectors 32-47 (pre-remap they would * overlap CPU exceptions 0-15 and a tick would look like a fault); * devices (serial/PIT/keyboard) are programmed before sti so no IRQ * fires into a half-wired handler; pic_remap masks everything and * each device opts in via pic_unmask, so stray lines stay silent. */ idt_init(); klog("idt", "exceptions 0-31 installed"); pic_remap(); klog("pic", "remapped IRQ 0-15 to vectors 32-47"); idt_install_irqs(); klog("idt", "IRQ gates 32-47 installed"); serial_init(); serial_present = serial_ok(); klog("serial", "COM1 38400 8N1 ready"); if (serial_present != 0) { serial_puts("Hello World from Nadir kernel\n"); } pit_init(); klog("pit", "100Hz on IRQ0"); if (serial_present != 0) { serial_puts("pit: 100Hz on IRQ0\n"); } keyboard_init(); klog("keyboard", "PS/2 ready on IRQ1"); if (serial_present != 0) { serial_puts("keyboard: PS/2 ready on IRQ1\n"); } /* pmm_init only reads the staged E820 globals and writes * its own .bss bitmap, so it runs * after all devices are programmed but before sti, with IF=0, so no * handler can observe a half-built allocator. Stats go through the * polling VGA console, which is IRQ-safe by construction. */ pmm_init(memmap_count, memmap_entries); klog_begin("pmm"); klog_str("E820 entries "); klog_dec(memmap_count); klog_str(", usable frames "); klog_dec(pmm_usable_count()); klog_str(" / total frames "); klog_dec(pmm_total_count()); klog_str(", free frames "); klog_dec(pmm_free_count()); klog_str(", reserved "); klog_dec(pmm_usable_count() - pmm_free_count()); klog_end(); /* Heap self-test. init, then alloc/write/free churn to prove the * free-list split and re-insert paths work before interrupts go * live. Runs with IF=0, polling console only. */ kmalloc_init(); { uint8_t *a = kmalloc(100); uint8_t *b = kmalloc(5000); /* spans 2 frames */ uint8_t *c = kmalloc(1); int ok = 1; if (a == 0 || b == 0 || c == 0) { ok = 0; } else { uint64_t i; for (i = 0; i < 100U; i++) { a[i] = (uint8_t)i; } for (i = 0; i < 100U; i++) { if (a[i] != (uint8_t)i) { ok = 0; } } b[0] = 0xAAU; b[4999] = 0x55U; if (b[0] != 0xAAU || b[4999] != 0x55U) { ok = 0; } } kfree(a); kfree(b); kfree(c); kfree(0); /* NULL must be a no-op */ klog_begin("kmalloc"); klog_str("self-test "); klog_str(ok != 0 ? "ok" : "FAILED"); klog_str(", free frames "); klog_dec(kmalloc_free_frames()); klog_str(", used frames "); klog_dec(kmalloc_used_frames()); klog_end(); } /* Explicit opt-in for the two live lines; idempotent even though * keyboard_init already unmasked IRQ1. */ pic_unmask(0); pic_unmask(1); __asm__ volatile("sti"); klog("nadir", "interrupts enabled (IRQs 0-1)"); if (serial_present != 0) { serial_puts("nadir: interrupts enabled (IRQs 0-1)\n"); } klog_begin("nadir"); klog_str("64-bit proof value: "); klog_hex64(PROOF_VALUE); klog_end(); klog("nadir", "halting in hlt loop (timer + keyboard live)"); /* Main loop: hlt sleeps until the next IRQ (IF=1 throughout), then * one poll pass prints uptime ~1/s and drains echoed keys. */ last_uptime = 0; for (;;) { uint64_t now; __asm__ volatile("hlt"); now = pit_ticks(); if ((now - last_uptime) >= 100U) { uint64_t secs = now / 100U; uint64_t tenths = (now / 10U) % 10U; last_uptime = now; klog_begin("nadir"); klog_str("uptime "); klog_dec(secs); klog_str("."); klog_dec(tenths); klog_str("s (ticks "); klog_hex64(now); klog_str(")"); klog_end(); if (serial_present != 0) { serial_puts("uptime (see VGA for value)\n"); } } while (keyboard_has_key() != 0) { char c = keyboard_dequeue(); /* console_putchar erases on '\b' and wraps on '\n'. */ console_putchar(c); serial_echo(c, serial_present); } } }