]> Projects (at) Tadryanom (dot) Me - AdrOS.git/commitdiff
refactor: per-CPU current_process via GS segment (SMP Phase 1)
authorTulio A M Mendes <[email protected]>
Mon, 16 Feb 2026 18:17:26 +0000 (15:17 -0300)
committerTulio A M Mendes <[email protected]>
Mon, 16 Feb 2026 18:17:26 +0000 (15:17 -0300)
Replace the global current_process variable with per-CPU access
through the GS-based percpu_data structure on x86:

- process.h: #define current_process percpu_current() on x86,
  keeps extern fallback for non-x86
- scheduler.c: write sites use percpu_set_current()
- interrupts.S: ISR entry now reloads percpu GS by reading LAPIC ID
  from MMIO (0xC0400020) and looking up the correct GS selector in
  _percpu_gs_lut[256] — solves the chicken-and-egg problem of
  needing GS to find the CPU but GS being clobbered by user TLS
- percpu.c: _percpu_gs_lut lookup table populated during percpu_init()
- hal_cpu_set_tls: no longer loads GS immediately (would clobber
  kernel percpu GS); user TLS GS is restored on ISR exit via pop

This is the foundation for running the scheduler on AP cores.

83/83 smoke tests pass (9s), cppcheck clean.

include/process.h
src/arch/x86/interrupts.S
src/arch/x86/percpu.c
src/hal/x86/cpu.c
src/kernel/scheduler.c

index dccc4ff382e77bc706d0fa61e6d6e477608f2982..30ebc11da98bd08dd2f02cd1231ff5dcb086098c 100644 (file)
@@ -133,8 +133,15 @@ struct process {
     uint8_t fpu_state[FPU_STATE_SIZE] __attribute__((aligned(FPU_STATE_ALIGN)));
 };
 
-// Global pointer to the currently running process
+// Per-CPU pointer to the currently running process.
+// On x86 SMP this reads from the GS-based percpu_data; on non-x86 it
+// falls back to a plain global (single-CPU only).
+#ifdef __i386__
+#include "arch/x86/percpu.h"
+#define current_process  percpu_current()
+#else
 extern struct process* current_process;
+#endif
 
 // Initialize the multitasking system
 void process_init(void);
index 5c1f559a3f4a0e79171711e90b70a5abc8ca2062..70c5ffe1324b2d15632023f61c49aff70378e3f7 100644 (file)
@@ -15,20 +15,28 @@ isr_common_stub:
     push %eax
     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. */
+    /* Load Kernel Data Segment for DS/ES/FS */
     mov $0x10, %ax
     mov %ax, %ds
     mov %ax, %es
     mov %ax, %fs
 
+    /* Reload per-CPU GS: read LAPIC ID from MMIO, look up GS selector.
+     * This is necessary because user mode may have changed GS (e.g. TLS).
+     * LAPIC_ID register is at KVA_LAPIC (0xC0400000) + 0x20.
+     * The ID is in bits 31:24 of the register value. */
+    mov 0xC0400020, %eax
+    shr $24, %eax
+    lea _percpu_gs_lut, %ecx
+    movzwl (%ecx,%eax,2), %eax
+    mov %ax, %gs
+
     /* Call C handler */
     push %esp   /* Pass pointer to stack structure */
     call isr_handler
     add $4, %esp
 
-    /* Restore GS (per-CPU) then DS/ES/FS */
+    /* Restore user GS then DS/ES/FS */
     pop %gs
     pop %eax
     mov %ax, %ds
index 7a18138bb1ac392662b573932067c384a2a16782..b18bca5e282509e9b8f7aa316bfd6e74434ee66c 100644 (file)
@@ -9,6 +9,12 @@
 
 static struct percpu_data g_percpu[SMP_MAX_CPUS];
 
+/* Lookup table: LAPIC ID → percpu GS selector.
+ * Used by the ISR entry stub (interrupts.S) to reload the correct
+ * percpu GS without needing GS itself (chicken-and-egg).
+ * Indexed by LAPIC ID (0–255), entries are 16-bit GDT selectors. */
+uint16_t _percpu_gs_lut[256] __attribute__((aligned(4)));
+
 /* We use GDT entries 6..6+N for per-CPU GS segments.
  * GDT layout: 0=null, 1=kcode, 2=kdata, 3=ucode, 4=udata, 5=TSS, 6+=percpu */
 #define PERCPU_GDT_BASE  6
@@ -28,6 +34,10 @@ void percpu_init(void) {
     uint32_t ncpus = smp_get_cpu_count();
     if (ncpus > SMP_MAX_CPUS) ncpus = SMP_MAX_CPUS;
 
+    /* Clear lookup table — default to BSP's GS selector as fallback */
+    uint16_t bsp_sel = (uint16_t)(PERCPU_GDT_BASE * 8);
+    for (int j = 0; j < 256; j++) _percpu_gs_lut[j] = bsp_sel;
+
     for (uint32_t i = 0; i < ncpus; i++) {
         const struct cpu_info* ci = smp_get_cpu(i);
         g_percpu[i].cpu_index = i;
@@ -38,6 +48,10 @@ void percpu_init(void) {
 
         /* Create a GDT entry for this CPU's GS segment */
         set_percpu_gdt_entry(PERCPU_GDT_BASE + i, (uint32_t)(uintptr_t)&g_percpu[i]);
+
+        /* Populate LAPIC-ID → GS-selector lookup table */
+        uint16_t sel = (uint16_t)((PERCPU_GDT_BASE + i) * 8);
+        if (ci) _percpu_gs_lut[ci->lapic_id] = sel;
     }
 
     kprintf("[PERCPU] Initialized for %u CPU(s).\n", (unsigned)ncpus);
index d863996c38afa79967f5035f91578bc253de1b36..779d76da489f9d12a79792f2f124d0e9f97cf876 100644 (file)
@@ -57,10 +57,9 @@ uint64_t hal_cpu_read_timestamp(void) {
 void hal_cpu_set_tls(uintptr_t base) {
     /* GDT entry 22: user TLS segment (ring 3, data RW) */
     gdt_set_gate_ext(22, (uint32_t)base, 0xFFFFF, 0xF2, 0xCF);
-    __asm__ volatile(
-        "mov $0xB3, %%ax\n"
-        "mov %%ax, %%gs\n" : : : "ax"
-    ); /* selector = 22*8 | RPL=3 = 0xB3 */
+    /* Do NOT reload GS here — kernel GS must stay as percpu selector.
+     * The user TLS GS (selector 0xB3) is loaded when returning to ring3
+     * via the saved register state on the interrupt/syscall stack. */
 }
 
 #else
index 8ec5f01c4538126adc61775e306ae7351d2b14d8..e594dcef6a850bb5c5987077f6a3abc0baa412b6 100644 (file)
@@ -14,7 +14,9 @@
 #include "sched_pcpu.h"
 #include <stddef.h>
 
+#ifndef __i386__
 struct process* current_process = NULL;
+#endif
 struct process* ready_queue_head = NULL;
 struct process* ready_queue_tail = NULL;
 static uint32_t next_pid = 1;
@@ -783,7 +785,7 @@ void process_init(void) {
     }
     kernel_proc->kernel_stack = (uint32_t*)kstack0;
 
-    current_process = kernel_proc;
+    percpu_set_current(kernel_proc);
     ready_queue_head = kernel_proc;
     ready_queue_tail = kernel_proc;
     kernel_proc->next = kernel_proc;
@@ -938,7 +940,7 @@ void schedule(void) {
         return;
     }
 
-    current_process = next;
+    percpu_set_current(next);
     current_process->state = PROCESS_RUNNING;
     current_process->time_slice = SCHED_TIME_SLICE;