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.
This commit is contained in:
2026-05-19 02:18:17 +03:00
parent b1022dfa39
commit ba5e010bb7
6 changed files with 651 additions and 1 deletions
@@ -0,0 +1,471 @@
diff --git a/drivers/graphics/fbbootlogd/src/main.rs b/drivers/graphics/fbbootlogd/src/main.rs
index 3e42d590..1f49a227 100644
--- a/drivers/graphics/fbbootlogd/src/main.rs
+++ b/drivers/graphics/fbbootlogd/src/main.rs
@@ -27 +27,4 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
- let event_queue = EventQueue::new().expect("fbbootlogd: failed to create event queue");
+ let event_queue = EventQueue::new().unwrap_or_else(|e| {
+ eprintln!("fbbootlogd: failed to create event queue: {}", e);
+ std::process::exit(1);
+ });
@@ -36 +39,4 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
- let socket = Socket::nonblock().expect("fbbootlogd: failed to create fbbootlog scheme");
+ let socket = Socket::nonblock().unwrap_or_else(|e| {
+ eprintln!("fbbootlogd: failed to create fbbootlog scheme: {}", e);
+ std::process::exit(1);
+ });
@@ -47 +53,4 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
- .expect("fbbootlogd: failed to subscribe to scheme events");
+ .unwrap_or_else(|e| {
+ eprintln!("fbbootlogd: failed to subscribe to scheme events: {}", e);
+ std::process::exit(1);
+ });
@@ -55 +64,4 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
- .expect("fbbootlogd: failed to subscribe to scheme events");
+ .unwrap_or_else(|e| {
+ eprintln!("fbbootlogd: failed to subscribe to scheme events: {}", e);
+ std::process::exit(1);
+ });
@@ -60 +72,4 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
- .expect("fbbootlogd: failed to create log fd");
+ .unwrap_or_else(|e| {
+ eprintln!("fbbootlogd: failed to create log fd: {}", e);
+ std::process::exit(1);
+ });
@@ -67 +82,4 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
- .expect("fbbootlogd: failed to open log/add_sink");
+ .unwrap_or_else(|e| {
+ eprintln!("fbbootlogd: failed to open log/add_sink: {}", e);
+ std::process::exit(1);
+ });
@@ -70 +88,4 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
- .expect("fbbootlogd: failed to send log fd to log scheme.");
+ .unwrap_or_else(|e| {
+ eprintln!("fbbootlogd: failed to send log fd to log scheme: {}", e);
+ 0
+ });
@@ -77,2 +97,0 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
- //libredox::call::setrens(0, 0).expect("fbbootlogd: failed to enter null namespace");
-
@@ -80 +99,2 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
- match event.expect("fbbootlogd: failed to get event").user_data {
+ match event {
+ Ok(event) => match event.user_data {
@@ -82,6 +102,7 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
- match handler
- .process_requests_nonblocking(&mut scheme)
- .expect("fbbootlogd: failed to process requests")
- {
- ControlFlow::Continue(()) => {}
- ControlFlow::Break(()) => break,
+ match handler.process_requests_nonblocking(&mut scheme) {
+ Ok(ControlFlow::Continue(())) => {}
+ Ok(ControlFlow::Break(())) => break,
+ Err(e) => {
+ eprintln!("fbbootlogd: failed to process requests: {}", e);
+ break;
+ }
@@ -93,7 +114,3 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
- match scheme
- .input_handle
- .read_events(&mut events)
- .expect("fbbootlogd: error while reading events")
- {
- ConsumerHandleEvent::Events(&[]) => break,
- ConsumerHandleEvent::Events(events) => {
+ match scheme.input_handle.read_events(&mut events) {
+ Ok(ConsumerHandleEvent::Events(&[]) ) => break,
+ Ok(ConsumerHandleEvent::Events(events)) => {
@@ -104 +121 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
- ConsumerHandleEvent::Handoff => {
+ Ok(ConsumerHandleEvent::Handoff) => {
@@ -107,0 +125,3 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
+ Err(e) => {
+ eprintln!("fbbootlogd: error while reading events: {}", e);
+ break;
@@ -111,0 +132,5 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
+ },
+ Err(e) => {
+ eprintln!("fbbootlogd: failed to get event: {}", e);
+ }
+ }
diff --git a/drivers/graphics/fbbootlogd/src/scheme.rs b/drivers/graphics/fbbootlogd/src/scheme.rs
index 08bd1805..67db5c54 100644
--- a/drivers/graphics/fbbootlogd/src/scheme.rs
+++ b/drivers/graphics/fbbootlogd/src/scheme.rs
@@ -29 +29,4 @@ impl FbbootlogScheme {
- input_handle: ConsumerHandle::bootlog_vt().expect("fbbootlogd: Failed to open vt"),
+ input_handle: ConsumerHandle::bootlog_vt().unwrap_or_else(|e| {
+ eprintln!("fbbootlogd: failed to open vt: {}", e);
+ std::process::exit(1);
+ }),
@@ -151 +154,3 @@ impl FbbootlogScheme {
- map.dirty_fb(total_damage).unwrap();
+ if let Err(e) = map.dirty_fb(total_damage) {
+ eprintln!("fbbootlogd: failed to sync framebuffer: {}", e);
+ }
@@ -245 +250,3 @@ impl SchemeSync for FbbootlogScheme {
- map.dirty_fb(damage).unwrap();
+ if let Err(e) = map.dirty_fb(damage) {
+ eprintln!("fbbootlogd: failed to sync framebuffer: {}", e);
+ }
diff --git a/drivers/graphics/fbcond/src/display.rs b/drivers/graphics/fbcond/src/display.rs
index e8543583..9bc6000f 100644
--- a/drivers/graphics/fbcond/src/display.rs
+++ b/drivers/graphics/fbcond/src/display.rs
@@ -86 +86,3 @@ impl Display {
- map.dirty_fb(damage).unwrap();
+ if let Err(e) = map.dirty_fb(damage) {
+ log::error!("fbcond: failed to sync framebuffer: {}", e);
+ }
diff --git a/drivers/graphics/fbcond/src/main.rs b/drivers/graphics/fbcond/src/main.rs
index d7d4abb8..45c04449 100644
--- a/drivers/graphics/fbcond/src/main.rs
+++ b/drivers/graphics/fbcond/src/main.rs
@@ -24 +24,6 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
- .map(|arg| arg.parse().expect("invalid vt number"))
+ .map(|arg| {
+ arg.parse().unwrap_or_else(|e| {
+ eprintln!("fbcond: invalid vt number: {}", e);
+ std::process::exit(1);
+ })
+ })
@@ -34 +39,4 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
- let mut event_queue = EventQueue::new().expect("fbcond: failed to create event queue");
+ let mut event_queue = EventQueue::new().unwrap_or_else(|e| {
+ eprintln!("fbcond: failed to create event queue: {}", e);
+ std::process::exit(1);
+ });
@@ -38 +46,4 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
- let mut socket = Socket::nonblock().expect("fbcond: failed to create fbcon scheme");
+ let mut socket = Socket::nonblock().unwrap_or_else(|e| {
+ eprintln!("fbcond: failed to create fbcon scheme: {}", e);
+ std::process::exit(1);
+ });
@@ -45 +56,4 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
- .expect("fbcond: failed to subscribe to scheme events");
+ .unwrap_or_else(|e| {
+ eprintln!("fbcond: failed to subscribe to scheme events: {}", e);
+ std::process::exit(1);
+ });
@@ -54,2 +67,0 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
- // libredox::call::setrens(0, 0).expect("fbcond: failed to enter null namespace");
-
@@ -71 +83,7 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! {
- let event = event.expect("fbcond: failed to read event from event queue");
+ let event = match event {
+ Ok(event) => event,
+ Err(e) => {
+ log::error!("fbcond: failed to read event from event queue: {}", e);
+ continue;
+ }
+ };
@@ -102 +120,4 @@ fn handle_event(
- Err(err) => panic!("fbcond: failed to read display scheme: {err}"),
+ Err(err) => {
+ log::error!("fbcond: failed to read display scheme: {err}");
+ break;
+ }
@@ -116 +137,4 @@ fn handle_event(
- .expect("fbcond: failed to write responses to fbcon scheme");
+ .unwrap_or_else(|e| {
+ log::error!("fbcond: failed to write responses to fbcon scheme: {}", e);
+ false
+ });
@@ -130 +154,4 @@ fn handle_event(
- .expect("fbcond: failed to write responses to fbcon scheme");
+ .unwrap_or_else(|e| {
+ log::error!("fbcond: failed to write responses to fbcon scheme: {}", e);
+ false
+ });
@@ -138 +165,4 @@ fn handle_event(
- .expect("fbcond: failed to write responses to fbcon scheme");
+ .unwrap_or_else(|e| {
+ log::error!("fbcond: failed to write responses to fbcon scheme: {}", e);
+ false
+ });
@@ -146 +176,4 @@ fn handle_event(
- .expect("fbcond: failed to write scheme");
+ .unwrap_or_else(|e| {
+ log::error!("fbcond: failed to write scheme: {}", e);
+ false
+ });
@@ -162 +195,4 @@ fn handle_event(
- .expect("vesad: failed to write display scheme");
+ .unwrap_or_else(|e| {
+ log::error!("vesad: failed to write display scheme: {}", e);
+ false
+ });
@@ -169 +205,4 @@ fn handle_event(
- let vt = scheme.vts.get_mut(&vt_i).unwrap();
+ let Some(vt) = scheme.vts.get_mut(&vt_i) else {
+ log::error!("fbcond: missing vt {:?}", vt_i);
+ return;
+ };
@@ -173,6 +212,9 @@ fn handle_event(
- match vt
- .display
- .input_handle
- .read_events(&mut events)
- .expect("fbcond: Error while reading events")
- {
+ let read_events = match vt.display.input_handle.read_events(&mut events) {
+ Ok(events) => events,
+ Err(e) => {
+ log::error!("fbcond: Error while reading events: {}", e);
+ break;
+ }
+ };
+
+ match read_events {
@@ -196,3 +238,4 @@ fn handle_event(
- let (op, caller) = blocked
- .get_mut(i)
- .expect("vesad: Failed to get blocked request");
+ let Some((op, caller)) = blocked.get_mut(i) else {
+ log::error!("vesad: Failed to get blocked request");
+ return;
+ };
@@ -222 +265,4 @@ fn handle_event(
- .expect("vesad: failed to write display scheme");
+ .unwrap_or_else(|e| {
+ log::error!("vesad: failed to write display scheme: {}", e);
+ false
+ });
@@ -247 +293,4 @@ fn handle_event(
- .expect("fbcond: failed to write display event");
+ .unwrap_or_else(|e| {
+ log::error!("fbcond: failed to write display event: {}", e);
+ false
+ });
diff --git a/drivers/graphics/fbcond/src/scheme.rs b/drivers/graphics/fbcond/src/scheme.rs
index b31ee2e3..2cd98086 100644
--- a/drivers/graphics/fbcond/src/scheme.rs
+++ b/drivers/graphics/fbcond/src/scheme.rs
@@ -54 +54,4 @@ impl FbconScheme {
- let display = Display::open_new_vt().expect("Failed to open display for vt");
+ let display = Display::open_new_vt().unwrap_or_else(|e| {
+ log::error!("fbcond: failed to open display for vt: {}", e);
+ std::process::exit(1);
+ });
@@ -61 +64,4 @@ impl FbconScheme {
- .expect("Failed to subscribe to input events for vt");
+ .unwrap_or_else(|e| {
+ log::error!("fbcond: failed to subscribe to input events for vt: {}", e);
+ std::process::exit(1);
+ });
@@ -140 +146 @@ impl SchemeSync for FbconScheme {
- write!(w, "{}", handle.vt_i.0).unwrap();
+ write!(w, "{}", handle.vt_i.0).map_err(|_| Error::new(ENOENT))?;
diff --git a/drivers/graphics/fbcond/src/text.rs b/drivers/graphics/fbcond/src/text.rs
index 8c85bf77..f9992d2b 100644
--- a/drivers/graphics/fbcond/src/text.rs
+++ b/drivers/graphics/fbcond/src/text.rs
@@ -122 +122,4 @@ impl TextScreen {
- buf[i] = self.input.pop_front().unwrap();
+ buf[i] = match self.input.pop_front() {
+ Some(v) => v,
+ None => break,
+ };
diff --git a/drivers/input/ps2d/src/main.rs b/drivers/input/ps2d/src/main.rs
index 86f903bf..569c73b9 100644
--- a/drivers/input/ps2d/src/main.rs
+++ b/drivers/input/ps2d/src/main.rs
@@ -32 +32,4 @@ fn daemon(daemon: daemon::Daemon) -> ! {
- acquire_port_io_rights().expect("ps2d: failed to get I/O permission");
+ if let Err(e) = acquire_port_io_rights() {
+ eprintln!("ps2d: failed to get I/O permission: {}", e);
+ process::exit(1);
+ }
@@ -34,2 +37,8 @@ fn daemon(daemon: daemon::Daemon) -> ! {
- let keyboard_input = InputProducer::new_named_or_fallback("ps2-keyboard").expect("ps2d: failed to open keyboard input");
- let mouse_input = InputProducer::new_named_or_fallback("ps2-mouse").expect("ps2d: failed to open mouse input");
+ let keyboard_input = InputProducer::new_named_or_fallback("ps2-keyboard").unwrap_or_else(|e| {
+ eprintln!("ps2d: failed to open keyboard input: {}", e);
+ process::exit(1);
+ });
+ let mouse_input = InputProducer::new_named_or_fallback("ps2-mouse").unwrap_or_else(|e| {
+ eprintln!("ps2d: failed to open mouse input: {}", e);
+ process::exit(1);
+ });
@@ -46 +55,4 @@ fn daemon(daemon: daemon::Daemon) -> ! {
- EventQueue::new().expect("ps2d: failed to create event queue");
+ EventQueue::new().unwrap_or_else(|e| {
+ eprintln!("ps2d: failed to create event queue: {}", e);
+ process::exit(1);
+ });
@@ -53 +65,4 @@ fn daemon(daemon: daemon::Daemon) -> ! {
- .expect("ps2d: failed to open /scheme/serio/0");
+ .unwrap_or_else(|e| {
+ eprintln!("ps2d: failed to open /scheme/serio/0: {}", e);
+ process::exit(1);
+ });
@@ -61 +76,4 @@ fn daemon(daemon: daemon::Daemon) -> ! {
- .unwrap();
+ .unwrap_or_else(|e| {
+ eprintln!("ps2d: failed to subscribe to keyboard events: {}", e);
+ process::exit(1);
+ });
@@ -68 +86,4 @@ fn daemon(daemon: daemon::Daemon) -> ! {
- .expect("ps2d: failed to open /scheme/serio/1");
+ .unwrap_or_else(|e| {
+ eprintln!("ps2d: failed to open /scheme/serio/1: {}", e);
+ process::exit(1);
+ });
@@ -76 +97,4 @@ fn daemon(daemon: daemon::Daemon) -> ! {
- .unwrap();
+ .unwrap_or_else(|e| {
+ eprintln!("ps2d: failed to subscribe to mouse events: {}", e);
+ process::exit(1);
+ });
@@ -83 +107,4 @@ fn daemon(daemon: daemon::Daemon) -> ! {
- .expect("ps2d: failed to open /scheme/time");
+ .unwrap_or_else(|e| {
+ eprintln!("ps2d: failed to open /scheme/time: {}", e);
+ process::exit(1);
+ });
@@ -91 +118,4 @@ fn daemon(daemon: daemon::Daemon) -> ! {
- .unwrap();
+ .unwrap_or_else(|e| {
+ eprintln!("ps2d: failed to subscribe to time events: {}", e);
+ process::exit(1);
+ });
@@ -93 +123,3 @@ fn daemon(daemon: daemon::Daemon) -> ! {
- libredox::call::setrens(0, 0).expect("ps2d: failed to enter null namespace");
+ if let Err(e) = libredox::call::setrens(0, 0) {
+ eprintln!("ps2d: failed to enter null namespace: {}", e);
+ }
@@ -100 +132,7 @@ fn daemon(daemon: daemon::Daemon) -> ! {
- for event in event_queue.map(|e| e.expect("ps2d: failed to get next event").user_data) {
+ for event in event_queue.map(|e| {
+ e.unwrap_or_else(|e2| {
+ eprintln!("ps2d: event read error: {}", e2);
+ process::exit(1);
+ })
+ .user_data
+ }) {
diff --git a/drivers/input/ps2d/src/state.rs b/drivers/input/ps2d/src/state.rs
index 8f5832f6..3e37f344 100644
--- a/drivers/input/ps2d/src/state.rs
+++ b/drivers/input/ps2d/src/state.rs
@@ -31,2 +31,2 @@ fn timespec_from_duration(duration: Duration) -> TimeSpec {
- tv_sec: duration.as_secs().try_into().unwrap(),
- tv_nsec: duration.subsec_nanos().try_into().unwrap(),
+ tv_sec: duration.as_secs().try_into().unwrap_or(i64::MAX),
+ tv_nsec: duration.subsec_nanos().try_into().unwrap_or(i32::MAX),
@@ -38,2 +38,2 @@ fn duration_from_timespec(timespec: TimeSpec) -> Duration {
- timespec.tv_sec.try_into().unwrap(),
- timespec.tv_nsec.try_into().unwrap(),
+ timespec.tv_sec.try_into().unwrap_or(u64::MAX),
+ timespec.tv_nsec.try_into().unwrap_or(u32::MAX),
@@ -320 +320,3 @@ impl Ps2d {
- .expect("failed to write key event");
+ .unwrap_or_else(|e| {
+ log::error!("ps2d: failed to write key event: {}", e);
+ });
@@ -350 +352,3 @@ impl Ps2d {
- .expect("ps2d: failed to write mouse event");
+ .unwrap_or_else(|e| {
+ log::error!("ps2d: failed to write mouse event: {}", e);
+ });
@@ -360 +364,3 @@ impl Ps2d {
- .expect("ps2d: failed to write mouse event");
+ .unwrap_or_else(|e| {
+ log::error!("ps2d: failed to write mouse event: {}", e);
+ });
@@ -373 +379,3 @@ impl Ps2d {
- .expect("ps2d: failed to write scroll event");
+ .unwrap_or_else(|e| {
+ log::error!("ps2d: failed to write scroll event: {}", e);
+ });
@@ -395 +403,3 @@ impl Ps2d {
- .expect("ps2d: failed to write button event");
+ .unwrap_or_else(|e| {
+ log::error!("ps2d: failed to write button event: {}", e);
+ });
@@ -494 +504,3 @@ impl Ps2d {
- .expect("ps2d: failed to write mouse event");
+ .unwrap_or_else(|e| {
+ log::error!("ps2d: failed to write mouse event: {}", e);
+ });
@@ -500 +512,3 @@ impl Ps2d {
- .expect("ps2d: failed to write scroll event");
+ .unwrap_or_else(|e| {
+ log::error!("ps2d: failed to write scroll event: {}", e);
+ });
@@ -526 +540,3 @@ impl Ps2d {
- .expect("ps2d: failed to write button event");
+ .unwrap_or_else(|e| {
+ log::error!("ps2d: failed to write button event: {}", e);
+ });
diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs
index 07aa943e..5b888a7c 100644
--- a/drivers/inputd/src/main.rs
+++ b/drivers/inputd/src/main.rs
@@ -277 +277 @@ impl SchemeSync for InputScheme {
- write!(w, "{vt}").unwrap();
+ write!(w, "{vt}").map_err(|_| SysError::new(EINVAL))?;
@@ -441 +441,4 @@ impl SchemeSync for InputScheme {
- assert!(matches!(handle, Handle::Producer));
+ if !matches!(handle, Handle::Producer) {
+ log::error!("inputd: unexpected non-producer handle in write");
+ return Err(SysError::new(EINVAL));
+ }
@@ -508,2 +511,2 @@ impl SchemeSync for InputScheme {
- match self.handles.remove(id).unwrap() {
- Handle::Consumer { vt, .. } => {
+ match self.handles.remove(id) {
+ Some(Handle::Consumer { vt, .. }) => {
@@ -519 +522,4 @@ impl SchemeSync for InputScheme {
- _ => {}
+ Some(_) => {}
+ None => {
+ log::error!("inputd: missing handle on close");
+ }
@@ -592 +598,4 @@ fn daemon_runner(daemon: daemon::SchemeDaemon) -> ! {
- deamon(daemon).unwrap();
+ if let Err(e) = deamon(daemon) {
+ log::error!("inputd: daemon failed: {:?}", e);
+ std::process::exit(1);
+ }
@@ -611 +620,7 @@ fn main() {
- let vt = args.next().unwrap().parse::<usize>().unwrap();
+ let vt = args
+ .next()
+ .and_then(|a| a.parse::<usize>().ok())
+ .unwrap_or_else(|| {
+ eprintln!("inputd: -A requires a VT number");
+ std::process::exit(1);
+ });
@@ -614,4 +629,8 @@ fn main() {
- inputd::ControlHandle::new().expect("inputd: failed to open control handle");
- handle
- .activate_vt(vt)
- .expect("inputd: failed to activate VT");
+ inputd::ControlHandle::new().unwrap_or_else(|e| {
+ eprintln!("inputd: failed to open control handle: {}", e);
+ std::process::exit(1);
+ });
+ handle.activate_vt(vt).unwrap_or_else(|e| {
+ eprintln!("inputd: failed to activate VT: {}", e);
+ std::process::exit(1);
+ });
@@ -634,4 +653,8 @@ fn main() {
- inputd::ControlHandle::new().expect("inputd: failed to open control handle");
- handle
- .activate_keymap(vt as usize)
- .expect("inputd: failed to activate keymap");
+ inputd::ControlHandle::new().unwrap_or_else(|e| {
+ eprintln!("inputd: failed to open control handle: {}", e);
+ std::process::exit(1);
+ });
+ handle.activate_keymap(vt as usize).unwrap_or_else(|e| {
+ eprintln!("inputd: failed to activate keymap: {}", e);
+ std::process::exit(1);
+ });
@@ -650 +673,4 @@ fn main() {
- _ => panic!("inputd: invalid argument: {}", val),
+ _ => {
+ eprintln!("inputd: invalid argument: {}", val);
+ std::process::exit(1);
+ }
@@ -0,0 +1,7 @@
diff --git a/init.initfs.d/50_rootfs.service b/init.initfs.d/50_rootfs.service
index c2d8e477..db7ba429 100644
--- a/init.initfs.d/50_rootfs.service
+++ b/init.initfs.d/50_rootfs.service
@@ -3 +3 @@ description = "Rootfs"
-requires_weak = ["40_drivers.target"]
+requires = ["40_drivers.target"]
@@ -0,0 +1,165 @@
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();
+ }
@@ -0,0 +1 @@
../../../local/patches/base/P23-rootfs-hard-dep-on-drivers.patch
+3 -1
View File
@@ -67,6 +67,8 @@ patches = [
"P19-init-startup-hardening.patch",
"P19-acpid-startup-hardening.patch",
"P20-ramfs-requires-randd.patch",
"P21-boot-daemon-graceful-panic.patch",
"P23-rootfs-hard-dep-on-drivers.patch",
]
[package]
@@ -244,4 +246,4 @@ done
mkdir -pv "${COOKBOOK_STAGE}/usr/lib/init.d"
cp -v "${COOKBOOK_SOURCE}/init.d"/* "${COOKBOOK_STAGE}/usr/lib/init.d/"
"""
"""
+4
View File
@@ -39,6 +39,10 @@ patches = [
# P21: x2APIC SMP bring-up fix — skip 8-bit LocalApic entries when x2APIC
# is active (BSP ID mismatch causes all APs to be skipped on bare metal Intel)
"../../../local/patches/kernel/P21-x2apic-smp-fix.patch",
# P22: x2APIC MADT fallback — when x2APIC is active but MADT has no
# LocalX2Apic entries (QEMU, some BIOS), fall back to processing LocalApic
# entries with zero-extended IDs using x2APIC 64-bit ICR format
"../../../local/patches/kernel/P22-x2apic-madt-fallback.patch",
]
[build]