/* StarForth — Steady-State Virtual Machine Runtime Copyright (c) 2023–2025 Robert A. James All rights reserved. This file is part of the StarForth project. Licensed under the StarForth License, Version 1.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: https://github.com/star.4th@proton.me/StarForth/LICENSE.txt This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and noninfringement. See the License for the specific language governing permissions and limitations under the License. StarForth — Steady-State Virtual Machine Runtime Copyright (c) 2023–2025 Robert A. James All rights reserved. This file is part of the StarForth project. Licensed under the StarForth License, Version 1.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: https://github.com/star.4th@proton.me/StarForth/LICENSE.txt This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and noninfringement. See the License for the specific language governing permissions and limitations under the License. */ /** * uefi_loader.c - UEFI boot loader for StarKernel * * This loader loads the StarKernel ELF binary from the ESP, * parses it, loads segments, applies relocations, and jumps to entry. */ #include "uefi.h" #include "arch.h" #include "elf_loader.h" #include "elf64.h" #include "starkernel/cmdline.h" #include "starkernel/boot_info_offsets.h" #include /* Verify BootInfo field offsets match boot_info_offsets.h (BOOT_INFO_OFFSETS) */ _Static_assert(offsetof(BootInfo, kernel_stack_base) == BOOT_INFO_KERNEL_STACK_BASE_OFFSET, "BOOT_INFO_KERNEL_STACK_BASE_OFFSET mismatch — update boot_info_offsets.h"); _Static_assert(offsetof(BootInfo, kernel_stack_size) == BOOT_INFO_KERNEL_STACK_SIZE_OFFSET, "BOOT_INFO_KERNEL_STACK_SIZE_OFFSET mismatch — update boot_info_offsets.h"); /* For monolithic build: kernel_main is linked directly */ #ifdef MONOLITHIC_BUILD extern void kernel_main(BootInfo *boot_info); #endif #if defined(ARCH_AMD64) #define COM1_BASE 0x3F8 /** * @brief Write a byte to an x86 I/O port via @c OUT (early-boot raw path). * * Used exclusively by the amd64 early-boot serial helper functions * (@c raw_serial_init(), @c raw_serial_putc()) before the kernel console * subsystem is initialised. Operates identically to @c outb() in * @c interrupts.c but is a separate inline to avoid a cross-unit dependency * in the UEFI loader compilation unit. * * Only compiled when @c ARCH_AMD64 is defined. * * @param port 16-bit I/O port address (e.g., @c COM1_BASE + 0 = 0x3F8). * @param val Byte value to write. */ static inline void raw_outb(uint16_t port, uint8_t val) { __asm__ volatile ("outb %0, %1" : : "a"(val), "Nd"(port)); } /** * @brief Read a byte from an x86 I/O port via @c IN (early-boot raw path). * * Used by @c raw_serial_putc() to poll the UART Line Status Register * (COM1 + 5) for the Transmitter Holding Register Empty (THRE) bit * before writing a character, preventing serial output corruption. * Only compiled when @c ARCH_AMD64 is defined. * * @param port 16-bit I/O port address (e.g., @c COM1_BASE + 5 = 0x3FD). * @return Byte value read from the port. */ static inline uint8_t raw_inb(uint16_t port) { uint8_t ret; __asm__ volatile ("inb %1, %0" : "=a"(ret) : "Nd"(port)); return ret; } /** * @brief Initialise COM1 (0x3F8) to 115200 8N1 before UEFI console exits. * * Programs the 16550-compatible UART at base address @c COM1_BASE using * direct port I/O. Sequence: * 1. Disable all UART interrupts (IER = 0x00). * 2. Assert DLAB (Divisor Latch Access Bit) to access the divisor registers. * 3. Write divisor = 1 (115200 baud at 115200 Hz base clock) to DLL and DLH. * 4. Clear DLAB; configure 8N1 (8 data bits, no parity, 1 stop bit; LCR=0x03). * 5. Enable FIFO, clear TX/RX FIFOs, set 14-byte interrupt threshold (FCR=0xC7). * 6. Assert RTS and DSR (MCR=0x0B). * * Called once at the beginning of @c efi_main() on amd64 to ensure the * @c RAW_LOG() macro can emit diagnostic messages throughout the boot * sequence, including phases where the UEFI console is no longer available. * Only compiled when @c ARCH_AMD64 is defined. */ static void raw_serial_init(void) { /* Disable interrupts */ raw_outb(COM1_BASE + 1, 0x00); /* Enable DLAB */ raw_outb(COM1_BASE + 3, 0x80); /* Divisor 1 = 115200 baud */ raw_outb(COM1_BASE + 0, 0x01); raw_outb(COM1_BASE + 1, 0x00); /* 8N1 */ raw_outb(COM1_BASE + 3, 0x03); /* Enable FIFO, clear, 14-byte threshold */ raw_outb(COM1_BASE + 2, 0xC7); /* RTS/DSR set */ raw_outb(COM1_BASE + 4, 0x0B); } /** * @brief Write a single character to COM1 with THRE polling. * * Spins on bit 5 (Transmitter Holding Register Empty) of the Line Status * Register (COM1 + 5) until the UART is ready to accept a new byte, then * writes @p c to the Transmitter Holding Register (COM1 + 0). This busy- * wait is acceptable in the UEFI loader phase because interrupts are * either managed by UEFI or not yet configured by the kernel. * * Only compiled when @c ARCH_AMD64 is defined. * * @param c Character to transmit. */ static void raw_serial_putc(char c) { while ((raw_inb(COM1_BASE + 5) & 0x20) == 0) { } raw_outb(COM1_BASE + 0, (uint8_t)c); } /** * @brief Write a NUL-terminated string to COM1 with implicit LF→CRLF conversion. * * Iterates over @p s and calls @c raw_serial_putc() for each character. * A bare @c '\\n' is preceded by a @c '\\r' to produce proper CRLF line * endings expected by serial terminals. Used via the @c RAW_LOG() macro * for diagnostic output before the kernel console subsystem is live. * * Only compiled when @c ARCH_AMD64 is defined. * * @param s NUL-terminated string to transmit. */ static void raw_serial_puts(const char *s) { while (*s) { char c = *s++; if (c == '\n') raw_serial_putc('\r'); raw_serial_putc(c); } } #define RAW_LOG(str) raw_serial_puts(str) #elif defined(__riscv) || defined(ARCH_RISCV64) /* * QEMU's riscv "virt" machine exposes a 16550-compatible UART (OpenSBI * reports "Platform Console Device: uart8250") as byte-addressed MMIO at * 0x10000000 (matches Domain0 Region03 in the OpenSBI boot banner). This * writes directly to the UART registers, exactly like the amd64 * raw_serial_* helpers above, so RAW_LOG() actually produces output on * riscv64 instead of silently no-op'ing (previously the case for every * arch except amd64). */ #define UART_MMIO_BASE 0x10000000UL static inline void raw_mmio_outb(uint64_t addr, uint8_t val) { *(volatile uint8_t *)addr = val; } static inline uint8_t raw_mmio_inb(uint64_t addr) { return *(volatile uint8_t *)addr; } static void raw_serial_putc(char c) { while ((raw_mmio_inb(UART_MMIO_BASE + 5) & 0x20) == 0) { } raw_mmio_outb(UART_MMIO_BASE + 0, (uint8_t)c); } static void raw_serial_puts(const char *s) { while (*s) { char c = *s++; if (c == '\n') raw_serial_putc('\r'); raw_serial_putc(c); } } #define RAW_LOG(str) raw_serial_puts(str) #else #define RAW_LOG(str) ((void)0) #endif static BootInfo g_boot_info = {0}; /* * Numbered UEFI debug checkpoints (pre-ExitBootServices bring-up aid). * * First-boot debugging on hardware with no serial/UART and no framebuffer * driver yet: ConOut (the UEFI firmware's own text console on the * HDMI/DP-connected monitor) is the only output available before * ExitBootServices(). Each checkpoint prints "[CKPT nnn]