]> Projects (at) Tadryanom (dot) Me - AdrOS.git/commitdiff
fix: VirtualBox compatibility — UART detection, alarm timing, usermode segments,...
authorTulio A M Mendes <[email protected]>
Tue, 17 Feb 2026 00:12:25 +0000 (21:12 -0300)
committerTulio A M Mendes <[email protected]>
Tue, 17 Feb 2026 00:12:25 +0000 (21:12 -0300)
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.

include/hal/uart.h
src/arch/x86/idt.c
src/arch/x86/lapic.c
src/arch/x86/usermode.c
src/hal/arm/uart.c
src/hal/mips/uart.c
src/hal/riscv/uart.c
src/hal/x86/uart.c
src/kernel/console.c
user/init.c

index 7548ce9c3b7e55f054a085a38543719d9ca5433f..4d430ff3776a26586135f464986adb22c5a9ee2b 100644 (file)
@@ -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);
index fea48e59e8a3fbdc1230cbbaa76b8bd2da8a9c23..dd8079c1f42b5e3ad4717adfd5ff1b67e5f0c10b 100644 (file)
@@ -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
index dae84578cd28b4954482534d9dfa0a72e1b12a58..0217e25f4c18063bb7f7c5e5ed3890589d59ca4e 100644 (file)
@@ -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);
 }
 
index 8638684100e521431dd93c6e809bf91721919097..75c547ec6626085fd76071920f99fe108483c0ad 100644 (file)
@@ -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();
index 477f6af449e80ac10cadc078a13eb8728d83e7a6..5fdf737c1db48f3c611a43f8f65ac74e873ea9a0 100644 (file)
@@ -6,6 +6,10 @@
 void hal_uart_init(void) {
 }
 
+int hal_uart_is_present(void) {
+    return 1;
+}
+
 void hal_uart_drain_rx(void) {
 }
 
index 035f5ae4a72679b31b8b780a59e9eaee9bb77fcd..961117acb9633d28455a4baefdf424025c9c541a 100644 (file)
@@ -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);
index e39d71728c9226d35f3446686bf1cdf9d148f8a6..352710c6c875bbead3d60aa3e80752b83ea96415 100644 (file)
@@ -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);
index b62b94c18a67cd45beed8cf370056f7165bf5e1d..049854fbe2d1e0b202fac3005663ab3703212f04 100644 (file)
@@ -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);
     }
index e3f5eac8dfb261559bbd059f4449cd4f415ebae2..2ffdcd7aecb55f89acee1e55ecef96b5989840d2 100644 (file)
@@ -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) {
index 01bf586b95019756db2cdb2f301d23baaa0cdca0..e75b3f2a45de934f897674d69c33c38467d76a23 100644 (file)
@@ -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);