sys/smp: Try to disable x2APIC when kernel does not support it
diff --git a/common/sys/lapic.c b/common/sys/lapic.c
index e79abf51..5579a00f 100644
--- a/common/sys/lapic.c
+++ b/common/sys/lapic.c
@@ -279,6 +279,37 @@ bool x2apic_enable(void) {
return true;
}
+bool x2apic_disable(void) {
+ uint64_t msr = rdmsr(0x1b);
+ if (!(msr & (1 << 10)))
+ return true;
+
+ // Check for LEGACY_XAPIC_DISABLED (Intel Meteor Lake+).
+ // CPUID.07H.0:EDX[29] enumerates IA32_ARCH_CAPABILITIES MSR (0x10A).
+ // IA32_ARCH_CAPABILITIES bit 21 = XAPIC_DISABLE feature supported.
+ // IA32_XAPIC_DISABLE_STATUS MSR (0xBD) bit 0 = xAPIC permanently disabled.
+ uint32_t eax, ebx, ecx, edx;
+ if (cpuid(7, 0, &eax, &ebx, &ecx, &edx) && (edx & (1 << 29))) {
+ uint64_t arch_caps = rdmsr(0x10a);
+ if (arch_caps & (1 << 21)) {
+ if (rdmsr(0xbd) & 1) {
+ return false;
+ }
+ }
+ }
+
+ // Transition x2APIC -> disabled -> xAPIC.
+ // Direct x2APIC -> xAPIC is an invalid transition (#GP).
+ msr &= ~((1ULL << 11) | (1ULL << 10));
+ wrmsr(0x1b, msr);
+
+ msr |= (1ULL << 11);
+ wrmsr(0x1b, msr);
+
+ x2apic_mode = false;
+ return true;
+}
+
void lapic_eoi(void) {
if (!x2apic_mode) {
lapic_write(0xb0, 0);
diff --git a/common/sys/lapic.h b/common/sys/lapic.h
index 3565f94f..02bf49f4 100644
--- a/common/sys/lapic.h
+++ b/common/sys/lapic.h
@@ -20,6 +20,7 @@ void lapic_write(uint32_t reg, uint32_t data);
bool x2apic_check(void);
bool x2apic_enable(void);
+bool x2apic_disable(void);
uint64_t x2apic_read(uint32_t reg);
void x2apic_write(uint32_t reg, uint64_t data);
diff --git a/common/sys/smp.c b/common/sys/smp.c
index 9b123452..30d71a7d 100644
--- a/common/sys/smp.c
+++ b/common/sys/smp.c
@@ -133,10 +133,13 @@ struct limine_mp_info *init_smp(size_t *cpu_count,
uint8_t bsp_lapic_id;
uint32_t bsp_x2apic_id;
- // If x2APIC already enabled by BIOS, then xAPIC is not available
+ // If x2APIC already enabled by firmware, try to revert to xAPIC
if (rdmsr(0x1b) & (1 << 10)) {
if (!x2apic) {
- panic(false, "smp: Kernel does not support x2APIC, but machine requires it");
+ if (!x2apic_disable()) {
+ panic(false, "smp: Kernel does not support x2APIC and x2APIC cannot be disabled");
+ }
+ printv("smp: Firmware had x2APIC enabled, reverted to xAPIC mode\n");
}
}
diff --git a/common/sys/smp_trampoline.asm_x86 b/common/sys/smp_trampoline.asm_x86
index 41ac841d..91f1a382 100644
--- a/common/sys/smp_trampoline.asm_x86
+++ b/common/sys/smp_trampoline.asm_x86
@@ -53,13 +53,25 @@ smp_trampoline_start:
mov ebx, esi
mov ecx, 0x1b
+ rdmsr
+ test eax, (1 << 10)
+ jz .write_apic_msr
+
+ ; Check if target also has x2APIC
+ test dword [ebx + (passed_info.bsp_apic_addr_msr_lo - smp_trampoline_start)], (1 << 10)
+ jnz .write_apic_msr
+
+ ; AP is x2APIC but target is xAPIC: go through disabled state
+ btr eax, 11
+ btr eax, 10
+ wrmsr
+
+ .write_apic_msr:
mov eax, [ebx + (passed_info.bsp_apic_addr_msr_lo - smp_trampoline_start)]
mov edx, [ebx + (passed_info.bsp_apic_addr_msr_hi - smp_trampoline_start)]
bts eax, 11
btr eax, 8
wrmsr
-
- .nox2apic:
mov esp, [ebx + (passed_info.temp_stack - smp_trampoline_start)]
mov eax, cr4
