// Initialize IDT and PIC
void idt_init(void);
+// Load IDT on an AP (same IDT as BSP, just needs lidt)
+void idt_load_ap(void);
+
// Register a custom handler for a specific interrupt
typedef void (*isr_handler_t)(struct registers*);
void register_interrupt_handler(uint8_t n, isr_handler_t handler);
/* Start the LAPIC timer at the given frequency (approximate). */
void lapic_timer_start(uint32_t frequency_hz);
+/* Start LAPIC timer on an AP using BSP-calibrated ticks (no PIT recalibration). */
+void lapic_timer_start_ap(void);
+
/* Stop the LAPIC timer. */
void lapic_timer_stop(void);
kprintf("[IDT] Loaded.\n");
}
+void idt_load_ap(void) {
+ __asm__ volatile("lidt %0" : : "m"(idtp));
+}
+
void register_interrupt_handler(uint8_t n, isr_handler_t handler) {
uintptr_t flags = spin_lock_irqsave(&idt_handlers_lock);
static volatile uint32_t* lapic_base = 0;
static int lapic_active = 0;
+static uint32_t lapic_timer_ticks_saved = 0; /* BSP-calibrated ticks for AP reuse */
uint64_t rdmsr(uint32_t msr) {
uint32_t lo, hi;
if (ticks_per_interrupt == 0) ticks_per_interrupt = 1;
+ /* Save calibrated value for AP reuse */
+ lapic_timer_ticks_saved = ticks_per_interrupt;
+
/* Configure periodic timer */
lapic_write(LAPIC_TIMER_LVT, LAPIC_TIMER_PERIODIC | LAPIC_TIMER_VEC);
lapic_write(LAPIC_TIMER_DCR, LAPIC_TIMER_DIV_16);
(unsigned)frequency_hz, ticks_per_interrupt);
}
+void lapic_timer_start_ap(void) {
+ /* Start LAPIC timer on an AP using BSP-calibrated ticks.
+ * All CPUs share the same bus clock, so the same ticks value works. */
+ if (!lapic_active || lapic_timer_ticks_saved == 0) return;
+
+ lapic_write(LAPIC_TIMER_LVT, LAPIC_TIMER_PERIODIC | LAPIC_TIMER_VEC);
+ lapic_write(LAPIC_TIMER_DCR, LAPIC_TIMER_DIV_16);
+ lapic_write(LAPIC_TIMER_ICR, lapic_timer_ticks_saved);
+}
+
void lapic_timer_stop(void) {
if (!lapic_active) return;
lapic_write(LAPIC_TIMER_LVT, LAPIC_LVT_MASKED);
#define AP_TRAMPOLINE_PHYS 0x8000U
#define AP_DATA_PHYS 0x8F00U
-/* Per-AP kernel stack size */
-#define AP_STACK_SIZE 4096U
+/* Per-AP kernel stack size — must be large enough for sched_ap_init()
+ * which calls kmalloc, kstack_alloc, kprintf under sched_lock. */
+#define AP_STACK_SIZE 8192U
#define KERNEL_VIRT_BASE 0xC0000000U
static struct cpu_info g_cpus[SMP_MAX_CPUS];
static volatile uint32_t g_cpu_count = 0;
+/* Flag set by BSP after process_init + timer_init to signal APs
+ * that they can safely initialize their per-CPU schedulers. */
+volatile uint32_t ap_sched_go = 0;
+
/* AP kernel stacks — statically allocated */
static uint8_t ap_stacks[SMP_MAX_CPUS][AP_STACK_SIZE] __attribute__((aligned(16)));
/* Called by each AP after it enters protected mode + paging.
* This runs on the AP's own stack. */
void ap_entry(void) {
+ /* Load the IDT on this AP (BSP already initialized it, APs just need lidt) */
+ idt_load_ap();
+
/* Enable LAPIC on this AP */
uint64_t apic_base_msr = rdmsr(0x1B);
if (!(apic_base_msr & (1ULL << 11))) {
uint32_t my_id = lapic_get_id();
/* Find our cpu_info slot, set up per-CPU GS, and mark started */
+ uint32_t my_cpu = 0;
for (uint32_t i = 0; i < g_cpu_count; i++) {
if (g_cpus[i].lapic_id == (uint8_t)my_id) {
+ my_cpu = i;
percpu_setup_gs(i);
__atomic_store_n(&g_cpus[i].started, 1, __ATOMIC_SEQ_CST);
break;
}
}
- /* AP is now idle — halt until needed.
- * In the future, each AP will run its own scheduler. */
+ /* Wait for BSP to finish scheduler init (process_init sets PID 0).
+ * We check by waiting for the ap_sched_go flag set by the BSP after
+ * timer_init completes. */
+ while (!__atomic_load_n(&ap_sched_go, __ATOMIC_ACQUIRE)) {
+ __asm__ volatile("pause");
+ }
+
+ /* Initialize this AP's scheduler: create idle process + set current */
+ extern void sched_ap_init(uint32_t);
+ sched_ap_init(my_cpu);
+
+ /* Start LAPIC timer using BSP-calibrated ticks */
+ lapic_timer_start_ap();
+ kprintf("[SMP] CPU%u scheduler active.\n", my_cpu);
+ (void)my_cpu;
+
+ /* AP enters idle loop — timer interrupts will call schedule() */
for (;;) {
__asm__ volatile("sti; hlt");
}
static void timer_irq(struct registers* regs) {
(void)regs;
- /* Only the BSP (LAPIC ID 0) drives the global tick, scheduling,
- * and VGA refresh. APs have no processes to run and would only
- * add spinlock contention on sched_lock / vga_lock. */
- if (lapic_is_enabled() && lapic_get_id() != 0) return;
+ if (lapic_is_enabled() && lapic_get_id() != 0) {
+ /* AP: only run the local scheduler — tick accounting, VGA flush,
+ * UART poll, and sleep-queue wake are handled by the BSP. */
+ extern void schedule(void);
+ schedule();
+ return;
+ }
if (g_tick_cb) g_tick_cb();
}
// 8. Start Timer (Preemption!) - 100Hz (like Linux CONFIG_HZ=100)
timer_init(TIMER_HZ);
+ // 8b. Signal APs to start their per-CPU schedulers
+ {
+ extern volatile uint32_t ap_sched_go;
+ __atomic_store_n(&ap_sched_go, 1, __ATOMIC_RELEASE);
+ }
+
hal_cpu_enable_interrupts();
int init_ret = init_start(bi);
spin_unlock_irqrestore(&sched_lock, flags);
}
+void sched_ap_init(uint32_t cpu) {
+ if (cpu == 0 || cpu >= SCHED_MAX_CPUS) return;
+
+ /* Allocate OUTSIDE sched_lock to avoid ABBA deadlock with heap lock:
+ * AP: sched_lock → heap_lock (kmalloc)
+ * BSP: heap_lock → timer ISR → sched_lock ← deadlock */
+ struct process* idle = (struct process*)kmalloc(sizeof(*idle));
+ if (!idle) {
+ kprintf("[SCHED] CPU%u: OOM allocating idle process.\n", cpu);
+ return;
+ }
+ memset(idle, 0, sizeof(*idle));
+
+ void* kstack = kstack_alloc();
+ if (!kstack) {
+ kfree(idle);
+ kprintf("[SCHED] CPU%u: OOM allocating idle kstack.\n", cpu);
+ return;
+ }
+
+ /* Fill in idle process fields (no lock needed — not yet visible) */
+ idle->parent_pid = 0;
+ idle->priority = SCHED_NUM_PRIOS - 1;
+ idle->nice = 19;
+ idle->state = PROCESS_RUNNING;
+ idle->addr_space = kernel_as;
+ idle->cpu_id = cpu;
+ strcpy(idle->cwd, "/");
+ for (int i = 0; i < PROCESS_MAX_MMAPS; i++)
+ idle->mmaps[i].shmid = -1;
+ idle->kernel_stack = (uint32_t*)kstack;
+ arch_fpu_init_state(idle->fpu_state);
+
+ /* Take sched_lock only for PID assignment + list insertion */
+ uintptr_t flags = spin_lock_irqsave(&sched_lock);
+
+ idle->pid = next_pid++;
+ idle->tgid = idle->pid;
+
+ /* Insert into global process list */
+ idle->next = ready_queue_head;
+ idle->prev = ready_queue_tail;
+ ready_queue_tail->next = idle;
+ ready_queue_head->prev = idle;
+ ready_queue_tail = idle;
+
+ /* Register as this CPU's idle process and current process */
+ pcpu_rq[cpu].idle = idle;
+ percpu_set_current(idle);
+
+ spin_unlock_irqrestore(&sched_lock, flags);
+
+ kprintf("[SCHED] CPU%u idle process PID %u ready.\n", cpu, idle->pid);
+}
+
extern spinlock_t sched_lock;
void thread_wrapper(void (*fn)(void)) {
hal_cpu_set_address_space(current_process->addr_space);
}
- if (current_process->kernel_stack) {
+ /* Only update TSS kernel stack on CPU 0 — the TSS is shared and
+ * only the BSP runs user processes that need ring 0 stack in TSS. */
+ if (cpu == 0 && current_process->kernel_stack) {
hal_cpu_set_kernel_stack((uintptr_t)current_process->kernel_stack + KSTACK_SIZE);
}