]> Projects (at) Tadryanom (dot) Me - AdrOS.git/commitdiff
fix: ISR GS clobber, serial IRQ stuck, ring3 page fault
authorTulio A M Mendes <[email protected]>
Sat, 14 Feb 2026 21:07:29 +0000 (18:07 -0300)
committerTulio A M Mendes <[email protected]>
Sat, 14 Feb 2026 21:07:29 +0000 (18:07 -0300)
1. **ISR GS clobber (III) — FIXED**
   - interrupts.S: save/restore GS separately instead of overwriting
     with 0x10. DS/ES/FS still set to kernel data, but GS now
     preserves the per-CPU selector across interrupt entry/exit.
   - struct registers: new 'gs' field at offset 0.
   - ARCH_REGS_SIZE: 64 → 68.
   - x86_enter_usermode_regs: updated all hardcoded register offsets
     (+4 for the new GS field).

2. **Serial keyboard blocking (II) — FIXED**
   - Root cause: hal_uart_init() runs early (under PIC), enabling
     UART RX interrupts. Later, IOAPIC routes IRQ 4 as edge-triggered.
     If any character arrived between PIC-era init and IOAPIC setup,
     the UART IRQ line stays asserted — the IOAPIC never sees a
     rising edge, permanently blocking all future serial input.
   - Fix: hal_uart_drain_rx() clears pending UART FIFO + IIR + MSR
     immediately after ioapic_route_irq(4, ...) to de-assert the
     IRQ line and allow future edges.

3. **Ring3 page fault at 0xae1000 (V) — FIXED**
   - The ring3 code emitter wrote to code_phys as a virtual address,
     relying on an identity mapping that doesn't exist for all
     physical addresses. Now uses P2V (phys + 0xC0000000) to access
     physical pages via the kernel's higher-half mapping.

Tests: 20/20 smoke (8s), 16/16 battery, cppcheck clean

include/arch/x86/arch_types.h
include/arch/x86/idt.h
include/hal/uart.h
src/arch/x86/arch_platform.c
src/arch/x86/interrupts.S
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

index 8296c37a5b389979a93a457360472781b0339367..a55ee3ece219de3e97b2889ca6089acd0e556cd0 100644 (file)
@@ -3,11 +3,11 @@
 
 /*
  * Size in bytes of the saved user-mode register frame (struct registers).
- * x86: ds(4) + pusha(32) + int_no+err(8) + iret_frame(20) = 64
+ * x86: gs(4) + ds(4) + pusha(32) + int_no+err(8) + iret_frame(20) = 68
  *
  * Used by struct process to hold an opaque register snapshot without
  * pulling in the full x86 IDT / register definitions.
  */
-#define ARCH_REGS_SIZE 64
+#define ARCH_REGS_SIZE 68
 
 #endif /* ARCH_X86_TYPES_H */
index 3319dee12b661af2f18a16cea2f69ace22a3be61..670e601f61fd1610de8586e901deab55a5fba011 100644 (file)
@@ -20,7 +20,8 @@ struct idt_ptr {
 
 /* Registers saved by our assembly ISR stub */
 struct registers {
-    uint32_t ds;                                     // Data segment selector
+    uint32_t gs;                                     // Per-CPU GS selector (pushed second)
+    uint32_t ds;                                     // Data segment selector (pushed first)
     uint32_t edi, esi, ebp, esp, ebx, edx, ecx, eax; // Pushed by pusha
     uint32_t int_no, err_code;                       // Interrupt number and error code
     uint32_t eip, cs, eflags, useresp, ss;           // Pushed by the processor automatically
index bdb71732202bf7675288d31c249e0d478b826ef3..0efb76f007baea832ee1bbe82f1f32d78741002e 100644 (file)
@@ -2,6 +2,7 @@
 #define HAL_UART_H
 
 void hal_uart_init(void);
+void hal_uart_drain_rx(void);
 void hal_uart_putc(char c);
 int  hal_uart_try_getc(void);
 void hal_uart_set_rx_callback(void (*cb)(char));
index 2cd54304067760ef13bdcb65547f14a3502678da..450f5a49da693bcc56f2e6d500632142dfaf0ea3 100644 (file)
@@ -14,6 +14,7 @@
 #include "heap.h"
 
 #include "hal/cpu.h"
+#include "hal/uart.h"
 #include "hal/usermode.h"
 #include "kernel/cmdline.h"
 
@@ -136,6 +137,7 @@ int arch_platform_setup(const struct boot_info* bi) {
             ioapic_route_irq(0,  32, (uint8_t)bsp_id);
             ioapic_route_irq(1,  33, (uint8_t)bsp_id);
             ioapic_route_irq(4,  36, (uint8_t)bsp_id); /* COM1 serial */
+            hal_uart_drain_rx(); /* Clear stale UART FIFO so edge-triggered IRQ4 isn't stuck high */
             ioapic_route_irq_level(11, 43, (uint8_t)bsp_id); /* E1000 NIC (PCI: level-triggered, active-low) */
             ioapic_route_irq(14, 46, (uint8_t)bsp_id); /* ATA primary */
             ioapic_route_irq(15, 47, (uint8_t)bsp_id); /* ATA secondary */
index 51fc18df97d7ed869de0d13ad3c813772e37abc4..5c1f559a3f4a0e79171711e90b70a5abc8ca2062 100644 (file)
@@ -9,36 +9,38 @@
 isr_common_stub:
     /* Save all registers */
     pusha
-    
-    /* Save Data Segment */
+
+    /* Save segment selectors (DS and GS saved separately) */
     mov %ds, %ax
     push %eax
-    
-    /* Load Kernel Data Segment */
+    push %gs
+
+    /* Load Kernel Data Segment for DS/ES/FS.
+     * GS is NOT touched — it holds the per-CPU selector set by
+     * percpu_setup_gs() and must survive ISR entry/exit. */
     mov $0x10, %ax
     mov %ax, %ds
     mov %ax, %es
     mov %ax, %fs
-    mov %ax, %gs
-    
+
     /* Call C handler */
     push %esp   /* Pass pointer to stack structure */
     call isr_handler
     add $4, %esp
-    
-    /* Restore Data Segment */
+
+    /* Restore GS (per-CPU) then DS/ES/FS */
+    pop %gs
     pop %eax
     mov %ax, %ds
     mov %ax, %es
     mov %ax, %fs
-    mov %ax, %gs
-    
+
     /* Restore registers */
     popa
-    
+
     /* Clean up error code and ISR number */
     add $8, %esp
-    
+
     /* Return from interrupt */
     iret
 
index 8a4e4b64e2f6ce64aab6e2d22d465cdcfc08c4c3..28a154720bedade604b9b5b744ecc10f35265310 100644 (file)
@@ -100,6 +100,10 @@ __attribute__((noreturn)) void x86_enter_usermode_regs(const struct registers* r
     }
 
     // Layout follows include/arch/x86/idt.h struct registers.
+    // struct registers { gs(0), ds(4), edi(8), esi(12), ebp(16),
+    //   esp(20), ebx(24), edx(28), ecx(32), eax(36),
+    //   int_no(40), err_code(44), eip(48), cs(52), eflags(56),
+    //   useresp(60), ss(64) };
     const uint32_t eflags = (regs->eflags | 0x200U);
 
     /* Use ESI as scratch to hold regs pointer, since we'll overwrite
@@ -116,18 +120,18 @@ __attribute__((noreturn)) void x86_enter_usermode_regs(const struct registers* r
         "mov %%ax, %%gs\n"
 
         "pushl $0x23\n"           /* ss */
-        "pushl 56(%%esi)\n"       /* useresp */
+        "pushl 60(%%esi)\n"       /* useresp */
         "pushl %[efl]\n"          /* eflags */
         "pushl $0x1B\n"           /* cs */
-        "pushl 44(%%esi)\n"       /* eip */
-
-        "mov 4(%%esi), %%edi\n"   /* edi */
-        "mov 12(%%esi), %%ebp\n"  /* ebp */
-        "mov 20(%%esi), %%ebx\n"  /* ebx */
-        "mov 24(%%esi), %%edx\n"  /* edx */
-        "mov 28(%%esi), %%ecx\n"  /* ecx */
-        "mov 32(%%esi), %%eax\n"  /* eax */
-        "mov 8(%%esi), %%esi\n"   /* esi (last — self-overwrite) */
+        "pushl 48(%%esi)\n"       /* eip */
+
+        "mov  8(%%esi), %%edi\n"  /* edi */
+        "mov 16(%%esi), %%ebp\n"  /* ebp */
+        "mov 24(%%esi), %%ebx\n"  /* ebx */
+        "mov 28(%%esi), %%edx\n"  /* edx */
+        "mov 32(%%esi), %%ecx\n"  /* ecx */
+        "mov 36(%%esi), %%eax\n"  /* eax */
+        "mov 12(%%esi), %%esi\n"  /* esi (last — self-overwrite) */
         "iret\n"
         :
         : [r] "r"(regs),
@@ -168,7 +172,10 @@ void x86_usermode_test_start(void) {
     const uint32_t t3_fail_len = 8;
     const uint32_t msg_len = 18;
 
-    struct emitter e = { .buf = (uint8_t*)(uintptr_t)code_phys, .pos = 0 };
+    /* Access the physical page via the kernel higher-half mapping (P2V)
+     * instead of relying on an identity mapping that may not exist. */
+    const uintptr_t code_kva = (uintptr_t)code_phys + 0xC0000000U;
+    struct emitter e = { .buf = (uint8_t*)code_kva, .pos = 0 };
 
     /* T1: write(valid buf) -> t1_ok_len */
     emit_mov_eax_imm(&e, SYSCALL_WRITE_NO);
@@ -248,19 +255,19 @@ void x86_usermode_test_start(void) {
     patch_rel8(e.buf, t3_fail_jne.at, t3_fail_pos);
     patch_rel8(e.buf, t3_to_exit.at, exit_pos);
 
-    memcpy((void*)((uintptr_t)code_phys + 0x200), "T1 OK\n", t1_ok_len);
-    memcpy((void*)((uintptr_t)code_phys + 0x210), "T1 FAIL\n", t1_fail_len);
-    memcpy((void*)((uintptr_t)code_phys + 0x220), "T2 OK\n", t2_ok_len);
-    memcpy((void*)((uintptr_t)code_phys + 0x230), "T2 FAIL\n", t2_fail_len);
-    memcpy((void*)((uintptr_t)code_phys + 0x240), "T3 OK\n", t3_ok_len);
-    memcpy((void*)((uintptr_t)code_phys + 0x250), "T3 FAIL\n", t3_fail_len);
-    memcpy((void*)((uintptr_t)code_phys + 0x300), "Hello from ring3!\n", msg_len);
+    memcpy((void*)(code_kva + 0x200), "T1 OK\n", t1_ok_len);
+    memcpy((void*)(code_kva + 0x210), "T1 FAIL\n", t1_fail_len);
+    memcpy((void*)(code_kva + 0x220), "T2 OK\n", t2_ok_len);
+    memcpy((void*)(code_kva + 0x230), "T2 FAIL\n", t2_fail_len);
+    memcpy((void*)(code_kva + 0x240), "T3 OK\n", t3_ok_len);
+    memcpy((void*)(code_kva + 0x250), "T3 FAIL\n", t3_fail_len);
+    memcpy((void*)(code_kva + 0x300), "Hello from ring3!\n", msg_len);
 
     /* Create a private address space so the ring3 user pages do NOT
      * pollute kernel_as (which is shared by all kernel threads).
-     * Code/data was emitted above via the identity mapping still
-     * active in kernel_as; now we switch to the new AS and map the
-     * physical pages at their user virtual addresses. */
+     * Code/data was emitted above via P2V (kernel higher-half mapping);
+     * now we switch to the new AS and map the physical pages at their
+     * user virtual addresses. */
     uintptr_t ring3_as = vmm_as_create_kernel_clone();
     if (!ring3_as) {
         kprintf("[USER] Failed to create ring3 address space.\n");
index cb86767970d6878ff0d2243205f414e50c7ae64b..f9ca6a0f3cb0ce2028bb3b3fc7a47bec33e06020 100644 (file)
@@ -6,6 +6,9 @@
 void hal_uart_init(void) {
 }
 
+void hal_uart_drain_rx(void) {
+}
+
 void hal_uart_putc(char c) {
     volatile uint32_t* uart = (volatile uint32_t*)UART_BASE;
     while (uart[6] & (1 << 5)) { }
index b170005633fa7359342ab80e45d52e77ae13b9e4..96b55d66422d604dbed303a3ecee018e4048362f 100644 (file)
@@ -11,6 +11,11 @@ void hal_uart_init(void) {
     /* Minimal init: assume firmware/QEMU defaults are usable */
 }
 
+void hal_uart_drain_rx(void) {
+    while (mmio_read8(UART_BASE + 5) & 0x01)
+        (void)mmio_read8(UART_BASE);
+}
+
 void hal_uart_putc(char c) {
     while ((mmio_read8(UART_BASE + 5) & 0x20) == 0) { }
     mmio_write8(UART_BASE, (uint8_t)c);
index 231bf1b498c8a0eee60955e1d384626de1575a83..4ca44ac112c29df79cd1bff936ef67100208fd68 100644 (file)
@@ -9,6 +9,11 @@ void hal_uart_init(void) {
     mmio_write8(UART_BASE + 1, 0x01);
 }
 
+void hal_uart_drain_rx(void) {
+    while (mmio_read8(UART_BASE + 5) & 0x01)
+        (void)mmio_read8(UART_BASE);
+}
+
 void hal_uart_putc(char c) {
     while ((mmio_read8(UART_BASE + 5) & 0x20) == 0) { }
     mmio_write8(UART_BASE, (uint8_t)c);
index 1c5b55eab918f0ee64b8c8258c8357cd70b544ce..4935d5e1c049dec12ba1da748be036761043fcd6 100644 (file)
@@ -32,6 +32,16 @@ void hal_uart_init(void) {
     outb(UART_BASE + 1, 0x01);
 }
 
+void hal_uart_drain_rx(void) {
+    /* Drain any pending characters from the UART FIFO.
+     * This de-asserts the IRQ line so that the next character
+     * produces a clean rising edge for the IOAPIC (edge-triggered). */
+    (void)inb(UART_BASE + 2);          /* Read IIR to ack any pending */
+    while (inb(UART_BASE + 5) & 0x01)  /* Drain RX FIFO */
+        (void)inb(UART_BASE);
+    (void)inb(UART_BASE + 6);          /* Read MSR to clear delta bits */
+}
+
 void hal_uart_set_rx_callback(void (*cb)(char)) {
     uart_rx_cb = cb;
 }