]> 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 81465ba4f56dbda79611d0c1be8033898e449f05..81b5b078e4dea1461893ec3301ed3bdbed8b9e9c 100644 (file)
@@ -11,6 +11,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 e7cff5ac3a7de627591c741f8064c7709728daf7..1f932187ad8417c7df0eadaf3ee8bbc3c2b63e18 100644 (file)
@@ -446,6 +446,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 fa085562c9594ab8706bae0b2dd4dd6529cdf6c2..bf6fb54a295304d2f2c953e1f8da34eed8f551ab 100644 (file)
@@ -205,7 +205,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 9da314f350053cfe69132138e5098424509692dc..27fc190493a7571e17f968f7fef7a6613f0a3653 100644 (file)
@@ -77,6 +77,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 */
@@ -85,7 +90,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 09d5a0b464f85fedef24485a28249bc44f187b12..7813a0c1ecd2d1576c4f82c241fa94c1b0c2e570 100644 (file)
 void hal_uart_init(void) {
 }
 
+int hal_uart_is_present(void) {
+    return 1;
+}
+
 void hal_uart_drain_rx(void) {
 }
 
index 52ca3ba35f20211468a026ae94a0bbea96bc1559..e5b0e8d6ec6a5cf17ab0fb8a5a239b433101bd6c 100644 (file)
@@ -22,6 +22,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 ab78d8924a85e00cf0b9d3726f37120b6447e393..cf01e8e409e74f5cc712a39cdabb89c52861ad38 100644 (file)
@@ -18,6 +18,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 9428848f4f4d44785aa269533f0399f51190ad23..7ddadd1b7b961c22bfbbe945d77b5825f5e6a335 100644 (file)
@@ -15,6 +15,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) {
@@ -26,6 +27,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 */
@@ -41,7 +57,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
@@ -78,6 +99,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
@@ -93,12 +115,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 3417abaa79d145d13204cbf0c5869b665b8bf0b0..f9f3fb6d58e2427b280c651ebce7e33e6a1ae90e 100644 (file)
@@ -46,7 +46,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 90463c00fa3354ea1f544454a2c16ab7d00be2d7..312c175b601e881077a6b9f207a2d25fa5321951 100644 (file)
@@ -3042,7 +3042,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);