:: commit 8df3c7ae5a145f749537f8376d8a3bd212e1ba7e

Mintsuki <mintsuki@protonmail.com> — 2026-02-20 13:33

parents: 73cc1a5ac5

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
tab: 248 wrap: offon