Files
RedBear-OS/local/patches/kernel/P22-x2apic-madt-fallback.patch
T
vasilito ba5e010bb7 P21-P23: boot daemon panic fixes, x2APIC MADT fallback, rootfs hard dep on drivers
P21: Replace 67 panic-grade calls across 9 boot daemon files with
graceful error handling. Affected: ps2d, inputd, fbcond, fbbootlogd.

P22: Add x2APIC MADT fallback for processors with LocalApic entries
instead of LocalX2Apic entries. QEMU KVM boots now correctly detect
all vCPUs via zero-extended APIC ID fallback.

P23: Change 50_rootfs.service from requires_weak to requires on
40_drivers.target, ensuring redoxfs waits for disk drivers before
attempting filesystem mount. This fixes the boot race where rootfs
mount failed before drivers were ready, causing init to have no
userland services after switchroot.
2026-05-19 02:18:17 +03:00

166 lines
7.5 KiB
Diff

diff --git a/src/acpi/madt/arch/x86.rs b/src/acpi/madt/arch/x86.rs
index 306ec154..983fc409 100644
--- a/src/acpi/madt/arch/x86.rs
+++ b/src/acpi/madt/arch/x86.rs
@@ -187,0 +188,10 @@ pub(super) fn init(madt: Madt) {
+ // Detect whether MADT contains any LocalX2Apic entries.
+ // Some firmware (notably QEMU and some older BIOS) provides only 8-bit
+ // LocalApic entries even when the CPU supports x2APIC. In that case we must
+ // fall back to processing LocalApic entries with zero-extended IDs.
+ let has_x2apic_entries = madt.iter().any(|e| matches!(e, MadtEntry::LocalX2Apic(_)));
+ let x2apic_fallback = local_apic.x2 && !has_x2apic_entries;
+ if x2apic_fallback {
+ warn!("MADT: x2APIC mode active but no LocalX2Apic entries found; falling back to LocalApic entries with zero-extended IDs");
+ }
+
@@ -196,0 +207,3 @@ pub(super) fn init(madt: Madt) {
+ MadtEntry::LocalApic(local) if local_apic.x2 && x2apic_fallback => {
+ u32::from(local.id) == me.get() || local.flags & 1 == 1
+ }
@@ -225,2 +238,6 @@ pub(super) fn init(madt: Madt) {
- // x2APIC mode: skip 8-bit LocalApic IDs; they conflict with
- // 32-bit x2APIC IDs. Dedup only among LocalX2Apic entries.
+ if x2apic_fallback {
+ let id = u32::from(local.id);
+ if !seen_apic_ids.insert(id) {
+ warn!("MADT: duplicate APIC ID {} in LocalApic entry (x2APIC fallback), firmware bug", id);
+ }
+ } else {
@@ -228,0 +246 @@ pub(super) fn init(madt: Madt) {
+ }
@@ -252 +270 @@ pub(super) fn init(madt: Madt) {
- if local_apic.x2 {
+ if local_apic.x2 && !x2apic_fallback {
@@ -256,0 +275,131 @@ pub(super) fn init(madt: Madt) {
+ } else if local_apic.x2 && x2apic_fallback {
+ let apic_id = u32::from(ap_local_apic.id);
+ if apic_id == me.get() {
+ debug!(" This is my local APIC (x2APIC fallback, id={})", apic_id);
+ } else if ap_local_apic.flags & 1 == 1 {
+ let alloc = match allocate_p2frame(4) {
+ Some(frame) => frame,
+ None => {
+ println!("KERNEL AP: CPU {} no memory for stack, skipping", apic_id);
+ continue;
+ }
+ };
+ let stack_start = RmmA::phys_to_virt(alloc.base()).data();
+ let stack_end = stack_start + (PAGE_SIZE << 4);
+
+ let cpu_id = LogicalCpuId::new(crate::CPU_COUNT.fetch_add(1, Ordering::SeqCst));
+ if cpu_id.get() >= crate::cpu_set::MAX_CPU_COUNT {
+ println!(
+ "KERNEL AP: CPU {} exceeds logical CPU limit, skipping",
+ apic_id
+ );
+ continue;
+ }
+
+ let pcr_ptr = crate::arch::gdt::allocate_and_init_pcr(cpu_id, stack_end);
+ let idt_ptr = crate::arch::idt::allocate_and_init_idt(cpu_id);
+
+ let args = KernelArgsAp {
+ stack_end: stack_end as *mut u8,
+ cpu_id,
+ pcr_ptr,
+ idt_ptr,
+ };
+
+ let ap_ready = (TRAMPOLINE + 8) as *mut u64;
+ let ap_args_ptr = unsafe { ap_ready.add(1) };
+ let ap_page_table = unsafe { ap_ready.add(2) };
+ let ap_code = unsafe { ap_ready.add(3) };
+
+ unsafe {
+ ap_ready.write(0);
+ ap_args_ptr.write(&args as *const _ as u64);
+ ap_page_table.write(page_table_physaddr as u64);
+ #[expect(clippy::fn_to_numeric_cast)]
+ ap_code.write(kstart_ap as u64);
+
+ core::sync::atomic::fence(Ordering::SeqCst);
+ };
+ AP_READY.store(false, Ordering::SeqCst);
+
+ // Clear APIC Error Status Register before starting AP.
+ unsafe { local_apic.esr(); }
+
+ // Send INIT IPI (Assert) — x2APIC uses 64-bit ICR format.
+ {
+ let mut icr = 0x4500u64;
+ icr |= u64::from(apic_id) << 32;
+ local_apic.set_icr(icr);
+ }
+
+ // Intel SDM Vol 3A §8.4.4: wait 10ms after INIT deassert
+ early_udelay(10_000);
+
+ // Send START IPI #1
+ {
+ let ap_segment = (TRAMPOLINE >> 12) & 0xFF;
+ let mut icr = 0x0600 | ap_segment as u64;
+ icr |= u64::from(apic_id) << 32;
+ local_apic.set_icr(icr);
+ }
+
+ early_udelay(200);
+
+ // Send START IPI #2 (recommended for compatibility)
+ {
+ let ap_segment = (TRAMPOLINE >> 12) & 0xFF;
+ let mut icr = 0x0600 | ap_segment as u64;
+ icr |= u64::from(apic_id) << 32;
+ local_apic.set_icr(icr);
+ }
+
+ early_udelay(200);
+
+ // Check ESR for delivery errors after SIPI sequence.
+ let esr_val = unsafe { local_apic.esr() };
+ if esr_val != 0 {
+ println!(
+ "KERNEL AP: CPU {} SIPI delivery error (ESR={:#x}), continuing",
+ apic_id, esr_val
+ );
+ }
+
+ let mut trampoline_ready = false;
+ for _ in 0..AP_SPIN_LIMIT {
+ if unsafe { (*ap_ready.cast::<AtomicU8>()).load(Ordering::SeqCst) } != 0 {
+ trampoline_ready = true;
+ break;
+ }
+ hint::spin_loop();
+ }
+ if !trampoline_ready {
+ println!("KERNEL AP: CPU {} trampoline timeout, skipping", apic_id);
+ continue;
+ }
+
+ let mut kernel_ready = false;
+ for _ in 0..AP_SPIN_LIMIT {
+ if AP_READY.load(Ordering::SeqCst) {
+ kernel_ready = true;
+ break;
+ }
+ hint::spin_loop();
+ }
+ if !kernel_ready {
+ println!("KERNEL AP: CPU {} AP_READY timeout, skipping", apic_id);
+ continue;
+ }
+
+ // Record APIC→CPU mapping for NUMA topology.
+ unsafe {
+ record_apic_mapping(apic_id, cpu_id);
+ }
+ // Set NUMA node from SRAT data.
+ if let Some(percpu) = crate::percpu::get_for_cpu(cpu_id) {
+ if let Some(node) = crate::acpi::srat::numa_node_for_apic(apic_id) {
+ percpu.numa_node.set(node);
+ }
+ }
+
+ RmmA::invalidate_all();
+ }