summaryrefslogtreecommitdiff
path: root/kernel/smp.cc
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/smp.cc')
-rw-r--r--kernel/smp.cc112
1 files changed, 112 insertions, 0 deletions
diff --git a/kernel/smp.cc b/kernel/smp.cc
new file mode 100644
index 0000000..1a79ac3
--- /dev/null
+++ b/kernel/smp.cc
@@ -0,0 +1,112 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+/*
+ * smp.cc — SMP IPI implementation
+ *
+ * Sending side: atomically set reason bits in target CPU's IpiData,
+ * then invoke arch-specific IPI delivery (SBI ecall on RISC-V).
+ *
+ * Receiving side: smp_handle_ipi() is called from the trap handler
+ * when an S-mode software interrupt arrives. It atomically consumes
+ * all pending reason bits and dispatches to the appropriate handler.
+ *
+ * Reference: docs/specs/linux_abi_mm_plan.md §SMP §2.2
+ */
+
+#include <lume/smp.h>
+#include <lume/klog.h>
+#include <lume/sched.h>
+#include <arch/cpu.h>
+#include <arch/mmu.h>
+#include <arch/sbi.h>
+
+/* Per-CPU IPI data — BSS zero-initialized */
+IpiData g_ipi_data[kMaxCpus];
+
+/* ------------------------------------------------------------------ */
+/* smp_init */
+/* ------------------------------------------------------------------ */
+
+void smp_init()
+{
+ for (uint32 i = 0; i < kMaxCpus; i++) {
+ g_ipi_data[i].pending.store(0, __ATOMIC_RELAXED);
+ g_ipi_data[i].tlb_start = 0;
+ g_ipi_data[i].tlb_end = 0;
+ }
+
+ printk(KERN_INFO "smp: IPI framework initialized (%u CPUs max)\n",
+ static_cast<unsigned>(kMaxCpus));
+}
+
+/* ------------------------------------------------------------------ */
+/* smp_send_ipi — send IPI to target CPUs */
+/* ------------------------------------------------------------------ */
+
+void smp_send_ipi(uint64 cpu_mask, uint32 reason)
+{
+ if (cpu_mask == 0) return;
+
+ /* Set pending bits on each target CPU's IpiData */
+ for (uint32 cpu = 0; cpu < kMaxCpus; cpu++) {
+ if (cpu_mask & (1ULL << cpu)) {
+ g_ipi_data[cpu].pending.fetch_or(reason, __ATOMIC_RELEASE);
+ }
+ }
+
+ /* Deliver hardware interrupt via SBI.
+ * RISC-V SBI IPI extension sends S-mode software interrupt
+ * to the specified harts. hart_mask maps 1:1 to cpu_mask
+ * (assuming hartid == cpu_id, which holds for QEMU virt). */
+ arch::sbi::sbi_send_ipi(cpu_mask, 0);
+}
+
+/* ------------------------------------------------------------------ */
+/* smp_handle_ipi — called from trap handler on software interrupt */
+/* ------------------------------------------------------------------ */
+
+void smp_handle_ipi()
+{
+ uint32 cpu = static_cast<uint32>(arch::cpu::id());
+
+ /* Clear the S-mode software interrupt pending bit (sip.SSIP).
+ * On RISC-V, we must clear this in software, otherwise the
+ * interrupt will keep firing. */
+ arch::cpu::clear_sip_ssip();
+
+ /* Atomically consume all pending reasons */
+ uint32 reasons = g_ipi_data[cpu].pending.exchange(0, __ATOMIC_ACQUIRE);
+
+ if (reasons == 0) return; /* Spurious */
+
+ /* Dispatch each reason */
+
+ if (reasons & IPI_RESCHEDULE) {
+ /* Just need to return from interrupt — schedule() will be
+ * called on the return-from-trap path if need_resched is set.
+ * For now, set the flag by yielding on next opportunity. */
+ /* TODO: set need_resched flag when preemption tracking is added */
+ }
+
+ if (reasons & IPI_TLB_SHOOTDOWN) {
+ /* Execute local TLB flush with the parameters the sender stored */
+ uint64 start = g_ipi_data[cpu].tlb_start;
+ uint64 end = g_ipi_data[cpu].tlb_end;
+ if (start == 0 && end == 0) {
+ arch::mmu::flush_tlb_all();
+ } else {
+ for (uint64 va = start; va < end; va += kPageSize) {
+ arch::mmu::flush_tlb_page(va);
+ }
+ }
+ }
+
+ if (reasons & IPI_HALT) {
+ printk(KERN_INFO "smp: CPU %u halting by IPI\n", cpu);
+ arch::cpu::intr_off();
+ while (true) {
+ arch::cpu::halt_until_interrupt();
+ }
+ }
+}