From: Tulio A M Mendes Date: Mon, 16 Feb 2026 18:17:26 +0000 (-0300) Subject: refactor: per-CPU current_process via GS segment (SMP Phase 1) X-Git-Url: https://projects.tadryanom.me/docs/static/gitweb.css?a=commitdiff_plain;h=08a23456f01b52b345f6c7445621f3d7f9446ba2;p=AdrOS.git refactor: per-CPU current_process via GS segment (SMP Phase 1) 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. --- diff --git a/include/process.h b/include/process.h index dccc4ff..30ebc11 100644 --- a/include/process.h +++ b/include/process.h @@ -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); diff --git a/src/arch/x86/interrupts.S b/src/arch/x86/interrupts.S index 5c1f559..70c5ffe 100644 --- a/src/arch/x86/interrupts.S +++ b/src/arch/x86/interrupts.S @@ -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 diff --git a/src/arch/x86/percpu.c b/src/arch/x86/percpu.c index 7a18138..b18bca5 100644 --- a/src/arch/x86/percpu.c +++ b/src/arch/x86/percpu.c @@ -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); diff --git a/src/hal/x86/cpu.c b/src/hal/x86/cpu.c index d863996..779d76d 100644 --- a/src/hal/x86/cpu.c +++ b/src/hal/x86/cpu.c @@ -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 diff --git a/src/kernel/scheduler.c b/src/kernel/scheduler.c index 8ec5f01..e594dce 100644 --- a/src/kernel/scheduler.c +++ b/src/kernel/scheduler.c @@ -14,7 +14,9 @@ #include "sched_pcpu.h" #include +#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;