Skip to content

Commit 8148341

Browse files
full AP initialisation into proc loop. theyll now wait in HLT until someone calls wake_cpu with their logical id
1 parent bc5bb4a commit 8148341

File tree

9 files changed

+251
-64
lines changed

9 files changed

+251
-64
lines changed

include/apic.h

Lines changed: 148 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,98 @@
11
/**
22
* @file apic.h
3-
* @author Craig Edwards (craigedwards@brainbox.cc)
3+
* @author Craig Edwards
44
* @copyright Copyright (c) 2012-2025
55
* @brief Local APIC (Advanced Programmable Interrupt Controller) interface
66
*
77
* This header provides low-level routines and register definitions to interface with
88
* the Local APIC (LAPIC) on x86_64 systems. It allows for reading and writing LAPIC registers,
9-
* detecting the current CPU's LAPIC ID, and querying the LAPIC base address.
9+
* detecting the current CPU's LAPIC ID, sending inter-processor interrupts (IPIs),
10+
* and querying the LAPIC base address.
1011
*
11-
* This interface is used during system initialisation to configure per-core interrupt handling,
12-
* timer setup, and inter-processor signalling on SMP systems.
12+
* The LAPIC is a per-core interrupt controller responsible for:
13+
* - Handling local interrupts (e.g., timer, thermal, performance counters).
14+
* - Providing per-core programmable timer functionality.
15+
* - Supporting Inter-Processor Interrupts (IPIs) for SMP coordination.
16+
* - Forwarding external interrupts via the I/O APIC.
17+
*
18+
* Both xAPIC (MMIO) and x2APIC (MSR-based) modes are supported by this interface.
1319
*/
1420

1521
#pragma once
1622

17-
/** Default virtual memory mapping address for the Local APIC (used by the kernel) */
18-
#define APIC_ADDRESS 0x4000
23+
/** @brief Default virtual memory mapping address for the Local APIC MMIO page. */
24+
#define APIC_ADDRESS 0x4000
25+
26+
/** @brief Model Specific Register (MSR) index for LAPIC base address (IA32_APIC_BASE). */
27+
#define APIC_BASE_MSR 0x1B
28+
29+
/** @brief Bitmask in IA32_APIC_BASE MSR to enable the Local APIC. */
30+
#define APIC_BASE_MSR_ENABLE 0x800
31+
32+
/* -------------------------------------------------------------------------- */
33+
/* LAPIC Register Offsets (xAPIC mode, relative to MMIO base) */
34+
/* -------------------------------------------------------------------------- */
35+
36+
/** @brief Local APIC ID Register (read-only). */
37+
#define APIC_ID 0x0020
38+
39+
/** @brief Local APIC Version Register (read-only). */
40+
#define APIC_VERSION 0x0030
41+
42+
/** @brief Task Priority Register (TPR). Controls interrupt priority threshold. */
43+
#define APIC_TPR 0x0080
44+
45+
/** @brief Interrupt Command Register (ICR), low dword (vector + control flags). */
46+
#define APIC_ICR_LOW 0x0300
47+
48+
/** @brief Interrupt Command Register (ICR), high dword (destination LAPIC ID). */
49+
#define APIC_ICR_HIGH 0x0310
50+
51+
/* -------------------------------------------------------------------------- */
52+
/* Delivery Modes for Inter-Processor Interrupts (IPI) */
53+
/* -------------------------------------------------------------------------- */
54+
55+
/** @brief Fixed delivery mode: deliver interrupt using vector field. */
56+
#define APIC_DM_FIXED 0x000
57+
58+
/** @brief Non-Maskable Interrupt (NMI) delivery mode. */
59+
#define APIC_DM_NMI 0x400
1960

20-
/** Model Specific Register for the LAPIC base address */
21-
#define APIC_BASE_MSR 0x1B
61+
/** @brief INIT delivery mode: reset target CPU to INIT state. */
62+
#define APIC_DM_INIT 0x500
2263

23-
/** Bitmask to enable the LAPIC via the APIC_BASE_MSR */
24-
#define APIC_BASE_MSR_ENABLE 0x800
64+
/** @brief Startup IPI delivery mode: start target CPU at specified vector. */
65+
#define APIC_DM_STARTUP 0x600
2566

26-
/* LAPIC register offsets (relative to the LAPIC base address) */
27-
#define APIC_ID 0x0020 /**< LAPIC ID Register (read-only) */
28-
#define APIC_VERSION 0x0030 /**< LAPIC Version Register (read-only) */
67+
/* -------------------------------------------------------------------------- */
68+
/* x2APIC Registers (MSR-based access only) */
69+
/* -------------------------------------------------------------------------- */
70+
71+
/** @brief MSR index for the x2APIC Interrupt Command Register (ICR). */
72+
#define IA32_X2APIC_ICR 0x830
73+
74+
/* -------------------------------------------------------------------------- */
75+
/* Miscellaneous constants */
76+
/* -------------------------------------------------------------------------- */
77+
78+
/** @brief Vector number used for custom AP wake-up IPIs. */
79+
#define APIC_WAKE_IPI 240
80+
81+
/** @brief Destination shorthand: no shorthand, use explicit LAPIC ID. */
82+
#define APIC_DEST_NO_SHORTHAND (0 << 18)
83+
84+
/** @brief Destination mode: physical addressing. */
85+
#define APIC_DEST_PHYSICAL (0 << 11)
86+
87+
/** @brief Assert level for IPI (used to trigger delivery). */
88+
#define APIC_LEVEL_ASSERT (1 << 14)
89+
90+
/** @brief Edge-triggered delivery mode for IPI. */
91+
#define APIC_TRIGGER_EDGE (0 << 15)
92+
93+
/* -------------------------------------------------------------------------- */
94+
/* Function prototypes */
95+
/* -------------------------------------------------------------------------- */
2996

3097
/**
3198
* @brief Read a Local APIC register.
@@ -34,7 +101,7 @@
34101
* - In xAPIC mode, the register is memory‑mapped at the LAPIC base address.
35102
* - In x2APIC mode, registers are accessed via MSRs at 0x800 + (reg >> 4).
36103
*
37-
* @param reg Register offset (e.g. APIC_EOI = 0xB0).
104+
* @param reg Register offset (e.g. APIC_TPR = 0x80).
38105
* @return uint32_t Value read from the register.
39106
*/
40107
uint32_t apic_read(uint64_t reg);
@@ -46,8 +113,8 @@ uint32_t apic_read(uint64_t reg);
46113
* - In xAPIC mode, the register is memory‑mapped at the LAPIC base address.
47114
* - In x2APIC mode, registers are written via MSRs at 0x800 + (reg >> 4).
48115
*
49-
* @param reg Register offset (e.g. APIC_EOI = 0xB0).
50-
* @param val Value to write.
116+
* @param reg Register offset (e.g. APIC_TPR = 0x80).
117+
* @param value Value to write.
51118
*/
52119
void apic_write(uint64_t reg, uint32_t value);
53120

@@ -118,4 +185,68 @@ uint32_t get_lapic_id_from_cpu_id(uint8_t cpu_id);
118185
*/
119186
uint8_t get_cpu_id_from_lapic_id(uint32_t lapic_id);
120187

121-
void boot_aps();
188+
/**
189+
* @brief Initialise and bring Application Processors (APs) online via Limine SMP.
190+
*
191+
* This function uses the Limine SMP response to discover all CPUs in the system,
192+
* map their LAPIC IDs to logical CPU IDs, and assign each AP an entry point
193+
* (`kmain_ap`). The Bootstrap Processor (BSP) is detected and skipped, as are
194+
* CPUs with processor IDs >= 255 (reserved for broadcast or outside kernel limits).
195+
*
196+
* Once all eligible APs are assigned their startup entry point, this function
197+
* spins until each AP reports online by incrementing @ref aps_online.
198+
*
199+
* @note This does not directly issue INIT/SIPI IPIs; Limine handles the actual
200+
* startup of APs. The kernel only assigns the entry function and waits.
201+
* @warning Systems with more than 254 CPUs are truncated to 254 usable cores.
202+
*
203+
* @see kmain_ap, aps_online, set_lapic_id_for_cpu_id
204+
*/
205+
void boot_aps();
206+
207+
208+
/**
209+
* @brief Send an Inter-Processor Interrupt (IPI) to a specific LAPIC ID.
210+
*
211+
* Supports both xAPIC (ICR registers) and x2APIC (IA32_X2APIC_ICR MSR).
212+
*
213+
* @param lapic_id Destination LAPIC ID.
214+
* @param vector Interrupt vector to deliver.
215+
*/
216+
void apic_send_ipi(uint32_t lapic_id, uint8_t vector);
217+
218+
/**
219+
* @brief Send a wake-up IPI to a target logical CPU.
220+
*
221+
* This function is used to wake an Application Processor (AP) from a halted or
222+
* waiting state. It resolves the kernel-assigned logical CPU ID into the
223+
* hardware LAPIC ID and then sends a fixed-mode IPI using the predefined
224+
* wake-up vector (APIC_WAKE_IPI).
225+
*
226+
* @param logical_cpu_id Logical CPU ID (zero-based, contiguous index assigned
227+
* by the kernel).
228+
*
229+
* @note The Local APIC on the target CPU must already be enabled for the wake-up
230+
* IPI to be accepted. This function assumes that LAPIC initialisation has
231+
* been performed during CPU bring-up.
232+
*/
233+
void wake_cpu(uint8_t logical_cpu_id);
234+
235+
/**
236+
* @brief Initialise the Local APIC on an Application Processor (AP).
237+
*
238+
* This function enables the Local APIC by setting the spurious interrupt
239+
* vector register's enable bit, and lowers the Task Priority Register (TPR)
240+
* to zero so that the AP can receive all interrupts.
241+
*
242+
* It is typically invoked during AP startup after IDT installation, allowing
243+
* the processor to handle interrupts and inter-processor IPIs.
244+
*
245+
* @note This does not configure LAPIC timers or IPI handlers; it only ensures
246+
* that the APIC is enabled and accepting interrupts.
247+
*
248+
* @see apic_write
249+
* @see APIC_SVR
250+
* @see APIC_TPR
251+
*/
252+
void apic_setup_ap();

include/idt.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,3 +150,17 @@ int alloc_msi_vector(uint8_t cpu);
150150
*/
151151
void free_msi_vector(uint8_t cpu, int vec);
152152

153+
/**
154+
* @brief Load the shared IDT and enable interrupts on an Application Processor (AP).
155+
*
156+
* This function loads the global Interrupt Descriptor Table (IDT) pointer into
157+
* the AP's IDTR register using the `lidt` instruction. After the IDT is loaded,
158+
* it enables external interrupts via `sti` (wrapped by interrupts_on()).
159+
*
160+
* It is typically called during AP startup before entering the scheduler loop,
161+
* ensuring that the AP can handle interrupts using the same IDT as the BSP.
162+
*
163+
* @note All APs share the same IDT (idt64) in this design.
164+
* @see lidt, interrupts_on
165+
*/
166+
void load_ap_shared_idt();

include/lapic_timer.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#ifndef __LAPIC_TIMER_H__
77
#define __LAPIC_TIMER_H__
88

9+
#define APIC_SVR 0xF0
910
#define APIC_TASKPRIOR 0x80
1011
#define APIC_EOI 0x0B0
1112
#define APIC_LDR 0x0D0
@@ -30,6 +31,6 @@
3031
#define TMR_PERIODIC 0x20000
3132
#define TMR_BASEDIV (1<<20)
3233

33-
void init_lapic_timer();
34+
void init_lapic_timer(uint64_t quantum);
3435

3536
#endif

src/ap.c

Lines changed: 14 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1+
/**
2+
* @file ap.c
3+
* @brief Bring up and enter the process loop for an AP
4+
* @author Craig Edwards (craigedwards@brainbox.cc)
5+
* @copyright Copyright (c) 2012-2025
6+
*/
7+
18
#include <kernel.h>
2-
#include <stdatomic.h>
39

410
volatile struct limine_smp_request smp_request = {
511
.id = LIMINE_SMP_REQUEST,
@@ -9,36 +15,15 @@ volatile struct limine_smp_request smp_request = {
915
size_t aps_online = 0;
1016
simple_cv_t boot_condition;
1117

12-
void kmain_ap(struct limine_smp_info *info)
13-
{
14-
// Load the shared IDT
15-
__asm__ volatile("lidtq (%0)" :: "r"(&idt64));
16-
interrupts_on();
17-
18+
void wait_for_interpreter_start(struct limine_smp_info *info) {
1819
kprintf("CPU: %u online; ID: %u\n", info->processor_id, info->lapic_id);
1920
atomic_fetch_add(&aps_online, 1);
20-
2121
simple_cv_wait(&boot_condition);
22-
/**
23-
* @todo Insert cpu-local scheduler loop here.
24-
* Each AP will run its own list of executing BASIC processes. Accessing
25-
* the list of other APs and the BSP will be strictly controlled via a
26-
* marshalled lookup system using a spinlock, e.g. if AP 1 wants to start a new
27-
* process on AP 2, it signals via this system.
28-
*
29-
* This will be done as follows:
30-
*
31-
* 1) Each AP can be instructed via a command queue to launch, query or kill a process.
32-
* 2) Each AP will have its own command queue
33-
* 3) Any AP can push a command onto the command queue for one or more other APs to action
34-
* 4) Initially AP's will wait for a start command in their queue,
35-
* they won't run their scheduler until they receive this command. This allows them all to
36-
* gracefully wait until the first BASIC process is ready to be loaded (/programs/init).
37-
* 5) A shared process list will be used for enumerating processes and checking process
38-
* state, so we dont have to peek at another scheduler instance's command queue to
39-
* see if a process ours is waiting on still lives.
40-
*/
41-
dprintf("Got start signal on cpu #%d\n", info->processor_id);
22+
}
4223

43-
proc_loop();
24+
void kmain_ap(struct limine_smp_info *info) {
25+
load_ap_shared_idt();
26+
wait_for_interpreter_start(info);
27+
apic_setup_ap();
28+
proc_loop();
4429
}

src/apic.c

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -68,17 +68,48 @@ uint64_t rdmsr(uint32_t msr) {
6868
int x2apic_supported(void) {
6969
unsigned int eax, ebx, ecx, edx;
7070

71-
if (!__get_cpuid(1, &eax, &ebx, &ecx, &edx))
71+
if (!__get_cpuid(1, &eax, &ebx, &ecx, &edx)) {
7272
return 0;
73+
}
7374

7475
return (ecx & (1u << 21)) != 0; // Bit 21 of ECX = x2APIC support
7576
}
7677

7778
int x2apic_enabled(void) {
78-
if (!x2apic_supported()) {
79-
return 0; // CPU doesn't even support x2APIC
80-
}
81-
// Read APIC base MSR to see if x2APIC mode is active
82-
uint64_t apic_base = rdmsr(IA32_APIC_BASE_MSR);
83-
return (apic_base & APIC_BASE_X2APIC_ENABLE) != 0;
84-
}
79+
if (!x2apic_supported()) {
80+
return 0; // CPU doesn't even support x2APIC
81+
}
82+
// Read APIC base MSR to see if x2APIC mode is active
83+
uint64_t apic_base = rdmsr(IA32_APIC_BASE_MSR);
84+
return (apic_base & APIC_BASE_X2APIC_ENABLE) != 0;
85+
}
86+
87+
void apic_send_ipi(uint32_t lapic_id, uint8_t vector) {
88+
if (x2apic_enabled()) {
89+
uint64_t icr = 0;
90+
icr |= vector; // vector
91+
icr |= (0ULL << 8); // delivery mode = fixed
92+
icr |= (0ULL << 11); // physical dest
93+
icr |= (1ULL << 14); // level = assert
94+
icr |= ((uint64_t)lapic_id << 32);
95+
wrmsr(IA32_X2APIC_ICR, icr);
96+
} else {
97+
apic_write(APIC_ICR_HIGH, ((uint32_t) lapic_id) << 24);
98+
apic_write(APIC_ICR_LOW,
99+
vector | APIC_DM_FIXED | APIC_DEST_NO_SHORTHAND |
100+
APIC_DEST_PHYSICAL | APIC_LEVEL_ASSERT | APIC_TRIGGER_EDGE);
101+
// Wait for delivery to complete (bit 12 = Delivery Status)
102+
while (apic_read(APIC_ICR_LOW) & (1 << 12)) {
103+
__builtin_ia32_pause();
104+
}
105+
}
106+
}
107+
108+
void wake_cpu(uint8_t logical_cpu_id) {
109+
apic_send_ipi(get_lapic_id_from_cpu_id(logical_cpu_id), APIC_WAKE_IPI);
110+
}
111+
112+
void apic_setup_ap() {
113+
apic_write(APIC_SVR, apic_read(APIC_SVR) | 0x100); // Set APIC enable bit
114+
apic_write(APIC_TPR, 0);
115+
}

src/idt.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ void init_idt() {
104104
#ifdef USE_IOAPIC
105105
pic_disable();
106106
remap_irqs_to_ioapic();
107-
init_lapic_timer();
107+
init_lapic_timer(100);
108108
#else
109109
pic_enable();
110110
#endif
@@ -134,3 +134,8 @@ void free_msi_vector(uint8_t cpu, int vec) {
134134
int bit = vec % 64;
135135
msi_bitmap[cpu][w] &= ~(1ULL << bit);
136136
}
137+
138+
void load_ap_shared_idt() {
139+
__asm__ volatile("lidtq (%0)" :: "r"(&idt64));
140+
interrupts_on();
141+
}

0 commit comments

Comments
 (0)