From: Tulio A M Mendes Date: Tue, 17 Feb 2026 00:12:25 +0000 (-0300) Subject: fix: VirtualBox compatibility — UART detection, alarm timing, usermode segments,... X-Git-Url: https://projects.tadryanom.me/sitemap.xml?a=commitdiff_plain;h=fbb15d5e93edb52af04ec841ae88d77792a4cdf5;p=AdrOS.git fix: VirtualBox compatibility — UART detection, alarm timing, usermode segments, user-mode #GP 4 fixes for VirtualBox compatibility + 1 cosmetic: 1. UART hardware detection (fixes boot freeze with serial disabled) - hal_uart_init() now probes the scratch register before configuring - All UART operations (putc, drain_rx, poll_rx, try_getc) guarded behind uart_present flag — prevents infinite loop on floating bus - console_init() auto-enables VGA when no UART detected so boot messages are visible - Added hal_uart_is_present() API + stubs for ARM/MIPS/RISC-V 2. alarm/SIGALRM test: replace 20M-iteration busy-loop with nanosleep polling (50ms × 40 = 2s max wait). Fast VirtualBox CPUs completed the busy-loop before the 1-second alarm fired. 3. x86_enter_usermode: load DS/ES/FS/GS=0x23 before iret to ring 3. Without this, iret nulls segment registers (kernel DPL=0 < new CPL=3 per Intel SDM §6.12.1). On QEMU this was masked by early context switches that fixed DS via x86_enter_usermode_regs, but VirtualBox with Hyper-V acceleration may expose the race window. 4. User-mode exception handling: deliver SIGSEGV for any ring-3 exception (#GP, #UD, etc.) instead of kernel panic. Previously only #PF (14) had this handling. A user-mode #GP now kills the process cleanly instead of halting the entire system. 5. LAPIC timer ticks printed in decimal instead of hex. 89/89 smoke tests pass, cppcheck clean. --- diff --git a/include/hal/uart.h b/include/hal/uart.h index 7548ce9..4d430ff 100644 --- a/include/hal/uart.h +++ b/include/hal/uart.h @@ -2,6 +2,7 @@ #define HAL_UART_H void hal_uart_init(void); +int hal_uart_is_present(void); void hal_uart_drain_rx(void); void hal_uart_poll_rx(void); void hal_uart_putc(char c); diff --git a/src/arch/x86/idt.c b/src/arch/x86/idt.c index fea48e5..dd8079c 100644 --- a/src/arch/x86/idt.c +++ b/src/arch/x86/idt.c @@ -437,6 +437,18 @@ void isr_handler(struct registers* regs) { } else { // If Exception (0-31), Panic if (regs->int_no < 32) { + // User-mode exceptions (ring 3): deliver SIGSEGV instead of panicking. + // Handles #GP (13), #PF (14), and other faults from user code. + if ((regs->cs & 3U) == 3U && regs->int_no != 14) { + const int SIG_SEGV = 11; + if (current_process) { + current_process->last_fault_addr = regs->eip; + current_process->sig_pending_mask |= (1U << (uint32_t)SIG_SEGV); + } + deliver_signals_to_usermode(regs); + return; + } + if (regs->int_no == 14) { // If page fault came from ring3, convert it into a SIGSEGV delivery. // Default action for SIGSEGV will terminate the process, but a user diff --git a/src/arch/x86/lapic.c b/src/arch/x86/lapic.c index dae8457..0217e25 100644 --- a/src/arch/x86/lapic.c +++ b/src/arch/x86/lapic.c @@ -196,7 +196,7 @@ void lapic_timer_start(uint32_t frequency_hz) { lapic_write(LAPIC_TIMER_DCR, LAPIC_TIMER_DIV_16); lapic_write(LAPIC_TIMER_ICR, ticks_per_interrupt); - kprintf("[LAPIC] Timer started at %uHz (ticks=0x%x)\n", + kprintf("[LAPIC] Timer started at %uHz (ticks=%u)\n", (unsigned)frequency_hz, ticks_per_interrupt); } diff --git a/src/arch/x86/usermode.c b/src/arch/x86/usermode.c index 8638684..75c547e 100644 --- a/src/arch/x86/usermode.c +++ b/src/arch/x86/usermode.c @@ -68,6 +68,11 @@ __attribute__((noreturn)) void x86_enter_usermode(uintptr_t user_eip, uintptr_t __asm__ volatile( "cli\n" + "mov $0x23, %%ax\n" /* user data segment (GDT entry 4, RPL=3) */ + "mov %%ax, %%ds\n" + "mov %%ax, %%es\n" + "mov %%ax, %%fs\n" + "mov %%ax, %%gs\n" "pushl $0x23\n" /* ss */ "pushl %[esp]\n" /* esp */ "pushl $0x202\n" /* eflags: IF=1 */ @@ -76,7 +81,7 @@ __attribute__((noreturn)) void x86_enter_usermode(uintptr_t user_eip, uintptr_t "iret\n" : : [eip] "r"(user_eip), [esp] "r"(user_esp) - : "memory" + : "memory", "eax" ); __builtin_unreachable(); diff --git a/src/hal/arm/uart.c b/src/hal/arm/uart.c index 477f6af..5fdf737 100644 --- a/src/hal/arm/uart.c +++ b/src/hal/arm/uart.c @@ -6,6 +6,10 @@ void hal_uart_init(void) { } +int hal_uart_is_present(void) { + return 1; +} + void hal_uart_drain_rx(void) { } diff --git a/src/hal/mips/uart.c b/src/hal/mips/uart.c index 035f5ae..961117a 100644 --- a/src/hal/mips/uart.c +++ b/src/hal/mips/uart.c @@ -13,6 +13,10 @@ void hal_uart_init(void) { /* Minimal init: assume firmware/QEMU defaults are usable */ } +int hal_uart_is_present(void) { + return 1; +} + void hal_uart_drain_rx(void) { while (mmio_read8(UART_BASE + 5) & 0x01) (void)mmio_read8(UART_BASE); diff --git a/src/hal/riscv/uart.c b/src/hal/riscv/uart.c index e39d717..352710c 100644 --- a/src/hal/riscv/uart.c +++ b/src/hal/riscv/uart.c @@ -9,6 +9,10 @@ void hal_uart_init(void) { mmio_write8(UART_BASE + 1, 0x01); } +int hal_uart_is_present(void) { + return 1; +} + void hal_uart_drain_rx(void) { while (mmio_read8(UART_BASE + 5) & 0x01) (void)mmio_read8(UART_BASE); diff --git a/src/hal/x86/uart.c b/src/hal/x86/uart.c index b62b94c..049854f 100644 --- a/src/hal/x86/uart.c +++ b/src/hal/x86/uart.c @@ -6,6 +6,7 @@ #define UART_BASE 0x3F8 +static int uart_present = 0; static void (*uart_rx_cb)(char) = 0; static void uart_irq_handler(struct registers* regs) { @@ -17,6 +18,21 @@ static void uart_irq_handler(struct registers* regs) { } void hal_uart_init(void) { + /* Detect UART hardware via scratch register (offset 7). + * Write a test value, read it back. If no 16550 is present the + * floating ISA bus returns 0xFF for all reads, so the test fails. */ + outb(UART_BASE + 7, 0xA5); + if (inb(UART_BASE + 7) != 0xA5) { + uart_present = 0; + return; /* No UART — skip all configuration */ + } + outb(UART_BASE + 7, 0x5A); + if (inb(UART_BASE + 7) != 0x5A) { + uart_present = 0; + return; + } + uart_present = 1; + outb(UART_BASE + 1, 0x00); /* Disable all interrupts */ outb(UART_BASE + 3, 0x80); /* Enable DLAB */ outb(UART_BASE + 0, 0x03); /* Baud 38400 */ @@ -32,7 +48,12 @@ void hal_uart_init(void) { outb(UART_BASE + 1, 0x01); } +int hal_uart_is_present(void) { + return uart_present; +} + void hal_uart_drain_rx(void) { + if (!uart_present) return; /* Full UART interrupt reinitialisation for IOAPIC hand-off. * * hal_uart_init() runs under the legacy PIC and enables IER bit 0 @@ -69,6 +90,7 @@ void hal_uart_drain_rx(void) { } void hal_uart_poll_rx(void) { + if (!uart_present) return; /* Timer-driven fallback: drain any pending characters from the * UART FIFO via polling. Called from the timer tick handler so * serial input works even if the IOAPIC edge-triggered IRQ for @@ -84,12 +106,14 @@ void hal_uart_set_rx_callback(void (*cb)(char)) { } void hal_uart_putc(char c) { + if (!uart_present) return; int timeout = 100000; while ((inb(UART_BASE + 5) & 0x20) == 0 && --timeout > 0) { } outb(UART_BASE, (uint8_t)c); } int hal_uart_try_getc(void) { + if (!uart_present) return -1; if (inb(UART_BASE + 5) & 0x01) { return (int)inb(UART_BASE); } diff --git a/src/kernel/console.c b/src/kernel/console.c index e3f5eac..2ffdcd7 100644 --- a/src/kernel/console.c +++ b/src/kernel/console.c @@ -37,7 +37,11 @@ static void klog_append(const char* s, size_t len) { void console_init(void) { spinlock_init(&g_console_lock); g_console_uart_enabled = 1; - g_console_vga_enabled = 0; + /* If no UART hardware is present, auto-enable VGA so boot messages + * are visible instead of being silently dropped. */ + if (!hal_uart_is_present()) { + g_console_vga_enabled = 1; + } } void console_enable_uart(int enabled) { diff --git a/user/init.c b/user/init.c index 01bf586..e75b3f2 100644 --- a/user/init.c +++ b/user/init.c @@ -3033,7 +3033,13 @@ void _start(void) { (void)sys_sigaction(SIGALRM, alrm_handler, 0); got_alrm = 0; (void)sys_alarm(1); - for (volatile uint32_t i = 0; i < 20000000U && !got_alrm; i++) { } + /* Wait up to 2 seconds for the alarm to fire. A busy-loop may + * complete too quickly on fast CPUs (e.g. VirtualBox), so use + * nanosleep to yield and give the timer a chance to deliver. */ + for (int _w = 0; _w < 40 && !got_alrm; _w++) { + struct timespec _ts = {0, 50000000}; /* 50ms */ + (void)sys_nanosleep(&_ts, 0); + } if (!got_alrm) { sys_write(1, "[init] alarm/SIGALRM not delivered\n", (uint32_t)(sizeof("[init] alarm/SIGALRM not delivered\n") - 1)); sys_exit(1);