Skip to content

Commit 8ffb439

Browse files
x2APIC support for when its forced on us by firmware
1 parent 8060e8b commit 8ffb439

File tree

11 files changed

+253
-86
lines changed

11 files changed

+253
-86
lines changed

include/acpi.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,12 @@ typedef struct {
9292
*/
9393
#define IRQ_DEFAULT_TRIGGER 0
9494

95+
/**
96+
* @brief Returned from @ref get_cpu_id_from_lapic_id if the CPUid
97+
* passed in does not map to a logical CPU id.
98+
*/
99+
#define INVALID_CPU_ID 255
100+
95101
/**
96102
* @brief Indicates the source of a PCI IRQ route.
97103
*/
@@ -208,4 +214,8 @@ const char *polarity_str(uint8_t pol);
208214
*/
209215
const char *sharing_str(uint8_t share);
210216

217+
/**
218+
* Main entrypoint for an SMP AP
219+
* @param info limine CPU info
220+
*/
211221
void kmain_ap(struct limine_smp_info *info);

include/apic.h

Lines changed: 71 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,35 +28,92 @@
2828
#define APIC_VERSION 0x0030 /**< LAPIC Version Register (read-only) */
2929

3030
/**
31-
* @brief Read a 32-bit value from a LAPIC register
31+
* @brief Read a Local APIC register.
3232
*
33-
* @param reg Register offset from LAPIC base
34-
* @return uint32_t The value read from the register
33+
* Works in both xAPIC and x2APIC modes:
34+
* - In xAPIC mode, the register is memory‑mapped at the LAPIC base address.
35+
* - In x2APIC mode, registers are accessed via MSRs at 0x800 + (reg >> 4).
36+
*
37+
* @param reg Register offset (e.g. APIC_EOI = 0xB0).
38+
* @return uint32_t Value read from the register.
3539
*/
3640
uint32_t apic_read(uint64_t reg);
3741

3842
/**
39-
* @brief Write a 32-bit value to a LAPIC register
43+
* @brief Write to a Local APIC register.
44+
*
45+
* Works in both xAPIC and x2APIC modes:
46+
* - In xAPIC mode, the register is memory‑mapped at the LAPIC base address.
47+
* - In x2APIC mode, registers are written via MSRs at 0x800 + (reg >> 4).
4048
*
41-
* @param reg Register offset from LAPIC base
42-
* @param value The value to write
49+
* @param reg Register offset (e.g. APIC_EOI = 0xB0).
50+
* @param val Value to write.
4351
*/
4452
void apic_write(uint64_t reg, uint32_t value);
4553

4654
/**
47-
* @brief Get the Local APIC ID of the current CPU
55+
* @brief Get the Local APIC ID of the current CPU.
56+
*
57+
* In xAPIC mode this is an 8-bit value read from the LAPIC ID register.
58+
* In x2APIC mode this is a 32-bit value read from MSR 0x802.
59+
*
60+
* @return uint32_t LAPIC ID of the current CPU.
61+
* @note LAPIC IDs are assigned by hardware/firmware and may be
62+
* sparse, non-contiguous, or not zero-based.
63+
*/
64+
uint32_t cpu_id(void);
65+
66+
/**
67+
* @brief Get the OS-assigned logical CPU ID of the current CPU.
68+
*
69+
* Logical CPU IDs are zero-based, contiguous indices used internally
70+
* by the kernel. They are mapped to LAPIC IDs via cpu_id_mapping[].
71+
*
72+
* @return uint8_t Logical CPU ID of the current CPU.
73+
*/
74+
uint8_t logical_cpu_id(void);
75+
76+
/**
77+
* @brief Get the physical address of the Local APIC MMIO region.
4878
*
49-
* @return uint8_t APIC ID read from the LAPIC ID register.
50-
* This value uniquely identifies the core in an SMP system and
51-
* may be non-contiguous or BIOS-assigned.
79+
* This is only valid when xAPIC mode is active. In x2APIC mode,
80+
* LAPIC registers are accessed via MSRs instead of MMIO.
81+
*
82+
* @return uint64_t Physical address of the LAPIC MMIO base.
83+
*/
84+
uint64_t get_lapic_address(void);
85+
86+
/**
87+
* @brief Determine if x2APIC mode is enabled.
88+
*
89+
* Checks both CPUID feature flags and the IA32_APIC_BASE MSR to confirm
90+
* whether x2APIC is active.
91+
*
92+
* @return int Non-zero if x2APIC mode is enabled, zero otherwise.
5293
*/
53-
uint8_t cpu_id();
94+
int x2apic_enabled(void);
5495

96+
/**
97+
* @brief Read a 64-bit Model Specific Register (MSR).
98+
*
99+
* @param msr The MSR address to read.
100+
* @return uint64_t The value read from the MSR.
101+
*/
102+
uint64_t rdmsr(uint32_t msr);
55103

56104
/**
57-
* @brief Get the physical address of the Local APIC
105+
* @brief Map a logical CPU ID to its LAPIC ID.
58106
*
59-
* @return uint64_t Physical address of the LAPIC MMIO region
107+
* @param cpu_id Logical CPU ID (zero-based, kernel-assigned).
108+
* @return uint32_t LAPIC ID for the given logical CPU ID.
60109
*/
61-
uint64_t get_lapic_address();
110+
uint32_t get_lapic_id_from_cpu_id(uint8_t cpu_id);
62111

112+
/**
113+
* @brief Map a LAPIC ID to its logical CPU ID.
114+
*
115+
* @param lapic_id LAPIC ID as reported by hardware.
116+
* @return uint8_t Logical CPU ID assigned to this LAPIC ID,
117+
* or 255 if unmapped.
118+
*/
119+
uint8_t get_cpu_id_from_lapic_id(uint32_t lapic_id);

include/idt.h

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
*/
66
#pragma once
77

8-
#include <kernel.h>
9-
108
#define USE_IOAPIC
119

1210
typedef struct idt_ptr_t {
@@ -26,6 +24,27 @@ typedef struct idt_entry_t {
2624

2725
extern volatile idt_ptr_t idt64;
2826

27+
/**
28+
* @note MAX_CPUS is set to 256 (not the expected physical core count).
29+
*
30+
* Local APIC IDs in xAPIC mode are 8-bit fields (0–255). These IDs are not
31+
* guaranteed to be sequential, zero-based, or densely packed. Some systems
32+
* leave gaps in the LAPIC ID space, and the bootstrap processor (BSP) may
33+
* not be assigned LAPIC ID 0.
34+
*
35+
* In x2APIC mode, APIC IDs extend to 32 bits. If firmware enables x2APIC
36+
* before handoff, we accept it and operate in that mode, but we currently
37+
* only initialise CPUs whose LAPIC IDs are <= 254. LAPIC ID 255 is reserved
38+
* for broadcast and never used. Any CPUs with IDs above 254 are ignored.
39+
*
40+
* By sizing arrays to 256, we can index directly by LAPIC ID without needing
41+
* a LAPIC→OS CPU remapping table. This simplifies per‑CPU structures at the
42+
* cost of a small amount of extra memory. Future support for >254 LAPIC IDs
43+
* may require remapping or dynamic allocation.
44+
*/
45+
#define MAX_CPUS 256
46+
47+
2948
/**
3049
* @brief Function pointer type for interrupt and IRQ handlers.
3150
*

include/pci.h

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -278,14 +278,13 @@ bool pci_enable_msix(pci_dev_t device, uint32_t vector, uint16_t entry, uint32_t
278278
*
279279
* @param name Human-readable device name (used in logs/debug output).
280280
* @param dev The PCI device handle.
281-
* @param lapic_id Local APIC ID of the target CPU that should receive the
282-
* interrupt.
281+
* @param logical_cpu_id Logical CPU ID of the target CPU that should receive the interrupt.
283282
* @param handler Interrupt service routine to register.
284283
* @param context Opaque pointer passed to the ISR when the interrupt fires.
285284
*
286-
* @warning The lapic_id parameter must be a valid Local APIC ID
285+
* @warning The lapic_id parameter must be a valid logical ID
287286
* corresponding to an online CPU. Passing an invalid or
288-
* offline APIC ID will result in undefined behaviour and
287+
* offline CPU ID will result in undefined behaviour and
289288
* may cause interrupts to be lost.
290289
*
291290
* @return The assigned interrupt vector (64–255 if MSI/MSI-X was used,
@@ -294,4 +293,4 @@ bool pci_enable_msix(pci_dev_t device, uint32_t vector, uint16_t entry, uint32_t
294293
* @note Drivers should always check the returned vector to confirm which
295294
* interrupt mechanism was actually configured.
296295
*/
297-
uint32_t pci_setup_interrupt(const char* name, pci_dev_t dev, uint8_t lapic_id, isr_t handler, void *context);
296+
uint32_t pci_setup_interrupt(const char* name, pci_dev_t dev, uint8_t logical_cpu_id, isr_t handler, void *context);

src/acpi.c

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,29 @@ static uint8_t ioapic_gsi_count[256] = {0};
2222

2323
pci_irq_route_t pci_irq_routes[MAX_PCI_ROUTES] = {};
2424

25+
uint32_t cpu_id_mapping[MAX_CPUS] = { 0 };
2526

2627
uint64_t mhz = 0, tsc_per_sec = 1;
2728

2829
void enumerate_all_gsis(void);
2930

31+
uint32_t get_lapic_id_from_cpu_id(uint8_t cpu_id) {
32+
return cpu_id_mapping[cpu_id];
33+
}
34+
35+
uint8_t get_cpu_id_from_lapic_id(uint32_t lapic_id) {
36+
for (uint8_t x = 0; x < MAX_CPUS - 1; ++x) {
37+
if (cpu_id_mapping[x] == lapic_id) {
38+
return x;
39+
}
40+
}
41+
return INVALID_CPU_ID;
42+
}
43+
44+
void set_lapic_id_for_cpu_id(uint8_t cpu_id, uint32_t lapic_id) {
45+
cpu_id_mapping[cpu_id] = lapic_id;
46+
}
47+
3048
void init_uacpi(void) {
3149
uacpi_status st;
3250

@@ -201,10 +219,21 @@ void init_cores() {
201219
return;
202220
}
203221

204-
for (uint64_t i = 0; i < smp_request.response->cpu_count; i++) {
222+
uint64_t limit = smp_request.response->cpu_count;
223+
if (limit > MAX_CPUS - 1) {
224+
kprintf("WARNING: Your system has more than 254 CPUs; only 254 will be enabled\n");
225+
limit = MAX_CPUS - 1;
226+
}
227+
for (uint64_t i = 0; i < limit; i++) {
205228
struct limine_smp_info *cpu = smp_request.response->cpus[i];
206-
if (cpu->lapic_id == smp_request.response->bsp_lapic_id) {
207-
continue; // Skip BSP
229+
if (cpu->processor_id < 255) {
230+
set_lapic_id_for_cpu_id(cpu->processor_id, cpu->lapic_id);
231+
}
232+
if (cpu->lapic_id == smp_request.response->bsp_lapic_id || cpu->processor_id > 254) {
233+
// Skip BSP and IDs over 254 (255 is broadcast, 256+ are too big for our array)
234+
continue;
235+
} else if (cpu->lapic_id == smp_request.response->bsp_lapic_id) {
236+
kprintf("CPU: %d online; ID: %d\n", cpu->processor_id, cpu->lapic_id);
208237
}
209238
cpu->goto_address = kmain_ap;
210239
}

src/ahci.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -594,7 +594,7 @@ void init_ahci()
594594
}
595595

596596
ahci_base = pci_mem_base(ahci_base);
597-
uint32_t irq_num = pci_setup_interrupt("AHCI", ahci_device, cpu_id(), ahci_handler, ahci_base);
597+
uint32_t irq_num = pci_setup_interrupt("AHCI", ahci_device, logical_cpu_id(), ahci_handler, ahci_base);
598598
dprintf("AHCI base MMIO: %08x INT %d\n", ahci_base, irq_num);
599599

600600
probe_port((ahci_hba_mem_t*)ahci_base, ahci_device);

src/apic.c

Lines changed: 79 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,90 @@
1-
#include <kernel.h>
1+
#include <kernel.h>
2+
#include <cpuid.h>
3+
4+
#define IA32_APIC_BASE_MSR 0x1B
5+
#define APIC_BASE_X2APIC_ENABLE (1ULL << 10)
26

37
static uint64_t saved_lapic = 0;
48

59
uint64_t get_lapic_address() {
6-
if (saved_lapic) {
7-
return saved_lapic;
10+
if (saved_lapic) {
11+
return saved_lapic;
812
}
9-
uint32_t eax, edx;
10-
__asm__ volatile (
11-
"rdmsr"
12-
: "=a"(eax), "=d"(edx)
13-
: "c"(0x1B) // APIC_BASE_MSR
14-
);
15-
uint64_t result = ((uint64_t)edx << 32) | eax;
16-
result &= 0xFFFFFFFFFFFFF000ULL;
17-
saved_lapic = result;
18-
return result;
19-
}
13+
uint32_t eax, edx;
14+
__asm__ volatile (
15+
"rdmsr"
16+
: "=a"(eax), "=d"(edx)
17+
: "c"(0x1B) // APIC_BASE_MSR
18+
);
19+
uint64_t result = ((uint64_t)edx << 32) | eax;
20+
result &= 0xFFFFFFFFFFFFF000ULL;
21+
saved_lapic = result;
22+
return result;
23+
}
24+
25+
static inline void wrmsr(uint32_t msr, uint64_t value) {
26+
uint32_t lo = (uint32_t)value;
27+
uint32_t hi = (uint32_t)(value >> 32);
28+
__asm__ volatile ("wrmsr"
29+
:
30+
: "c"(msr), "a"(lo), "d"(hi));
31+
}
2032

21-
uint32_t apic_read(uint64_t reg)
22-
{
23-
uint64_t lapic = get_lapic_address();
24-
uint32_t res = *((volatile uint32_t *)(lapic + reg));
25-
return res;
33+
34+
void apic_write(uint64_t reg, uint32_t val) {
35+
if (x2apic_enabled()) {
36+
wrmsr(0x800 + (reg >> 4), val);
37+
} else {
38+
uint64_t lapic_base = get_lapic_address();
39+
*(volatile uint32_t *)(lapic_base + reg) = val;
40+
}
2641
}
2742

28-
void apic_write(uint64_t reg, uint32_t value)
29-
{
30-
uint64_t lapic = get_lapic_address();
31-
*((volatile uint32_t *)(lapic + reg)) = value;
43+
uint32_t apic_read(uint64_t reg) {
44+
if (x2apic_enabled()) {
45+
return (uint32_t)rdmsr(0x800 + (reg >> 4));
46+
} else {
47+
uint64_t lapic_base = get_lapic_address();
48+
return *(volatile uint32_t *)(lapic_base + reg);
49+
}
3250
}
3351

34-
uint8_t cpu_id()
35-
{
36-
uint32_t id = apic_read(APIC_ID);
37-
id = (id >> 24) & 0xff;
38-
return id;
52+
uint32_t cpu_id(void) {
53+
if (x2apic_enabled()) {
54+
// x2APIC: full 32-bit ID from MSR 0x802
55+
return (uint32_t)rdmsr(0x802);
56+
} else {
57+
// xAPIC: 8-bit ID from MMIO register (0x20 >> 24)
58+
return (apic_read(APIC_ID) >> 24) & 0xFF;
59+
}
3960
}
61+
62+
uint8_t logical_cpu_id(void) {
63+
return get_cpu_id_from_lapic_id(cpu_id());
64+
}
65+
66+
uint64_t rdmsr(uint32_t msr) {
67+
uint32_t lo, hi;
68+
__asm__ volatile ("rdmsr"
69+
: "=a"(lo), "=d"(hi)
70+
: "c"(msr));
71+
return ((uint64_t)hi << 32) | lo;
72+
}
73+
74+
int x2apic_supported(void) {
75+
unsigned int eax, ebx, ecx, edx;
76+
77+
if (!__get_cpuid(1, &eax, &ebx, &ecx, &edx))
78+
return 0;
79+
80+
return (ecx & (1u << 21)) != 0; // Bit 21 of ECX = x2APIC support
81+
}
82+
83+
int x2apic_enabled(void) {
84+
if (!x2apic_supported()) {
85+
return 0; // CPU doesn't even support x2APIC
86+
}
87+
// Read APIC base MSR to see if x2APIC mode is active
88+
uint64_t apic_base = rdmsr(IA32_APIC_BASE_MSR);
89+
return (apic_base & APIC_BASE_X2APIC_ENABLE) != 0;
90+
}

src/e1000.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ bool e1000_start(pci_dev_t *pci_device) {
320320

321321
if (e1000_device_id == E1000_82540EM) {
322322
/* Attempting MSI setup is safe here */
323-
pci_setup_interrupt("e1000", *pci_device, cpu_id(), e1000_handler, NULL);
323+
pci_setup_interrupt("e1000", *pci_device, logical_cpu_id(), e1000_handler, NULL);
324324
} else {
325325
/* But not here! The 82541PI actively torpedoes the system if you enable MSI, see its errata:
326326
*

0 commit comments

Comments
 (0)