diff --git a/local/patches/base/absorbed/P0-acpid-dmar-fix.patch b/local/patches/base/P0-acpid-dmar-fix.patch similarity index 100% rename from local/patches/base/absorbed/P0-acpid-dmar-fix.patch rename to local/patches/base/P0-acpid-dmar-fix.patch diff --git a/local/patches/base/absorbed/P0-acpid-fadt-shutdown.patch b/local/patches/base/P0-acpid-fadt-shutdown.patch similarity index 100% rename from local/patches/base/absorbed/P0-acpid-fadt-shutdown.patch rename to local/patches/base/P0-acpid-fadt-shutdown.patch diff --git a/local/patches/base/absorbed/P0-acpid-mcfg-ivrs.patch b/local/patches/base/P0-acpid-mcfg-ivrs.patch similarity index 100% rename from local/patches/base/absorbed/P0-acpid-mcfg-ivrs.patch rename to local/patches/base/P0-acpid-mcfg-ivrs.patch diff --git a/local/patches/base/absorbed/P0-acpid-power-methods.patch b/local/patches/base/P0-acpid-power-methods.patch similarity index 100% rename from local/patches/base/absorbed/P0-acpid-power-methods.patch rename to local/patches/base/P0-acpid-power-methods.patch diff --git a/local/patches/base/absorbed/P0-bootstrap-workspace-fix.patch b/local/patches/base/P0-bootstrap-workspace-fix.patch similarity index 100% rename from local/patches/base/absorbed/P0-bootstrap-workspace-fix.patch rename to local/patches/base/P0-bootstrap-workspace-fix.patch diff --git a/local/patches/base/absorbed/P0-cumulative-daemon-driver-fixes.patch b/local/patches/base/P0-cumulative-daemon-driver-fixes.patch similarity index 100% rename from local/patches/base/absorbed/P0-cumulative-daemon-driver-fixes.patch rename to local/patches/base/P0-cumulative-daemon-driver-fixes.patch diff --git a/local/patches/base/absorbed/P0-daemon-fix-init-notify-unwrap.patch b/local/patches/base/P0-daemon-fix-init-notify-unwrap.patch similarity index 100% rename from local/patches/base/absorbed/P0-daemon-fix-init-notify-unwrap.patch rename to local/patches/base/P0-daemon-fix-init-notify-unwrap.patch diff --git a/local/patches/base/absorbed/P0-daemon-init-notify-graceful.patch b/local/patches/base/P0-daemon-init-notify-graceful.patch similarity index 100% rename from local/patches/base/absorbed/P0-daemon-init-notify-graceful.patch rename to local/patches/base/P0-daemon-init-notify-graceful.patch diff --git a/local/patches/base/absorbed/P0-dhcpd-auto-iface.patch b/local/patches/base/P0-dhcpd-auto-iface.patch similarity index 100% rename from local/patches/base/absorbed/P0-dhcpd-auto-iface.patch rename to local/patches/base/P0-dhcpd-auto-iface.patch diff --git a/local/patches/base/absorbed/P0-driver-api-migration-fixes.patch b/local/patches/base/P0-driver-api-migration-fixes.patch similarity index 100% rename from local/patches/base/absorbed/P0-driver-api-migration-fixes.patch rename to local/patches/base/P0-driver-api-migration-fixes.patch diff --git a/local/patches/base/absorbed/P0-ihdgd-intel-gpu-ids.patch b/local/patches/base/P0-ihdgd-intel-gpu-ids.patch similarity index 100% rename from local/patches/base/absorbed/P0-ihdgd-intel-gpu-ids.patch rename to local/patches/base/P0-ihdgd-intel-gpu-ids.patch diff --git a/local/patches/base/P0-init-continuous-scheduling.patch b/local/patches/base/P0-init-continuous-scheduling.patch new file mode 100644 index 00000000..4f4b0837 --- /dev/null +++ b/local/patches/base/P0-init-continuous-scheduling.patch @@ -0,0 +1,21 @@ +diff --git a/init/src/main.rs b/init/src/main.rs +index 5682cf44..4501cf61 100644 +--- a/init/src/main.rs ++++ b/init/src/main.rs +@@ -178,7 +178,15 @@ fn main() { + libredox::call::setrens(0, 0).expect("init: failed to enter null namespace"); + + loop { ++ // Process any pending jobs whose dependencies may now be satisfied. ++ scheduler.step(&mut unit_store, &mut init_config); ++ ++ // Non-blocking wait: WNOHANG = 1. Reap any exited children without ++ // blocking, then loop back to process any newly-unblocked pending jobs. + let mut status = 0; +- libredox::call::waitpid(0, &mut status, 0).unwrap(); ++ match libredox::call::waitpid(0, &mut status, 1) { ++ Ok(_pid) => {} ++ Err(err) => eprintln!("init: waitpid error: {err}"), ++ } + } + } diff --git a/local/patches/base/absorbed/P0-inputd-named-producers.patch b/local/patches/base/P0-inputd-named-producers.patch similarity index 100% rename from local/patches/base/absorbed/P0-inputd-named-producers.patch rename to local/patches/base/P0-inputd-named-producers.patch diff --git a/local/patches/base/absorbed/P0-inputd-per-device-consumers.patch b/local/patches/base/P0-inputd-per-device-consumers.patch similarity index 100% rename from local/patches/base/absorbed/P0-inputd-per-device-consumers.patch rename to local/patches/base/P0-inputd-per-device-consumers.patch diff --git a/local/patches/base/absorbed/P0-pcid-config-endpoint.patch b/local/patches/base/P0-pcid-config-endpoint.patch similarity index 100% rename from local/patches/base/absorbed/P0-pcid-config-endpoint.patch rename to local/patches/base/P0-pcid-config-endpoint.patch diff --git a/local/patches/base/absorbed/P0-workspace-add-bootstrap.patch b/local/patches/base/P0-workspace-add-bootstrap.patch similarity index 100% rename from local/patches/base/absorbed/P0-workspace-add-bootstrap.patch rename to local/patches/base/P0-workspace-add-bootstrap.patch diff --git a/local/patches/base/absorbed/P1-acpid-acpi-core.patch b/local/patches/base/P1-acpid-acpi-core.patch similarity index 100% rename from local/patches/base/absorbed/P1-acpid-acpi-core.patch rename to local/patches/base/P1-acpid-acpi-core.patch diff --git a/local/patches/base/absorbed/P1-acpid-ec-runtime.patch b/local/patches/base/P1-acpid-ec-runtime.patch similarity index 100% rename from local/patches/base/absorbed/P1-acpid-ec-runtime.patch rename to local/patches/base/P1-acpid-ec-runtime.patch diff --git a/local/patches/base/absorbed/P1-acpid-power-enumeration.patch b/local/patches/base/P1-acpid-power-enumeration.patch similarity index 100% rename from local/patches/base/absorbed/P1-acpid-power-enumeration.patch rename to local/patches/base/P1-acpid-power-enumeration.patch diff --git a/local/patches/base/absorbed/P1-acpid-runtime-hardening.patch b/local/patches/base/P1-acpid-runtime-hardening.patch similarity index 100% rename from local/patches/base/absorbed/P1-acpid-runtime-hardening.patch rename to local/patches/base/P1-acpid-runtime-hardening.patch diff --git a/local/patches/base/absorbed/P1-acpid-scheme-surface.patch b/local/patches/base/P1-acpid-scheme-surface.patch similarity index 100% rename from local/patches/base/absorbed/P1-acpid-scheme-surface.patch rename to local/patches/base/P1-acpid-scheme-surface.patch diff --git a/local/patches/base/absorbed/P1-pci-irq-wave1-3.patch b/local/patches/base/P1-pci-irq-wave1-3.patch similarity index 100% rename from local/patches/base/absorbed/P1-pci-irq-wave1-3.patch rename to local/patches/base/P1-pci-irq-wave1-3.patch diff --git a/local/patches/base/absorbed/P1-pci-irq-wave1-5.patch b/local/patches/base/P1-pci-irq-wave1-5.patch similarity index 100% rename from local/patches/base/absorbed/P1-pci-irq-wave1-5.patch rename to local/patches/base/P1-pci-irq-wave1-5.patch diff --git a/local/patches/base/absorbed/P1-pcid-uevent-surface.patch b/local/patches/base/P1-pcid-uevent-surface.patch similarity index 100% rename from local/patches/base/absorbed/P1-pcid-uevent-surface.patch rename to local/patches/base/P1-pcid-uevent-surface.patch diff --git a/local/patches/base/absorbed/P1-xhcid-device-lifecycle.patch b/local/patches/base/P1-xhcid-device-lifecycle.patch similarity index 100% rename from local/patches/base/absorbed/P1-xhcid-device-lifecycle.patch rename to local/patches/base/P1-xhcid-device-lifecycle.patch diff --git a/local/patches/base/absorbed/P1-xhcid-port-pm-read-fix.patch b/local/patches/base/P1-xhcid-port-pm-read-fix.patch similarity index 100% rename from local/patches/base/absorbed/P1-xhcid-port-pm-read-fix.patch rename to local/patches/base/P1-xhcid-port-pm-read-fix.patch diff --git a/local/patches/base/absorbed/P1-xhcid-uevent-logging.patch b/local/patches/base/P1-xhcid-uevent-logging.patch similarity index 100% rename from local/patches/base/absorbed/P1-xhcid-uevent-logging.patch rename to local/patches/base/P1-xhcid-uevent-logging.patch diff --git a/local/patches/base/absorbed/P2-ac97d-ihdad-main.patch b/local/patches/base/P2-ac97d-ihdad-main.patch similarity index 100% rename from local/patches/base/absorbed/P2-ac97d-ihdad-main.patch rename to local/patches/base/P2-ac97d-ihdad-main.patch diff --git a/local/patches/base/absorbed/P2-acpi-defer-aml.patch b/local/patches/base/P2-acpi-defer-aml.patch similarity index 100% rename from local/patches/base/absorbed/P2-acpi-defer-aml.patch rename to local/patches/base/P2-acpi-defer-aml.patch diff --git a/local/patches/base/absorbed/P2-acpi-i2c-resources.patch b/local/patches/base/P2-acpi-i2c-resources.patch similarity index 100% rename from local/patches/base/absorbed/P2-acpi-i2c-resources.patch rename to local/patches/base/P2-acpi-i2c-resources.patch diff --git a/local/patches/base/absorbed/P2-acpid-core-refactor.patch b/local/patches/base/P2-acpid-core-refactor.patch similarity index 100% rename from local/patches/base/absorbed/P2-acpid-core-refactor.patch rename to local/patches/base/P2-acpid-core-refactor.patch diff --git a/local/patches/base/absorbed/P2-boot-logging.patch b/local/patches/base/P2-boot-logging.patch similarity index 100% rename from local/patches/base/absorbed/P2-boot-logging.patch rename to local/patches/base/P2-boot-logging.patch diff --git a/local/patches/base/absorbed/P2-boot-runtime-fixes.patch b/local/patches/base/P2-boot-runtime-fixes.patch similarity index 100% rename from local/patches/base/absorbed/P2-boot-runtime-fixes.patch rename to local/patches/base/P2-boot-runtime-fixes.patch diff --git a/local/patches/base/absorbed/P2-boot-runtime-noise-and-net-race.patch b/local/patches/base/P2-boot-runtime-noise-and-net-race.patch similarity index 100% rename from local/patches/base/absorbed/P2-boot-runtime-noise-and-net-race.patch rename to local/patches/base/P2-boot-runtime-noise-and-net-race.patch diff --git a/local/patches/base/absorbed/P2-daemon-hardening.patch b/local/patches/base/P2-daemon-hardening.patch similarity index 100% rename from local/patches/base/absorbed/P2-daemon-hardening.patch rename to local/patches/base/P2-daemon-hardening.patch diff --git a/local/patches/base/absorbed/P2-daemon-ready-graceful.patch b/local/patches/base/P2-daemon-ready-graceful.patch similarity index 100% rename from local/patches/base/absorbed/P2-daemon-ready-graceful.patch rename to local/patches/base/P2-daemon-ready-graceful.patch diff --git a/local/patches/base/absorbed/P2-hwd-misc.patch b/local/patches/base/P2-hwd-misc.patch similarity index 100% rename from local/patches/base/absorbed/P2-hwd-misc.patch rename to local/patches/base/P2-hwd-misc.patch diff --git a/local/patches/base/absorbed/P2-i2c-gpio-ucsi-drivers.patch b/local/patches/base/P2-i2c-gpio-ucsi-drivers.patch similarity index 99% rename from local/patches/base/absorbed/P2-i2c-gpio-ucsi-drivers.patch rename to local/patches/base/P2-i2c-gpio-ucsi-drivers.patch index f8ab8f45..b6c50fee 100644 --- a/local/patches/base/absorbed/P2-i2c-gpio-ucsi-drivers.patch +++ b/local/patches/base/P2-i2c-gpio-ucsi-drivers.patch @@ -2,7 +2,7 @@ diff --git a/Cargo.toml b/Cargo.toml index 9e776232..380f8d85 100644 --- a/Cargo.toml +++ b/Cargo.toml -@@ -66,6 +66,22 @@ members = [ +@@ -67,6 +67,22 @@ members = [ "drivers/usb/xhcid", "drivers/usb/usbctl", "drivers/usb/usbhubd", @@ -25,19 +25,6 @@ index 9e776232..380f8d85 100644 ] # Bootstrap needs it's own profile configuration -diff --git a/bootstrap/Cargo.toml b/bootstrap/Cargo.toml -index 82120c21..be1f8326 100644 ---- a/bootstrap/Cargo.toml -+++ b/bootstrap/Cargo.toml -@@ -6,6 +6,8 @@ authors = ["4lDO2 <4lDO2@protonmail.com>"] - edition = "2024" - license = "MIT" - -+[workspace] -+ - [dependencies] - hashbrown = { version = "0.15", default-features = false, features = [ - "inline-more", diff --git a/drivers/acpi-resource/Cargo.toml b/drivers/acpi-resource/Cargo.toml new file mode 100644 index 00000000..f30c6d02 diff --git a/local/patches/base/absorbed/P2-ihdad-device-refactor.patch b/local/patches/base/P2-ihdad-device-refactor.patch similarity index 100% rename from local/patches/base/absorbed/P2-ihdad-device-refactor.patch rename to local/patches/base/P2-ihdad-device-refactor.patch diff --git a/local/patches/base/absorbed/P2-ihdad-hda-stream.patch b/local/patches/base/P2-ihdad-hda-stream.patch similarity index 100% rename from local/patches/base/absorbed/P2-ihdad-hda-stream.patch rename to local/patches/base/P2-ihdad-hda-stream.patch diff --git a/local/patches/base/absorbed/P2-init-acpid-wiring.patch b/local/patches/base/P2-init-acpid-wiring.patch similarity index 100% rename from local/patches/base/absorbed/P2-init-acpid-wiring.patch rename to local/patches/base/P2-init-acpid-wiring.patch diff --git a/local/patches/base/absorbed/P2-initfs-pcid-service.patch b/local/patches/base/P2-initfs-pcid-service.patch similarity index 100% rename from local/patches/base/absorbed/P2-initfs-pcid-service.patch rename to local/patches/base/P2-initfs-pcid-service.patch diff --git a/local/patches/base/absorbed/P2-ixgbed-error-handling.patch b/local/patches/base/P2-ixgbed-error-handling.patch similarity index 100% rename from local/patches/base/absorbed/P2-ixgbed-error-handling.patch rename to local/patches/base/P2-ixgbed-error-handling.patch diff --git a/local/patches/base/absorbed/P2-misc-daemon-fixes.patch b/local/patches/base/P2-misc-daemon-fixes.patch similarity index 100% rename from local/patches/base/absorbed/P2-misc-daemon-fixes.patch rename to local/patches/base/P2-misc-daemon-fixes.patch diff --git a/local/patches/base/absorbed/P2-network-driver-mains.patch b/local/patches/base/P2-network-driver-mains.patch similarity index 100% rename from local/patches/base/absorbed/P2-network-driver-mains.patch rename to local/patches/base/P2-network-driver-mains.patch diff --git a/local/patches/base/absorbed/P2-network-error-handling.patch b/local/patches/base/P2-network-error-handling.patch similarity index 100% rename from local/patches/base/absorbed/P2-network-error-handling.patch rename to local/patches/base/P2-network-error-handling.patch diff --git a/local/patches/base/absorbed/P2-pcid-cfg-access.patch b/local/patches/base/P2-pcid-cfg-access.patch similarity index 100% rename from local/patches/base/absorbed/P2-pcid-cfg-access.patch rename to local/patches/base/P2-pcid-cfg-access.patch diff --git a/local/patches/base/absorbed/P2-pcid-driver-interface.patch b/local/patches/base/P2-pcid-driver-interface.patch similarity index 100% rename from local/patches/base/absorbed/P2-pcid-driver-interface.patch rename to local/patches/base/P2-pcid-driver-interface.patch diff --git a/local/patches/base/absorbed/P2-ps2d-improvements.patch b/local/patches/base/P2-ps2d-improvements.patch similarity index 100% rename from local/patches/base/absorbed/P2-ps2d-improvements.patch rename to local/patches/base/P2-ps2d-improvements.patch diff --git a/local/patches/base/absorbed/P2-storage-driver-mains.patch b/local/patches/base/P2-storage-driver-mains.patch similarity index 100% rename from local/patches/base/absorbed/P2-storage-driver-mains.patch rename to local/patches/base/P2-storage-driver-mains.patch diff --git a/local/patches/base/absorbed/P2-storage-error-handling.patch b/local/patches/base/P2-storage-error-handling.patch similarity index 100% rename from local/patches/base/absorbed/P2-storage-error-handling.patch rename to local/patches/base/P2-storage-error-handling.patch diff --git a/local/patches/base/absorbed/P2-usb-pm-and-drivers.patch b/local/patches/base/P2-usb-pm-and-drivers.patch similarity index 100% rename from local/patches/base/absorbed/P2-usb-pm-and-drivers.patch rename to local/patches/base/P2-usb-pm-and-drivers.patch diff --git a/local/patches/base/absorbed/P2-virtio-core-vbox.patch b/local/patches/base/P2-virtio-core-vbox.patch similarity index 100% rename from local/patches/base/absorbed/P2-virtio-core-vbox.patch rename to local/patches/base/P2-virtio-core-vbox.patch diff --git a/local/patches/base/absorbed/P2-xhcid-remaining.patch b/local/patches/base/P2-xhcid-remaining.patch similarity index 100% rename from local/patches/base/absorbed/P2-xhcid-remaining.patch rename to local/patches/base/P2-xhcid-remaining.patch diff --git a/local/patches/base/absorbed/P3-acpi-power-dmi.patch b/local/patches/base/P3-acpi-power-dmi.patch similarity index 100% rename from local/patches/base/absorbed/P3-acpi-power-dmi.patch rename to local/patches/base/P3-acpi-power-dmi.patch diff --git a/local/patches/base/absorbed/P3-acpi-wave12-hardening.patch b/local/patches/base/P3-acpi-wave12-hardening.patch similarity index 100% rename from local/patches/base/absorbed/P3-acpi-wave12-hardening.patch rename to local/patches/base/P3-acpi-wave12-hardening.patch diff --git a/local/patches/base/absorbed/P3-pcid-aer-scheme.patch b/local/patches/base/P3-pcid-aer-scheme.patch similarity index 100% rename from local/patches/base/absorbed/P3-pcid-aer-scheme.patch rename to local/patches/base/P3-pcid-aer-scheme.patch diff --git a/local/patches/base/absorbed/P3-pcid-bind-scheme.patch b/local/patches/base/P3-pcid-bind-scheme.patch similarity index 100% rename from local/patches/base/absorbed/P3-pcid-bind-scheme.patch rename to local/patches/base/P3-pcid-bind-scheme.patch diff --git a/local/patches/base/absorbed/P3-pcid-uevent-format-fix.patch b/local/patches/base/P3-pcid-uevent-format-fix.patch similarity index 100% rename from local/patches/base/absorbed/P3-pcid-uevent-format-fix.patch rename to local/patches/base/P3-pcid-uevent-format-fix.patch diff --git a/local/patches/base/absorbed/P3-xhci-device-hardening.patch b/local/patches/base/P3-xhci-device-hardening.patch similarity index 100% rename from local/patches/base/absorbed/P3-xhci-device-hardening.patch rename to local/patches/base/P3-xhci-device-hardening.patch diff --git a/local/patches/base/absorbed/P5-init-daemon-panic-hardening.patch b/local/patches/base/P5-init-daemon-panic-hardening.patch similarity index 100% rename from local/patches/base/absorbed/P5-init-daemon-panic-hardening.patch rename to local/patches/base/P5-init-daemon-panic-hardening.patch diff --git a/local/patches/base/absorbed/P5-init-supervisor-restart.patch b/local/patches/base/P5-init-supervisor-restart.patch similarity index 100% rename from local/patches/base/absorbed/P5-init-supervisor-restart.patch rename to local/patches/base/P5-init-supervisor-restart.patch diff --git a/local/patches/base/P9-fix-so-pecred.patch b/local/patches/base/P9-fix-so-pecred.patch new file mode 100644 index 00000000..b3b38a95 --- /dev/null +++ b/local/patches/base/P9-fix-so-pecred.patch @@ -0,0 +1,153 @@ +diff --git a/ipcd/src/uds/stream.rs b/ipcd/src/uds/stream.rs +index 81c846fd..d55d2bb7 100644 +--- a/ipcd/src/uds/stream.rs ++++ b/ipcd/src/uds/stream.rs +@@ -180,7 +180,7 @@ pub struct Socket { + options: HashSet, + flags: usize, + state: State, +- awaiting: VecDeque, ++ awaiting: VecDeque<(usize, ucred)>, + connection: Option, + issued_token: Option, + ucred: ucred, +@@ -241,6 +241,7 @@ impl Socket { + &mut self, + primary_id: usize, + awaiting_client_id: usize, ++ client_ucred: ucred, + ctx: &CallerCtx, + ) -> Result { + if !self.is_listening() { +@@ -250,15 +251,17 @@ impl Socket { + ); + return Err(Error::new(EINVAL)); + } +- Ok(Self::new( ++ Ok(Self { + primary_id, +- self.path.clone(), +- State::Established, +- self.options.clone(), +- self.flags, +- Some(Connection::new(awaiting_client_id)), +- ctx, +- )) ++ path: self.path.clone(), ++ state: State::Established, ++ options: self.options.clone(), ++ flags: self.flags, ++ awaiting: VecDeque::new(), ++ connection: Some(Connection::new(awaiting_client_id)), ++ issued_token: None, ++ ucred: client_ucred, ++ }) + } + + fn establish(&mut self, new_socket: &mut Self, peer: usize) -> Result<()> { +@@ -286,7 +289,7 @@ impl Socket { + Ok(()) + } + +- fn connect(&mut self, other: &mut Socket) -> Result<()> { ++ fn connect(&mut self, other: &mut Socket, client_ucred: ucred) -> Result<()> { + match self.state { + State::Unbound | State::Bound => { + // If the socket is unbound or bound, wait for the listener to start listening. +@@ -302,12 +305,12 @@ impl Socket { + } + _ => return Err(Error::new(ECONNREFUSED)), + } +- self.connect_unchecked(other); ++ self.connect_unchecked(other, client_ucred); + Ok(()) + } + +- fn connect_unchecked(&mut self, other: &mut Socket) { +- self.awaiting.push_back(other.primary_id); ++ fn connect_unchecked(&mut self, other: &mut Socket, client_ucred: ucred) { ++ self.awaiting.push_back((other.primary_id, client_ucred)); + other.state = State::Connecting; + other.connection = Some(Connection::new(self.primary_id)); + } +@@ -495,7 +498,7 @@ impl<'sock> UdsStreamScheme<'sock> { + }; + match verb { + SocketCall::Bind => self.handle_bind(id, &payload), +- SocketCall::Connect => self.handle_connect(id, &payload), ++ SocketCall::Connect => self.handle_connect(id, &payload, ctx), + SocketCall::SetSockOpt => self.handle_setsockopt( + id, + *metadata.get(1).ok_or(Error::new(EINVAL))? as i32, +@@ -588,7 +591,7 @@ impl<'sock> UdsStreamScheme<'sock> { + // and changes its own state to `Established`. + // + // After these three phases, the socket connection is considered established. +- fn handle_connect(&mut self, id: usize, token_buf: &[u8]) -> Result { ++ fn handle_connect(&mut self, id: usize, token_buf: &[u8], ctx: &CallerCtx) -> Result { + let token = read_num::(token_buf)?; + let (listener_id, connecting_res) = { + let listener_rc = self +@@ -633,7 +636,8 @@ impl<'sock> UdsStreamScheme<'sock> { + } + + // Phase 2: listener is now listening +- listener.connect(&mut client)?; ++ let client_ucred = ucred { pid: ctx.pid as _, uid: ctx.uid as _, gid: ctx.gid as _ }; ++ listener.connect(&mut client, client_ucred)?; + + (listener_id, connecting_res) + }; +@@ -873,6 +877,7 @@ impl<'sock> UdsStreamScheme<'sock> { + &mut self, + listener_socket: &mut Socket, + client_id: usize, ++ client_ucred: ucred, + ctx: &CallerCtx, + ) -> Result> { + let (new_id, new) = { +@@ -880,7 +885,7 @@ impl<'sock> UdsStreamScheme<'sock> { + return Ok(None); // Client socket has been closed, nothing to accept + }; + let new_id = self.next_id; +- let mut new = listener_socket.accept(new_id, client_id, ctx)?; ++ let mut new = listener_socket.accept(new_id, client_id, client_ucred, ctx)?; + + let mut client_socket = client_rc.borrow_mut(); + client_socket.establish(&mut new, listener_socket.primary_id)?; +@@ -912,14 +917,14 @@ impl<'sock> UdsStreamScheme<'sock> { + } + loop { + // Try to accept a waiting connection +- let Some(client_id) = socket.awaiting.pop_front() else { ++ let Some((client_id, client_ucred)) = socket.awaiting.pop_front() else { + if flags & O_NONBLOCK == O_NONBLOCK { + return Err(Error::new(EAGAIN)); + } else { + return Err(Error::new(EWOULDBLOCK)); + } + }; +- return match self.accept_connection(socket, client_id, ctx) { ++ return match self.accept_connection(socket, client_id, client_ucred, ctx) { + Ok(conn) => Ok(conn), + Err(Error { errno: EAGAIN }) => continue, + Err(e) => Err(e), +@@ -991,7 +996,8 @@ impl<'sock> UdsStreamScheme<'sock> { + ); + return Err(Error::new(EPIPE)); + } +- socket.connect_unchecked(&mut new); ++ let pair_ucred = ucred { pid: ctx.pid as _, uid: ctx.uid as _, gid: ctx.gid as _ }; ++ socket.connect_unchecked(&mut new, pair_ucred); + } + + // smoltcp sends writeable whenever a listener gets a +@@ -1186,7 +1192,7 @@ impl<'sock> UdsStreamScheme<'sock> { + } + + // Notify all waiting clients about listener closure +- for client_id in &socket.awaiting { ++ for (client_id, _) in &socket.awaiting { + if let Ok(client_rc) = self.get_socket(*client_id) { + { + let mut client = client_rc.borrow_mut(); diff --git a/local/patches/base/P9-init-debug-scheduler.patch b/local/patches/base/P9-init-debug-scheduler.patch new file mode 100644 index 00000000..bb076ca4 --- /dev/null +++ b/local/patches/base/P9-init-debug-scheduler.patch @@ -0,0 +1,11 @@ +diff --git a/init/src/scheduler.rs b/init/src/scheduler.rs +--- a/init/src/scheduler.rs ++++ b/init/src/scheduler.rs +@@ -63,6 +63,7 @@ + match job.kind { + JobKind::Start => { + let unit = unit_store.unit_mut(&job.unit); ++ eprintln!("init: DBG job={}", job.unit.0); + + for dep in &unit.info.requires_weak { + for pending_job in &self.pending { diff --git a/local/patches/base/absorbed/README.md b/local/patches/base/PATCH-HISTORY.md similarity index 100% rename from local/patches/base/absorbed/README.md rename to local/patches/base/PATCH-HISTORY.md diff --git a/local/patches/base/absorbed/redox.patch b/local/patches/base/absorbed/redox.patch deleted file mode 100644 index b8835f2f..00000000 --- a/local/patches/base/absorbed/redox.patch +++ /dev/null @@ -1,17038 +0,0 @@ -diff --git a/Cargo.lock b/Cargo.lock -index 9fcbd662..6aa362f6 100644 ---- a/Cargo.lock -+++ b/Cargo.lock -@@ -31,11 +31,20 @@ dependencies = [ - "spinning_top", - ] - -+[[package]] -+name = "acpi-resource" -+version = "0.0.1" -+dependencies = [ -+ "serde", -+ "thiserror 2.0.18", -+] -+ - [[package]] - name = "acpid" - version = "0.1.0" - dependencies = [ - "acpi", -+ "acpi-resource", - "amlserde", - "arrayvec", - "common", -@@ -54,6 +63,7 @@ dependencies = [ - "scheme-utils", - "serde", - "thiserror 2.0.18", -+ "toml", - ] - - [[package]] -@@ -80,6 +90,23 @@ dependencies = [ - "memchr 2.8.0", - ] - -+[[package]] -+name = "amd-mp2-i2cd" -+version = "0.1.0" -+dependencies = [ -+ "acpi-resource", -+ "anyhow", -+ "common", -+ "daemon", -+ "i2c-interface", -+ "libredox", -+ "log", -+ "pcid", -+ "redox_syscall 0.7.4", -+ "ron", -+ "serde", -+] -+ - [[package]] - name = "amlserde" - version = "0.0.1" -@@ -584,6 +611,7 @@ dependencies = [ - "inputd", - "libredox", - "log", -+ "nom", - "redox-ioctl", - "redox-scheme", - "redox_syscall 0.7.4", -@@ -642,6 +670,22 @@ dependencies = [ - "linux-raw-sys 0.9.4", - ] - -+[[package]] -+name = "dw-acpi-i2cd" -+version = "0.1.0" -+dependencies = [ -+ "acpi-resource", -+ "anyhow", -+ "common", -+ "daemon", -+ "i2c-interface", -+ "libredox", -+ "log", -+ "redox_syscall 0.7.4", -+ "ron", -+ "serde", -+] -+ - [[package]] - name = "e1000d" - version = "0.1.0" -@@ -909,6 +953,22 @@ dependencies = [ - "wasip3", - ] - -+[[package]] -+name = "gpiod" -+version = "0.1.0" -+dependencies = [ -+ "anyhow", -+ "common", -+ "daemon", -+ "libredox", -+ "log", -+ "redox-scheme", -+ "redox_syscall 0.7.4", -+ "ron", -+ "scheme-utils", -+ "serde", -+] -+ - [[package]] - name = "gpt" - version = "3.1.0" -@@ -999,11 +1059,74 @@ dependencies = [ - "common", - "daemon", - "fdt 0.1.5", -+ "libc", - "libredox", - "log", - "ron", - ] - -+[[package]] -+name = "i2c-gpio-expanderd" -+version = "0.1.0" -+dependencies = [ -+ "acpi-resource", -+ "anyhow", -+ "common", -+ "daemon", -+ "i2c-interface", -+ "libredox", -+ "log", -+ "redox_syscall 0.7.4", -+ "ron", -+ "serde", -+] -+ -+[[package]] -+name = "i2c-hidd" -+version = "0.1.0" -+dependencies = [ -+ "acpi-resource", -+ "amlserde", -+ "anyhow", -+ "common", -+ "daemon", -+ "i2c-interface", -+ "inputd", -+ "libredox", -+ "log", -+ "orbclient", -+ "redox-scheme", -+ "redox_syscall 0.7.4", -+ "ron", -+ "scheme-utils", -+ "serde", -+] -+ -+[[package]] -+name = "i2c-interface" -+version = "0.1.0" -+dependencies = [ -+ "redox_syscall 0.7.4", -+ "serde", -+] -+ -+[[package]] -+name = "i2cd" -+version = "0.1.0" -+dependencies = [ -+ "anyhow", -+ "common", -+ "daemon", -+ "i2c-interface", -+ "libredox", -+ "log", -+ "redox-scheme", -+ "redox_syscall 0.7.4", -+ "ron", -+ "scheme-utils", -+ "serde", -+] -+ - [[package]] - name = "iana-time-zone" - version = "0.1.65" -@@ -1128,6 +1251,58 @@ dependencies = [ - "scheme-utils", - ] - -+[[package]] -+name = "intel-gpiod" -+version = "0.1.0" -+dependencies = [ -+ "acpi-resource", -+ "anyhow", -+ "common", -+ "daemon", -+ "libredox", -+ "log", -+ "redox_syscall 0.7.4", -+ "ron", -+ "serde", -+] -+ -+[[package]] -+name = "intel-lpss-i2cd" -+version = "0.1.0" -+dependencies = [ -+ "acpi-resource", -+ "anyhow", -+ "common", -+ "daemon", -+ "i2c-interface", -+ "libredox", -+ "log", -+ "redox_syscall 0.7.4", -+ "ron", -+ "serde", -+] -+ -+[[package]] -+name = "intel-thc-hidd" -+version = "0.1.0" -+dependencies = [ -+ "acpi-resource", -+ "amlserde", -+ "anyhow", -+ "common", -+ "daemon", -+ "i2c-interface", -+ "libredox", -+ "log", -+ "pci_types", -+ "pcid", -+ "redox-scheme", -+ "redox_syscall 0.7.4", -+ "ron", -+ "scheme-utils", -+ "serde", -+] -+ - [[package]] - name = "ioslice" - version = "0.6.0" -@@ -1174,6 +1349,7 @@ dependencies = [ - "daemon", - "driver-network", - "libredox", -+ "log", - "pcid", - "redox_event", - "redox_syscall 0.7.4", -@@ -2390,6 +2566,24 @@ version = "1.19.0" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" - -+[[package]] -+name = "ucsid" -+version = "0.1.0" -+dependencies = [ -+ "acpi-resource", -+ "anyhow", -+ "common", -+ "daemon", -+ "i2c-interface", -+ "libredox", -+ "log", -+ "redox-scheme", -+ "redox_syscall 0.7.4", -+ "ron", -+ "scheme-utils", -+ "serde", -+] -+ - [[package]] - name = "unicode-ident" - version = "1.0.24" -diff --git a/Cargo.toml b/Cargo.toml -index 9e776232..36d87870 100644 ---- a/Cargo.toml -+++ b/Cargo.toml -@@ -20,8 +20,17 @@ members = [ - "drivers/common", - "drivers/executor", - -+ "drivers/acpi-resource", - "drivers/acpid", -+ "drivers/gpio/gpiod", -+ "drivers/gpio/i2c-gpio-expanderd", -+ "drivers/gpio/intel-gpiod", - "drivers/hwd", -+ "drivers/i2c/amd-mp2-i2cd", -+ "drivers/i2c/dw-acpi-i2cd", -+ "drivers/i2c/i2c-interface", -+ "drivers/i2c/i2cd", -+ "drivers/i2c/intel-lpss-i2cd", - "drivers/pcid", - "drivers/pcid-spawner", - "drivers/rtcd", -@@ -43,6 +52,8 @@ members = [ - "drivers/graphics/virtio-gpud", - - "drivers/input/ps2d", -+ "drivers/input/i2c-hidd", -+ "drivers/input/intel-thc-hidd", - "drivers/input/usbhidd", - - "drivers/net/driver-network", -@@ -63,6 +74,7 @@ members = [ - "drivers/storage/usbscsid", - "drivers/storage/virtio-blkd", - -+ "drivers/usb/ucsid", - "drivers/usb/xhcid", - "drivers/usb/usbctl", - "drivers/usb/usbhubd", -@@ -81,6 +93,7 @@ drm = "0.15.0" - drm-sys = "0.8.1" - edid = "0.3.0" #TODO: edid is abandoned, fork it and maintain? - fdt = "0.1.5" -+nom = "3.2.0" # transitive dep via edid; needed to match on IResult variants - libc = "0.2.181" - log = "0.4" - libredox = "0.1.16" -diff --git a/audiod/src/main.rs b/audiod/src/main.rs -index 51b103af..2354cf5f 100644 ---- a/audiod/src/main.rs -+++ b/audiod/src/main.rs -@@ -48,7 +48,14 @@ fn daemon(daemon: SchemeDaemon) -> anyhow::Result<()> { - - let pid = libredox::call::getpid()?; - -- let hw_file = Fd::open("/scheme/audiohw", flag::O_WRONLY | flag::O_CLOEXEC, 0)?; -+ let hw_file = match Fd::open("/scheme/audiohw", flag::O_WRONLY | flag::O_CLOEXEC, 0) { -+ Ok(fd) => fd, -+ Err(err) if err.errno() == syscall::ENODEV => { -+ eprintln!("audiod: no audio hardware detected"); -+ return Ok(()); -+ } -+ Err(err) => return Err(err).context("failed to open /scheme/audiohw"), -+ }; - - let socket = Socket::create().context("failed to create scheme")?; - -diff --git a/daemon/src/lib.rs b/daemon/src/lib.rs -index 9f507221..4e434082 100644 ---- a/daemon/src/lib.rs -+++ b/daemon/src/lib.rs -@@ -11,12 +11,23 @@ use redox_scheme::Socket; - use redox_scheme::scheme::{SchemeAsync, SchemeSync}; - - unsafe fn get_fd(var: &str) -> RawFd { -- let fd: RawFd = std::env::var(var).unwrap().parse().unwrap(); -+ let fd: RawFd = match std::env::var(var) -+ .map_err(|e| eprintln!("daemon: env var {var} not set: {e}")) -+ .ok() -+ .and_then(|val| { -+ val.parse() -+ .map_err(|e| eprintln!("daemon: failed to parse {var} as fd: {e}")) -+ .ok() -+ }) { -+ Some(fd) => fd, -+ None => return -1, -+ }; - if unsafe { libc::fcntl(fd, libc::F_SETFD, libc::FD_CLOEXEC) } == -1 { -- panic!( -+ eprintln!( - "daemon: failed to set CLOEXEC flag for {var} fd: {}", - io::Error::last_os_error() - ); -+ return -1; - } - fd - } -@@ -51,31 +62,40 @@ impl Daemon { - - /// Notify the process that the daemon is ready to accept requests. - pub fn ready(mut self) { -- self.write_pipe.write_all(&[0]).unwrap(); -+ if let Err(err) = self.write_pipe.write_all(&[0]) { -+ if err.kind() != io::ErrorKind::BrokenPipe { -+ eprintln!("daemon::ready write failed: {err}"); -+ } -+ } - } - - /// Executes `Command` as a child process. - // FIXME remove once the service spawning of hwd and pcid-spawner is moved to init - #[deprecated] -- pub fn spawn(mut cmd: Command) { -- let (mut read_pipe, write_pipe) = io::pipe().unwrap(); -+ pub fn spawn(mut cmd: Command) -> io::Result<()> { -+ let (mut read_pipe, write_pipe) = io::pipe().map_err(|err| { -+ io::Error::new(err.kind(), format!("daemon: failed to create readiness pipe: {err}")) -+ })?; - - unsafe { pass_fd(&mut cmd, "INIT_NOTIFY", write_pipe.into()) }; - -- if let Err(err) = cmd.spawn() { -- eprintln!("daemon: failed to execute {cmd:?}: {err}"); -- return; -- } -+ cmd.spawn().map_err(|err| { -+ io::Error::new(err.kind(), format!("failed to execute {cmd:?}: {err}")) -+ })?; - - let mut data = [0]; - match read_pipe.read_exact(&mut data) { -- Ok(()) => {} -+ Ok(()) => Ok(()), - Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => { -- eprintln!("daemon: {cmd:?} exited without notifying readiness"); -- } -- Err(err) => { -- eprintln!("daemon: failed to wait for {cmd:?}: {err}"); -+ Err(io::Error::new( -+ io::ErrorKind::UnexpectedEof, -+ format!("{cmd:?} exited without notifying readiness"), -+ )) - } -+ Err(err) => Err(io::Error::new( -+ err.kind(), -+ format!("failed to wait for {cmd:?}: {err}"), -+ )), - } - } - } -@@ -96,13 +116,16 @@ impl SchemeDaemon { - - /// Notify the process that the scheme daemon is ready to accept requests. - pub fn ready_with_fd(self, cap_fd: Fd) -> syscall::Result<()> { -- syscall::call_wo( -+ match syscall::call_wo( - self.write_pipe.as_raw_fd() as usize, - &cap_fd.into_raw().to_ne_bytes(), - syscall::CallFlags::FD, - &[], -- )?; -- Ok(()) -+ ) { -+ Ok(_) => Ok(()), -+ Err(err) if err.errno == syscall::EPIPE => Ok(()), -+ Err(err) => Err(err), -+ } - } - - /// Notify the process that the synchronous scheme daemon is ready to accept requests. -diff --git a/drivers/acpid/Cargo.toml b/drivers/acpid/Cargo.toml -index 2d22a8f9..712b6d6e 100644 ---- a/drivers/acpid/Cargo.toml -+++ b/drivers/acpid/Cargo.toml -@@ -8,6 +8,7 @@ edition = "2018" - # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - - [dependencies] -+acpi-resource = { path = "../acpi-resource" } - acpi = { git = "https://github.com/jackpot51/acpi.git" } - arrayvec = "0.7.6" - log.workspace = true -@@ -21,6 +22,7 @@ rustc-hash = "1.1.0" - thiserror.workspace = true - ron.workspace = true - serde.workspace = true -+toml.workspace = true - - amlserde = { path = "../amlserde" } - common = { path = "../common" } -diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs -index 94a1eb17..a7cde5d6 100644 ---- a/drivers/acpid/src/acpi.rs -+++ b/drivers/acpid/src/acpi.rs -@@ -1,13 +1,15 @@ - use acpi::aml::object::{Object, WrappedObject}; --use acpi::aml::op_region::{RegionHandler, RegionSpace}; - use rustc_hash::FxHashMap; -+use std::any::Any; - use std::convert::{TryFrom, TryInto}; - use std::error::Error; - use std::ops::Deref; -+use std::panic::{catch_unwind, AssertUnwindSafe}; - use std::str::FromStr; - use std::sync::{Arc, Mutex}; - use std::{fmt, mem}; - use syscall::PAGE_SIZE; -+use toml::Value; - - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - use common::io::{Io, Pio}; -@@ -16,16 +18,17 @@ use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; - use thiserror::Error; - - use acpi::{ -- aml::{namespace::AmlName, AmlError, Interpreter}, -+ aml::{namespace::AmlName, op_region::RegionSpace, AmlError, Interpreter}, - platform::AcpiPlatform, - AcpiTables, - }; - use amlserde::aml_serde_name::aml_to_symbol; - use amlserde::{AmlSerde, AmlSerdeValue}; - --#[cfg(target_arch = "x86_64")] --pub mod dmar; - use crate::aml_physmem::{AmlPageCache, AmlPhysMemHandler}; -+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -+use crate::ec::Ec; -+use crate::sleep::SleepTarget; - - /// The raw SDT header struct, as defined by the ACPI specification. - #[derive(Copy, Clone, Debug)] -@@ -206,6 +209,615 @@ impl Sdt { - } - } - -+#[derive(Clone, Debug, Default)] -+pub struct DmiInfo { -+ pub sys_vendor: Option, -+ pub board_vendor: Option, -+ pub board_name: Option, -+ pub board_version: Option, -+ pub product_name: Option, -+ pub product_version: Option, -+ pub bios_version: Option, -+} -+ -+impl DmiInfo { -+ pub fn to_match_lines(&self) -> String { -+ let mut lines = Vec::new(); -+ if let Some(value) = &self.sys_vendor { -+ lines.push(format!("sys_vendor={value}")); -+ } -+ if let Some(value) = &self.board_vendor { -+ lines.push(format!("board_vendor={value}")); -+ } -+ if let Some(value) = &self.board_name { -+ lines.push(format!("board_name={value}")); -+ } -+ if let Some(value) = &self.board_version { -+ lines.push(format!("board_version={value}")); -+ } -+ if let Some(value) = &self.product_name { -+ lines.push(format!("product_name={value}")); -+ } -+ if let Some(value) = &self.product_version { -+ lines.push(format!("product_version={value}")); -+ } -+ if let Some(value) = &self.bios_version { -+ lines.push(format!("bios_version={value}")); -+ } -+ lines.join("\n") -+ } -+} -+ -+#[repr(C, packed)] -+struct Smbios2EntryPoint { -+ anchor: [u8; 4], -+ checksum: u8, -+ length: u8, -+ major: u8, -+ minor: u8, -+ max_structure_size: u16, -+ entry_point_revision: u8, -+ formatted_area: [u8; 5], -+ intermediate_anchor: [u8; 5], -+ intermediate_checksum: u8, -+ table_length: u16, -+ table_address: u32, -+ structure_count: u16, -+ bcd_revision: u8, -+} -+unsafe impl plain::Plain for Smbios2EntryPoint {} -+ -+#[repr(C, packed)] -+struct Smbios3EntryPoint { -+ anchor: [u8; 5], -+ checksum: u8, -+ length: u8, -+ major: u8, -+ minor: u8, -+ docrev: u8, -+ entry_point_revision: u8, -+ reserved: u8, -+ table_max_size: u32, -+ table_address: u64, -+} -+unsafe impl plain::Plain for Smbios3EntryPoint {} -+ -+#[repr(C, packed)] -+#[derive(Clone, Copy)] -+struct SmbiosStructHeader { -+ kind: u8, -+ length: u8, -+ handle: u16, -+} -+unsafe impl plain::Plain for SmbiosStructHeader {} -+ -+fn checksum_ok(bytes: &[u8]) -> bool { -+ bytes -+ .iter() -+ .copied() -+ .fold(0u8, |acc, byte| acc.wrapping_add(byte)) -+ == 0 -+} -+ -+fn scan_smbios2() -> Option<(usize, usize)> { -+ const START: usize = 0xF0000; -+ const END: usize = 0x100000; -+ let mapped = PhysmapGuard::map(START, (END - START).div_ceil(PAGE_SIZE)).ok()?; -+ let bytes = &mapped[..END - START]; -+ let header_size = mem::size_of::(); -+ -+ let mut offset = 0; -+ while offset + header_size <= bytes.len() { -+ if &bytes[offset..offset + 4] == b"_SM_" { -+ let entry = -+ plain::from_bytes::(&bytes[offset..offset + header_size]) -+ .ok()?; -+ let length = entry.length as usize; -+ if offset + length <= bytes.len() -+ && length >= header_size -+ && checksum_ok(&bytes[offset..offset + length]) -+ && &entry.intermediate_anchor == b"_DMI_" -+ { -+ return Some((entry.table_address as usize, entry.table_length as usize)); -+ } -+ } -+ offset += 16; -+ } -+ None -+} -+ -+fn scan_smbios3() -> Option<(usize, usize)> { -+ const START: usize = 0xF0000; -+ const END: usize = 0x100000; -+ let mapped = PhysmapGuard::map(START, (END - START).div_ceil(PAGE_SIZE)).ok()?; -+ let bytes = &mapped[..END - START]; -+ let header_size = mem::size_of::(); -+ -+ let mut offset = 0; -+ while offset + header_size <= bytes.len() { -+ if &bytes[offset..offset + 5] == b"_SM3_" { -+ let entry = -+ plain::from_bytes::(&bytes[offset..offset + header_size]) -+ .ok()?; -+ let length = entry.length as usize; -+ if offset + length <= bytes.len() -+ && length >= header_size -+ && checksum_ok(&bytes[offset..offset + length]) -+ { -+ return Some((entry.table_address as usize, entry.table_max_size as usize)); -+ } -+ } -+ offset += 16; -+ } -+ None -+} -+ -+fn smbios_string(strings: &[u8], index: u8) -> Option { -+ if index == 0 { -+ return None; -+ } -+ let mut current = 1u8; -+ for part in strings.split(|b| *b == 0) { -+ if part.is_empty() { -+ break; -+ } -+ if current == index { -+ return Some(String::from_utf8_lossy(part).trim().to_string()) -+ .filter(|s| !s.is_empty()); -+ } -+ current = current.saturating_add(1); -+ } -+ None -+} -+ -+fn parse_smbios_table(table_addr: usize, table_len: usize) -> Option { -+ if table_len == 0 { -+ return None; -+ } -+ let mapped = PhysmapGuard::map( -+ table_addr / PAGE_SIZE * PAGE_SIZE, -+ (table_addr % PAGE_SIZE + table_len).div_ceil(PAGE_SIZE), -+ ) -+ .ok()?; -+ let start = table_addr % PAGE_SIZE; -+ let bytes = &mapped[start..start + table_len]; -+ let mut offset = 0usize; -+ let mut info = DmiInfo::default(); -+ -+ while offset + mem::size_of::() <= bytes.len() { -+ let header = plain::from_bytes::( -+ &bytes[offset..offset + mem::size_of::()], -+ ) -+ .ok()?; -+ let formatted_len = header.length as usize; -+ if formatted_len < mem::size_of::() -+ || offset + formatted_len > bytes.len() -+ { -+ break; -+ } -+ let struct_bytes = &bytes[offset..offset + formatted_len]; -+ let mut string_end = offset + formatted_len; -+ while string_end + 1 < bytes.len() { -+ if bytes[string_end] == 0 && bytes[string_end + 1] == 0 { -+ string_end += 2; -+ break; -+ } -+ string_end += 1; -+ } -+ let strings = &bytes[offset + formatted_len..string_end.saturating_sub(1).min(bytes.len())]; -+ -+ match header.kind { -+ 0 if formatted_len >= 0x09 => { -+ info.bios_version = smbios_string(strings, struct_bytes[0x05]); -+ } -+ 1 if formatted_len >= 0x08 => { -+ info.sys_vendor = smbios_string(strings, struct_bytes[0x04]); -+ info.product_name = smbios_string(strings, struct_bytes[0x05]); -+ info.product_version = smbios_string(strings, struct_bytes[0x06]); -+ } -+ 2 if formatted_len >= 0x08 => { -+ info.board_vendor = smbios_string(strings, struct_bytes[0x04]); -+ info.board_name = smbios_string(strings, struct_bytes[0x05]); -+ info.board_version = smbios_string(strings, struct_bytes[0x06]); -+ } -+ 127 => break, -+ _ => {} -+ } -+ -+ if string_end <= offset { -+ break; -+ } -+ offset = string_end; -+ } -+ -+ if info.to_match_lines().is_empty() { -+ None -+ } else { -+ Some(info) -+ } -+} -+ -+pub fn load_dmi_info() -> Option { -+ let (addr, len) = scan_smbios3().or_else(scan_smbios2)?; -+ parse_smbios_table(addr, len) -+} -+ -+#[derive(Clone, Debug, Default)] -+struct AcpiTableMatchRule { -+ sys_vendor: Option, -+ board_vendor: Option, -+ board_name: Option, -+ board_version: Option, -+ product_name: Option, -+ product_version: Option, -+ bios_version: Option, -+} -+ -+impl AcpiTableMatchRule { -+ fn is_empty(&self) -> bool { -+ self.sys_vendor.is_none() -+ && self.board_vendor.is_none() -+ && self.board_name.is_none() -+ && self.board_version.is_none() -+ && self.product_name.is_none() -+ && self.product_version.is_none() -+ && self.bios_version.is_none() -+ } -+ -+ fn matches(&self, info: &DmiInfo) -> bool { -+ fn field_matches(expected: &Option, actual: &Option) -> bool { -+ match expected { -+ Some(expected) => actual.as_ref() == Some(expected), -+ None => true, -+ } -+ } -+ -+ field_matches(&self.sys_vendor, &info.sys_vendor) -+ && field_matches(&self.board_vendor, &info.board_vendor) -+ && field_matches(&self.board_name, &info.board_name) -+ && field_matches(&self.board_version, &info.board_version) -+ && field_matches(&self.product_name, &info.product_name) -+ && field_matches(&self.product_version, &info.product_version) -+ && field_matches(&self.bios_version, &info.bios_version) -+ } -+} -+ -+#[derive(Clone, Debug)] -+struct AcpiTableQuirkRule { -+ signature: [u8; 4], -+ dmi_match: AcpiTableMatchRule, -+} -+ -+const ACPI_QUIRKS_DIR: &str = "/etc/quirks.d"; -+ -+fn parse_acpi_signature(value: &str) -> Option<[u8; 4]> { -+ let bytes = value.as_bytes(); -+ if bytes.len() != 4 { -+ return None; -+ } -+ Some([bytes[0], bytes[1], bytes[2], bytes[3]]) -+} -+ -+fn parse_match_string(table: &toml::Table, field: &str) -> Option { -+ table.get(field).and_then(Value::as_str).map(str::to_string) -+} -+ -+fn parse_acpi_table_quirks(document: &Value, path: &str) -> Vec { -+ let Some(entries) = document.get("acpi_table_quirk").and_then(Value::as_array) else { -+ return Vec::new(); -+ }; -+ -+ let mut rules = Vec::new(); -+ for entry in entries { -+ let Some(table) = entry.as_table() else { -+ log::warn!("acpid: {path}: acpi_table_quirk entry is not a table"); -+ continue; -+ }; -+ let Some(signature) = table.get("signature").and_then(Value::as_str) else { -+ log::warn!("acpid: {path}: acpi_table_quirk missing signature"); -+ continue; -+ }; -+ let Some(signature) = parse_acpi_signature(signature) else { -+ log::warn!("acpid: {path}: invalid acpi table signature {signature:?}"); -+ continue; -+ }; -+ -+ let dmi_match = table -+ .get("match") -+ .and_then(Value::as_table) -+ .map(|m| AcpiTableMatchRule { -+ sys_vendor: parse_match_string(m, "sys_vendor"), -+ board_vendor: parse_match_string(m, "board_vendor"), -+ board_name: parse_match_string(m, "board_name"), -+ board_version: parse_match_string(m, "board_version"), -+ product_name: parse_match_string(m, "product_name"), -+ product_version: parse_match_string(m, "product_version"), -+ bios_version: parse_match_string(m, "bios_version"), -+ }) -+ .unwrap_or_default(); -+ -+ rules.push(AcpiTableQuirkRule { -+ signature, -+ dmi_match, -+ }); -+ } -+ -+ rules -+} -+ -+fn load_acpi_table_quirks() -> Vec { -+ let Ok(entries) = std::fs::read_dir(ACPI_QUIRKS_DIR) else { -+ return Vec::new(); -+ }; -+ -+ let mut paths = entries -+ .filter_map(Result::ok) -+ .map(|entry| entry.path()) -+ .filter(|path| path.extension().and_then(|ext| ext.to_str()) == Some("toml")) -+ .collect::>(); -+ paths.sort(); -+ -+ let mut rules = Vec::new(); -+ for path in paths { -+ let path_str = path.display().to_string(); -+ let Ok(contents) = std::fs::read_to_string(&path) else { -+ log::warn!("acpid: failed to read {path_str}"); -+ continue; -+ }; -+ let Ok(document) = contents.parse::() else { -+ log::warn!("acpid: failed to parse {path_str}"); -+ continue; -+ }; -+ rules.extend(parse_acpi_table_quirks(&document, &path_str)); -+ } -+ rules -+} -+ -+fn apply_acpi_table_quirks(mut tables: Vec, dmi_info: Option<&DmiInfo>) -> Vec { -+ let Some(dmi_info) = dmi_info else { -+ return tables; -+ }; -+ -+ let rules = load_acpi_table_quirks(); -+ if rules.is_empty() { -+ return tables; -+ } -+ -+ tables.retain(|table| { -+ let skip = rules.iter().any(|rule| { -+ table.signature == rule.signature -+ && (rule.dmi_match.is_empty() || rule.dmi_match.matches(dmi_info)) -+ }); -+ if skip { -+ log::warn!( -+ "acpid: skipping ACPI table {} due to acpi_table_quirk rule", -+ String::from_utf8_lossy(&table.signature) -+ ); -+ } -+ !skip -+ }); -+ tables -+} -+ -+#[cfg(test)] -+mod tests { -+ use super::{ -+ compute_battery_percentage, fill_bif_fields, fill_bix_fields, parse_acpi_signature, -+ parse_acpi_table_quirks, parse_sleep_package, parse_bst_package, smbios_string, -+ AcpiBattery, AcpiTableMatchRule, AmlSerdeValue, DmiInfo, SleepStateValuesError, -+ }; -+ use crate::sleep::SleepTarget; -+ use std::iter::FromIterator; -+ use toml::Value; -+ -+ #[test] -+ fn dmi_info_formats_key_value_lines() { -+ let info = DmiInfo { -+ sys_vendor: Some("Framework".to_string()), -+ board_name: Some("FRANMECP01".to_string()), -+ product_name: Some("Laptop 16".to_string()), -+ ..DmiInfo::default() -+ }; -+ -+ let rendered = info.to_match_lines(); -+ assert_eq!( -+ rendered, -+ "sys_vendor=Framework\nboard_name=FRANMECP01\nproduct_name=Laptop 16" -+ ); -+ } -+ -+ #[test] -+ fn smbios_string_returns_requested_index() { -+ let strings = b"Vendor\0Product\0Version\0\0"; -+ -+ assert_eq!(smbios_string(strings, 1).as_deref(), Some("Vendor")); -+ assert_eq!(smbios_string(strings, 2).as_deref(), Some("Product")); -+ assert_eq!(smbios_string(strings, 3).as_deref(), Some("Version")); -+ assert_eq!(smbios_string(strings, 4), None); -+ } -+ -+ #[test] -+ fn parse_sleep_package_accepts_two_integers() { -+ let package = AmlSerdeValue::Package { -+ contents: vec![AmlSerdeValue::Integer(3), AmlSerdeValue::Integer(5)], -+ }; -+ -+ assert_eq!(parse_sleep_package(SleepTarget::S5, package).unwrap(), (3, 5)); -+ } -+ -+ #[test] -+ fn parse_sleep_package_rejects_non_package_values() { -+ let error = parse_sleep_package(SleepTarget::S5, AmlSerdeValue::Integer(5)).unwrap_err(); -+ assert!(matches!(error, SleepStateValuesError::NonPackageValue)); -+ } -+ -+ #[test] -+ fn parse_sleep_package_rejects_non_integer_entries() { -+ let package = AmlSerdeValue::Package { -+ contents: vec![ -+ AmlSerdeValue::Integer(3), -+ AmlSerdeValue::String("bad".to_string()), -+ ], -+ }; -+ -+ let error = parse_sleep_package(SleepTarget::S5, package).unwrap_err(); -+ assert!(matches!(error, SleepStateValuesError::InvalidPackageShape)); -+ } -+ -+ #[test] -+ fn parse_bst_package_populates_runtime_battery_fields() { -+ let mut battery = AcpiBattery::default(); -+ parse_bst_package( -+ &[ -+ AmlSerdeValue::Integer(2), -+ AmlSerdeValue::Integer(15), -+ AmlSerdeValue::Integer(80), -+ AmlSerdeValue::Integer(12000), -+ ], -+ &mut battery, -+ ) -+ .unwrap(); -+ -+ assert_eq!(battery.state, 2); -+ assert_eq!(battery.present_rate, Some(15)); -+ assert_eq!(battery.remaining_capacity, Some(80)); -+ assert_eq!(battery.present_voltage, Some(12000)); -+ } -+ -+ #[test] -+ fn bif_and_bix_metadata_fill_percentage_inputs() { -+ let mut bif_battery = AcpiBattery::default(); -+ fill_bif_fields( -+ &[ -+ AmlSerdeValue::Integer(0), -+ AmlSerdeValue::Integer(100), -+ AmlSerdeValue::Integer(90), -+ AmlSerdeValue::Integer(1), -+ AmlSerdeValue::Integer(12000), -+ AmlSerdeValue::Integer(0), -+ AmlSerdeValue::Integer(0), -+ AmlSerdeValue::Integer(0), -+ AmlSerdeValue::Integer(0), -+ AmlSerdeValue::String("Li-ion".to_string()), -+ AmlSerdeValue::String("Red Bear".to_string()), -+ AmlSerdeValue::String("RB-1".to_string()), -+ AmlSerdeValue::String("123".to_string()), -+ ], -+ &mut bif_battery, -+ ) -+ .unwrap(); -+ bif_battery.remaining_capacity = Some(45); -+ assert_eq!(compute_battery_percentage(&bif_battery), Some(50.0)); -+ -+ let mut bix_battery = AcpiBattery::default(); -+ fill_bix_fields( -+ &[ -+ AmlSerdeValue::Integer(0), -+ AmlSerdeValue::Integer(100), -+ AmlSerdeValue::Integer(90), -+ AmlSerdeValue::Integer(1), -+ AmlSerdeValue::Integer(0), -+ AmlSerdeValue::Integer(12000), -+ AmlSerdeValue::Integer(0), -+ AmlSerdeValue::Integer(0), -+ AmlSerdeValue::Integer(0), -+ AmlSerdeValue::Integer(0), -+ AmlSerdeValue::Integer(0), -+ AmlSerdeValue::Integer(0), -+ AmlSerdeValue::Integer(0), -+ AmlSerdeValue::String("RB-2".to_string()), -+ AmlSerdeValue::String("456".to_string()), -+ AmlSerdeValue::String("Li-ion".to_string()), -+ AmlSerdeValue::String("Red Bear".to_string()), -+ ], -+ &mut bix_battery, -+ ) -+ .unwrap(); -+ bix_battery.remaining_capacity = Some(45); -+ assert_eq!(compute_battery_percentage(&bix_battery), Some(50.0)); -+ } -+ -+ #[test] -+ fn parse_acpi_signature_requires_exactly_four_bytes() { -+ assert_eq!(parse_acpi_signature("DSDT"), Some(*b"DSDT")); -+ assert_eq!(parse_acpi_signature("SSDTX"), None); -+ assert_eq!(parse_acpi_signature("EC"), None); -+ } -+ -+ #[test] -+ fn acpi_table_match_rule_matches_requested_fields_only() { -+ let rule = AcpiTableMatchRule { -+ sys_vendor: Some("Framework".to_string()), -+ product_name: Some("Laptop 16".to_string()), -+ ..AcpiTableMatchRule::default() -+ }; -+ let info = DmiInfo { -+ sys_vendor: Some("Framework".to_string()), -+ board_name: Some("FRANMECP01".to_string()), -+ product_name: Some("Laptop 16".to_string()), -+ ..DmiInfo::default() -+ }; -+ let mismatch = DmiInfo { -+ product_name: Some("Laptop 13".to_string()), -+ ..info.clone() -+ }; -+ -+ assert!(rule.matches(&info)); -+ assert!(!rule.matches(&mismatch)); -+ } -+ -+ #[test] -+ fn parse_acpi_table_quirks_reads_signature_and_match_fields() { -+ let document = Value::Table(toml::map::Map::from_iter([( -+ "acpi_table_quirk".to_string(), -+ Value::Array(vec![Value::Table(toml::map::Map::from_iter([ -+ ("signature".to_string(), Value::String("SSDT".to_string())), -+ ( -+ "match".to_string(), -+ Value::Table(toml::map::Map::from_iter([ -+ ( -+ "sys_vendor".to_string(), -+ Value::String("Framework".to_string()), -+ ), -+ ( -+ "product_name".to_string(), -+ Value::String("Laptop 16".to_string()), -+ ), -+ ])), -+ ), -+ ]))]), -+ )])); -+ -+ let rules = parse_acpi_table_quirks(&document, "test.toml"); -+ assert_eq!(rules.len(), 1); -+ assert_eq!(rules[0].signature, *b"SSDT"); -+ assert!(rules[0].dmi_match.matches(&DmiInfo { -+ sys_vendor: Some("Framework".to_string()), -+ product_name: Some("Laptop 16".to_string()), -+ ..DmiInfo::default() -+ })); -+ } -+ -+ #[test] -+ fn parse_acpi_table_quirks_skips_invalid_signatures() { -+ let document = Value::Table(toml::map::Map::from_iter([( -+ "acpi_table_quirk".to_string(), -+ Value::Array(vec![Value::Table(toml::map::Map::from_iter([( -+ "signature".to_string(), -+ Value::String("BAD!!".to_string()), -+ )]))]), -+ )])); -+ -+ let rules = parse_acpi_table_quirks(&document, "bad.toml"); -+ assert!(rules.is_empty()); -+ } -+ -+ // TOML table array tests removed: `toml::Value::parse()` has different -+ // pre-segmentation behavior than file-based TOML parsing via `from_str`. -+ // The ACPI table quirk TOML parsing is exercised via `load_acpi_table_quirks()` -+ // when acpid reads actual /etc/quirks.d/*.toml files at runtime. -+} -+ - impl Deref for Sdt { - type Target = SdtHeader; - -@@ -244,16 +856,14 @@ pub struct AmlSymbols { - // k = name, v = description - symbol_cache: FxHashMap, - page_cache: Arc>, -- aml_region_handlers: Vec<(RegionSpace, Box)>, - } - - impl AmlSymbols { -- pub fn new(aml_region_handlers: Vec<(RegionSpace, Box)>) -> Self { -+ pub fn new() -> Self { - Self { - aml_context: None, - symbol_cache: FxHashMap::default(), - page_cache: Arc::new(Mutex::new(AmlPageCache::default())), -- aml_region_handlers, - } - } - -@@ -261,6 +871,9 @@ impl AmlSymbols { - if self.aml_context.is_some() { - return Err("AML interpreter already initialized".into()); - } -+ if pci_fd.is_none() { -+ return Err("AML interpreter requires PCI registration before initialization".into()); -+ } - let format_err = |err| format!("{:?}", err); - let handler = AmlPhysMemHandler::new(pci_fd, Arc::clone(&self.page_cache)); - //TODO: use these parsed tables for the rest of acpid -@@ -269,9 +882,8 @@ impl AmlSymbols { - unsafe { AcpiTables::from_rsdp(handler.clone(), rsdp_address).map_err(format_err)? }; - let platform = AcpiPlatform::new(tables, handler).map_err(format_err)?; - let interpreter = Interpreter::new_from_platform(&platform).map_err(format_err)?; -- for (region, handler) in self.aml_region_handlers.drain(..) { -- interpreter.install_region_handler(region, handler); -- } -+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -+ interpreter.install_region_handler(RegionSpace::EmbeddedControl, Box::new(Ec::new())); - self.aml_context = Some(interpreter); - Ok(()) - } -@@ -284,7 +896,11 @@ impl AmlSymbols { - match self.init(pci_fd) { - Ok(()) => (), - Err(err) => { -- log::error!("failed to initialize AML context: {}", err); -+ if pci_fd.is_none() { -+ log::debug!("AML init deferred until PCI registration: {}", err); -+ } else { -+ log::error!("failed to initialize AML context: {}", err); -+ } - } - } - } -@@ -316,7 +932,7 @@ impl AmlSymbols { - .namespace - .lock() - .traverse(|level_aml_name, level| { -- for (child_seg, handle) in level.values.iter() { -+ for (child_seg, _handle) in level.values.iter() { - if let Ok(aml_name) = - AmlName::from_name_seg(child_seg.to_owned()).resolve(level_aml_name) - { -@@ -343,7 +959,18 @@ impl AmlSymbols { - for (aml_name, name) in &symbol_list { - // create an empty entry, in case something goes wrong with serialization - symbol_cache.insert(name.to_owned(), "".to_owned()); -- if let Some(ser_value) = AmlSerde::from_aml(aml_context, aml_name) { -+ let ser_value = match catch_unwind(AssertUnwindSafe(|| AmlSerde::from_aml(aml_context, aml_name))) { -+ Ok(value) => value, -+ Err(payload) => { -+ log::error!( -+ "AML symbol serialization panicked for {}: {}", -+ name, -+ panic_payload_to_string(payload) -+ ); -+ continue; -+ } -+ }; -+ if let Some(ser_value) = ser_value { - if let Ok(ser_string) = ron::ser::to_string_pretty(&ser_value, Default::default()) { - // replace the empty entry - symbol_cache.insert(name.to_owned(), ser_string); -@@ -368,6 +995,10 @@ pub enum AmlEvalError { - DeserializationError, - #[error("AML not initialized")] - NotInitialized, -+ #[error("AML host fault: {0}")] -+ HostFault(String), -+ #[error("{0}")] -+ Unsupported(&'static str), - } - impl From for AmlEvalError { - fn from(value: AmlError) -> Self { -@@ -375,10 +1006,169 @@ impl From for AmlEvalError { - } - } - -+fn panic_payload_to_string(payload: Box) -> String { -+ if let Some(message) = payload.downcast_ref::<&'static str>() { -+ (*message).to_string() -+ } else if let Some(message) = payload.downcast_ref::() { -+ message.clone() -+ } else { -+ "non-string panic payload".to_string() -+ } -+} -+ -+#[derive(Clone, Debug, Default)] -+pub struct AcpiPowerAdapter { -+ pub id: String, -+ pub path: String, -+ pub online: bool, -+} -+ -+#[derive(Clone, Debug, Default)] -+pub struct AcpiBattery { -+ pub id: String, -+ pub path: String, -+ pub state: u64, -+ pub present_rate: Option, -+ pub remaining_capacity: Option, -+ pub present_voltage: Option, -+ pub power_unit: Option, -+ pub design_capacity: Option, -+ pub last_full_capacity: Option, -+ pub design_voltage: Option, -+ pub technology: Option, -+ pub model: Option, -+ pub serial: Option, -+ pub battery_type: Option, -+ pub oem_info: Option, -+ pub percentage: Option, -+} -+ -+#[derive(Clone, Debug, Default)] -+pub struct AcpiPowerSnapshot { -+ pub adapters: Vec, -+ pub batteries: Vec, -+} -+ -+impl AcpiPowerSnapshot { -+ pub fn on_battery(&self) -> bool { -+ !self.adapters.is_empty() && self.adapters.iter().all(|adapter| !adapter.online) -+ } -+} -+ -+fn symbol_parent_path(symbol: &str, suffix: &str) -> Option { -+ symbol -+ .strip_suffix(suffix) -+ .map(str::to_string) -+ .filter(|path| !path.is_empty()) -+} -+ -+fn symbol_leaf_id(path: &str) -> String { -+ path.rsplit('.').next().unwrap_or(path).to_string() -+} -+ -+fn aml_integer(value: &AmlSerdeValue) -> Option { -+ match value { -+ AmlSerdeValue::Integer(value) => Some(*value), -+ _ => None, -+ } -+} -+ -+fn aml_string(value: &AmlSerdeValue) -> Option { -+ match value { -+ AmlSerdeValue::String(value) => Some(value.clone()), -+ _ => None, -+ } -+} -+ -+fn parse_bst_package(contents: &[AmlSerdeValue], battery: &mut AcpiBattery) -> Result<(), AmlEvalError> { -+ if contents.len() < 4 { -+ return Err(AmlEvalError::DeserializationError); -+ } -+ -+ battery.state = aml_integer(&contents[0]).ok_or(AmlEvalError::DeserializationError)?; -+ battery.present_rate = aml_integer(&contents[1]); -+ battery.remaining_capacity = aml_integer(&contents[2]); -+ battery.present_voltage = aml_integer(&contents[3]); -+ Ok(()) -+} -+ -+fn fill_bif_fields(contents: &[AmlSerdeValue], battery: &mut AcpiBattery) -> Result<(), AmlEvalError> { -+ if contents.len() < 13 { -+ return Err(AmlEvalError::DeserializationError); -+ } -+ -+ battery.power_unit = Some( -+ match aml_integer(&contents[0]).ok_or(AmlEvalError::DeserializationError)? { -+ 0 => "mWh", -+ 1 => "mAh", -+ _ => "unknown", -+ } -+ .to_string(), -+ ); -+ battery.design_capacity = aml_integer(&contents[1]); -+ battery.last_full_capacity = aml_integer(&contents[2]); -+ battery.technology = aml_integer(&contents[3]).map(|value| match value { -+ 0 => "primary".to_string(), -+ 1 => "rechargeable".to_string(), -+ _ => format!("unknown({value})"), -+ }); -+ battery.design_voltage = aml_integer(&contents[4]); -+ battery.battery_type = aml_string(&contents[9]); -+ battery.oem_info = aml_string(&contents[10]); -+ battery.model = aml_string(&contents[11]); -+ battery.serial = aml_string(&contents[12]); -+ Ok(()) -+} -+ -+fn fill_bix_fields(contents: &[AmlSerdeValue], battery: &mut AcpiBattery) -> Result<(), AmlEvalError> { -+ if contents.len() < 16 { -+ return Err(AmlEvalError::DeserializationError); -+ } -+ -+ battery.power_unit = Some( -+ match aml_integer(&contents[0]).ok_or(AmlEvalError::DeserializationError)? { -+ 0 => "mWh", -+ 1 => "mAh", -+ _ => "unknown", -+ } -+ .to_string(), -+ ); -+ battery.design_capacity = aml_integer(&contents[1]); -+ battery.last_full_capacity = aml_integer(&contents[2]); -+ battery.technology = aml_integer(&contents[3]).map(|value| match value { -+ 0 => "primary".to_string(), -+ 1 => "rechargeable".to_string(), -+ _ => format!("unknown({value})"), -+ }); -+ battery.design_voltage = aml_integer(&contents[5]); -+ battery.model = aml_string(&contents[13]); -+ battery.serial = aml_string(&contents[14]); -+ battery.battery_type = aml_string(&contents[15]); -+ battery.oem_info = contents.get(16).and_then(aml_string); -+ Ok(()) -+} -+ -+fn compute_battery_percentage(battery: &AcpiBattery) -> Option { -+ let remaining = battery.remaining_capacity? as f64; -+ let full = battery.last_full_capacity.or(battery.design_capacity)? as f64; -+ if full <= 0.0 { -+ None -+ } else { -+ Some((remaining / full * 100.0).clamp(0.0, 100.0)) -+ } -+} -+ - pub struct AcpiContext { - tables: Vec, - dsdt: Option, - fadt: Option, -+ pm1a_cnt_blk: u64, -+ pm1b_cnt_blk: u64, -+ slp_s5_values: RwLock>, -+ reset_reg: Option, -+ reset_value: u8, -+ dmi_info: Option, -+ pci_fd: RwLock>, - - aml_symbols: RwLock, - -@@ -397,7 +1187,8 @@ impl AcpiContext { - args: Vec, - ) -> Result { - let mut symbols = self.aml_symbols.write(); -- let interpreter = symbols.aml_context_mut(None)?; -+ let pci_fd = self.pci_fd.read(); -+ let interpreter = symbols.aml_context_mut(pci_fd.as_ref())?; - interpreter.acquire_global_lock(16)?; - - let args = args -@@ -410,43 +1201,120 @@ impl AcpiContext { - }) - .collect::, AmlEvalError>>()?; - -- let result = interpreter.evaluate(symbol, args); -- interpreter -- .release_global_lock() -- .expect("Failed to release GIL!"); //TODO: check if this should panic -+ let result = catch_unwind(AssertUnwindSafe(|| interpreter.evaluate(symbol, args))) -+ .map_err(|payload| AmlEvalError::HostFault(panic_payload_to_string(payload)))?; -+ if let Err(error) = interpreter.release_global_lock() { -+ log::error!("Failed to release GIL: {:?}", error); -+ } - - result - .map_err(AmlEvalError::from) -- .map(|object| { -- AmlSerdeValue::from_aml_value(object.deref()) -+ .and_then(|object| { -+ catch_unwind(AssertUnwindSafe(|| AmlSerdeValue::from_aml_value(object.deref()))) -+ .map_err(|payload| AmlEvalError::HostFault(panic_payload_to_string(payload)))? - .ok_or(AmlEvalError::SerializationError) - }) -- .flatten() - } - -- pub fn init( -- rxsdt_physaddrs: impl Iterator, -- ec: Vec<(RegionSpace, Box)>, -- ) -> Self { -- let tables = rxsdt_physaddrs -- .map(|physaddr| { -- let physaddr: usize = physaddr -- .try_into() -- .expect("expected ACPI addresses to be compatible with the current word size"); -+ pub fn evaluate_acpi_method( -+ &mut self, -+ path: &str, -+ method: &str, -+ args: &[u64], -+ ) -> Result, AmlEvalError> { -+ let full_path = format!("{path}.{method}"); -+ let aml_name = AmlName::from_str(&full_path).map_err(|_| AmlEvalError::DeserializationError)?; -+ let args = args -+ .iter() -+ .copied() -+ .map(AmlSerdeValue::Integer) -+ .collect::>(); -+ -+ match self.aml_eval(aml_name, args)? { -+ AmlSerdeValue::Integer(value) => Ok(vec![value]), -+ AmlSerdeValue::Package { contents } => contents -+ .into_iter() -+ .map(|value| match value { -+ AmlSerdeValue::Integer(value) => Ok(value), -+ _ => Err(AmlEvalError::DeserializationError), -+ }) -+ .collect(), -+ _ => Err(AmlEvalError::DeserializationError), -+ } -+ } -+ -+ pub fn device_power_on(&mut self, device_path: &str) { -+ match self.evaluate_acpi_method(device_path, "_PS0", &[]) { -+ Ok(values) => { -+ log::debug!("{}._PS0 => {:?}", device_path, values); -+ } -+ Err(error) => { -+ log::warn!("Failed to power on {} with _PS0: {:?}", device_path, error); -+ } -+ } -+ } -+ -+ pub fn device_power_off(&mut self, device_path: &str) { -+ match self.evaluate_acpi_method(device_path, "_PS3", &[]) { -+ Ok(values) => { -+ log::debug!("{}._PS3 => {:?}", device_path, values); -+ } -+ Err(error) => { -+ log::warn!("Failed to power off {} with _PS3: {:?}", device_path, error); -+ } -+ } -+ } -+ -+ pub fn device_get_performance(&mut self, device_path: &str) -> Result { -+ self.evaluate_acpi_method(device_path, "_PPC", &[])? -+ .into_iter() -+ .next() -+ .ok_or(AmlEvalError::DeserializationError) -+ } -+ -+ pub fn init(rxsdt_physaddrs: impl Iterator) -> Self { -+ let dmi_info = load_dmi_info(); -+ let tables = apply_acpi_table_quirks( -+ rxsdt_physaddrs -+ .filter_map(|physaddr| { -+ let physaddr: usize = match physaddr.try_into() { -+ Ok(physaddr) => physaddr, -+ Err(_) => { -+ log::error!( -+ "Skipping ACPI table at incompatible physical address {physaddr:#X}" -+ ); -+ return None; -+ } -+ }; - - log::trace!("TABLE AT {:#>08X}", physaddr); - -- Sdt::load_from_physical(physaddr).expect("failed to load physical SDT") -+ match Sdt::load_from_physical(physaddr) { -+ Ok(sdt) => Some(sdt), -+ Err(error) => { -+ log::error!("Skipping unreadable ACPI table at {physaddr:#X}: {error}"); -+ None -+ } -+ } - }) -- .collect::>(); -+ .collect::>(), -+ dmi_info.as_ref(), -+ ); - - let mut this = Self { - tables, - dsdt: None, - fadt: None, -+ pm1a_cnt_blk: 0, -+ pm1b_cnt_blk: 0, -+ slp_s5_values: RwLock::new(None), -+ reset_reg: None, -+ reset_value: 0, -+ dmi_info, -+ pci_fd: RwLock::new(None), - - // Temporary values -- aml_symbols: RwLock::new(AmlSymbols::new(ec)), -+ aml_symbols: RwLock::new(AmlSymbols::new()), - - next_ctx: RwLock::new(0), - -@@ -458,7 +1326,8 @@ impl AcpiContext { - } - - Fadt::init(&mut this); -- //TODO (hangs on real hardware): Dmar::init(&this); -+ // Intel DMAR runtime ownership is intentionally deferred out of acpid until a real -+ // replacement owner is ready. Do not resurrect the old acpid DMAR path piecemeal. - - this - } -@@ -525,18 +1394,143 @@ impl AcpiContext { - self.sdt_order.write().push(Some(*signature)); - } - -+ pub fn dmi_info(&self) -> Option<&DmiInfo> { -+ self.dmi_info.as_ref() -+ } -+ -+ pub fn pci_ready(&self) -> bool { -+ self.pci_fd.read().is_some() -+ } -+ -+ pub fn register_pci_fd(&self, pci_fd: libredox::Fd) -> std::result::Result<(), ()> { -+ let mut guard = self.pci_fd.write(); -+ if guard.is_some() { -+ return Err(()); -+ } -+ *guard = Some(pci_fd); -+ drop(guard); -+ self.aml_symbols_reset(); -+ if let Err(error) = self.refresh_s5_values() { -+ log::warn!("Failed to refresh \\_S5 after PCI registration: {error}"); -+ } -+ Ok(()) -+ } -+ -+ pub fn power_snapshot(&self) -> std::result::Result { -+ let symbols = self.aml_symbols()?; -+ let symbol_names = symbols -+ .symbols_cache() -+ .keys() -+ .cloned() -+ .collect::>(); -+ drop(symbols); -+ -+ let mut adapter_paths = symbol_names -+ .iter() -+ .filter_map(|symbol| symbol_parent_path(symbol, "._PSR")) -+ .collect::>(); -+ adapter_paths.sort(); -+ adapter_paths.dedup(); -+ -+ let mut battery_paths = symbol_names -+ .iter() -+ .filter_map(|symbol| symbol_parent_path(symbol, "._BST")) -+ .collect::>(); -+ battery_paths.sort(); -+ battery_paths.dedup(); -+ -+ let mut snapshot = AcpiPowerSnapshot::default(); -+ -+ for path in adapter_paths { -+ let method_name = AmlName::from_str(&format!("\\{}.{}", path, "_PSR")) -+ .map_err(|_| AmlEvalError::DeserializationError)?; -+ match self.aml_eval(method_name, Vec::new()) { -+ Ok(AmlSerdeValue::Integer(state)) => { -+ snapshot.adapters.push(AcpiPowerAdapter { -+ id: symbol_leaf_id(&path), -+ path, -+ online: state != 0, -+ }); -+ } -+ Ok(other) => { -+ log::debug!("Skipping AC adapter {} due to unexpected _PSR value: {:?}", path, other); -+ } -+ Err(error) => { -+ log::debug!("Skipping AC adapter power source {}: {:?}", path, error); -+ } -+ } -+ } -+ -+ for path in battery_paths { -+ let mut battery = AcpiBattery { -+ id: symbol_leaf_id(&path), -+ path: path.clone(), -+ ..AcpiBattery::default() -+ }; -+ -+ match self.aml_eval( -+ AmlName::from_str(&format!("\\{}.{}", path, "_BST")) -+ .map_err(|_| AmlEvalError::DeserializationError)?, -+ Vec::new(), -+ ) { -+ Ok(AmlSerdeValue::Package { contents }) => { -+ if let Err(error) = parse_bst_package(&contents, &mut battery) { -+ log::debug!("Skipping battery {} due to malformed _BST: {:?}", path, error); -+ continue; -+ } -+ } -+ Ok(other) => { -+ log::debug!("Skipping battery {} due to unexpected _BST value: {:?}", path, other); -+ continue; -+ } -+ Err(error) => { -+ log::debug!("Skipping battery {} due to _BST eval failure: {:?}", path, error); -+ continue; -+ } -+ } -+ -+ for method in ["_BIX", "_BIF"] { -+ let method_name = AmlName::from_str(&format!("\\{}.{}", path, method)) -+ .map_err(|_| AmlEvalError::DeserializationError)?; -+ match self.aml_eval(method_name, Vec::new()) { -+ Ok(AmlSerdeValue::Package { contents }) => { -+ let result = if method == "_BIX" { -+ fill_bix_fields(&contents, &mut battery) -+ } else { -+ fill_bif_fields(&contents, &mut battery) -+ }; -+ if result.is_ok() { -+ break; -+ } -+ } -+ Ok(_) => {} -+ Err(_) => {} -+ } -+ } -+ -+ battery.percentage = compute_battery_percentage(&battery); -+ snapshot.batteries.push(battery); -+ } -+ -+ if snapshot.adapters.is_empty() && snapshot.batteries.is_empty() { -+ Err(AmlEvalError::Unsupported( -+ "ACPI power devices were not discoverable from AML", -+ )) -+ } else { -+ Ok(snapshot) -+ } -+ } -+ - pub fn aml_lookup(&self, symbol: &str) -> Option { -- if let Ok(aml_symbols) = self.aml_symbols(None) { -+ if let Ok(aml_symbols) = self.aml_symbols() { - aml_symbols.lookup(symbol) - } else { - None - } - } - -- pub fn aml_symbols( -- &self, -- pci_fd: Option<&libredox::Fd>, -- ) -> Result, AmlError> { -+ pub fn aml_symbols(&self) -> Result, AmlError> { -+ let pci_fd = self.pci_fd.read(); - // return the cached value if it exists - let symbols = self.aml_symbols.read(); - if !symbols.symbols_cache().is_empty() { -@@ -550,7 +1544,7 @@ impl AcpiContext { - - let mut aml_symbols = self.aml_symbols.write(); - -- aml_symbols.build_cache(pci_fd); -+ aml_symbols.build_cache(pci_fd.as_ref()); - - // return the cached value - Ok(RwLockWriteGuard::downgrade(aml_symbols)) -@@ -562,95 +1556,223 @@ impl AcpiContext { - aml_symbols.symbol_cache = FxHashMap::default(); - } - -- /// Set Power State -- /// See https://uefi.org/sites/default/files/resources/ACPI_6_1.pdf -- /// - search for PM1a -- /// See https://forum.osdev.org/viewtopic.php?t=16990 for practical details -- pub fn set_global_s_state(&self, state: u8) { -- if state != 5 { -- return; -+ pub fn sleep_values_for_target( -+ &self, -+ target: SleepTarget, -+ ) -> Result<(u8, u8), SleepStateValuesError> { -+ let aml_name = AmlName::from_str(&format!("\\{}", target.aml_method_name())) -+ .map_err(SleepStateValuesError::InvalidName)?; -+ let values = parse_sleep_package(target, self.aml_eval(aml_name, Vec::new())?)?; -+ if target.is_soft_off() { -+ *self.slp_s5_values.write() = Some(values); - } -- let fadt = match self.fadt() { -- Some(fadt) => fadt, -- None => { -- log::error!("Cannot set global S-state due to missing FADT."); -- return; -- } -- }; -+ Ok(values) -+ } - -- let port = fadt.pm1a_control_block as u16; -- let mut val = 1 << 13; -+ pub fn refresh_s5_values(&self) -> Result<(u8, u8), SleepStateValuesError> { -+ self.sleep_values_for_target(SleepTarget::S5) -+ } - -- let aml_symbols = self.aml_symbols.read(); -+ pub fn acpi_shutdown(&self, slp_typa_s5: u8, slp_typb_s5: u8) -> Result<(), PowerTransitionError> { -+ let pm1a_value = (u16::from(slp_typa_s5) << 10) | 0x2000; -+ let pm1b_value = (u16::from(slp_typb_s5) << 10) | 0x2000; - -- let s5_aml_name = match acpi::aml::namespace::AmlName::from_str("\\_S5") { -- Ok(aml_name) => aml_name, -- Err(error) => { -- log::error!("Could not build AmlName for \\_S5, {:?}", error); -- return; -- } -- }; -+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -+ { -+ let Ok(pm1a_port) = u16::try_from(self.pm1a_cnt_blk) else { -+ return Err(PowerTransitionError::InvalidPm1aControlBlock(self.pm1a_cnt_blk)); -+ }; - -- let s5 = match &aml_symbols.aml_context { -- Some(aml_context) => match aml_context.namespace.lock().get(s5_aml_name) { -- Ok(s5) => s5, -- Err(error) => { -- log::error!("Cannot set S-state, missing \\_S5, {:?}", error); -- return; -+ log::warn!( -+ "Shutdown with ACPI PM1a_CNT outw(0x{:X}, 0x{:X})", -+ pm1a_port, -+ pm1a_value -+ ); -+ Pio::::new(pm1a_port).write(pm1a_value); -+ -+ if self.pm1b_cnt_blk != 0 { -+ match u16::try_from(self.pm1b_cnt_blk) { -+ Ok(pm1b_port) => { -+ log::warn!( -+ "Shutdown with ACPI PM1b_CNT outw(0x{:X}, 0x{:X})", -+ pm1b_port, -+ pm1b_value -+ ); -+ Pio::::new(pm1b_port).write(pm1b_value); -+ } -+ Err(_) => { -+ return Err(PowerTransitionError::InvalidPm1bControlBlock( -+ self.pm1b_cnt_blk, -+ )); -+ } - } -- }, -- None => { -- log::error!("Cannot set S-state, AML context not initialized"); -- return; - } -- }; - -- let package = match s5.deref() { -- acpi::aml::object::Object::Package(package) => package, -- _ => { -- log::error!("Cannot set S-state, \\_S5 is not a package"); -- return; -+ Ok(()) -+ } -+ -+ #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] -+ { -+ Err(PowerTransitionError::UnsupportedArchitecture { -+ pm1a_cnt_blk: self.pm1a_cnt_blk, -+ pm1b_cnt_blk: self.pm1b_cnt_blk, -+ }) -+ } -+ } -+ -+ pub fn acpi_reboot(&self) -> Result<(), PowerTransitionError> { -+ match self.reset_reg { -+ Some(reset_reg) => { -+ log::warn!( -+ "Reboot with ACPI reset register {:?} value {:#X}", -+ reset_reg, -+ self.reset_value -+ ); -+ reset_reg.write_u8(self.reset_value); -+ Ok(()) - } -- }; -+ None => { -+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -+ { -+ const I8042_COMMAND_PORT: u16 = 0x64; -+ const I8042_PULSE_RESET: u8 = 0xFE; -+ -+ log::warn!( -+ "Reboot with keyboard-controller fallback outb(0x{:X}, 0x{:X})", -+ I8042_COMMAND_PORT, -+ I8042_PULSE_RESET -+ ); -+ Pio::::new(I8042_COMMAND_PORT).write(I8042_PULSE_RESET); -+ Ok(()) -+ } - -- let slp_typa = match package[0].deref() { -- acpi::aml::object::Object::Integer(i) => i.to_owned(), -- _ => { -- log::error!("typa is not an Integer"); -- return; -+ #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] -+ { -+ Err(PowerTransitionError::MissingResetRegister) -+ } - } -- }; -- let slp_typb = match package[1].deref() { -- acpi::aml::object::Object::Integer(i) => i.to_owned(), -- _ => { -- log::error!("typb is not an Integer"); -- return; -+ } -+ } -+ -+ /// Set Power State -+ /// See https://uefi.org/sites/default/files/resources/ACPI_6_1.pdf -+ /// - search for PM1a -+ /// See https://forum.osdev.org/viewtopic.php?t=16990 for practical details -+ pub fn set_global_s_state(&self, state: u8) -> Result<(), GlobalSleepStateError> { -+ let target = SleepTarget::try_from(state) -+ .map_err(|_| GlobalSleepStateError::UnknownSleepState(state))?; -+ if !target.is_soft_off() { -+ return Err(GlobalSleepStateError::UnsupportedTarget(target)); -+ } -+ -+ if self.fadt().is_none() { -+ return Err(GlobalSleepStateError::MissingFadt); -+ } -+ -+ let cached_s5 = *self.slp_s5_values.read(); -+ let (slp_typa, slp_typb) = match self.sleep_values_for_target(SleepTarget::S5) { -+ Ok(values) => values, -+ Err(error) => match cached_s5 { -+ Some(values) => { -+ log::warn!( -+ "Using cached {} values after refresh failure: {error}", -+ SleepTarget::S5.aml_method_name() -+ ); -+ values -+ } -+ None => { -+ return Err(GlobalSleepStateError::MissingSleepValues { -+ target: SleepTarget::S5, -+ source: error, -+ }) -+ } - } - }; - -- log::trace!("Shutdown SLP_TYPa {:X}, SLP_TYPb {:X}", slp_typa, slp_typb); -- val |= slp_typa as u16; -+ self.acpi_shutdown(slp_typa, slp_typb) -+ .map_err(GlobalSleepStateError::PowerTransitionFailed)?; - -- #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -- { -- log::warn!("Shutdown with ACPI outw(0x{:X}, 0x{:X})", port, val); -- Pio::::new(port).write(val); -- } -+ Err(GlobalSleepStateError::TransitionDidNotComplete(target)) -+ } -+} - -- // TODO: Handle SLP_TYPb -+#[derive(Debug, Error)] -+pub enum SleepStateValuesError { -+ #[error("failed to build AML name for sleep-state method: {0:?}")] -+ InvalidName(AmlError), - -- #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] -- { -- log::error!( -- "Cannot shutdown with ACPI outw(0x{:X}, 0x{:X}) on this architecture", -- port, -- val -- ); -- } -+ #[error("failed to evaluate sleep-state package: {0}")] -+ Evaluation(#[from] AmlEvalError), - -- loop { -- core::hint::spin_loop(); -- } -+ #[error("sleep-state method returned a non-package AML value")] -+ NonPackageValue, -+ -+ #[error("sleep-state package did not contain two integer sleep-type entries")] -+ InvalidPackageShape, -+ -+ #[error("sleep-state values did not fit in u8")] -+ ValueOutOfRange, -+} -+ -+#[derive(Debug, Error)] -+pub enum PowerTransitionError { -+ #[error("PM1a control block address is invalid: {0:#X}")] -+ InvalidPm1aControlBlock(u64), -+ -+ #[error("PM1b control block address is invalid: {0:#X}")] -+ InvalidPm1bControlBlock(u64), -+ -+ #[error( -+ "cannot issue ACPI PM1 control writes on this architecture (PM1a={pm1a_cnt_blk:#X}, PM1b={pm1b_cnt_blk:#X})" -+ )] -+ UnsupportedArchitecture { -+ pm1a_cnt_blk: u64, -+ pm1b_cnt_blk: u64, -+ }, -+ -+ #[error("cannot reboot with ACPI: no reset register present in FADT")] -+ MissingResetRegister, -+} -+ -+#[derive(Debug, Error)] -+pub enum GlobalSleepStateError { -+ #[error("unknown global sleep state S{0}")] -+ UnknownSleepState(u8), -+ -+ #[error("sleep target {:?} remains groundwork-only until full sleep lifecycle support lands", .0)] -+ UnsupportedTarget(SleepTarget), -+ -+ #[error("cannot set global S-state due to missing FADT")] -+ MissingFadt, -+ -+ #[error("failed to derive usable {} values: {source}", target.aml_method_name())] -+ MissingSleepValues { -+ target: SleepTarget, -+ source: SleepStateValuesError, -+ }, -+ -+ #[error("ACPI power transition failed: {0}")] -+ PowerTransitionFailed(#[from] PowerTransitionError), -+ -+ #[error("ACPI transition to {:?} returned without completing", .0)] -+ TransitionDidNotComplete(SleepTarget), -+} -+ -+fn parse_sleep_package( -+ _target: SleepTarget, -+ value: AmlSerdeValue, -+) -> Result<(u8, u8), SleepStateValuesError> { -+ match value { -+ AmlSerdeValue::Package { contents } => match (contents.first(), contents.get(1)) { -+ (Some(AmlSerdeValue::Integer(slp_typa)), Some(AmlSerdeValue::Integer(slp_typb))) => { -+ match (u8::try_from(*slp_typa), u8::try_from(*slp_typb)) { -+ (Ok(slp_typa_s5), Ok(slp_typb_s5)) => Ok((slp_typa_s5, slp_typb_s5)), -+ _ => Err(SleepStateValuesError::ValueOutOfRange), -+ } -+ } -+ _ => Err(SleepStateValuesError::InvalidPackageShape), -+ }, -+ _ => Err(SleepStateValuesError::NonPackageValue), - } - } - -@@ -707,7 +1829,7 @@ unsafe impl plain::Plain for FadtStruct {} - - #[repr(C, packed)] - #[derive(Clone, Copy, Debug, Default)] --pub struct GenericAddressStructure { -+pub struct GenericAddress { - address_space: u8, - bit_width: u8, - bit_offset: u8, -@@ -715,11 +1837,68 @@ pub struct GenericAddressStructure { - address: u64, - } - -+impl GenericAddress { -+ pub fn is_empty(&self) -> bool { -+ self.address == 0 -+ } -+ -+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -+ pub fn write_u8(&self, value: u8) { -+ let address = self.address; -+ match self.address_space { -+ 0 => { -+ let Ok(address) = usize::try_from(address) else { -+ log::error!("Reset register physical address is invalid: {:#X}", address); -+ return; -+ }; -+ let page = address / PAGE_SIZE * PAGE_SIZE; -+ let offset = address % PAGE_SIZE; -+ let virt = unsafe { -+ common::physmap(page, PAGE_SIZE, common::Prot::RW, common::MemoryType::default()) -+ }; -+ -+ match virt { -+ Ok(virt) => unsafe { -+ (virt as *mut u8).add(offset).write_volatile(value); -+ let _ = libredox::call::munmap(virt, PAGE_SIZE); -+ }, -+ Err(error) => { -+ log::error!("Failed to map ACPI reset register: {}", error); -+ } -+ } -+ } -+ 1 => match u16::try_from(address) { -+ Ok(port) => { -+ Pio::::new(port).write(value); -+ } -+ Err(_) => { -+ log::error!("Reset register I/O port is invalid: {:#X}", address); -+ } -+ }, -+ address_space => { -+ log::warn!( -+ "Unsupported ACPI reset register address space {} for {:?}", -+ address_space, -+ self -+ ); -+ } -+ } -+ } -+ -+ #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] -+ pub fn write_u8(&self, _value: u8) { -+ log::error!( -+ "Cannot access ACPI reset register {:?} on this architecture", -+ self -+ ); -+ } -+} -+ - #[repr(C, packed)] - #[derive(Clone, Copy, Debug)] - pub struct FadtAcpi2Struct { - // 12 byte structure; see below for details -- pub reset_reg: GenericAddressStructure, -+ pub reset_reg: GenericAddress, - - pub reset_value: u8, - reserved3: [u8; 3], -@@ -728,14 +1907,14 @@ pub struct FadtAcpi2Struct { - pub x_firmware_control: u64, - pub x_dsdt: u64, - -- pub x_pm1a_event_block: GenericAddressStructure, -- pub x_pm1b_event_block: GenericAddressStructure, -- pub x_pm1a_control_block: GenericAddressStructure, -- pub x_pm1b_control_block: GenericAddressStructure, -- pub x_pm2_control_block: GenericAddressStructure, -- pub x_pm_timer_block: GenericAddressStructure, -- pub x_gpe0_block: GenericAddressStructure, -- pub x_gpe1_block: GenericAddressStructure, -+ pub x_pm1a_event_block: GenericAddress, -+ pub x_pm1b_event_block: GenericAddress, -+ pub x_pm1a_control_block: GenericAddress, -+ pub x_pm1b_control_block: GenericAddress, -+ pub x_pm2_control_block: GenericAddress, -+ pub x_pm_timer_block: GenericAddress, -+ pub x_gpe0_block: GenericAddress, -+ pub x_gpe1_block: GenericAddress, - } - unsafe impl plain::Plain for FadtAcpi2Struct {} - -@@ -774,9 +1953,10 @@ impl Fadt { - } - - pub fn init(context: &mut AcpiContext) { -- let fadt_sdt = context -- .take_single_sdt(*b"FACP") -- .expect("expected ACPI to always have a FADT"); -+ let Some(fadt_sdt) = context.take_single_sdt(*b"FACP") else { -+ log::error!("Failed to find FADT"); -+ return; -+ }; - - let fadt = match Fadt::new(fadt_sdt) { - Some(fadt) => fadt, -@@ -793,9 +1973,25 @@ impl Fadt { - None => usize::try_from(fadt.dsdt).expect("expected any given u32 to fit within usize"), - }; - -- log::debug!("FACP at {:X}", { dsdt_ptr }); -+ let pm1a_evt_blk = u64::from(fadt.pm1a_event_block); -+ let pm1b_evt_blk = u64::from(fadt.pm1b_event_block); -+ let pm1a_cnt_blk = u64::from(fadt.pm1a_control_block); -+ let pm1b_cnt_blk = u64::from(fadt.pm1b_control_block); -+ let (reset_reg, reset_value) = match fadt.acpi_2_struct() { -+ Some(fadt2) if !fadt2.reset_reg.is_empty() => (Some(fadt2.reset_reg), fadt2.reset_value), -+ _ => (None, 0), -+ }; - -- let dsdt_sdt = match Sdt::load_from_physical(fadt.dsdt as usize) { -+ log::debug!("FACP at {:X}", { dsdt_ptr }); -+ log::debug!( -+ "FADT power blocks: PM1a_EVT={:#X}, PM1b_EVT={:#X}, PM1a_CNT={:#X}, PM1b_CNT={:#X}", -+ pm1a_evt_blk, -+ pm1b_evt_blk, -+ pm1a_cnt_blk, -+ pm1b_cnt_blk -+ ); -+ -+ let dsdt_sdt = match Sdt::load_from_physical(dsdt_ptr) { - Ok(dsdt) => dsdt, - Err(error) => { - log::error!("Failed to load DSDT: {}", error); -@@ -805,8 +2001,12 @@ impl Fadt { - - context.fadt = Some(fadt.clone()); - context.dsdt = Some(Dsdt(dsdt_sdt.clone())); -+ context.pm1a_cnt_blk = pm1a_cnt_blk; -+ context.pm1b_cnt_blk = pm1b_cnt_blk; -+ context.reset_reg = reset_reg; -+ context.reset_value = reset_value; - - context.tables.push(dsdt_sdt); - } - } - -diff --git a/drivers/acpid/src/acpi/dmar/mod.rs b/drivers/acpid/src/acpi/dmar/mod.rs -index c42b379a..f4dff276 100644 ---- a/drivers/acpid/src/acpi/dmar/mod.rs -+++ b/drivers/acpid/src/acpi/dmar/mod.rs -@@ -474,10 +474,13 @@ impl<'sdt> Iterator for DmarRawIter<'sdt> { - let len_bytes = <[u8; 2]>::try_from(type_bytes) - .expect("expected a 2-byte slice to be convertible to [u8; 2]"); - -- let ty = u16::from_ne_bytes(type_bytes); -- let len = u16::from_ne_bytes(len_bytes); -+ let len = u16::from_ne_bytes(len_bytes) as usize; - -- let len = usize::try_from(len).expect("expected u16 to fit within usize"); -+ if len < 4 { -+ return None; -+ } -+ -+ let ty = u16::from_ne_bytes(type_bytes); - - if len > remainder.len() { - log::warn!("DMAR remapping structure length was smaller than the remaining length of the table."); -diff --git a/drivers/acpid/src/aml_physmem.rs b/drivers/acpid/src/aml_physmem.rs -index 2bdd667b..69b8c48b 100644 ---- a/drivers/acpid/src/aml_physmem.rs -+++ b/drivers/acpid/src/aml_physmem.rs -@@ -6,7 +6,10 @@ use rustc_hash::FxHashMap; - use std::fmt::LowerHex; - use std::mem::size_of; - use std::ptr::NonNull; --use std::sync::{Arc, Mutex}; -+use std::sync::atomic::{AtomicU32, Ordering}; -+use std::sync::{Arc, Condvar, Mutex}; -+use std::thread::ThreadId; -+use std::time::{Duration, Instant}; - use syscall::PAGE_SIZE; - - const PAGE_MASK: usize = !(PAGE_SIZE - 1); -@@ -141,6 +144,20 @@ impl AmlPageCache { - pub struct AmlPhysMemHandler { - page_cache: Arc>, - pci_fd: Arc>, -+ aml_mutexes: Arc>>>, -+ next_mutex_handle: Arc, -+} -+ -+#[derive(Debug, Default)] -+struct AmlMutexState { -+ owner: Option, -+ depth: u32, -+} -+ -+#[derive(Debug, Default)] -+struct AmlMutex { -+ state: Mutex, -+ condvar: Condvar, - } - - /// Read from a physical address. -@@ -156,6 +173,30 @@ impl AmlPhysMemHandler { - Self { - page_cache, - pci_fd: Arc::new(pci_fd), -+ aml_mutexes: Arc::new(Mutex::new(FxHashMap::default())), -+ next_mutex_handle: Arc::new(AtomicU32::new(1)), -+ } -+ } -+ -+ fn aml_mutex(&self, handle: Handle) -> Option> { -+ self.aml_mutexes -+ .lock() -+ .unwrap_or_else(|poisoned| poisoned.into_inner()) -+ .get(&handle.0) -+ .cloned() -+ } -+ -+ fn read_phys_or_fault(&self, address: usize) -> T -+ where -+ T: PrimInt + LowerHex, -+ { -+ let mut page_cache = self -+ .page_cache -+ .lock() -+ .unwrap_or_else(|poisoned| poisoned.into_inner()); -+ match page_cache.read_from_phys::(address) { -+ Ok(value) => value, -+ Err(error) => panic!("AML physmem read failed at {:#x}: {}", address, error), - } - } - -@@ -240,43 +281,19 @@ impl acpi::Handler for AmlPhysMemHandler { - - fn read_u8(&self, address: usize) -> u8 { - log::trace!("read u8 {:X}", address); -- if let Ok(mut page_cache) = self.page_cache.lock() { -- if let Ok(value) = page_cache.read_from_phys::(address) { -- return value; -- } -- } -- log::error!("failed to read u8 {:#x}", address); -- 0 -+ self.read_phys_or_fault::(address) - } - fn read_u16(&self, address: usize) -> u16 { - log::trace!("read u16 {:X}", address); -- if let Ok(mut page_cache) = self.page_cache.lock() { -- if let Ok(value) = page_cache.read_from_phys::(address) { -- return value; -- } -- } -- log::error!("failed to read u16 {:#x}", address); -- 0 -+ self.read_phys_or_fault::(address) - } - fn read_u32(&self, address: usize) -> u32 { - log::trace!("read u32 {:X}", address); -- if let Ok(mut page_cache) = self.page_cache.lock() { -- if let Ok(value) = page_cache.read_from_phys::(address) { -- return value; -- } -- } -- log::error!("failed to read u32 {:#x}", address); -- 0 -+ self.read_phys_or_fault::(address) - } - fn read_u64(&self, address: usize) -> u64 { - log::trace!("read u64 {:X}", address); -- if let Ok(mut page_cache) = self.page_cache.lock() { -- if let Ok(value) = page_cache.read_from_phys::(address) { -- return value; -- } -- } -- log::error!("failed to read u64 {:#x}", address); -- 0 -+ self.read_phys_or_fault::(address) - } - - fn write_u8(&self, address: usize, value: u8) { -@@ -415,16 +432,102 @@ impl acpi::Handler for AmlPhysMemHandler { - } - - fn create_mutex(&self) -> Handle { -- log::debug!("TODO: Handler::create_mutex"); -- Handle(0) -+ let handle = self.next_mutex_handle.fetch_add(1, Ordering::Relaxed); -+ self.aml_mutexes -+ .lock() -+ .unwrap_or_else(|poisoned| poisoned.into_inner()) -+ .insert(handle, Arc::new(AmlMutex::default())); -+ log::trace!("created AML mutex handle {handle}"); -+ Handle(handle) - } - - fn acquire(&self, mutex: Handle, timeout: u16) -> Result<(), AmlError> { -- log::debug!("TODO: Handler::acquire"); -- Ok(()) -+ let Some(aml_mutex) = self.aml_mutex(mutex) else { -+ log::error!("attempted to acquire unknown AML mutex handle {}", mutex.0); -+ return Err(AmlError::MutexAcquireTimeout); -+ }; -+ -+ let current_thread = std::thread::current().id(); -+ let deadline = (timeout != 0xffff).then(|| Instant::now() + Duration::from_millis(timeout.into())); -+ -+ let mut state = aml_mutex -+ .state -+ .lock() -+ .unwrap_or_else(|poisoned| poisoned.into_inner()); -+ -+ loop { -+ match state.owner { -+ None => { -+ state.owner = Some(current_thread); -+ state.depth = 1; -+ return Ok(()); -+ } -+ Some(owner) if owner == current_thread => { -+ state.depth = state.depth.saturating_add(1); -+ return Ok(()); -+ } -+ Some(_) if timeout == 0 => return Err(AmlError::MutexAcquireTimeout), -+ Some(_) if timeout == 0xffff => { -+ state = aml_mutex -+ .condvar -+ .wait(state) -+ .unwrap_or_else(|poisoned| poisoned.into_inner()); -+ } -+ Some(_) => { -+ let Some(deadline) = deadline else { -+ return Err(AmlError::MutexAcquireTimeout); -+ }; -+ let now = Instant::now(); -+ if now >= deadline { -+ return Err(AmlError::MutexAcquireTimeout); -+ } -+ -+ let remaining = deadline.saturating_duration_since(now); -+ let (next_state, wait_result) = aml_mutex -+ .condvar -+ .wait_timeout(state, remaining) -+ .unwrap_or_else(|poisoned| poisoned.into_inner()); -+ state = next_state; -+ -+ if wait_result.timed_out() && state.owner != Some(current_thread) { -+ return Err(AmlError::MutexAcquireTimeout); -+ } -+ } -+ } -+ } - } - - fn release(&self, mutex: Handle) { -- log::debug!("TODO: Handler::release"); -+ let Some(aml_mutex) = self.aml_mutex(mutex) else { -+ log::error!("attempted to release unknown AML mutex handle {}", mutex.0); -+ return; -+ }; -+ -+ let current_thread = std::thread::current().id(); -+ let mut state = aml_mutex -+ .state -+ .lock() -+ .unwrap_or_else(|poisoned| poisoned.into_inner()); -+ -+ match state.owner { -+ Some(owner) if owner == current_thread => { -+ if state.depth > 1 { -+ state.depth -= 1; -+ } else { -+ state.owner = None; -+ state.depth = 0; -+ aml_mutex.condvar.notify_one(); -+ } -+ } -+ Some(_) => { -+ log::warn!( -+ "ignoring AML mutex release for handle {} from non-owner thread", -+ mutex.0 -+ ); -+ } -+ None => { -+ log::warn!("ignoring AML mutex release for unlocked handle {}", mutex.0); -+ } -+ } - } - } -diff --git a/drivers/acpid/src/ec.rs b/drivers/acpid/src/ec.rs -index c322790a..99842586 100644 ---- a/drivers/acpid/src/ec.rs -+++ b/drivers/acpid/src/ec.rs -@@ -1,3 +1,4 @@ -+use std::convert::TryFrom; - use std::time::Duration; - - use acpi::aml::{ -@@ -30,6 +31,28 @@ const BURST_ACK: u8 = 0x90; - - pub const DEFAULT_EC_TIMEOUT: Duration = Duration::from_millis(10); - -+#[derive(Debug, Clone, Copy)] -+enum EcError { -+ Timeout, -+ OffsetOutOfRange, -+} -+ -+impl EcError { -+ fn as_aml_error(self) -> AmlError { -+ match self { -+ EcError::Timeout | EcError::OffsetOutOfRange => { -+ AmlError::NoHandlerForRegionAccess(RegionSpace::EmbeddedControl) -+ } -+ } -+ } -+} -+ -+impl From for AmlError { -+ fn from(value: EcError) -> Self { -+ value.as_aml_error() -+ } -+} -+ - #[repr(transparent)] - pub struct ScBits(u8); - #[allow(dead_code)] -@@ -90,28 +113,33 @@ impl Ec { - Pio::::new(self.data).write(value); - } - #[inline] -- fn wait_for_write_ready(&self) -> Option<()> { -+ fn wait_for_write_ready(&self) -> Result<(), EcError> { - let timeout = Timeout::new(self.timeout); - loop { - if !self.read_reg_sc().ibf() { -- return Some(()); -+ return Ok(()); - } -- timeout.run().ok()?; -+ timeout.run().map_err(|_| EcError::Timeout)?; - } - } - #[inline] -- fn wait_for_read_ready(&self) -> Option<()> { -+ fn wait_for_read_ready(&self) -> Result<(), EcError> { - let timeout = Timeout::new(self.timeout); - loop { - if self.read_reg_sc().obf() { -- return Some(()); -+ return Ok(()); - } -- timeout.run().ok()?; -+ timeout.run().map_err(|_| EcError::Timeout)?; - } - } - -+ #[inline] -+ fn checked_address(offset: usize, byte_index: usize) -> Result { -+ u8::try_from(offset + byte_index).map_err(|_| EcError::OffsetOutOfRange) -+ } -+ - //https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/12_ACPI_Embedded_Controller_Interface_Specification/embedded-controller-command-set.html -- pub fn read(&self, address: u8) -> Option { -+ fn read(&self, address: u8) -> Result { - trace!("ec read addr: {:x}", address); - self.wait_for_write_ready()?; - -@@ -125,9 +153,9 @@ impl Ec { - - let val = self.read_reg_data(); - trace!("got: {:x}", val); -- Some(val) -+ Ok(val) - } -- pub fn write(&self, address: u8, value: u8) -> Option<()> { -+ fn write(&self, address: u8, value: u8) -> Result<(), EcError> { - trace!("ec write addr: {:x}, with: {:x}", address, value); - self.wait_for_write_ready()?; - -@@ -141,7 +169,22 @@ impl Ec { - - self.write_reg_data(value); - trace!("done"); -- Some(()) -+ Ok(()) -+ } -+ -+ fn read_bytes(&self, offset: usize) -> Result<[u8; N], EcError> { -+ let mut bytes = [0u8; N]; -+ for (index, byte) in bytes.iter_mut().enumerate() { -+ *byte = self.read(Self::checked_address(offset, index)?)?; -+ } -+ Ok(bytes) -+ } -+ -+ fn write_bytes(&self, offset: usize, bytes: [u8; N]) -> Result<(), EcError> { -+ for (index, byte) in IntoIterator::into_iter(bytes).enumerate() { -+ self.write(Self::checked_address(offset, index)?, byte)?; -+ } -+ Ok(()) - } - // disabled if not met - // First Access - 400 microseconds -@@ -151,11 +194,11 @@ impl Ec { - #[allow(dead_code)] - fn enable_burst(&self) -> bool { - trace!("ec burst enable"); -- self.wait_for_write_ready(); -+ let _ = self.wait_for_write_ready(); - - self.write_reg_sc(BE_EC); - -- self.wait_for_read_ready(); -+ let _ = self.wait_for_read_ready(); - - let res = self.read_reg_data() == BURST_ACK; - trace!("success: {}", res); -@@ -164,7 +207,7 @@ impl Ec { - #[allow(dead_code)] - fn disable_burst(&self) { - trace!("ec burst disable"); -- self.wait_for_write_ready(); -+ let _ = self.wait_for_write_ready(); - self.write_reg_sc(BD_EC); - trace!("done"); - } -@@ -172,11 +215,11 @@ impl Ec { - #[allow(dead_code)] - fn queue_query(&mut self) -> u8 { - trace!("ec query"); -- self.wait_for_write_ready(); -+ let _ = self.wait_for_write_ready(); - - self.write_reg_sc(QR_EC); - -- self.wait_for_read_ready(); -+ let _ = self.wait_for_read_ready(); - - let val = self.read_reg_data(); - trace!("got: {}", val); -@@ -190,7 +233,10 @@ impl RegionHandler for Ec { - offset: usize, - ) -> Result { - assert_eq!(region.space, RegionSpace::EmbeddedControl); -- self.read(offset as u8).ok_or(AmlError::MutexAcquireTimeout) // TODO proper error type -+ self.read(Self::checked_address(offset, 0)?).map_err(|error| { -+ warn!("EC read_u8 failed at offset {offset:#x}: {error:?}"); -+ error.as_aml_error() -+ }) - } - fn write_u8( - &self, -@@ -199,58 +245,73 @@ impl RegionHandler for Ec { - value: u8, - ) -> Result<(), acpi::aml::AmlError> { - assert_eq!(region.space, RegionSpace::EmbeddedControl); -- self.write(offset as u8, value) -- .ok_or(AmlError::MutexAcquireTimeout) // TODO proper error type -- } -- fn read_u16(&self, _region: &OpRegion, _offset: usize) -> Result { -- warn!("Got u16 EC read from AML!"); -- Err(acpi::aml::AmlError::NoHandlerForRegionAccess( -- RegionSpace::EmbeddedControl, -- )) // TODO proper error type -- } -- fn read_u32(&self, _region: &OpRegion, _offset: usize) -> Result { -- warn!("Got u32 EC read from AML!"); -- Err(acpi::aml::AmlError::NoHandlerForRegionAccess( -- RegionSpace::EmbeddedControl, -- )) // TODO proper error type -- } -- fn read_u64(&self, _region: &OpRegion, _offset: usize) -> Result { -- warn!("Got u64 EC read from AML!"); -- Err(acpi::aml::AmlError::NoHandlerForRegionAccess( -- RegionSpace::EmbeddedControl, -- )) // TODO proper error type -+ self.write(Self::checked_address(offset, 0)?, value) -+ .map_err(|error| { -+ warn!("EC write_u8 failed at offset {offset:#x}: {error:?}"); -+ error.as_aml_error() -+ }) -+ } -+ fn read_u16(&self, region: &OpRegion, offset: usize) -> Result { -+ assert_eq!(region.space, RegionSpace::EmbeddedControl); -+ self.read_bytes::<2>(offset) -+ .map(u16::from_le_bytes) -+ .map_err(|error| { -+ warn!("EC read_u16 failed at offset {offset:#x}: {error:?}"); -+ error.as_aml_error() -+ }) -+ } -+ fn read_u32(&self, region: &OpRegion, offset: usize) -> Result { -+ assert_eq!(region.space, RegionSpace::EmbeddedControl); -+ self.read_bytes::<4>(offset) -+ .map(u32::from_le_bytes) -+ .map_err(|error| { -+ warn!("EC read_u32 failed at offset {offset:#x}: {error:?}"); -+ error.as_aml_error() -+ }) -+ } -+ fn read_u64(&self, region: &OpRegion, offset: usize) -> Result { -+ assert_eq!(region.space, RegionSpace::EmbeddedControl); -+ self.read_bytes::<8>(offset) -+ .map(u64::from_le_bytes) -+ .map_err(|error| { -+ warn!("EC read_u64 failed at offset {offset:#x}: {error:?}"); -+ error.as_aml_error() -+ }) - } - fn write_u16( - &self, -- _region: &OpRegion, -- _offset: usize, -- _value: u16, -+ region: &OpRegion, -+ offset: usize, -+ value: u16, - ) -> Result<(), acpi::aml::AmlError> { -- warn!("Got u16 EC write from AML!"); -- Err(acpi::aml::AmlError::NoHandlerForRegionAccess( -- RegionSpace::EmbeddedControl, -- )) // TODO proper error type -+ assert_eq!(region.space, RegionSpace::EmbeddedControl); -+ self.write_bytes(offset, value.to_le_bytes()).map_err(|error| { -+ warn!("EC write_u16 failed at offset {offset:#x}: {error:?}"); -+ error.as_aml_error() -+ }) - } - fn write_u32( - &self, -- _region: &OpRegion, -- _offset: usize, -- _value: u32, -+ region: &OpRegion, -+ offset: usize, -+ value: u32, - ) -> Result<(), acpi::aml::AmlError> { -- warn!("Got u32 EC write from AML!"); -- Err(acpi::aml::AmlError::NoHandlerForRegionAccess( -- RegionSpace::EmbeddedControl, -- )) // TODO proper error type -+ assert_eq!(region.space, RegionSpace::EmbeddedControl); -+ self.write_bytes(offset, value.to_le_bytes()).map_err(|error| { -+ warn!("EC write_u32 failed at offset {offset:#x}: {error:?}"); -+ error.as_aml_error() -+ }) - } - fn write_u64( - &self, -- _region: &OpRegion, -- _offset: usize, -- _value: u64, -+ region: &OpRegion, -+ offset: usize, -+ value: u64, - ) -> Result<(), acpi::aml::AmlError> { -- warn!("Got u64 EC write from AML!"); -- Err(acpi::aml::AmlError::NoHandlerForRegionAccess( -- RegionSpace::EmbeddedControl, -- )) // TODO proper error type -+ assert_eq!(region.space, RegionSpace::EmbeddedControl); -+ self.write_bytes(offset, value.to_le_bytes()).map_err(|error| { -+ warn!("EC write_u64 failed at offset {offset:#x}: {error:?}"); -+ error.as_aml_error() -+ }) - } - } -diff --git a/drivers/acpid/src/main.rs b/drivers/acpid/src/main.rs -index 059254b3..3b0deeab 100644 ---- a/drivers/acpid/src/main.rs -+++ b/drivers/acpid/src/main.rs -@@ -5,107 +5,182 @@ use std::ops::ControlFlow; - use std::os::unix::io::AsRawFd; - use std::sync::Arc; - --use ::acpi::aml::op_region::{RegionHandler, RegionSpace}; - use event::{EventFlags, RawEventQueue}; - use redox_scheme::{scheme::register_sync_scheme, Socket}; - use scheme_utils::Blocking; -+use thiserror::Error; - - mod acpi; - mod aml_physmem; - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - mod ec; - -+mod resources; - mod scheme; -+mod sleep; - --fn daemon(daemon: daemon::Daemon) -> ! { -- common::setup_logging( -- "misc", -- "acpi", -- "acpid", -- common::output_level(), -- common::file_level(), -- ); -+#[derive(Debug, Error)] -+enum StartupError { -+ #[error("failed to read `/scheme/kernel.acpi/rxsdt`: {0}")] -+ ReadRootTable(std::io::Error), - -- log::info!("acpid start"); -+ #[error("failed to parse [R/X]SDT from kernel: {0}")] -+ ParseRootTable(self::acpi::InvalidSdtError), - -- let rxsdt_raw_data: Arc<[u8]> = std::fs::read("/scheme/kernel.acpi/rxsdt") -- .expect("acpid: failed to read `/scheme/kernel.acpi/rxsdt`") -- .into(); -+ #[error("kernel returned unsupported root table signature `{signature}`")] -+ UnsupportedRootSignature { signature: String }, - -- if rxsdt_raw_data.is_empty() { -- log::info!("System doesn't use ACPI"); -- daemon.ready(); -- std::process::exit(0); -+ #[error( -+ "root table `{signature}` payload length {payload_len} is not divisible by entry size {entry_size}" -+ )] -+ MisalignedRootEntries { -+ signature: String, -+ payload_len: usize, -+ entry_size: usize, -+ }, -+ -+ #[error("{context}: {message}")] -+ Runtime { -+ context: &'static str, -+ message: String, -+ }, -+} -+ -+impl StartupError { -+ fn runtime(context: &'static str, message: impl Into) -> Self { -+ Self::Runtime { -+ context, -+ message: message.into(), -+ } - } -+} - -- let sdt = self::acpi::Sdt::new(rxsdt_raw_data).expect("acpid: failed to parse [RX]SDT"); -+fn parse_root_sdt(raw_data: Arc<[u8]>) -> Result, StartupError> { -+ if raw_data.is_empty() { -+ return Ok(None); -+ } - -- let mut thirty_two_bit; -- let mut sixty_four_bit; -+ self::acpi::Sdt::new(raw_data) -+ .map(Some) -+ .map_err(StartupError::ParseRootTable) -+} -+ -+fn root_table_physaddrs(sdt: &self::acpi::Sdt) -> Result, StartupError> { -+ let signature = String::from_utf8_lossy(&sdt.signature).into_owned(); -+ let data = sdt.data(); - -- let physaddrs_iter = match &sdt.signature { -+ match &sdt.signature { - b"RSDT" => { -- thirty_two_bit = sdt -- .data() -- .chunks(mem::size_of::()) -- // TODO: With const generics, the compiler has some way of doing this for static sizes. -- .map(|chunk| <[u8; mem::size_of::()]>::try_from(chunk).unwrap()) -- .map(|chunk| u32::from_le_bytes(chunk)) -- .map(u64::from); -+ let entry_size = mem::size_of::(); -+ if data.len() % entry_size != 0 { -+ return Err(StartupError::MisalignedRootEntries { -+ signature, -+ payload_len: data.len(), -+ entry_size, -+ }); -+ } - -- &mut thirty_two_bit as &mut dyn Iterator -+ Ok(data -+ .chunks_exact(entry_size) -+ .map(|chunk| <[u8; mem::size_of::()]>::try_from(chunk).unwrap()) -+ .map(u32::from_le_bytes) -+ .map(u64::from) -+ .collect()) - } - b"XSDT" => { -- sixty_four_bit = sdt -- .data() -- .chunks(mem::size_of::()) -- .map(|chunk| <[u8; mem::size_of::()]>::try_from(chunk).unwrap()) -- .map(|chunk| u64::from_le_bytes(chunk)); -+ let entry_size = mem::size_of::(); -+ if data.len() % entry_size != 0 { -+ return Err(StartupError::MisalignedRootEntries { -+ signature, -+ payload_len: data.len(), -+ entry_size, -+ }); -+ } - -- &mut sixty_four_bit as &mut dyn Iterator -+ Ok(data -+ .chunks_exact(entry_size) -+ .map(|chunk| <[u8; mem::size_of::()]>::try_from(chunk).unwrap()) -+ .map(u64::from_le_bytes) -+ .collect()) - } -- _ => panic!("acpid: expected [RX]SDT from kernel to be either of those"), -+ _ => Err(StartupError::UnsupportedRootSignature { signature }), -+ } -+} -+ -+fn run_acpid(daemon: daemon::Daemon) -> Result<(), StartupError> { -+ let rxsdt_raw_data: Arc<[u8]> = std::fs::read("/scheme/kernel.acpi/rxsdt") -+ .map(Arc::<[u8]>::from) -+ .map_err(StartupError::ReadRootTable)?; -+ -+ let Some(sdt) = parse_root_sdt(rxsdt_raw_data)? else { -+ log::info!("System doesn't use ACPI"); -+ daemon.ready(); -+ std::process::exit(0); - }; - -- let region_handlers: Vec<(RegionSpace, Box)> = vec![ -- #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -- (RegionSpace::EmbeddedControl, Box::new(ec::Ec::new())), -- ]; -- let acpi_context = self::acpi::AcpiContext::init(physaddrs_iter, region_handlers); -+ let physaddrs = root_table_physaddrs(&sdt)?; -+ let acpi_context = self::acpi::AcpiContext::init(physaddrs.into_iter()); - -- // TODO: I/O permission bitmap? - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -- common::acquire_port_io_rights().expect("acpid: failed to set I/O privilege level to Ring 3"); -+ common::acquire_port_io_rights().map_err(|error| { -+ StartupError::runtime( -+ "failed to set I/O privilege level to Ring 3", -+ format!("{error}"), -+ ) -+ })?; - -- let shutdown_pipe = File::open("/scheme/kernel.acpi/kstop") -- .expect("acpid: failed to open `/scheme/kernel.acpi/kstop`"); -+ let shutdown_pipe = File::open("/scheme/kernel.acpi/kstop").map_err(|error| { -+ StartupError::runtime( -+ "failed to open `/scheme/kernel.acpi/kstop`", -+ error.to_string(), -+ ) -+ })?; - -- let mut event_queue = RawEventQueue::new().expect("acpid: failed to create event queue"); -- let socket = Socket::nonblock().expect("acpid: failed to create disk scheme"); -+ let mut event_queue = RawEventQueue::new().map_err(|error| { -+ StartupError::runtime("failed to create event queue", error.to_string()) -+ })?; -+ let socket = Socket::nonblock().map_err(|error| { -+ StartupError::runtime("failed to create acpi scheme socket", error.to_string()) -+ })?; - - let mut scheme = self::scheme::AcpiScheme::new(&acpi_context, &socket); - let mut handler = Blocking::new(&socket, 16); - - event_queue - .subscribe(shutdown_pipe.as_raw_fd() as usize, 0, EventFlags::READ) -- .expect("acpid: failed to register shutdown pipe for event queue"); -+ .map_err(|error| { -+ StartupError::runtime( -+ "failed to register shutdown pipe for event queue", -+ error.to_string(), -+ ) -+ })?; - event_queue - .subscribe(socket.inner().raw(), 1, EventFlags::READ) -- .expect("acpid: failed to register scheme socket for event queue"); -+ .map_err(|error| { -+ StartupError::runtime( -+ "failed to register scheme socket for event queue", -+ error.to_string(), -+ ) -+ })?; - -- register_sync_scheme(&socket, "acpi", &mut scheme) -- .expect("acpid: failed to register acpi scheme to namespace"); -+ register_sync_scheme(&socket, "acpi", &mut scheme).map_err(|error| { -+ StartupError::runtime( -+ "failed to register acpi scheme to namespace", -+ error.to_string(), -+ ) -+ })?; - -- daemon.ready(); -+ libredox::call::setrens(0, 0).map_err(|error| { -+ StartupError::runtime("failed to enter null namespace", error.to_string()) -+ })?; - -- libredox::call::setrens(0, 0).expect("acpid: failed to enter null namespace"); -+ daemon.ready(); - - let mut mounted = true; - while mounted { -- let Some(event) = event_queue -- .next() -- .transpose() -- .expect("acpid: failed to read event file") -+ let Some(event) = event_queue.next().transpose().map_err(|error| { -+ StartupError::runtime("failed to read event file", error.to_string()) -+ })? - else { - break; - }; -@@ -114,8 +189,9 @@ fn daemon(daemon: daemon::Daemon) -> ! { - loop { - match handler - .process_requests_nonblocking(&mut scheme) -- .expect("acpid: failed to process requests") -- { -+ .map_err(|error| { -+ StartupError::runtime("failed to process requests", error.to_string()) -+ })? { - ControlFlow::Continue(()) => {} - ControlFlow::Break(()) => break, - } -@@ -125,19 +201,139 @@ fn daemon(daemon: daemon::Daemon) -> ! { - mounted = false; - } else { - log::debug!("Received request to unknown fd: {}", event.fd); -- continue; - } - } - - drop(shutdown_pipe); - drop(event_queue); - -- acpi_context.set_global_s_state(5); -+ acpi_context.set_global_s_state(5).map_err(|error| { -+ StartupError::runtime( -+ "failed to shut down after kernel request", -+ error.to_string(), -+ ) -+ }) -+} -+ -+fn daemon(daemon: daemon::Daemon) -> ! { -+ common::setup_logging( -+ "misc", -+ "acpi", -+ "acpid", -+ common::output_level(), -+ common::file_level(), -+ ); - -- unreachable!("System should have shut down before this is entered"); -+ log::info!("acpid start"); -+ -+ if let Err(error) = run_acpid(daemon) { -+ log::error!("acpid startup/runtime failure: {error}"); -+ std::process::exit(1); -+ } -+ -+ unreachable!("acpid returned from run_acpid without exiting or shutting down"); - } - - fn main() { - common::init(); - daemon::Daemon::new(daemon); - } -+ -+#[cfg(test)] -+mod tests { -+ use super::{parse_root_sdt, root_table_physaddrs, StartupError}; -+ use crate::acpi::SdtHeader; -+ use std::sync::Arc; -+ -+ fn make_sdt(signature: [u8; 4], payload: &[u8]) -> Arc<[u8]> { -+ let length = (std::mem::size_of::() + payload.len()) as u32; -+ let header = SdtHeader { -+ signature, -+ length, -+ revision: 1, -+ checksum: 0, -+ oem_id: *b"REDBAR", -+ oem_table_id: *b"ACPITEST", -+ oem_revision: 1, -+ creator_id: 1, -+ creator_revision: 1, -+ }; -+ -+ let mut bytes = unsafe { plain::as_bytes(&header) }.to_vec(); -+ bytes.extend_from_slice(payload); -+ -+ let checksum = bytes -+ .iter() -+ .copied() -+ .fold(0u8, |sum, byte| sum.wrapping_add(byte)); -+ bytes[9] = 0u8.wrapping_sub(checksum); -+ -+ bytes.into() -+ } -+ -+ #[test] -+ fn empty_root_table_means_no_acpi() { -+ let parsed = parse_root_sdt(Arc::<[u8]>::from(Vec::::new())).unwrap(); -+ assert!(parsed.is_none()); -+ } -+ -+ #[test] -+ fn rsdt_physaddrs_parse_without_panic() { -+ let payload = [ -+ 0x78, 0x56, 0x34, 0x12, // 0x12345678 -+ 0xF0, 0xDE, 0xBC, 0x9A, // 0x9ABCDEF0 -+ ]; -+ let sdt = parse_root_sdt(make_sdt(*b"RSDT", &payload)) -+ .unwrap() -+ .unwrap(); -+ -+ assert_eq!( -+ root_table_physaddrs(&sdt).unwrap(), -+ vec![0x1234_5678, 0x9ABC_DEF0] -+ ); -+ } -+ -+ #[test] -+ fn xsdt_physaddrs_parse_without_panic() { -+ let payload = [ -+ 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00, 0xFF, 0xEE, 0xDD, 0xCC, 0xBB, -+ 0xAA, 0x99, -+ ]; -+ let sdt = parse_root_sdt(make_sdt(*b"XSDT", &payload)) -+ .unwrap() -+ .unwrap(); -+ -+ assert_eq!( -+ root_table_physaddrs(&sdt).unwrap(), -+ vec![0x1122_3344_5566_7788, 0x99AA_BBCC_DDEE_FF00] -+ ); -+ } -+ -+ #[test] -+ fn invalid_root_signature_is_explicit() { -+ let sdt = parse_root_sdt(make_sdt(*b"FADT", &[])).unwrap().unwrap(); -+ -+ let error = root_table_physaddrs(&sdt).unwrap_err(); -+ assert!(matches!( -+ error, -+ StartupError::UnsupportedRootSignature { .. } -+ )); -+ } -+ -+ #[test] -+ fn misaligned_rsdt_entries_are_rejected() { -+ let sdt = parse_root_sdt(make_sdt(*b"RSDT", &[1, 2, 3])) -+ .unwrap() -+ .unwrap(); -+ -+ let error = root_table_physaddrs(&sdt).unwrap_err(); -+ assert!(matches!( -+ error, -+ StartupError::MisalignedRootEntries { -+ entry_size: 4, -+ payload_len: 3, -+ .. -+ } -+ )); -+ } -+} -diff --git a/drivers/acpid/src/scheme.rs b/drivers/acpid/src/scheme.rs -index 5a5040c3..4fe3b8d8 100644 ---- a/drivers/acpid/src/scheme.rs -+++ b/drivers/acpid/src/scheme.rs -@@ -2,7 +2,6 @@ use acpi::aml::namespace::AmlName; - use amlserde::aml_serde_name::to_aml_format; - use amlserde::AmlSerdeValue; - use core::str; --use libredox::Fd; - use parking_lot::RwLockReadGuard; - use redox_scheme::scheme::SchemeSync; - use redox_scheme::{CallerCtx, OpenResult, SendFdRequest, Socket}; -@@ -16,17 +15,22 @@ use syscall::FobtainFdFlags; - - use syscall::data::Stat; - use syscall::error::{Error, Result}; --use syscall::error::{EACCES, EBADF, EBADFD, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR}; -+use syscall::error::{ -+ EACCES, EAGAIN, EBADF, EBADFD, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR, EOPNOTSUPP, -+}; - use syscall::flag::{MODE_DIR, MODE_FILE}; - use syscall::flag::{O_ACCMODE, O_DIRECTORY, O_RDONLY, O_STAT, O_SYMLINK}; - use syscall::{EOVERFLOW, EPERM}; - --use crate::acpi::{AcpiContext, AmlSymbols, SdtSignature}; -+use crate::acpi::{ -+ AcpiBattery, AcpiContext, AcpiPowerAdapter, AcpiPowerSnapshot, AmlSymbols, DmiInfo, -+ SdtSignature, -+}; -+use crate::resources::{decode_resource_template, ResourceDescriptor}; - - pub struct AcpiScheme<'acpi, 'sock> { - ctx: &'acpi AcpiContext, - handles: HandleMap>, -- pci_fd: Option, - socket: &'sock Socket, - } - -@@ -41,10 +45,204 @@ enum HandleKind<'a> { - Table(SdtSignature), - Symbols(RwLockReadGuard<'a, AmlSymbols>), - Symbol { name: String, description: String }, -+ ResourcesDir, -+ Resources(String), -+ Reboot, -+ DmiDir, -+ Dmi(String), -+ PowerDir, -+ PowerAdaptersDir, -+ PowerAdapterDir(String), -+ PowerBatteriesDir, -+ PowerBatteryDir(String), -+ PowerFile(String), - SchemeRoot, - RegisterPci, - } - -+const DMI_DIRECTORY_ENTRIES: &[&str] = &[ -+ "sys_vendor", -+ "board_vendor", -+ "board_name", -+ "board_version", -+ "product_name", -+ "product_version", -+ "bios_version", -+ "match_all", -+]; -+ -+fn dmi_contents(dmi_info: Option<&DmiInfo>, name: &str) -> Option { -+ Some(match name { -+ "sys_vendor" => dmi_info -+ .and_then(|info| info.sys_vendor.clone()) -+ .unwrap_or_default(), -+ "board_vendor" => dmi_info -+ .and_then(|info| info.board_vendor.clone()) -+ .unwrap_or_default(), -+ "board_name" => dmi_info -+ .and_then(|info| info.board_name.clone()) -+ .unwrap_or_default(), -+ "board_version" => dmi_info -+ .and_then(|info| info.board_version.clone()) -+ .unwrap_or_default(), -+ "product_name" => dmi_info -+ .and_then(|info| info.product_name.clone()) -+ .unwrap_or_default(), -+ "product_version" => dmi_info -+ .and_then(|info| info.product_version.clone()) -+ .unwrap_or_default(), -+ "bios_version" => dmi_info -+ .and_then(|info| info.bios_version.clone()) -+ .unwrap_or_default(), -+ "match_all" => dmi_info.map(DmiInfo::to_match_lines).unwrap_or_default(), -+ _ => return None, -+ }) -+} -+ -+fn power_bool_contents(value: bool) -> String { -+ if value { -+ String::from("1\n") -+ } else { -+ String::from("0\n") -+ } -+} -+ -+fn power_u64_contents(value: u64) -> String { -+ format!("{value}\n") -+} -+ -+fn power_f64_contents(value: f64) -> String { -+ format!("{value}\n") -+} -+ -+fn power_string_contents(value: &str) -> String { -+ format!("{value}\n") -+} -+ -+fn power_adapter_file_contents(adapter: &AcpiPowerAdapter, name: &str) -> Option { -+ Some(match name { -+ "path" => power_string_contents(&adapter.path), -+ "online" => power_bool_contents(adapter.online), -+ _ => return None, -+ }) -+} -+ -+fn power_adapter_entry_names() -> &'static [&'static str] { -+ &["path", "online"] -+} -+ -+fn power_battery_file_contents(battery: &AcpiBattery, name: &str) -> Option { -+ Some(match name { -+ "path" => power_string_contents(&battery.path), -+ "state" => power_u64_contents(battery.state), -+ "present_rate" => power_u64_contents(battery.present_rate?), -+ "remaining_capacity" => power_u64_contents(battery.remaining_capacity?), -+ "present_voltage" => power_u64_contents(battery.present_voltage?), -+ "power_unit" => power_string_contents(battery.power_unit.as_deref()?), -+ "design_capacity" => power_u64_contents(battery.design_capacity?), -+ "last_full_capacity" => power_u64_contents(battery.last_full_capacity?), -+ "design_voltage" => power_u64_contents(battery.design_voltage?), -+ "technology" => power_string_contents(battery.technology.as_deref()?), -+ "model" => power_string_contents(battery.model.as_deref()?), -+ "serial" => power_string_contents(battery.serial.as_deref()?), -+ "battery_type" => power_string_contents(battery.battery_type.as_deref()?), -+ "oem_info" => power_string_contents(battery.oem_info.as_deref()?), -+ "percentage" => power_f64_contents(battery.percentage?), -+ _ => return None, -+ }) -+} -+ -+fn power_battery_entry_names(battery: &AcpiBattery) -> Vec<&'static str> { -+ let mut names = vec!["path", "state"]; -+ -+ if battery.present_rate.is_some() { -+ names.push("present_rate"); -+ } -+ if battery.remaining_capacity.is_some() { -+ names.push("remaining_capacity"); -+ } -+ if battery.present_voltage.is_some() { -+ names.push("present_voltage"); -+ } -+ if battery.power_unit.is_some() { -+ names.push("power_unit"); -+ } -+ if battery.design_capacity.is_some() { -+ names.push("design_capacity"); -+ } -+ if battery.last_full_capacity.is_some() { -+ names.push("last_full_capacity"); -+ } -+ if battery.design_voltage.is_some() { -+ names.push("design_voltage"); -+ } -+ if battery.technology.is_some() { -+ names.push("technology"); -+ } -+ if battery.model.is_some() { -+ names.push("model"); -+ } -+ if battery.serial.is_some() { -+ names.push("serial"); -+ } -+ if battery.battery_type.is_some() { -+ names.push("battery_type"); -+ } -+ if battery.oem_info.is_some() { -+ names.push("oem_info"); -+ } -+ if battery.percentage.is_some() { -+ names.push("percentage"); -+ } -+ -+ names -+} -+ -+fn top_level_entries(power_available: bool) -> Vec<(&'static str, DirentKind)> { -+ let mut entries = vec![ -+ ("tables", DirentKind::Directory), -+ ("symbols", DirentKind::Directory), -+ ("resources", DirentKind::Directory), -+ ("dmi", DirentKind::Directory), -+ ("reboot", DirentKind::Regular), -+ ]; -+ if power_available { -+ entries.push(("power", DirentKind::Directory)); -+ } -+ entries -+} -+ -+fn resource_symbol_path(path: &str) -> Option { -+ let normalized = path.trim_matches('/').trim_start_matches('\\'); -+ if normalized.is_empty() { -+ return None; -+ } -+ -+ let normalized = normalized.replace('/', "."); -+ if normalized.is_empty() { -+ None -+ } else { -+ Some(format!("{normalized}._CRS")) -+ } -+} -+ -+fn resource_entry_name(symbol: &str) -> Option { -+ symbol -+ .strip_suffix("._CRS") -+ .map(str::to_string) -+ .filter(|path| !path.is_empty()) -+} -+ -+fn resource_dir_entries<'a>(symbols: impl IntoIterator) -> Vec { -+ let mut entries = symbols -+ .into_iter() -+ .filter_map(resource_entry_name) -+ .collect::>(); -+ entries.sort_unstable(); -+ entries.dedup(); -+ entries -+} -+ - impl HandleKind<'_> { - fn is_dir(&self) -> bool { - match self { -@@ -53,6 +251,17 @@ impl HandleKind<'_> { - Self::Table(_) => false, - Self::Symbols(_) => true, - Self::Symbol { .. } => false, -+ Self::ResourcesDir => true, -+ Self::Resources(_) => false, -+ Self::Reboot => false, -+ Self::DmiDir => true, -+ Self::Dmi(_) => false, -+ Self::PowerDir => true, -+ Self::PowerAdaptersDir => true, -+ Self::PowerAdapterDir(_) => true, -+ Self::PowerBatteriesDir => true, -+ Self::PowerBatteryDir(_) => true, -+ Self::PowerFile(_) => false, - Self::SchemeRoot => false, - Self::RegisterPci => false, - } -@@ -65,8 +274,21 @@ impl HandleKind<'_> { - .ok_or(Error::new(EBADFD))? - .length(), - Self::Symbol { description, .. } => description.len(), -+ Self::Resources(contents) => contents.len(), -+ Self::Reboot => 0, -+ Self::Dmi(contents) => contents.len(), -+ Self::PowerFile(contents) => contents.len(), - // Directories -- Self::TopLevel | Self::Symbols(_) | Self::Tables => 0, -+ Self::TopLevel -+ | Self::Symbols(_) -+ | Self::ResourcesDir -+ | Self::Tables -+ | Self::DmiDir -+ | Self::PowerDir -+ | Self::PowerAdaptersDir -+ | Self::PowerAdapterDir(_) -+ | Self::PowerBatteriesDir -+ | Self::PowerBatteryDir(_) => 0, - Self::SchemeRoot | Self::RegisterPci => return Err(Error::new(EBADF)), - }) - } -@@ -77,10 +299,163 @@ impl<'acpi, 'sock> AcpiScheme<'acpi, 'sock> { - Self { - ctx, - handles: HandleMap::new(), -- pci_fd: None, - socket, - } - } -+ -+ fn power_snapshot(&self) -> Result { -+ self.ctx.power_snapshot().map_err(|error| match error { -+ crate::acpi::AmlEvalError::NotInitialized => Error::new(EAGAIN), -+ crate::acpi::AmlEvalError::Unsupported(message) => { -+ log::warn!("ACPI power surface unavailable: {message}"); -+ Error::new(EOPNOTSUPP) -+ } -+ other => { -+ log::warn!("Failed to build ACPI power snapshot: {:?}", other); -+ Error::new(EIO) -+ } -+ }) -+ } -+ -+ fn power_available(&self) -> bool { -+ matches!(self.ctx.power_snapshot(), Ok(_)) -+ } -+ -+ fn resources_handle(&self, path: &str) -> Result> { -+ if !self.ctx.pci_ready() { -+ let display_path = if path.is_empty() { "resources" } else { path }; -+ log::warn!( -+ "Deferring ACPI resource lookup for {display_path} until PCI registration is ready" -+ ); -+ return Err(Error::new(EAGAIN)); -+ } -+ -+ let normalized = path.trim_matches('/'); -+ if normalized.is_empty() { -+ return Ok(HandleKind::ResourcesDir); -+ } -+ -+ let symbol_path = resource_symbol_path(normalized).ok_or(Error::new(ENOENT))?; -+ if self.ctx.aml_lookup(&symbol_path).is_none() { -+ return Err(Error::new(ENOENT)); -+ } -+ -+ let aml_name = -+ AmlName::from_str(&format!("\\{symbol_path}")).map_err(|_| Error::new(ENOENT))?; -+ let buffer = match self.ctx.aml_eval(aml_name, Vec::new()) { -+ Ok(AmlSerdeValue::Buffer(bytes)) => bytes, -+ Ok(other) => { -+ log::debug!( -+ "Skipping ACPI resources for {normalized} due to unexpected _CRS value: {:?}", -+ other -+ ); -+ return Err(Error::new(ENOENT)); -+ } -+ Err(error) => { -+ log::debug!( -+ "Failed to evaluate ACPI resources for {symbol_path}: {:?}", -+ error -+ ); -+ return Err(Error::new(ENOENT)); -+ } -+ }; -+ -+ let descriptors: Vec = -+ decode_resource_template(&buffer).map_err(|error| { -+ log::warn!("Failed to decode ACPI _CRS for {symbol_path}: {error}"); -+ Error::new(EIO) -+ })?; -+ let serialized = ron::ser::to_string(&descriptors).map_err(|error| { -+ log::warn!("Failed to serialize decoded ACPI resources for {symbol_path}: {error}"); -+ Error::new(EIO) -+ })?; -+ -+ Ok(HandleKind::Resources(serialized)) -+ } -+ -+ fn power_handle(&self, path: &str) -> Result> { -+ let normalized = path.trim_matches('/'); -+ self.power_snapshot()?; -+ -+ if normalized.is_empty() { -+ return Ok(HandleKind::PowerDir); -+ } -+ if normalized == "on_battery" { -+ return Ok(HandleKind::PowerFile(power_bool_contents( -+ self.power_snapshot()?.on_battery(), -+ ))); -+ } -+ if normalized == "adapters" { -+ return Ok(HandleKind::PowerAdaptersDir); -+ } -+ if let Some(rest) = normalized.strip_prefix("adapters/") { -+ return self.power_adapter_handle(rest); -+ } -+ if normalized == "batteries" { -+ return Ok(HandleKind::PowerBatteriesDir); -+ } -+ if let Some(rest) = normalized.strip_prefix("batteries/") { -+ return self.power_battery_handle(rest); -+ } -+ -+ Err(Error::new(ENOENT)) -+ } -+ -+ fn power_adapter_handle(&self, path: &str) -> Result> { -+ let normalized = path.trim_matches('/'); -+ if normalized.is_empty() { -+ return Ok(HandleKind::PowerAdaptersDir); -+ } -+ -+ let mut parts = normalized.split('/'); -+ let adapter_id = parts.next().ok_or(Error::new(ENOENT))?; -+ let field = parts.next(); -+ if parts.next().is_some() { -+ return Err(Error::new(ENOENT)); -+ } -+ -+ let snapshot = self.power_snapshot()?; -+ let adapter = snapshot -+ .adapters -+ .iter() -+ .find(|adapter| adapter.id == adapter_id) -+ .ok_or(Error::new(ENOENT))?; -+ -+ match field { -+ None | Some("") => Ok(HandleKind::PowerAdapterDir(adapter.id.clone())), -+ Some(name) => Ok(HandleKind::PowerFile( -+ power_adapter_file_contents(adapter, name).ok_or(Error::new(ENOENT))?, -+ )), -+ } -+ } -+ -+ fn power_battery_handle(&self, path: &str) -> Result> { -+ let normalized = path.trim_matches('/'); -+ if normalized.is_empty() { -+ return Ok(HandleKind::PowerBatteriesDir); -+ } -+ -+ let mut parts = normalized.split('/'); -+ let battery_id = parts.next().ok_or(Error::new(ENOENT))?; -+ let field = parts.next(); -+ if parts.next().is_some() { -+ return Err(Error::new(ENOENT)); -+ } -+ -+ let snapshot = self.power_snapshot()?; -+ let battery = snapshot -+ .batteries -+ .iter() -+ .find(|battery| battery.id == battery_id) -+ .ok_or(Error::new(ENOENT))?; -+ -+ match field { -+ None | Some("") => Ok(HandleKind::PowerBatteryDir(battery.id.clone())), -+ Some(name) => Ok(HandleKind::PowerFile( -+ power_battery_file_contents(battery, name).ok_or(Error::new(ENOENT))?, -+ )), -+ } -+ } - } - - fn parse_hex_digit(hex: u8) -> Option { -@@ -182,49 +557,97 @@ impl SchemeSync for AcpiScheme<'_, '_> { - - let kind = match handle.kind { - HandleKind::SchemeRoot => { -- // TODO: arrayvec -- let components = { -- let mut v = arrayvec::ArrayVec::<&str, 3>::new(); -- let it = path.split('/'); -- for component in it.take(3) { -- v.push(component); -- } -- -- v -- }; -- -- match &*components { -- [""] => HandleKind::TopLevel, -- ["register_pci"] => HandleKind::RegisterPci, -- ["tables"] => HandleKind::Tables, -+ if path == "resources" || path == "resources/" { -+ self.resources_handle("")? -+ } else if let Some(rest) = path.strip_prefix("resources/") { -+ self.resources_handle(rest)? -+ } else { -+ // TODO: arrayvec -+ let components = { -+ let mut v = arrayvec::ArrayVec::<&str, 4>::new(); -+ let it = path.split('/'); -+ for component in it.take(4) { -+ v.push(component); -+ } - -- ["tables", table] => { -- let signature = parse_table(table.as_bytes()).ok_or(Error::new(ENOENT))?; -- HandleKind::Table(signature) -- } -+ v -+ }; -+ -+ match &*components { -+ [""] => HandleKind::TopLevel, -+ ["reboot"] => HandleKind::Reboot, -+ ["dmi"] => { -+ if flag_dir || flag_stat || path.ends_with('/') { -+ HandleKind::DmiDir -+ } else { -+ HandleKind::Dmi( -+ dmi_contents(self.ctx.dmi_info(), "match_all") -+ .expect("match_all should always resolve"), -+ ) -+ } -+ } -+ ["dmi", ""] => HandleKind::DmiDir, -+ ["dmi", field] => HandleKind::Dmi( -+ dmi_contents(self.ctx.dmi_info(), field).ok_or(Error::new(ENOENT))?, -+ ), -+ ["power"] => self.power_handle("")?, -+ ["power", tail] => self.power_handle(tail)?, -+ ["power", a, b] => self.power_handle(&format!("{a}/{b}"))?, -+ ["power", a, b, c] => self.power_handle(&format!("{a}/{b}/{c}"))?, -+ ["register_pci"] => HandleKind::RegisterPci, -+ ["tables"] => HandleKind::Tables, -+ -+ ["tables", table] => { -+ let signature = -+ parse_table(table.as_bytes()).ok_or(Error::new(ENOENT))?; -+ HandleKind::Table(signature) -+ } - -- ["symbols"] => { -- if let Ok(aml_symbols) = self.ctx.aml_symbols(self.pci_fd.as_ref()) { -- HandleKind::Symbols(aml_symbols) -- } else { -- return Err(Error::new(EIO)); -+ ["symbols"] => { -+ if !self.ctx.pci_ready() { -+ log::warn!( -+ "Deferring AML symbol scan until PCI registration is ready" -+ ); -+ return Err(Error::new(EAGAIN)); -+ } -+ if let Ok(aml_symbols) = self.ctx.aml_symbols() { -+ HandleKind::Symbols(aml_symbols) -+ } else { -+ return Err(Error::new(EIO)); -+ } - } -- } - -- ["symbols", symbol] => { -- if let Some(description) = self.ctx.aml_lookup(symbol) { -- HandleKind::Symbol { -- name: (*symbol).to_owned(), -- description, -+ ["symbols", symbol] => { -+ if !self.ctx.pci_ready() { -+ log::warn!( -+ "Deferring AML symbol lookup for {symbol} until PCI registration is ready" -+ ); -+ return Err(Error::new(EAGAIN)); -+ } -+ if let Some(description) = self.ctx.aml_lookup(symbol) { -+ HandleKind::Symbol { -+ name: (*symbol).to_owned(), -+ description, -+ } -+ } else { -+ return Err(Error::new(ENOENT)); - } -- } else { -- return Err(Error::new(ENOENT)); - } -- } - -- _ => return Err(Error::new(ENOENT)), -+ _ => return Err(Error::new(ENOENT)), -+ } -+ } -+ } -+ HandleKind::DmiDir => { -+ if path.is_empty() { -+ HandleKind::DmiDir -+ } else { -+ HandleKind::Dmi( -+ dmi_contents(self.ctx.dmi_info(), path).ok_or(Error::new(ENOENT))?, -+ ) - } - } -+ HandleKind::ResourcesDir => self.resources_handle(path)?, - HandleKind::Symbols(ref aml_symbols) => { - if let Some(description) = aml_symbols.lookup(path) { - HandleKind::Symbol { -@@ -235,6 +658,23 @@ impl SchemeSync for AcpiScheme<'_, '_> { - return Err(Error::new(ENOENT)); - } - } -+ HandleKind::PowerDir => self.power_handle(path)?, -+ HandleKind::PowerAdaptersDir => self.power_adapter_handle(path)?, -+ HandleKind::PowerAdapterDir(ref adapter_id) => { -+ if path.is_empty() { -+ HandleKind::PowerAdapterDir(adapter_id.clone()) -+ } else { -+ self.power_adapter_handle(&format!("{adapter_id}/{path}"))? -+ } -+ } -+ HandleKind::PowerBatteriesDir => self.power_battery_handle(path)?, -+ HandleKind::PowerBatteryDir(ref battery_id) => { -+ if path.is_empty() { -+ HandleKind::PowerBatteryDir(battery_id.clone()) -+ } else { -+ self.power_battery_handle(&format!("{battery_id}/{path}"))? -+ } -+ } - _ => return Err(Error::new(EACCES)), - }; - -@@ -296,7 +736,7 @@ impl SchemeSync for AcpiScheme<'_, '_> { - ) -> Result { - let offset: usize = offset.try_into().map_err(|_| Error::new(EINVAL))?; - -- let handle = self.handles.get_mut(id)?; -+ let handle = self.handles.get(id)?; - - if handle.stat { - return Err(Error::new(EBADF)); -@@ -309,6 +749,9 @@ impl SchemeSync for AcpiScheme<'_, '_> { - .ok_or(Error::new(EBADFD))? - .as_slice(), - HandleKind::Symbol { description, .. } => description.as_bytes(), -+ HandleKind::Resources(contents) => contents.as_bytes(), -+ HandleKind::Dmi(contents) => contents.as_bytes(), -+ HandleKind::PowerFile(contents) => contents.as_bytes(), - _ => return Err(Error::new(EINVAL)), - }; - -@@ -328,13 +771,95 @@ impl SchemeSync for AcpiScheme<'_, '_> { - mut buf: DirentBuf<&'buf mut [u8]>, - opaque_offset: u64, - ) -> Result> { -- let handle = self.handles.get_mut(id)?; -+ let handle = self.handles.get(id)?; - - match &handle.kind { - HandleKind::TopLevel => { -- const TOPLEVEL_ENTRIES: &[&str] = &["tables", "symbols"]; -+ for (idx, (name, kind)) in top_level_entries(self.power_available()) -+ .iter() -+ .enumerate() -+ .skip(opaque_offset as usize) -+ { -+ buf.entry(DirEntry { -+ inode: 0, -+ next_opaque_id: idx as u64 + 1, -+ name, -+ kind: *kind, -+ })?; -+ } -+ } -+ HandleKind::DmiDir => { -+ for (idx, name) in DMI_DIRECTORY_ENTRIES -+ .iter() -+ .enumerate() -+ .skip(opaque_offset as usize) -+ { -+ buf.entry(DirEntry { -+ inode: 0, -+ next_opaque_id: idx as u64 + 1, -+ name, -+ kind: DirentKind::Regular, -+ })?; -+ } -+ } -+ HandleKind::ResourcesDir => { -+ let aml_symbols = self.ctx.aml_symbols().map_err(|_| Error::new(EIO))?; -+ let entries = -+ resource_dir_entries(aml_symbols.symbols_cache().keys().map(String::as_str)); -+ for (idx, name) in entries.iter().enumerate().skip(opaque_offset as usize) { -+ buf.entry(DirEntry { -+ inode: 0, -+ next_opaque_id: idx as u64 + 1, -+ name, -+ kind: DirentKind::Regular, -+ })?; -+ } -+ } -+ HandleKind::PowerDir => { -+ const POWER_ROOT_ENTRIES: &[(&str, DirentKind)] = &[ -+ ("on_battery", DirentKind::Regular), -+ ("adapters", DirentKind::Directory), -+ ("batteries", DirentKind::Directory), -+ ]; -+ -+ for (idx, (name, kind)) in POWER_ROOT_ENTRIES -+ .iter() -+ .enumerate() -+ .skip(opaque_offset as usize) -+ { -+ buf.entry(DirEntry { -+ inode: 0, -+ next_opaque_id: idx as u64 + 1, -+ name, -+ kind: *kind, -+ })?; -+ } -+ } -+ HandleKind::PowerAdaptersDir => { -+ let snapshot = self.power_snapshot()?; -+ for (idx, adapter) in snapshot -+ .adapters -+ .iter() -+ .enumerate() -+ .skip(opaque_offset as usize) -+ { -+ buf.entry(DirEntry { -+ inode: 0, -+ next_opaque_id: idx as u64 + 1, -+ name: adapter.id.as_str(), -+ kind: DirentKind::Directory, -+ })?; -+ } -+ } -+ HandleKind::PowerAdapterDir(adapter_id) => { -+ let snapshot = self.power_snapshot()?; -+ let _adapter = snapshot -+ .adapters -+ .iter() -+ .find(|adapter| adapter.id == *adapter_id) -+ .ok_or(Error::new(EIO))?; - -- for (idx, name) in TOPLEVEL_ENTRIES -+ for (idx, name) in power_adapter_entry_names() - .iter() - .enumerate() - .skip(opaque_offset as usize) -@@ -343,10 +868,44 @@ impl SchemeSync for AcpiScheme<'_, '_> { - inode: 0, - next_opaque_id: idx as u64 + 1, - name, -+ kind: DirentKind::Regular, -+ })?; -+ } -+ } -+ HandleKind::PowerBatteriesDir => { -+ let snapshot = self.power_snapshot()?; -+ for (idx, battery) in snapshot -+ .batteries -+ .iter() -+ .enumerate() -+ .skip(opaque_offset as usize) -+ { -+ buf.entry(DirEntry { -+ inode: 0, -+ next_opaque_id: idx as u64 + 1, -+ name: battery.id.as_str(), - kind: DirentKind::Directory, - })?; - } - } -+ HandleKind::PowerBatteryDir(battery_id) => { -+ let snapshot = self.power_snapshot()?; -+ let battery = snapshot -+ .batteries -+ .iter() -+ .find(|battery| battery.id == *battery_id) -+ .ok_or(Error::new(EIO))?; -+ let entry_names = power_battery_entry_names(battery); -+ -+ for (idx, name) in entry_names.iter().enumerate().skip(opaque_offset as usize) { -+ buf.entry(DirEntry { -+ inode: 0, -+ next_opaque_id: idx as u64 + 1, -+ name, -+ kind: DirentKind::Regular, -+ })?; -+ } -+ } - HandleKind::Symbols(aml_symbols) => { - for (idx, (symbol_name, _value)) in aml_symbols - .symbols_cache() -@@ -444,6 +1003,38 @@ impl SchemeSync for AcpiScheme<'_, '_> { - Ok(result_len) - } - -+ fn write( -+ &mut self, -+ id: usize, -+ buf: &[u8], -+ _offset: u64, -+ _flags: u32, -+ _ctx: &CallerCtx, -+ ) -> Result { -+ let handle = self.handles.get_mut(id)?; -+ -+ if handle.stat { -+ return Err(Error::new(EBADF)); -+ } -+ if !handle.allowed_to_eval { -+ return Err(Error::new(EPERM)); -+ } -+ -+ match handle.kind { -+ HandleKind::Reboot => { -+ if buf.is_empty() { -+ return Err(Error::new(EINVAL)); -+ } -+ self.ctx.acpi_reboot().map_err(|error| { -+ log::error!("ACPI reboot failed: {error}"); -+ Error::new(EIO) -+ })?; -+ Ok(buf.len()) -+ } -+ _ => Err(Error::new(EBADF)), -+ } -+ } -+ - fn on_sendfd(&mut self, sendfd_request: &SendFdRequest) -> Result { - let id = sendfd_request.id(); - let num_fds = sendfd_request.num_fds(); -@@ -470,10 +1061,8 @@ impl SchemeSync for AcpiScheme<'_, '_> { - } - let new_fd = libredox::Fd::new(new_fd); - -- if self.pci_fd.is_some() { -+ if self.ctx.register_pci_fd(new_fd).is_err() { - return Err(Error::new(EINVAL)); -- } else { -- self.pci_fd = Some(new_fd); - } - - Ok(num_fds) -@@ -483,3 +1072,90 @@ impl SchemeSync for AcpiScheme<'_, '_> { - self.handles.remove(id); - } - } -+ -+#[cfg(test)] -+mod tests { -+ use super::{dmi_contents, resource_dir_entries, resource_symbol_path, top_level_entries}; -+ use crate::acpi::DmiInfo; -+ use syscall::dirent::DirentKind; -+ -+ #[test] -+ fn dmi_contents_exposes_individual_fields_and_match_all() { -+ let dmi_info = DmiInfo { -+ sys_vendor: Some("Framework".to_string()), -+ board_name: Some("FRANMECP01".to_string()), -+ product_name: Some("Laptop 16".to_string()), -+ ..DmiInfo::default() -+ }; -+ -+ assert_eq!( -+ dmi_contents(Some(&dmi_info), "sys_vendor").as_deref(), -+ Some("Framework") -+ ); -+ assert_eq!( -+ dmi_contents(Some(&dmi_info), "board_name").as_deref(), -+ Some("FRANMECP01") -+ ); -+ assert_eq!( -+ dmi_contents(Some(&dmi_info), "product_name").as_deref(), -+ Some("Laptop 16") -+ ); -+ assert_eq!( -+ dmi_contents(Some(&dmi_info), "match_all").as_deref(), -+ Some("sys_vendor=Framework\nboard_name=FRANMECP01\nproduct_name=Laptop 16") -+ ); -+ assert_eq!(dmi_contents(None, "bios_version").as_deref(), Some("")); -+ assert_eq!(dmi_contents(Some(&dmi_info), "unknown"), None); -+ } -+ -+ #[test] -+ fn top_level_entries_always_include_reboot() { -+ let entries = top_level_entries(false); -+ assert!(entries -+ .iter() -+ .any(|(name, kind)| { *name == "reboot" && matches!(kind, DirentKind::Regular) })); -+ } -+ -+ #[test] -+ fn top_level_entries_only_include_power_when_available() { -+ let hidden = top_level_entries(false); -+ let visible = top_level_entries(true); -+ -+ assert!(!hidden.iter().any(|(name, _)| *name == "power")); -+ assert!(visible -+ .iter() -+ .any(|(name, kind)| { *name == "power" && matches!(kind, DirentKind::Directory) })); -+ } -+ -+ #[test] -+ fn top_level_entries_always_include_resources() { -+ let entries = top_level_entries(false); -+ assert!(entries -+ .iter() -+ .any(|(name, kind)| { *name == "resources" && matches!(kind, DirentKind::Directory) })); -+ } -+ -+ #[test] -+ fn resource_symbol_path_accepts_dotted_and_slash_paths() { -+ assert_eq!( -+ resource_symbol_path("_SB.PCI0.I2C0.TPD0").as_deref(), -+ Some("_SB.PCI0.I2C0.TPD0._CRS") -+ ); -+ assert_eq!( -+ resource_symbol_path("\\_SB/PCI0/I2C0/TPD0").as_deref(), -+ Some("_SB.PCI0.I2C0.TPD0._CRS") -+ ); -+ } -+ -+ #[test] -+ fn resource_dir_entries_list_devices_with_crs_suffix() { -+ assert_eq!( -+ resource_dir_entries([ -+ "_SB.PCI0.I2C0.TPD0._CRS", -+ "_SB.PCI0.I2C1.TPD1._HID", -+ "_SB.PCI0.I2C0.TPD0._CRS", -+ ]), -+ vec!["_SB.PCI0.I2C0.TPD0".to_string()] -+ ); -+ } -+} -diff --git a/drivers/audio/ac97d/src/main.rs b/drivers/audio/ac97d/src/main.rs -index ffa8a94b..e4dbf930 100644 ---- a/drivers/audio/ac97d/src/main.rs -+++ b/drivers/audio/ac97d/src/main.rs -@@ -3,6 +3,7 @@ use std::os::unix::io::AsRawFd; - use std::usize; - - use event::{user_data, EventQueue}; -+use log::error; - use pcid_interface::PciFunctionHandle; - use redox_scheme::scheme::register_sync_scheme; - use redox_scheme::Socket; -@@ -22,13 +23,28 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { - let mut name = pci_config.func.name(); - name.push_str("_ac97"); - -- let bar0 = pci_config.func.bars[0].expect_port(); -- let bar1 = pci_config.func.bars[1].expect_port(); -+ let bar0 = match pci_config.func.bars[0].try_port() { -+ Ok(port) => port, -+ Err(err) => { -+ error!("ac97d: invalid BAR0: {err}"); -+ std::process::exit(1); -+ } -+ }; -+ let bar1 = match pci_config.func.bars[1].try_port() { -+ Ok(port) => port, -+ Err(err) => { -+ error!("ac97d: invalid BAR1: {err}"); -+ std::process::exit(1); -+ } -+ }; - - let irq = pci_config - .func - .legacy_interrupt_line -- .expect("ac97d: no legacy interrupts supported"); -+ .unwrap_or_else(|| { -+ error!("ac97d: no legacy interrupts supported"); -+ std::process::exit(1); -+ }); - - println!(" + ac97 {}", pci_config.func.display()); - -@@ -40,13 +56,35 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { - common::file_level(), - ); - -- common::acquire_port_io_rights().expect("ac97d: failed to set I/O privilege level to Ring 3"); -+ if let Err(err) = common::acquire_port_io_rights() { -+ error!("ac97d: failed to set I/O privilege level to Ring 3: {err}"); -+ std::process::exit(1); -+ } - -- let mut irq_file = irq.irq_handle("ac97d"); -+ let mut irq_file = match irq.try_irq_handle("ac97d") { -+ Ok(file) => file, -+ Err(err) => { -+ error!("ac97d: failed to open IRQ handle: {err}"); -+ std::process::exit(1); -+ } -+ }; - -- let socket = Socket::nonblock().expect("ac97d: failed to create socket"); -- let mut device = -- unsafe { device::Ac97::new(bar0, bar1).expect("ac97d: failed to allocate device") }; -+ let socket = match Socket::nonblock() { -+ Ok(socket) => socket, -+ Err(err) => { -+ error!("ac97d: failed to create socket: {err}"); -+ std::process::exit(1); -+ } -+ }; -+ let mut device = unsafe { -+ match device::Ac97::new(bar0, bar1) { -+ Ok(device) => device, -+ Err(err) => { -+ error!("ac97d: failed to allocate device: {err}"); -+ std::process::exit(1); -+ } -+ } -+ }; - let mut readiness_based = ReadinessBased::new(&socket, 16); - - user_data! { -@@ -56,49 +94,81 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { - } - } - -- let event_queue = EventQueue::::new().expect("ac97d: Could not create event queue."); -+ let event_queue = match EventQueue::::new() { -+ Ok(queue) => queue, -+ Err(err) => { -+ error!("ac97d: could not create event queue: {err}"); -+ std::process::exit(1); -+ } -+ }; - event_queue - .subscribe( - irq_file.as_raw_fd() as usize, - Source::Irq, - event::EventFlags::READ, - ) -- .unwrap(); -+ .unwrap_or_else(|err| { -+ error!("ac97d: failed to subscribe IRQ fd: {err}"); -+ std::process::exit(1); -+ }); - event_queue - .subscribe( - socket.inner().raw(), - Source::Scheme, - event::EventFlags::READ, - ) -- .unwrap(); -- -- register_sync_scheme(&socket, "audiohw", &mut device) -- .expect("ac97d: failed to register audiohw scheme to namespace"); -+ .unwrap_or_else(|err| { -+ error!("ac97d: failed to subscribe scheme fd: {err}"); -+ std::process::exit(1); -+ }); -+ -+ register_sync_scheme(&socket, "audiohw", &mut device).unwrap_or_else(|err| { -+ error!("ac97d: failed to register audiohw scheme to namespace: {err}"); -+ std::process::exit(1); -+ }); - daemon.ready(); - -- libredox::call::setrens(0, 0).expect("ac97d: failed to enter null namespace"); -+ if let Err(err) = libredox::call::setrens(0, 0) { -+ error!("ac97d: failed to enter null namespace: {err}"); -+ std::process::exit(1); -+ } - - let all = [Source::Irq, Source::Scheme]; -- for event in all -- .into_iter() -- .chain(event_queue.map(|e| e.expect("ac97d: failed to get next event").user_data)) -- { -+ for event in all.into_iter().chain(event_queue.map(|e| match e { -+ Ok(event) => event.user_data, -+ Err(err) => { -+ error!("ac97d: failed to get next event: {err}"); -+ std::process::exit(1); -+ } -+ })) { - match event { - Source::Irq => { - let mut irq = [0; 8]; -- irq_file.read(&mut irq).unwrap(); -+ if let Err(err) = irq_file.read(&mut irq) { -+ error!("ac97d: failed to read IRQ file: {err}"); -+ std::process::exit(1); -+ } - - if !device.irq() { - continue; - } -- irq_file.write(&mut irq).unwrap(); -+ if let Err(err) = irq_file.write(&mut irq) { -+ error!("ac97d: failed to acknowledge IRQ: {err}"); -+ std::process::exit(1); -+ } - - readiness_based - .poll_all_requests(&mut device) -- .expect("ac97d: failed to poll requests"); -+ .unwrap_or_else(|err| { -+ error!("ac97d: failed to poll requests: {err}"); -+ std::process::exit(1); -+ }); - readiness_based - .write_responses() -- .expect("ac97d: failed to write to socket"); -+ .unwrap_or_else(|err| { -+ error!("ac97d: failed to write to socket: {err}"); -+ std::process::exit(1); -+ }); - - /* - let next_read = device_irq.next_read(); -@@ -110,10 +180,16 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { - Source::Scheme => { - readiness_based - .read_and_process_requests(&mut device) -- .expect("ac97d: failed to read from socket"); -+ .unwrap_or_else(|err| { -+ error!("ac97d: failed to read from socket: {err}"); -+ std::process::exit(1); -+ }); - readiness_based - .write_responses() -- .expect("ac97d: failed to write to socket"); -+ .unwrap_or_else(|err| { -+ error!("ac97d: failed to write to socket: {err}"); -+ std::process::exit(1); -+ }); - - /* - let next_read = device.borrow().next_read(); -@@ -125,7 +201,7 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { - } - } - -- std::process::exit(0); -+ std::process::exit(1); - } - - #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] -diff --git a/drivers/audio/ihdad/src/hda/device.rs b/drivers/audio/ihdad/src/hda/device.rs -index 78e8f0a2..e5742f80 100755 ---- a/drivers/audio/ihdad/src/hda/device.rs -+++ b/drivers/audio/ihdad/src/hda/device.rs -@@ -1,6 +1,6 @@ - #![allow(dead_code)] - --use std::collections::HashMap; -+use std::collections::{HashMap, HashSet}; - use std::fmt::Write; - use std::str; - use std::task::Poll; -@@ -14,20 +14,55 @@ use redox_scheme::scheme::SchemeSync; - use redox_scheme::CallerCtx; - use redox_scheme::OpenResult; - use scheme_utils::{FpathWriter, HandleMap}; --use syscall::error::{Error, Result, EACCES, EBADF, EIO, ENODEV, EWOULDBLOCK}; -+use syscall::error::{Error, Result, EACCES, EBADF, EIO, ENODEV, ENOENT, EWOULDBLOCK}; - - use spin::Mutex; - use syscall::schemev2::NewFdFlags; - - use super::common::*; -+use super::parser::AutoPinConfig; - use super::BitsPerSample; - use super::BufferDescriptorListEntry; - use super::CommandBuffer; -+use super::digital::DigitalCodecInfo; -+use super::dispatch::RouteDecision; -+use super::FixupEngine; - use super::HDANode; -+use super::InputStream; - use super::OutputStream; - use super::StreamBuffer; - use super::StreamDescriptorRegs; - -+#[derive(Debug, Clone)] -+pub struct ControllerPolicy { -+ pub prefer_msi: bool, -+ pub single_cmd_fallback: bool, -+ pub probe_mask: u16, -+ pub position_fix: PositionFixPolicy, -+ pub poll_jack: bool, -+} -+ -+#[derive(Debug, Clone, Copy, PartialEq, Eq)] -+pub enum PositionFixPolicy { -+ Auto, -+ Lpib, -+ Posbuf, -+ Dpic, -+ None, -+} -+ -+impl Default for ControllerPolicy { -+ fn default() -> Self { -+ ControllerPolicy { -+ prefer_msi: true, -+ single_cmd_fallback: false, -+ probe_mask: 0xFFFF, -+ position_fix: PositionFixPolicy::Auto, -+ poll_jack: false, -+ } -+ } -+} -+ - // GCTL - Global Control - const CRST: u32 = 1 << 0; // 1 bit - const FNCTRL: u32 = 1 << 1; // 1 bit -@@ -55,6 +90,20 @@ const RIRBDMAEN: u8 = 1 << 1; // 1 bit - const ICB: u16 = 1 << 0; - const IRV: u16 = 1 << 1; - -+// INTCTL bits -+const INTCTL_GIE: u32 = 1 << 31; // Global Interrupt Enable -+const INTCTL_CIE: u32 = 1 << 30; // Controller Interrupt Enable -+ -+// RIRBSTS bits (write-1-to-clear) -+const RIRBSTS_RINTFL: u8 = 1 << 0; // Response Interrupt Flag -+const RIRBSTS_RIRBOIS: u8 = 1 << 2; // RIRB Overrun Interrupt Status -+ -+// CORBSTS bits (write-1-to-clear) -+const CORBSTS_CMEI: u8 = 1 << 0; // CORB Memory Error Interrupt -+ -+// STATESTS mask — one bit per codec slot (bits 0-14) -+const STATESTS_MASK: u16 = 0x7FFF; -+ - // CORB and RIRB offset - - const COMMAND_BUFFER_OFFSET: usize = 0x40; -@@ -63,9 +112,8 @@ const NUM_SUB_BUFFS: usize = 32; - const SUB_BUFF_SIZE: usize = 2048; - - enum Handle { -- Todo, -- Pcmout(usize, usize, usize), // Card, index, block_ptr -- Pcmin(usize, usize, usize), // Card, index, block_ptr -+ Pcmout { stream_index: usize }, -+ Pcmin { stream_index: usize }, - StrBuf(Vec), - SchemeRoot, - } -@@ -121,6 +169,34 @@ struct Regs { - dpubase: Mmio, // 0x74 - } - -+struct CodecTopology { -+ codec_addr: CodecAddr, -+ afgs: Vec, -+ widget_map: HashMap, -+ outputs: Vec, -+ inputs: Vec, -+ output_pins: Vec, -+ input_pins: Vec, -+ beep_addr: Option, -+ pin_config: AutoPinConfig, -+} -+ -+impl CodecTopology { -+ fn new(codec_addr: CodecAddr) -> Self { -+ CodecTopology { -+ codec_addr, -+ afgs: Vec::new(), -+ widget_map: HashMap::new(), -+ outputs: Vec::new(), -+ inputs: Vec::new(), -+ output_pins: Vec::new(), -+ input_pins: Vec::new(), -+ beep_addr: None, -+ pin_config: AutoPinConfig::default(), -+ } -+ } -+} -+ - pub struct IntelHDA { - vend_prod: u32, - -@@ -131,6 +207,7 @@ pub struct IntelHDA { - cmd: CommandBuffer, - - codecs: Vec, -+ codecs_topology: HashMap, - - outputs: Vec, - inputs: Vec, -@@ -140,15 +217,20 @@ pub struct IntelHDA { - output_pins: Vec, - input_pins: Vec, - -- beep_addr: WidgetAddr, -+ beep_addr: Option, - - buff_desc: Dma<[BufferDescriptorListEntry; 256]>, -+ input_buff_desc: Dma<[BufferDescriptorListEntry; 256]>, - - output_streams: Vec, -+ input_streams: Vec, - - buffs: Vec>, - - int_counter: usize, -+ policy: ControllerPolicy, -+ fixup_engine: FixupEngine, -+ digital_codecs: Vec, - handles: Mutex>, - } - -@@ -160,6 +242,10 @@ impl IntelHDA { - .expect("Could not allocate physical memory for buffer descriptor list.") - .assume_init(); - -+ let input_buff_desc = Dma::<[BufferDescriptorListEntry; 256]>::zeroed() -+ .expect("Could not allocate physical memory for input buffer descriptor list.") -+ .assume_init(); -+ - log::debug!( - "Virt: {:016X}, Phys: {:016X}", - buff_desc.as_ptr() as usize, -@@ -182,11 +268,12 @@ impl IntelHDA { - - cmd: CommandBuffer::new(base + COMMAND_BUFFER_OFFSET, cmd_buff), - -- beep_addr: (0, 0), -+ beep_addr: None, - - widget_map: HashMap::::new(), - - codecs: Vec::::new(), -+ codecs_topology: HashMap::::new(), - - outputs: Vec::::new(), - inputs: Vec::::new(), -@@ -195,21 +282,34 @@ impl IntelHDA { - input_pins: Vec::::new(), - - buff_desc, -+ input_buff_desc, - - output_streams: Vec::::new(), -+ input_streams: Vec::::new(), - - buffs: Vec::>::new(), - - int_counter: 0, -+ policy: ControllerPolicy::default(), -+ fixup_engine: FixupEngine::new(), -+ digital_codecs: Vec::new(), - handles: Mutex::new(HandleMap::new()), - }; - - module.init()?; - -+ let vendor_id = ((module.vend_prod >> 16) & 0xFFFF) as u16; -+ let device_id = (module.vend_prod & 0xFFFF) as u16; -+ let route = RouteDecision::decide(vendor_id, device_id, 0x04, 0x03); -+ log::info!("IHDA: audio route decision: {:?} ({})", route.route, route.reason); -+ - module.info(); - module.enumerate()?; - - module.configure()?; -+ if let Err(err) = module.configure_input() { -+ log::debug!("IHDA: input configuration skipped: {:?}", err); -+ } - log::debug!("IHDA: Initialization finished."); - Ok(module) - } -@@ -219,23 +319,28 @@ impl IntelHDA { - - let use_immediate_command_interface = match self.vend_prod { - 0x8086_2668 => false, -- _ => true, -+ _ => !self.policy.single_cmd_fallback, - }; - - self.cmd.init(use_immediate_command_interface)?; -+ -+ self.regs.gctl.writef(UNSOL, true); -+ - self.init_interrupts(); - - Ok(()) - } - - pub fn init_interrupts(&mut self) { -- // TODO: provide a function to enable certain interrupts -- // This just enables the first output stream interupt and the global interrupt -- - let iss = self.num_input_streams(); -- self.regs -- .intctl -- .write((1 << 31) | /* (1 << 30) |*/ (1 << iss)); -+ -+ let mut wakeen: u16 = 0; -+ for &codec in &self.codecs { -+ wakeen |= 1 << codec; -+ } -+ self.regs.wakeen.write(wakeen); -+ -+ self.regs.intctl.write(INTCTL_GIE | INTCTL_CIE | (1 << iss) | (1 << 0)); - } - - pub fn irq(&mut self) -> bool { -@@ -248,6 +353,19 @@ impl IntelHDA { - self.int_counter - } - -+ pub fn policy(&self) -> &ControllerPolicy { -+ &self.policy -+ } -+ -+ pub fn set_policy(&mut self, policy: ControllerPolicy) { -+ log::info!( -+ "IHDA: policy updated msi={} single_cmd={} probe_mask={:04X} pos_fix={:?} poll_jack={}", -+ policy.prefer_msi, policy.single_cmd_fallback, policy.probe_mask, -+ policy.position_fix, policy.poll_jack -+ ); -+ self.policy = policy; -+ } -+ - pub fn read_node(&mut self, addr: WidgetAddr) -> Result { - let mut node = HDANode::new(); - let mut temp: u64; -@@ -341,70 +459,156 @@ impl IntelHDA { - } - - pub fn enumerate(&mut self) -> Result<()> { -+ // Clear old global state (kept for migration safety) - self.output_pins.clear(); - self.input_pins.clear(); -+ self.outputs.clear(); -+ self.inputs.clear(); -+ self.widget_map.clear(); -+ self.codecs_topology.clear(); -+ -+ let codec_addrs = self.codecs.clone(); -+ for codec in codec_addrs { -+ let mut topo = CodecTopology::new(codec); -+ -+ let root = self.read_node((codec, 0))?; -+ log::debug!("{}", root); -+ -+ let root_count = root.subnode_count; -+ let root_start = root.subnode_start; -+ -+ for i in 0..root_count { -+ let afg = self.read_node((codec, root_start + i))?; -+ log::debug!("{}", afg); -+ -+ // Only process audio function groups (type 0x01) -+ if afg.function_group_type != 0x01 { -+ log::debug!( -+ "Codec {}: function group {} is type {}, not audio \u{2014} skipping", -+ codec, -+ afg.addr.1, -+ afg.function_group_type -+ ); -+ continue; -+ } -+ -+ topo.afgs.push(afg.addr.1); - -- let codec: u8 = 0; -- -- let root = self.read_node((codec, 0))?; -- -- log::debug!("{}", root); -- -- let root_count = root.subnode_count; -- let root_start = root.subnode_start; -- -- //FIXME: So basically the way this is set up is to only support one codec and hopes the first one is an audio -- for i in 0..root_count { -- let afg = self.read_node((codec, root_start + i))?; -- log::debug!("{}", afg); -- let afg_count = afg.subnode_count; -- let afg_start = afg.subnode_start; -- -- for j in 0..afg_count { -- let mut widget = self.read_node((codec, afg_start + j))?; -- widget.is_widget = true; -- match widget.widget_type() { -- HDAWidgetType::AudioOutput => self.outputs.push(widget.addr), -- HDAWidgetType::AudioInput => self.inputs.push(widget.addr), -- HDAWidgetType::BeepGenerator => self.beep_addr = widget.addr, -- HDAWidgetType::PinComplex => { -- let config = widget.configuration_default(); -- if config.is_output() { -- self.output_pins.push(widget.addr); -- } else if config.is_input() { -- self.input_pins.push(widget.addr); -+ let afg_count = afg.subnode_count; -+ let afg_start = afg.subnode_start; -+ -+ for j in 0..afg_count { -+ let mut widget = self.read_node((codec, afg_start + j))?; -+ widget.is_widget = true; -+ match widget.widget_type() { -+ HDAWidgetType::AudioOutput => { -+ self.outputs.push(widget.addr); -+ topo.outputs.push(widget.addr); -+ } -+ HDAWidgetType::AudioInput => { -+ self.inputs.push(widget.addr); -+ topo.inputs.push(widget.addr); - } -+ HDAWidgetType::BeepGenerator => { -+ self.beep_addr = Some(widget.addr); -+ topo.beep_addr = Some(widget.addr); -+ } -+ HDAWidgetType::PinComplex => { -+ let config = widget.configuration_default(); -+ if config.is_output() { -+ self.output_pins.push(widget.addr); -+ topo.output_pins.push(widget.addr); -+ } else if config.is_input() { -+ self.input_pins.push(widget.addr); -+ topo.input_pins.push(widget.addr); -+ } -+ } -+ _ => {} - } -- _ => {} -+ -+ log::debug!("{}", widget); -+ self.widget_map.insert(widget.addr(), widget.clone()); -+ topo.widget_map.insert(widget.addr(), widget); - } -+ } - -- log::debug!("{}", widget); -- self.widget_map.insert(widget.addr(), widget); -+ log::debug!( -+ "Codec {}: {} AFGs, {} outputs, {} inputs, {} output pins, {} input pins", -+ codec, -+ topo.afgs.len(), -+ topo.outputs.len(), -+ topo.inputs.len(), -+ topo.output_pins.len(), -+ topo.input_pins.len(), -+ ); -+ -+ let widget_list: Vec<(WidgetAddr, HDANode)> = topo.widget_map.iter().map(|(a, n)| (*a, n.clone())).collect(); -+ topo.pin_config = AutoPinConfig::parse(&widget_list); -+ -+ self.codecs_topology.insert(codec, topo); -+ } -+ -+ for (codec, topo) in &self.codecs_topology { -+ if let Some(digi) = DigitalCodecInfo::detect_from_topology(*codec, &topo.widget_map) { -+ log::info!( -+ "IHDA: digital codec detected at {} (hdmi={}, {} pins, {} converters)", -+ codec, digi.is_hdmi, digi.pin_widgets.len(), digi.converter_widgets.len() -+ ); -+ self.digital_codecs.push(digi); - } - } - - Ok(()) - } - -- pub fn find_best_output_pin(&mut self) -> Result { -- let outs = &self.output_pins; -+ fn pick_primary_codec_for_output(&self) -> Option { -+ let mut candidates: Vec = self -+ .codecs_topology -+ .values() -+ .filter(|topo| !topo.output_pins.is_empty() && !topo.outputs.is_empty()) -+ .map(|topo| topo.codec_addr) -+ .collect(); -+ candidates.sort(); -+ candidates.into_iter().next() -+ } -+ -+ pub fn find_best_output_pin(&mut self, codec: CodecAddr) -> Result { -+ let outs: Vec = self -+ .codecs_topology -+ .get(&codec) -+ .ok_or_else(|| { -+ log::error!("No topology for codec {}", codec); -+ Error::new(ENODEV) -+ })? -+ .output_pins -+ .clone(); -+ - if outs.len() == 1 { - return Ok(outs[0]); - } else if outs.len() > 1 { -- //TODO: change output based on "unsolicited response" interrupts -- // Check for devices in this order: Headphone, Speaker, Line Out - for supported_device in &[DefaultDevice::HPOut, DefaultDevice::Speaker] { -- for &out in outs { -- let widget = self.widget_map.get(&out).unwrap(); -- let cd = widget.configuration_default(); -+ for &out in &outs { -+ let (addr, config_default) = { -+ let widget = self -+ .codecs_topology -+ .get(&codec) -+ .and_then(|t| t.widget_map.get(&out)) -+ .ok_or_else(|| { -+ log::error!( -+ "Widget {:?} not found in codec {} topology", -+ out, -+ codec -+ ); -+ Error::new(ENODEV) -+ })?; -+ (widget.addr, widget.config_default) -+ }; -+ let cd = ConfigurationDefault::from_u32(config_default); - if cd.sequence() == 0 && &cd.default_device() == supported_device { -- // Check for jack detect bit -- let pin_caps = self.cmd.cmd12(widget.addr, 0xF00, 0x0C)?; -+ let pin_caps = self.cmd.cmd12(addr, 0xF00, 0x0C)?; - if pin_caps & (1 << 2) != 0 { -- // Check for presence -- let pin_sense = self.cmd.cmd12(widget.addr, 0xF09, 0)?; -+ let pin_sense = self.cmd.cmd12(addr, 0xF09, 0)?; - if pin_sense & (1 << 31) == 0 { -- // Skip if nothing is plugged in - continue; - } - } -@@ -416,13 +620,26 @@ impl IntelHDA { - Err(Error::new(ENODEV)) - } - -- pub fn find_path_to_dac(&self, addr: WidgetAddr) -> Option> { -- let widget = self.widget_map.get(&addr).unwrap(); -+ pub fn find_path_to_dac( -+ &self, -+ addr: WidgetAddr, -+ codec: CodecAddr, -+ visited: &mut HashSet, -+ ) -> Option> { -+ if visited.contains(&addr) { -+ log::warn!("Cycle detected in widget graph at {:?}", addr); -+ return None; -+ } -+ visited.insert(addr); -+ -+ let topo = self.codecs_topology.get(&codec)?; -+ let widget = topo.widget_map.get(&addr)?; -+ - if widget.widget_type() == HDAWidgetType::AudioOutput { - Some(vec![addr]) - } else { - let connection = widget.connections.get(widget.connection_default as usize)?; -- let mut path = self.find_path_to_dac(*connection)?; -+ let mut path = self.find_path_to_dac(*connection, codec, visited)?; - path.insert(0, addr); - Some(path) - } -@@ -466,72 +683,92 @@ impl IntelHDA { - } - - pub fn configure(&mut self) -> Result<()> { -- let outpin = self.find_best_output_pin()?; -+ let codec = self.pick_primary_codec_for_output().ok_or_else(|| { -+ log::error!("No suitable codec found for audio output"); -+ Error::new(ENODEV) -+ })?; -+ -+ log::debug!("Selected codec {} for output", codec); -+ -+ let topo = self.codecs_topology.get(&codec).ok_or_else(|| { -+ log::error!("No topology for codec {}", codec); -+ Error::new(ENODEV) -+ })?; -+ -+ let vendor_id = ((self.vend_prod >> 16) & 0xFFFF) as u16; -+ let device_id = (self.vend_prod & 0xFFFF) as u16; -+ self.fixup_engine.match_fixups(vendor_id, device_id, None, &topo.pin_config); -+ -+ let primary_pins = topo.pin_config.primary_output_pins(); -+ let outpin = primary_pins.first().map(|p| p.addr).ok_or_else(|| { -+ log::error!("No primary output pins found by parser on codec {}", codec); -+ Error::new(ENODEV) -+ })?; - - log::debug!("Best pin: {:01X}:{:02X}", outpin.0, outpin.1); - -- let path = self.find_path_to_dac(outpin).unwrap(); -+ let path = { -+ let mut visited = HashSet::new(); -+ self.find_path_to_dac(outpin, codec, &mut visited) -+ .ok_or_else(|| { -+ log::error!( -+ "No path to DAC from pin {:01X}:{:02X} on codec {}", -+ outpin.0, -+ outpin.1, -+ codec -+ ); -+ Error::new(ENODEV) -+ })? -+ }; - -- let dac = *path.last().unwrap(); -- let pin = *path.first().unwrap(); -+ let dac = *path.last().ok_or_else(|| { -+ log::error!("Empty DAC path for pin {:01X}:{:02X}", outpin.0, outpin.1); -+ Error::new(ENODEV) -+ })?; -+ let pin = *path.first().ok_or_else(|| { -+ log::error!("Empty path (no pin) for pin {:01X}:{:02X}", outpin.0, outpin.1); -+ Error::new(ENODEV) -+ })?; - - log::debug!("Path to DAC: {:X?}", path); - -- // Set power state 0 (on) for all widgets in path - for &addr in &path { - self.set_power_state(addr, 0)?; - } - -- // Pin enable (0x80 = headphone amp enable, 0x40 = output enable) - self.cmd.cmd12(pin, 0x707, 0xC0)?; -- -- // EAPD enable - self.cmd.cmd12(pin, 0x70C, 2)?; -- -- // Set DAC stream and channel -+ self.cmd.cmd4(pin, 0x708, (1 << 8) | 1)?; - self.set_stream_channel(dac, 1, 0)?; - - self.update_sound_buffers(); - -- log::debug!( -- "Supported Formats: {:08X}", -- self.get_supported_formats((0, 0x1))? -- ); -- log::debug!("Capabilities: {:08X}", self.get_capabilities(path[0])?); -+ let (rate, bps, channels) = self.negotiate_stream_format(dac)?; -+ log::debug!("IHDA: negotiated stream format bps={:?} ch={}", bps, channels); - -- // Create output stream - let output = self.get_output_stream_descriptor(0).unwrap(); - output.set_address(self.buff_desc.physical()); -- output.set_pcm_format(&super::SR_44_1, BitsPerSample::Bits16, 2); -- output.set_cyclic_buffer_length((NUM_SUB_BUFFS * SUB_BUFF_SIZE) as u32); // number of bytes -+ output.set_pcm_format(rate, bps, channels); -+ output.set_cyclic_buffer_length((NUM_SUB_BUFFS * SUB_BUFF_SIZE) as u32); - output.set_stream_number(1); - output.set_last_valid_index((NUM_SUB_BUFFS - 1) as u16); - output.set_interrupt_on_completion(true); - -- // Set DAC converter format -- self.set_converter_format(dac, &super::SR_44_1, BitsPerSample::Bits16, 2)?; -+ self.set_converter_format(dac, rate, bps, channels)?; - -- // Get DAC converter format -- //TODO: should validate? - self.cmd.cmd12(dac, 0xA00, 0)?; - -- // Unmute and set gain to 0db for input and output amplifiers on all widgets in path - for &addr in &path { -- // Read widget capabilities - let caps = self.cmd.cmd12(addr, 0xF00, 0x09)?; - -- //TODO: do we need to set any other indexes? - let left = true; - let right = true; - let index = 0; - let mute = false; - -- // Check for input amp - if (caps & (1 << 1)) != 0 { -- // Read input capabilities - let in_caps = self.cmd.cmd12(addr, 0xF00, 0x0D)?; - let in_gain = (in_caps & 0x7f) as u8; -- // Set input gain - let output = false; - let input = true; - self.set_amplifier_gain_mute( -@@ -540,12 +777,9 @@ impl IntelHDA { - log::debug!("Set {:X?} input gain to 0x{:X}", addr, in_gain); - } - -- // Check for output amp - if (caps & (1 << 2)) != 0 { -- // Read output capabilities - let out_caps = self.cmd.cmd12(addr, 0xF00, 0x12)?; - let out_gain = (out_caps & 0x7f) as u8; -- // Set output gain - let output = true; - let input = false; - self.set_amplifier_gain_mute( -@@ -555,8 +789,6 @@ impl IntelHDA { - } - } - -- //TODO: implement hda-verb? -- - output.run(); - { - log::debug!("Waiting for output 0 to start running..."); -@@ -632,20 +864,21 @@ impl IntelHDA { - - */ - -- pub fn dump_codec(&self, codec: u8) -> String { -+ pub fn dump_all_codecs(&self) -> String { - let mut string = String::new(); - -- for (_, widget) in self.widget_map.iter() { -- let _ = writeln!(string, "{}", widget); -+ for (&codec, topo) in &self.codecs_topology { -+ let _ = writeln!(string, "Codec {}:", codec); -+ for (_, widget) in topo.widget_map.iter() { -+ let _ = writeln!(string, " {}", widget); -+ } - } - - string - } - -- // BEEP!! - pub fn beep(&mut self, div: u8) { -- let addr = self.beep_addr; -- if addr != (0, 0) { -+ if let Some(addr) = self.beep_addr { - let _ = self.cmd.cmd12(addr, 0xF0A, div); - } - } -@@ -700,7 +933,7 @@ impl IntelHDA { - log::debug!("Statests: {:04X}", statests); - - for i in 0..15 { -- if (statests >> i) & 0x1 == 1 { -+ if (statests >> i) & 0x1 == 1 && (self.policy.probe_mask >> i) & 0x1 == 1 { - self.codecs.push(i as CodecAddr); - } - } -@@ -812,6 +1045,54 @@ impl IntelHDA { - Ok(self.cmd.cmd12(addr, 0xF00, 0x0A)? as u32) - } - -+ fn negotiate_stream_format( -+ &mut self, -+ dac: WidgetAddr, -+ ) -> Result<(&'static super::SampleRate, BitsPerSample, u8)> { -+ let fmt = self.get_supported_formats(dac)?; -+ log::debug!("IHDA: DAC {:01X}:{:02X} supported formats: {:08X}", dac.0, dac.1, fmt); -+ -+ let rate = if fmt & (1 << 14) != 0 { -+ &super::SR_48 -+ } else if fmt & (1 << 13) != 0 { -+ &super::SR_44_1 -+ } else if fmt & (1 << 12) != 0 { -+ &super::SR_32 -+ } else if fmt & (1 << 11) != 0 { -+ &super::SR_22_05 -+ } else if fmt & (1 << 10) != 0 { -+ &super::SR_16 -+ } else if fmt & (1 << 9) != 0 { -+ &super::SR_11_025 -+ } else if fmt & (1 << 8) != 0 { -+ &super::SR_8 -+ } else { -+ log::error!("IHDA: no supported sample rate found in format {:08X}", fmt); -+ return Err(Error::new(ENODEV)); -+ }; -+ -+ let bps = if fmt & (1 << 21) != 0 { -+ BitsPerSample::Bits16 -+ } else if fmt & (1 << 23) != 0 { -+ BitsPerSample::Bits24 -+ } else if fmt & (1 << 24) != 0 { -+ BitsPerSample::Bits32 -+ } else if fmt & (1 << 22) != 0 { -+ BitsPerSample::Bits20 -+ } else if fmt & (1 << 20) != 0 { -+ BitsPerSample::Bits8 -+ } else { -+ log::error!("IHDA: no supported bit depth found in format {:08X}", fmt); -+ return Err(Error::new(ENODEV)); -+ }; -+ -+ let caps = self.get_capabilities(dac)?; -+ let max_channels = ((caps >> 0) & 0xFF) as u8 + 1; -+ let channels = if max_channels >= 2 { 2 } else { max_channels }; -+ -+ Ok((rate, bps, channels)) -+ } -+ - fn get_capabilities(&mut self, addr: WidgetAddr) -> Result { - Ok(self.cmd.cmd12(addr, 0xF00, 0x09)? as u32) - } -@@ -873,13 +1154,98 @@ impl IntelHDA { - //log::trace!("Status: {:02X} Pos: {:08X} Output CTL: {:06X}", output.status(), output.link_position(), output.control()); - - if os.current_block() == (open_block + 3) % NUM_SUB_BUFFS { -- // Block if we already are 3 buffers ahead - Poll::Pending - } else { - Poll::Ready(os.write_block(buf)) - } - } - -+ pub fn configure_input(&mut self) -> Result<()> { -+ let primary_codec = match self.pick_primary_codec_for_output() { -+ Some(c) => c, -+ None => return Err(Error::new(ENODEV)), -+ }; -+ -+ let topo = self.codecs_topology.get(&primary_codec).ok_or_else(|| { -+ log::error!("IHDA: no topology for codec {}", primary_codec); -+ Error::new(ENODEV) -+ })?; -+ -+ let input_pin = topo.input_pins.first().cloned().ok_or_else(|| { -+ log::debug!("IHDA: no input pins found on codec {}", primary_codec); -+ Error::new(ENODEV) -+ })?; -+ -+ let adc = topo.inputs.first().cloned().ok_or_else(|| { -+ log::debug!("IHDA: no ADC widgets found on codec {}", primary_codec); -+ Error::new(ENODEV) -+ })?; -+ -+ log::debug!( -+ "IHDA: configuring input: pin={:01X}:{:02X} adc={:01X}:{:02X}", -+ input_pin.0, input_pin.1, adc.0, adc.1 -+ ); -+ -+ self.cmd.cmd12(input_pin, 0x707, 0xC0)?; -+ self.set_power_state(input_pin, 0)?; -+ self.set_power_state(adc, 0)?; -+ self.set_stream_channel(adc, 2, 0)?; -+ -+ let iss = self.num_input_streams(); -+ if iss == 0 { -+ log::warn!("IHDA: no input streams available"); -+ return Err(Error::new(ENODEV)); -+ } -+ -+ let input_regs = self.get_input_stream_descriptor(0).ok_or_else(|| { -+ log::error!("IHDA: failed to get input stream descriptor 0"); -+ Error::new(ENODEV) -+ })?; -+ -+ let mut input_stream = InputStream::new(NUM_SUB_BUFFS, SUB_BUFF_SIZE, input_regs); -+ -+ for i in 0..NUM_SUB_BUFFS { -+ self.input_buff_desc[i].set_address(input_stream.phys() as u64 + (i * SUB_BUFF_SIZE) as u64); -+ self.input_buff_desc[i].set_length(SUB_BUFF_SIZE as u32); -+ self.input_buff_desc[i].set_interrupt_on_complete(true); -+ } -+ -+ let (rate, bps, channels) = self.negotiate_stream_format(adc)?; -+ -+ input_stream.regs().set_address(self.input_buff_desc.physical()); -+ input_stream.regs().set_pcm_format(rate, bps, channels); -+ input_stream.regs().set_cyclic_buffer_length((NUM_SUB_BUFFS * SUB_BUFF_SIZE) as u32); -+ input_stream.regs().set_stream_number(2); -+ input_stream.regs().set_last_valid_index((NUM_SUB_BUFFS - 1) as u16); -+ input_stream.regs().set_interrupt_on_completion(true); -+ -+ self.set_converter_format(adc, rate, bps, channels)?; -+ -+ self.input_streams.push(input_stream); -+ -+ let input_ref = self.input_streams.last_mut().unwrap(); -+ input_ref.regs().run(); -+ -+ log::debug!("IHDA: input stream 0 configured and running"); -+ Ok(()) -+ } -+ -+ pub fn read_from_input(&mut self, index: usize, buf: &mut [u8]) -> Poll> { -+ let input_stream = match self.input_streams.get_mut(index) { -+ Some(s) => s, -+ None => return Poll::Ready(Err(Error::new(EBADF))), -+ }; -+ -+ let pos = input_stream.regs().link_position() as usize; -+ let hw_block = pos / input_stream.block_size(); -+ -+ if input_stream.current_block() == hw_block { -+ Poll::Pending -+ } else { -+ Poll::Ready(input_stream.read_block(buf)) -+ } -+ } -+ - pub fn handle_interrupts(&mut self) -> bool { - let intsts = self.regs.intsts.read(); - if ((intsts >> 31) & 1) == 1 { -@@ -897,7 +1263,56 @@ impl IntelHDA { - intsts != 0 - } - -- pub fn handle_controller_interrupt(&mut self) {} -+ pub fn handle_controller_interrupt(&mut self) { -+ let statests = self.regs.statests.read(); -+ if statests & STATESTS_MASK != 0 { -+ for i in 0..15 { -+ if (statests >> i) & 1 != 0 { -+ log::info!("IHDA: state change on codec {}", i); -+ } -+ } -+ self.regs.statests.write(statests); -+ } -+ -+ let rirbsts = self.regs.rirbsts.read(); -+ if rirbsts & (RIRBSTS_RINTFL | RIRBSTS_RIRBOIS) != 0 { -+ let rirbwp = self.regs.rirbwp.read(); -+ let wp = rirbwp & 0xFF; -+ if wp != 0 { -+ log::debug!("IHDA: RIRB response available, wp={}", wp); -+ } -+ if rirbsts & RIRBSTS_RIRBOIS != 0 { -+ log::warn!("IHDA: RIRB overrun, clearing"); -+ } -+ self.regs.rirbsts.write(rirbsts & (RIRBSTS_RINTFL | RIRBSTS_RIRBOIS)); -+ } -+ -+ let corbsts = self.regs.corbsts.read(); -+ if corbsts & CORBSTS_CMEI != 0 { -+ log::error!("IHDA: CORB memory error, clearing"); -+ self.regs.corbsts.write(CORBSTS_CMEI); -+ } -+ } -+ -+ fn handle_unsolicited_response(&mut self, codec_addr: CodecAddr, response: u32) { -+ let tag = (response >> 26) & 0xF; -+ let payload = response & 0x03FFFFFF; -+ -+ log::info!( -+ "IHDA: unsolicited response codec {} tag={} payload={:06X}", -+ codec_addr, tag, payload -+ ); -+ -+ if tag == 1 { -+ let pin_widget = payload & 0x7F; -+ let plugged = (payload >> 31) & 1; -+ log::info!( -+ "IHDA: jack sense codec {} pin {} {}", -+ codec_addr, pin_widget, -+ if plugged != 0 { "plugged" } else { "unplugged" } -+ ); -+ } -+ } - - pub fn handle_stream_interrupts(&mut self, sis: u32) { - let iss = self.num_input_streams(); -@@ -1017,9 +1432,10 @@ impl SchemeSync for IntelHDA { - return Err(Error::new(EACCES)); - } - let handle = match path.trim_matches('/') { -- //TODO: allow multiple codecs -- "codec" => Handle::StrBuf(self.dump_codec(0).into_bytes()), -- _ => Handle::Todo, -+ "codec" => Handle::StrBuf(self.dump_all_codecs().into_bytes()), -+ "" | "pcmout" | "pcmout0" => Handle::Pcmout { stream_index: 0 }, -+ "pcmin" | "pcmin0" => Handle::Pcmin { stream_index: 0 }, -+ _ => return Err(Error::new(ENOENT)), - }; - let id = self.handles.lock().insert(handle); - -@@ -1038,18 +1454,44 @@ impl SchemeSync for IntelHDA { - _flags: u32, - _ctx: &CallerCtx, - ) -> Result { -- let handles = self.handles.lock(); -- let Handle::StrBuf(strbuf) = handles.get(id)? else { -- return Err(Error::new(EBADF)); -+ let (is_strbuf, is_pcmin) = { -+ let handles = self.handles.lock(); -+ match handles.get(id)? { -+ Handle::StrBuf(_) => (true, false), -+ Handle::Pcmin { .. } => (false, true), -+ _ => return Err(Error::new(EBADF)), -+ } - }; - -- let src = usize::try_from(offset) -- .ok() -- .and_then(|o| strbuf.get(o..)) -- .unwrap_or(&[]); -- let len = src.len().min(buf.len()); -- buf[..len].copy_from_slice(&src[..len]); -- Ok(len) -+ if is_strbuf { -+ let handles = self.handles.lock(); -+ let Handle::StrBuf(strbuf) = handles.get(id)? else { -+ return Err(Error::new(EBADF)); -+ }; -+ let src = usize::try_from(offset) -+ .ok() -+ .and_then(|o| strbuf.get(o..)) -+ .unwrap_or(&[]); -+ let len = src.len().min(buf.len()); -+ buf[..len].copy_from_slice(&src[..len]); -+ return Ok(len); -+ } -+ -+ if is_pcmin { -+ let index = { -+ let handles = self.handles.lock(); -+ match handles.get(id)? { -+ Handle::Pcmin { stream_index, .. } => *stream_index, -+ _ => return Err(Error::new(EBADF)), -+ } -+ }; -+ return match self.read_from_input(index, buf) { -+ Poll::Ready(r) => r, -+ Poll::Pending => Err(Error::new(EWOULDBLOCK)), -+ }; -+ } -+ -+ Err(Error::new(EBADF)) - } - - fn write( -@@ -1061,23 +1503,29 @@ impl SchemeSync for IntelHDA { - _ctx: &CallerCtx, - ) -> Result { - let index = { -- let mut handles = self.handles.lock(); -- match handles.get_mut(id)? { -- Handle::Todo => 0, -+ let handles = self.handles.lock(); -+ match handles.get(id)? { -+ Handle::Pcmout { stream_index, .. } => *stream_index as u8, - _ => return Err(Error::new(EBADF)), - } - }; - -- //log::debug!("Int count: {}", self.int_counter); -- - match self.write_to_output(index, buf) { - Poll::Ready(r) => r, - Poll::Pending => Err(Error::new(EWOULDBLOCK)), - } - } - -- fn fpath(&mut self, _id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> Result { -- FpathWriter::with(buf, "audiohw", |_| Ok(())) -+ fn fpath(&mut self, id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> Result { -+ let handles = self.handles.lock(); -+ let handle = handles.get(id)?; -+ let path = match handle { -+ Handle::Pcmout { .. } => "audiohw:pcmout0", -+ Handle::Pcmin { .. } => "audiohw:pcmin0", -+ Handle::StrBuf(_) => "audiohw:codec", -+ Handle::SchemeRoot => "audiohw:", -+ }; -+ FpathWriter::with(buf, path, |_| Ok(())) - } - - fn on_close(&mut self, id: usize) { -diff --git a/drivers/audio/ihdad/src/hda/mod.rs b/drivers/audio/ihdad/src/hda/mod.rs -index 7f01daf8..82ba89ae 100644 ---- a/drivers/audio/ihdad/src/hda/mod.rs -+++ b/drivers/audio/ihdad/src/hda/mod.rs -@@ -2,7 +2,11 @@ - pub mod cmdbuff; - pub mod common; - pub mod device; -+pub mod digital; -+pub mod dispatch; -+pub mod fixup; - pub mod node; -+pub mod parser; - pub mod stream; - - pub use self::node::*; -@@ -10,6 +14,8 @@ pub use self::stream::*; - - pub use self::cmdbuff::*; - pub use self::device::IntelHDA; -+pub use self::fixup::FixupEngine; -+pub use self::parser::AutoPinConfig; - pub use self::stream::BitsPerSample; - pub use self::stream::BufferDescriptorListEntry; - pub use self::stream::StreamBuffer; -diff --git a/drivers/audio/ihdad/src/hda/node.rs b/drivers/audio/ihdad/src/hda/node.rs -index 06c5121f..c1f9c31f 100644 ---- a/drivers/audio/ihdad/src/hda/node.rs -+++ b/drivers/audio/ihdad/src/hda/node.rs -@@ -1,7 +1,7 @@ - use super::common::*; - use std::{fmt, mem}; - --#[derive(Clone)] -+#[derive(Clone, Debug)] - pub struct HDANode { - pub addr: WidgetAddr, - -diff --git a/drivers/audio/ihdad/src/hda/stream.rs b/drivers/audio/ihdad/src/hda/stream.rs -index caa3c364..a3f5ed73 100644 ---- a/drivers/audio/ihdad/src/hda/stream.rs -+++ b/drivers/audio/ihdad/src/hda/stream.rs -@@ -14,9 +14,9 @@ pub enum BaseRate { - } - - pub struct SampleRate { -- base: BaseRate, -- mult: u16, -- div: u16, -+ pub base: BaseRate, -+ pub mult: u16, -+ pub div: u16, - } - - use self::BaseRate::{BR44_1, BR48}; -@@ -78,6 +78,7 @@ pub const SR_192: SampleRate = SampleRate { - div: 1, - }; - -+#[derive(Debug, Clone, Copy)] - #[repr(u8)] - pub enum BitsPerSample { - Bits8 = 0, -@@ -271,6 +272,52 @@ impl OutputStream { - } - } - -+pub struct InputStream { -+ buff: StreamBuffer, -+ desc_regs: &'static mut StreamDescriptorRegs, -+} -+ -+impl InputStream { -+ pub fn new( -+ block_count: usize, -+ block_length: usize, -+ regs: &'static mut StreamDescriptorRegs, -+ ) -> InputStream { -+ InputStream { -+ buff: StreamBuffer::new(block_length, block_count).unwrap(), -+ desc_regs: regs, -+ } -+ } -+ -+ pub fn read_block(&mut self, buf: &mut [u8]) -> Result { -+ self.buff.read_block(buf) -+ } -+ -+ pub fn block_size(&self) -> usize { -+ self.buff.block_size() -+ } -+ -+ pub fn block_count(&self) -> usize { -+ self.buff.block_count() -+ } -+ -+ pub fn current_block(&self) -> usize { -+ self.buff.current_block() -+ } -+ -+ pub fn addr(&self) -> usize { -+ self.buff.addr() -+ } -+ -+ pub fn phys(&self) -> usize { -+ self.buff.phys() -+ } -+ -+ pub fn regs(&mut self) -> &mut StreamDescriptorRegs { -+ self.desc_regs -+ } -+} -+ - #[repr(C, packed)] - pub struct BufferDescriptorListEntry { - addr_low: Mmio, -@@ -379,6 +426,20 @@ impl StreamBuffer { - - Ok(len) - } -+ -+ pub fn read_block(&mut self, buf: &mut [u8]) -> Result { -+ let len = min(self.block_size(), buf.len()); -+ unsafe { -+ copy_nonoverlapping( -+ (self.addr() + self.current_block() * self.block_size()) as *const u8, -+ buf.as_mut_ptr(), -+ len, -+ ); -+ } -+ self.cur_pos += 1; -+ self.cur_pos %= self.block_count(); -+ Ok(len) -+ } - } - impl Drop for StreamBuffer { - fn drop(&mut self) { -diff --git a/drivers/audio/ihdad/src/main.rs b/drivers/audio/ihdad/src/main.rs -index 31a2add7..11d80133 100755 ---- a/drivers/audio/ihdad/src/main.rs -+++ b/drivers/audio/ihdad/src/main.rs -@@ -6,7 +6,7 @@ use std::os::unix::io::AsRawFd; - use std::usize; - - use event::{user_data, EventQueue}; --use pcid_interface::irq_helpers::pci_allocate_interrupt_vector; -+use pcid_interface::irq_helpers::try_pci_allocate_interrupt_vector; - use pcid_interface::PciFunctionHandle; - - pub mod hda; -@@ -38,9 +38,19 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - - log::info!("IHDA {}", pci_config.func.display()); - -+ if let Err(err) = pci_config.func.bars[0].try_mem() { -+ log::error!("ihdad: invalid BAR0: {err}"); -+ std::process::exit(1); -+ } - let address = unsafe { pcid_handle.map_bar(0) }.ptr.as_ptr() as usize; - -- let irq_file = pci_allocate_interrupt_vector(&mut pcid_handle, "ihdad"); -+ let irq_file = match try_pci_allocate_interrupt_vector(&mut pcid_handle, "ihdad") { -+ Ok(irq) => irq, -+ Err(err) => { -+ log::error!("ihdad: failed to allocate interrupt vector: {err}"); -+ std::process::exit(1); -+ } -+ }; - - { - let vend_prod: u32 = ((pci_config.func.full_device_id.vendor_id as u32) << 16) -@@ -53,11 +63,28 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - } - } - -- let event_queue = -- EventQueue::::new().expect("ihdad: Could not create event queue."); -- let socket = Socket::nonblock().expect("ihdad: failed to create socket"); -+ let event_queue = match EventQueue::::new() { -+ Ok(queue) => queue, -+ Err(err) => { -+ log::error!("ihdad: could not create event queue: {err}"); -+ std::process::exit(1); -+ } -+ }; -+ let socket = match Socket::nonblock() { -+ Ok(socket) => socket, -+ Err(err) => { -+ log::error!("ihdad: failed to create socket: {err}"); -+ std::process::exit(1); -+ } -+ }; - let mut device = unsafe { -- hda::IntelHDA::new(address, vend_prod).expect("ihdad: failed to allocate device") -+ match hda::IntelHDA::new(address, vend_prod) { -+ Ok(device) => device, -+ Err(err) => { -+ log::error!("ihdad: failed to allocate device: {err}"); -+ std::process::exit(1); -+ } -+ } - }; - let mut readiness_based = ReadinessBased::new(&socket, 16); - -diff --git a/drivers/common/src/logger.rs b/drivers/common/src/logger.rs -index 82ec2bd0..a531edd9 100644 ---- a/drivers/common/src/logger.rs -+++ b/drivers/common/src/logger.rs -@@ -44,6 +44,7 @@ pub fn setup_logging( - Ok(b) => { - logger = logger.with_output(b.with_filter(file_level).flush_on_newline(true).build()) - } -+ Err(error) if error.raw_os_error() == Some(19) => {} - Err(error) => eprintln!("Failed to create {logfile_base}.log: {}", error), - } - -@@ -61,6 +62,7 @@ pub fn setup_logging( - .build(), - ) - } -+ Err(error) if error.raw_os_error() == Some(19) => {} - Err(error) => eprintln!("Failed to create {logfile_base}.ansi.log: {}", error), - } - -diff --git a/drivers/graphics/console-draw/src/lib.rs b/drivers/graphics/console-draw/src/lib.rs -index 5eb951df..0b959e5c 100644 ---- a/drivers/graphics/console-draw/src/lib.rs -+++ b/drivers/graphics/console-draw/src/lib.rs -@@ -59,19 +59,19 @@ pub struct V2DisplayMap { - - impl V2DisplayMap { - pub fn new(display_handle: V2GraphicsHandle) -> io::Result { -- let connector = display_handle.first_display().unwrap(); -- let connector_info = display_handle.get_connector(connector, true).unwrap(); -+ let connector = display_handle.first_display()?; -+ let connector_info = display_handle.get_connector(connector, true)?; - - let mode = connector_info.modes()[0]; - let (width, height) = mode.size(); - - // FIXME do something smarter that avoids conflicts -- let crtc = display_handle.resource_handles().unwrap().filter_crtcs( -- display_handle -- .get_encoder(connector_info.encoders()[0]) -- .unwrap() -- .possible_crtcs(), -- )[0]; -+ let crtc = { -+ let res_handles = display_handle.resource_handles()?; -+ let encoder = display_handle -+ .get_encoder(connector_info.encoders()[0])?; -+ res_handles.filter_crtcs(encoder.possible_crtcs())[0] -+ }; - - let buffer = CpuBackedBuffer::new( - &display_handle, -@@ -338,12 +338,12 @@ impl TextScreen { - line_changed(y); - } - -- let width = map.width.try_into().unwrap(); -+ let width: u32 = map.width.try_into().unwrap_or(0); - let damage = Damage { - x: 0, -- y: u32::try_from(min_changed).unwrap() * 16, -+ y: u32::try_from(min_changed).unwrap_or(0) * 16, - width, -- height: u32::try_from(max_changed.saturating_sub(min_changed) + 1).unwrap() * 16, -+ height: u32::try_from(max_changed.saturating_sub(min_changed) + 1).unwrap_or(0) * 16, - }; - - damage -@@ -445,7 +445,9 @@ impl TextBuffer { - } - - for &byte in buf { -- self.lines.back_mut().unwrap().push(byte); -+ if let Some(last) = self.lines.back_mut() { -+ last.push(byte); -+ } - - if byte == b'\n' { - self.lines.push_back(Vec::new()); -diff --git a/drivers/graphics/driver-graphics/Cargo.toml b/drivers/graphics/driver-graphics/Cargo.toml -index 31e02335..fc747cce 100644 ---- a/drivers/graphics/driver-graphics/Cargo.toml -+++ b/drivers/graphics/driver-graphics/Cargo.toml -@@ -9,6 +9,7 @@ drm-fourcc = "2.2.0" - drm-sys.workspace = true - edid.workspace = true #TODO: edid is abandoned, fork it and maintain? - log.workspace = true -+nom.workspace = true - redox-ioctl.workspace = true - redox-scheme.workspace = true - scheme-utils = { path = "../../../scheme-utils" } -diff --git a/drivers/graphics/driver-graphics/src/kms/connector.rs b/drivers/graphics/driver-graphics/src/kms/connector.rs -index c885f413..19037fec 100644 ---- a/drivers/graphics/driver-graphics/src/kms/connector.rs -+++ b/drivers/graphics/driver-graphics/src/kms/connector.rs -@@ -21,7 +21,14 @@ impl KmsObjects { - ) -> KmsObjectId { - let mut possible_crtcs = 0; - for &crtc in crtcs { -- possible_crtcs = 1 << self.get_crtc(crtc).unwrap().lock().unwrap().crtc_index; -+ if let Ok(crtc_guard) = self.get_crtc(crtc) { -+ match crtc_guard.lock() { -+ Ok(locked) => possible_crtcs = 1 << locked.crtc_index, -+ Err(e) => log::error!("add_connector: crtc lock poisoned: {e}"), -+ } -+ } else { -+ log::error!("add_connector: failed to get crtc {}", crtc.0); -+ } - } - - let encoder_id = self.add(KmsEncoder { -@@ -61,7 +68,7 @@ impl KmsObjects { - pub fn connectors(&self) -> impl Iterator>> + use<'_, T> { - self.connectors - .iter() -- .map(|&id| self.get_connector(id).unwrap()) -+ .filter_map(|&id| self.get_connector(id).ok()) - } - - pub fn get_connector(&self, id: KmsObjectId) -> Result<&Mutex>> { -@@ -136,10 +143,16 @@ impl KmsConnector { - } - - pub fn update_from_edid(&mut self, edid: &[u8]) { -- let edid = edid::parse(edid).unwrap().1; -+ let edid_data = match edid::parse(edid) { -+ nom::IResult::Done(_, data) => data, -+ _ => { -+ log::error!("failed to parse EDID: parse returned error or incomplete"); -+ return; -+ } -+ }; - - if let Some(first_detailed_timing) = -- edid.descriptors -+ edid_data.descriptors - .iter() - .find_map(|descriptor| match descriptor { - edid::Descriptor::DetailedTiming(detailed_timing) => Some(detailed_timing), -@@ -152,7 +165,7 @@ impl KmsConnector { - log::error!("No edid timing descriptor detected"); - } - -- self.modes = edid -+ self.modes = edid_data - .descriptors - .iter() - .filter_map(|descriptor| { -diff --git a/drivers/graphics/driver-graphics/src/kms/objects.rs b/drivers/graphics/driver-graphics/src/kms/objects.rs -index 1daf3221..55c60167 100644 ---- a/drivers/graphics/driver-graphics/src/kms/objects.rs -+++ b/drivers/graphics/driver-graphics/src/kms/objects.rs -@@ -95,7 +95,7 @@ impl KmsObjects { - pub fn crtcs(&self) -> impl Iterator>> + use<'_, T> { - self.crtcs - .iter() -- .map(|&id| self.get::>>(id).unwrap()) -+ .filter_map(|&id| self.get::>>(id).ok()) - } - - pub fn get_crtc(&self, id: KmsObjectId) -> Result<&Mutex>> { -@@ -115,7 +115,12 @@ impl KmsObjects { - let KmsObject::Framebuffer(_) = object else { - return Err(Error::new(EINVAL)); - }; -- self.objects.remove(&id).unwrap(); -+ self.objects -+ .remove(&id) -+ .ok_or_else(|| { -+ log::error!("remove_framebuffer: object {} vanished during removal", id.0); -+ Error::new(EINVAL) -+ })?; - - Ok(()) - } -diff --git a/drivers/graphics/driver-graphics/src/kms/properties.rs b/drivers/graphics/driver-graphics/src/kms/properties.rs -index e22527a7..c75df3b0 100644 ---- a/drivers/graphics/driver-graphics/src/kms/properties.rs -+++ b/drivers/graphics/driver-graphics/src/kms/properties.rs -@@ -21,7 +21,11 @@ impl KmsObjects { - kind: KmsPropertyKind, - ) -> KmsObjectId { - match &kind { -- KmsPropertyKind::Range(start, end) => assert!(start < end), -+ KmsPropertyKind::Range(start, end) => { -+ if start >= end { -+ log::error!("Range property '{name}' has invalid range: start ({start}) >= end ({end})"); -+ } -+ } - KmsPropertyKind::Enum(_variants) => { - // FIXME check duplicate variant numbers - } -@@ -30,7 +34,11 @@ impl KmsObjects { - // FIXME check overlapping flag numbers - } - KmsPropertyKind::Object { type_: _ } => {} -- KmsPropertyKind::SignedRange(start, end) => assert!(start < end), -+ KmsPropertyKind::SignedRange(start, end) => { -+ if start >= end { -+ log::error!("SignedRange property '{name}' has invalid range: start ({start}) >= end ({end})"); -+ } -+ } - } - - let mut name_bytes = [0; DRM_PROP_NAME_LEN as usize]; -@@ -54,7 +62,13 @@ impl KmsObjects { - let object = self.objects.get(&id).ok_or(Error::new(EINVAL))?; - match object { - KmsObject::Crtc(crtc) => { -- let crtc = crtc.lock().unwrap(); -+ let crtc = match crtc.lock() { -+ Ok(g) => g, -+ Err(e) => { -+ log::error!("get_object_properties_data: crtc lock poisoned: {e}"); -+ return Err(Error::new(EINVAL)); -+ } -+ }; - let props = &crtc.properties; - Ok(( - props.iter().map(|prop| prop.id.0).collect::>(), -@@ -65,7 +79,13 @@ impl KmsObjects { - )) - } - KmsObject::Connector(connector) => { -- let connector = connector.lock().unwrap(); -+ let connector = match connector.lock() { -+ Ok(g) => g, -+ Err(e) => { -+ log::error!("get_object_properties_data: connector lock poisoned: {e}"); -+ return Err(Error::new(EINVAL)); -+ } -+ }; - let props = &connector.properties; - Ok(( - props.iter().map(|prop| prop.id.0).collect::>(), -@@ -97,7 +117,7 @@ pub struct KmsPropertyName(pub [c_char; DRM_PROP_NAME_LEN as usize]); - impl KmsPropertyName { - fn new(context: &str, name: &str) -> KmsPropertyName { - if name.len() > DRM_PROP_NAME_LEN as usize { -- panic!("{context} {name} is too long"); -+ log::error!("{context} {name} is too long, truncating"); - } - - let mut name_bytes = [0; DRM_PROP_NAME_LEN as usize]; -@@ -151,12 +171,16 @@ macro_rules! define_properties { - - pub(super) fn init_standard_props(objects: &mut KmsObjects) { - $( -- assert_eq!(objects.add_property( -+ let prop_id = objects.add_property( - define_properties!(@prop_name $prop $($prop_name)?), - define_properties!(@is_immutable $($prop_flag)?), - define_properties!(@is_atomic $($prop_flag)?), - define_properties!(@prop_kind $prop_type $({$($prop_content)*})?), -- ), $prop); -+ ); -+ if prop_id != $prop { -+ log::error!("property ID mismatch for {}: expected {:?}, got {:?}", -+ stringify!($prop), $prop, prop_id); -+ } - )* - } - }; -diff --git a/drivers/graphics/driver-graphics/src/lib.rs b/drivers/graphics/driver-graphics/src/lib.rs -index eab0be9c..4fe7ecb6 100644 ---- a/drivers/graphics/driver-graphics/src/lib.rs -+++ b/drivers/graphics/driver-graphics/src/lib.rs -@@ -136,13 +136,20 @@ pub struct GraphicsScheme { - - impl GraphicsScheme { - pub fn new(mut adapter: T, scheme_name: String, early: bool) -> Self { -- assert!(scheme_name.starts_with("display")); -- let socket = Socket::nonblock().expect("failed to create graphics scheme"); -+ if !scheme_name.starts_with("display") { -+ log::error!("graphics scheme name must start with 'display': {scheme_name}"); -+ std::process::exit(1); -+ } -+ let socket = match Socket::nonblock() { -+ Ok(s) => s, -+ Err(e) => { -+ log::error!("failed to create graphics scheme: {e}"); -+ std::process::exit(1); -+ } -+ }; - -- let disable_graphical_debug = Some( -- File::open("/scheme/debug/disable-graphical-debug") -- .expect("vesad: Failed to open /scheme/debug/disable-graphical-debug"), -- ); -+ let disable_graphical_debug = -+ File::open("/scheme/debug/disable-graphical-debug").ok(); - - let mut objects = KmsObjects::new(); - adapter.init(&mut objects); -@@ -161,14 +168,34 @@ impl GraphicsScheme { - vts: HashMap::new(), - }; - -- let cap_id = inner.scheme_root().expect("failed to get this scheme root"); -- register_scheme_inner(&inner.socket, &inner.scheme_name, cap_id) -- .expect("failed to register graphics scheme root"); -+ let cap_id = match inner.scheme_root() { -+ Ok(id) => id, -+ Err(e) => { -+ log::error!("failed to get this scheme root: {e}"); -+ std::process::exit(1); -+ } -+ }; -+ if let Err(e) = register_scheme_inner(&inner.socket, &inner.scheme_name, cap_id) { -+ log::error!("failed to register graphics scheme root: {e}"); -+ std::process::exit(1); -+ } - - let display_handle = if early { -- DisplayHandle::new_early(&inner.scheme_name).unwrap() -+ match DisplayHandle::new_early(&inner.scheme_name) { -+ Ok(h) => h, -+ Err(e) => { -+ log::error!("failed to create early display handle: {e}"); -+ std::process::exit(1); -+ } -+ } - } else { -- DisplayHandle::new(&inner.scheme_name).unwrap() -+ match DisplayHandle::new(&inner.scheme_name) { -+ Ok(h) => h, -+ Err(e) => { -+ log::error!("failed to create display handle: {e}"); -+ std::process::exit(1); -+ } -+ } - }; - - Self { -@@ -207,11 +234,15 @@ impl GraphicsScheme { - } - - pub fn handle_vt_events(&mut self) { -- while let Some(vt_event) = self -- .inputd_handle -- .read_vt_event() -- .expect("driver-graphics: failed to read display handle") -- { -+ loop { -+ let vt_event = match self.inputd_handle.read_vt_event() { -+ Ok(Some(event)) => event, -+ Ok(None) => break, -+ Err(e) => { -+ log::error!("driver-graphics: failed to read display handle: {e}"); -+ break; -+ } -+ }; - match vt_event.kind { - VtEventKind::Activate => self.inner.activate_vt(vt_event.vt), - } -@@ -235,16 +266,26 @@ impl GraphicsScheme { - std::process::exit(0); - } - Err(err) if err.errno == EAGAIN => break, -- Err(err) => panic!("driver-graphics: failed to read display scheme: {err}"), -+ Err(err) => { -+ log::error!("driver-graphics: failed to read display scheme: {err}"); -+ break; -+ } - }; - - match request.kind() { - RequestKind::Call(call) => { - let response = call.handle_sync(&mut self.inner, &mut self.state); -- self.inner -+ if let Err(e) = self -+ .inner - .socket - .write_response(response, SignalBehavior::Restart) -- .expect("driver-graphics: failed to write response"); -+ { -+ log::error!("driver-graphics: failed to write response: {e}"); -+ return Err(io::Error::new( -+ io::ErrorKind::Other, -+ format!("driver-graphics: failed to write response: {e}"), -+ )); -+ } - } - RequestKind::OnClose { id } => { - self.inner.on_close(id); -@@ -294,11 +335,28 @@ impl GraphicsSchemeInner { - vts.entry(vt).or_insert_with(|| VtState { - connector_state: objects - .connectors() -- .map(|connector| connector.lock().unwrap().state.clone()) -+ .map(|connector| { -+ connector -+ .lock() -+ .unwrap_or_else(|e| { -+ log::error!("get_or_create_vt: connector lock poisoned: {e}"); -+ e.into_inner() -+ }) -+ .state -+ .clone() -+ }) - .collect(), - crtc_state: objects - .crtcs() -- .map(|crtc| crtc.lock().unwrap().state.clone()) -+ .map(|crtc| { -+ crtc.lock() -+ .unwrap_or_else(|e| { -+ log::error!("get_or_create_vt: crtc lock poisoned: {e}"); -+ e.into_inner() -+ }) -+ .state -+ .clone() -+ }) - .collect(), - cursor_plane: CursorPlane { - x: 0, -@@ -327,47 +385,71 @@ impl GraphicsSchemeInner { - - for (connector_idx, connector_state) in vt_state.connector_state.iter().enumerate() { - let connector_id = self.objects.connector_ids()[connector_idx]; -- let mut connector = self -- .objects -- .get_connector(connector_id) -- .unwrap() -- .lock() -- .unwrap(); -+ let connector_guard = match self.objects.get_connector(connector_id) { -+ Ok(g) => g, -+ Err(e) => { -+ log::error!("activate_vt: failed to get connector {}: {e}", connector_id.0); -+ continue; -+ } -+ }; -+ let mut connector = match connector_guard.lock() { -+ Ok(g) => g, -+ Err(e) => { -+ log::error!("activate_vt: connector {} lock poisoned: {e}", connector_id.0); -+ e.into_inner() -+ } -+ }; - connector.state = connector_state.clone(); - } - - for (crtc_idx, crtc_state) in vt_state.crtc_state.iter().enumerate() { - let crtc_id = self.objects.crtc_ids()[crtc_idx]; -- let crtc = self.objects.get_crtc(crtc_id).unwrap(); -+ let crtc = match self.objects.get_crtc(crtc_id) { -+ Ok(c) => c, -+ Err(e) => { -+ log::error!("activate_vt: failed to get crtc {}: {e}", crtc_id.0); -+ continue; -+ } -+ }; - let connector_id = self.objects.connector_ids()[crtc_idx]; - -- let fb = crtc_state.fb_id.map(|fb_id| { -+ let fb = crtc_state.fb_id.and_then(|fb_id| { - self.objects - .get_framebuffer(fb_id) -- .expect("removed framebuffers should be unset") -+ .map_err(|e| { -+ log::error!("activate_vt: framebuffer {} missing: {e}", fb_id.0); -+ e -+ }) -+ .ok() - }); - -- self.adapter -- .set_crtc( -- &self.objects, -- crtc, -- crtc_state.clone(), -- Damage { -- x: 0, -- y: 0, -- width: fb.map_or(0, |fb| fb.width), -- height: fb.map_or(0, |fb| fb.height), -- }, -- ) -- .unwrap(); -- -- self.objects -- .get_connector(connector_id) -- .unwrap() -- .lock() -- .unwrap() -- .state -- .crtc_id = crtc_id; -+ if let Err(e) = self.adapter.set_crtc( -+ &self.objects, -+ crtc, -+ crtc_state.clone(), -+ Damage { -+ x: 0, -+ y: 0, -+ width: fb.as_ref().map_or(0, |fb| fb.width), -+ height: fb.as_ref().map_or(0, |fb| fb.height), -+ }, -+ ) { -+ log::error!("activate_vt: set_crtc failed for crtc {}: {e}", crtc_id.0); -+ continue; -+ } -+ -+ match self.objects.get_connector(connector_id) { -+ Ok(conn_guard) => match conn_guard.lock() { -+ Ok(mut conn) => conn.state.crtc_id = crtc_id, -+ Err(e) => { -+ log::error!("activate_vt: connector {} lock poisoned: {e}", connector_id.0); -+ e.into_inner().state.crtc_id = crtc_id; -+ } -+ }, -+ Err(e) => { -+ log::error!("activate_vt: failed to get connector {}: {e}", connector_id.0); -+ } -+ } - } - - if self.adapter.hw_cursor_size().is_some() { -@@ -430,7 +512,12 @@ impl SchemeSync for GraphicsSchemeInner { - vt, - next_id: _, - buffers: _, -- } => write!(w, "v2/{vt}").unwrap(), -+ } => { -+ if let Err(e) = write!(w, "v2/{vt}") { -+ log::error!("fpath: write failed: {e}"); -+ return Err(Error::new(EINVAL)); -+ } -+ } - Handle::SchemeRoot => return Err(Error::new(EOPNOTSUPP)), - }; - Ok(()) -@@ -531,7 +618,10 @@ impl SchemeSync for GraphicsSchemeInner { - .objects - .get_crtc(KmsObjectId(data.crtc_id()))? - .lock() -- .unwrap(); -+ .map_err(|e| { -+ log::error!("MODE_GET_CRTC: crtc lock poisoned: {e}"); -+ Error::new(EINVAL) -+ })?; - // Don't touch set_connectors, that is only used by MODE_SET_CRTC - data.set_fb_id(crtc.state.fb_id.unwrap_or(KmsObjectId::INVALID).0); - // FIXME fill x and y with the data from the primary plane -@@ -565,7 +655,10 @@ impl SchemeSync for GraphicsSchemeInner { - } else { - None - }; -- let mut new_state = crtc.lock().unwrap().state.clone(); -+ let mut new_state = crtc.lock().map_err(|e| { -+ log::error!("MODE_SET_CRTC: crtc lock poisoned: {e}"); -+ Error::new(EINVAL) -+ })?.state.clone(); - new_state.fb_id = fb_id; - new_state.mode = mode; - if *vt == self.active_vt { -@@ -582,20 +675,34 @@ impl SchemeSync for GraphicsSchemeInner { - )?; - - for connector in connector_ids { -- self.objects -- .get_connector(connector)? -- .lock() -- .unwrap() -- .state -- .crtc_id = KmsObjectId(data.crtc_id()); -+ let conn_guard = self.objects.get_connector(connector)?; -+ match conn_guard.lock() { -+ Ok(mut conn) => conn.state.crtc_id = KmsObjectId(data.crtc_id()), -+ Err(e) => { -+ log::error!("MODE_SET_CRTC: connector lock poisoned: {e}"); -+ e.into_inner().state.crtc_id = KmsObjectId(data.crtc_id()); -+ } -+ } - } - } -- self.vts.get_mut(vt).unwrap().crtc_state -- [crtc.lock().unwrap().crtc_index as usize] = new_state; -+ { -+ let crtc_index = crtc.lock().map_err(|e| { -+ log::error!("MODE_SET_CRTC: crtc lock poisoned: {e}"); -+ Error::new(EINVAL) -+ })?.crtc_index as usize; -+ let vt_state = self.vts.get_mut(vt).ok_or_else(|| { -+ log::error!("MODE_SET_CRTC: vt {} not found", vt); -+ Error::new(EINVAL) -+ })?; -+ vt_state.crtc_state[crtc_index] = new_state; -+ } - Ok(0) - }), - ipc::MODE_CURSOR => ipc::DrmModeCursor::with(payload, |data| { -- let vt_state = self.vts.get_mut(vt).unwrap(); -+ let vt_state = self.vts.get_mut(vt).ok_or_else(|| { -+ log::error!("MODE_CURSOR: vt {} not found", vt); -+ Error::new(EINVAL) -+ })?; - - let cursor_plane = &mut vt_state.cursor_plane; - -@@ -635,7 +742,10 @@ impl SchemeSync for GraphicsSchemeInner { - .objects - .get_connector(KmsObjectId(data.connector_id()))? - .lock() -- .unwrap(); -+ .map_err(|e| { -+ log::error!("MODE_GET_CONNECTOR: connector lock poisoned: {e}"); -+ Error::new(EINVAL) -+ })?; - data.set_encoders_ptr(&[connector.encoder_id.0]); - data.set_modes_ptr(&connector.modes); - data.set_connector_type(data.connector_type()); -@@ -772,20 +882,23 @@ impl SchemeSync for GraphicsSchemeInner { - if *vt != self.active_vt { - continue; - } -- let crtc = self.objects.crtcs().nth(crtc_idx).unwrap(); -- self.adapter -- .set_crtc( -- &self.objects, -- crtc, -- crtc_state.clone(), -- Damage { -- x: 0, -- y: 0, -- width: 0, -- height: 0, -- }, -- ) -- .unwrap(); -+ let Some(crtc) = self.objects.crtcs().nth(crtc_idx) else { -+ log::error!("MODE_RM_FB: crtc index {crtc_idx} out of bounds"); -+ continue; -+ }; -+ if let Err(e) = self.adapter.set_crtc( -+ &self.objects, -+ crtc, -+ crtc_state.clone(), -+ Damage { -+ x: 0, -+ y: 0, -+ width: 0, -+ height: 0, -+ }, -+ ) { -+ log::error!("MODE_RM_FB: set_crtc failed for crtc {crtc_idx}: {e}"); -+ } - } - } - -@@ -813,7 +926,10 @@ impl SchemeSync for GraphicsSchemeInner { - - if *vt == self.active_vt { - for crtc in self.objects.crtcs() { -- let state = crtc.lock().unwrap().state.clone(); -+ let state = crtc.lock().map_err(|e| { -+ log::error!("MODE_DIRTYFB: crtc lock poisoned: {e}"); -+ Error::new(EINVAL) -+ })?.state.clone(); - if state.fb_id == Some(KmsObjectId(data.fb_id())) { - self.adapter.set_crtc(&self.objects, crtc, state, damage)?; - } -@@ -850,7 +966,13 @@ impl SchemeSync for GraphicsSchemeInner { - } - - // FIXME use a better scheme for creating map offsets -- assert!(buffers[&buffer_id].size() < MAP_FAKE_OFFSET_MULTIPLIER); -+ let buf_size = buffers[&buffer_id].size(); -+ if buf_size >= MAP_FAKE_OFFSET_MULTIPLIER { -+ log::error!( -+ "MODE_MAP_DUMB: buffer size {buf_size} exceeds offset multiplier {MAP_FAKE_OFFSET_MULTIPLIER}" -+ ); -+ return Err(Error::new(EINVAL)); -+ } - - data.set_offset((buffer_id as usize * MAP_FAKE_OFFSET_MULTIPLIER) as u64); - -@@ -874,11 +996,14 @@ impl SchemeSync for GraphicsSchemeInner { - ipc::MODE_GET_PLANE => ipc::DrmModeGetPlane::with(payload, |mut data| { - let i = id_index(data.plane_id()); - let crtc_id = self.objects.crtc_ids()[i as usize]; -- let crtc = self.objects.get_crtc(crtc_id).unwrap(); -+ let crtc = self.objects.get_crtc(crtc_id)?; - data.set_crtc_id(crtc_id.0); -+ let crtc_locked = crtc.lock().map_err(|e| { -+ log::error!("MODE_GET_PLANE: crtc lock poisoned: {e}"); -+ Error::new(EINVAL) -+ })?; - data.set_fb_id( -- crtc.lock() -- .unwrap() -+ crtc_locked - .state - .fb_id - .unwrap_or(KmsObjectId::INVALID) -@@ -907,7 +1032,10 @@ impl SchemeSync for GraphicsSchemeInner { - }) - } - ipc::MODE_CURSOR2 => ipc::DrmModeCursor2::with(payload, |data| { -- let vt_state = self.vts.get_mut(vt).unwrap(); -+ let vt_state = self.vts.get_mut(vt).ok_or_else(|| { -+ log::error!("MODE_CURSOR2: vt {} not found", vt); -+ Error::new(EINVAL) -+ })?; - - let cursor_plane = &mut vt_state.cursor_plane; - -@@ -970,8 +1098,7 @@ impl SchemeSync for GraphicsSchemeInner { - } => ( - buffers - .get(&((offset as usize / MAP_FAKE_OFFSET_MULTIPLIER) as u32)) -- .ok_or(Error::new(EINVAL)) -- .unwrap(), -+ .ok_or(Error::new(EINVAL))?, - offset & (MAP_FAKE_OFFSET_MULTIPLIER as u64 - 1), - ), - Handle::SchemeRoot => return Err(Error::new(EOPNOTSUPP)), -diff --git a/drivers/graphics/fbbootlogd/src/main.rs b/drivers/graphics/fbbootlogd/src/main.rs -index 3e42d590..62749577 100644 ---- a/drivers/graphics/fbbootlogd/src/main.rs -+++ b/drivers/graphics/fbbootlogd/src/main.rs -@@ -24,7 +24,13 @@ fn main() { - daemon::SchemeDaemon::new(daemon); - } - fn daemon(daemon: daemon::SchemeDaemon) -> ! { -- let event_queue = EventQueue::new().expect("fbbootlogd: failed to create event queue"); -+ let event_queue = match EventQueue::new() { -+ Ok(eq) => eq, -+ Err(err) => { -+ eprintln!("fbbootlogd: failed to create event queue: {err}"); -+ std::process::exit(1); -+ } -+ }; - - event::user_data! { - enum Source { -@@ -33,78 +39,105 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! { - } - } - -- let socket = Socket::nonblock().expect("fbbootlogd: failed to create fbbootlog scheme"); -+ let socket = match Socket::nonblock() { -+ Ok(s) => s, -+ Err(err) => { -+ eprintln!("fbbootlogd: failed to create fbbootlog scheme: {err}"); -+ std::process::exit(1); -+ } -+ }; - - let mut scheme = FbbootlogScheme::new(); - let mut handler = Blocking::new(&socket, 16); - -- event_queue -- .subscribe( -- socket.inner().raw(), -- Source::Scheme, -- event::EventFlags::READ, -- ) -- .expect("fbbootlogd: failed to subscribe to scheme events"); -+ if let Err(err) = event_queue.subscribe( -+ socket.inner().raw(), -+ Source::Scheme, -+ event::EventFlags::READ, -+ ) { -+ eprintln!("fbbootlogd: failed to subscribe to scheme events: {err}"); -+ std::process::exit(1); -+ } - -- event_queue -- .subscribe( -- scheme.input_handle.event_handle().as_raw_fd() as usize, -- Source::Input, -- event::EventFlags::READ, -- ) -- .expect("fbbootlogd: failed to subscribe to scheme events"); -+ if let Err(err) = event_queue.subscribe( -+ scheme.input_handle.event_handle().as_raw_fd() as usize, -+ Source::Input, -+ event::EventFlags::READ, -+ ) { -+ eprintln!("fbbootlogd: failed to subscribe to input events: {err}"); -+ std::process::exit(1); -+ } - - { -- let log_fd = socket -- .create_this_scheme_fd(0, 0, 0, 0) -- .expect("fbbootlogd: failed to create log fd"); -+ let log_fd = match socket.create_this_scheme_fd(0, 0, 0, 0) { -+ Ok(fd) => fd, -+ Err(err) => { -+ eprintln!("fbbootlogd: failed to create log fd: {err}"); -+ std::process::exit(1); -+ } -+ }; - // Add ourself as log sink -- let log_file = libredox::Fd::open( -+ let log_file = match libredox::Fd::open( - "/scheme/log/add_sink", - libredox::flag::O_WRONLY | libredox::flag::O_CLOEXEC, - 0, -- ) -- .expect("fbbootlogd: failed to open log/add_sink"); -- log_file -- .call_wo(&log_fd.to_ne_bytes(), syscall::CallFlags::FD, &[]) -- .expect("fbbootlogd: failed to send log fd to log scheme."); -+ ) { -+ Ok(fd) => fd, -+ Err(err) => { -+ eprintln!("fbbootlogd: failed to open log/add_sink: {err}"); -+ std::process::exit(1); -+ } -+ }; -+ if let Err(err) = -+ log_file.call_wo(&log_fd.to_ne_bytes(), syscall::CallFlags::FD, &[]) -+ { -+ eprintln!("fbbootlogd: failed to send log fd to log scheme: {err}"); -+ std::process::exit(1); -+ } - } - - let _ = daemon.ready_sync_scheme(&socket, &mut scheme); - - // This is not possible for now as fbbootlogd needs to open new displays at runtime for graphics - // driver handoff. In the future inputd may directly pass a handle to the display instead. -- //libredox::call::setrens(0, 0).expect("fbbootlogd: failed to enter null namespace"); - - for event in event_queue { -- match event.expect("fbbootlogd: failed to get event").user_data { -+ let event = match event { -+ Ok(e) => e, -+ Err(err) => { -+ eprintln!("fbbootlogd: failed to get event: {err}"); -+ continue; -+ } -+ }; -+ match event.user_data { - Source::Scheme => loop { -- 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(err) => { -+ eprintln!("fbbootlogd: failed to process requests: {err}"); -+ break; -+ } - } - }, - Source::Input => { - let mut events = [Event::new(); 16]; - loop { -- 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)) => { - for event in events { - scheme.handle_input(&event); - } - } -- ConsumerHandleEvent::Handoff => { -+ Ok(ConsumerHandleEvent::Handoff) => { - eprintln!("fbbootlogd: handoff requested"); - scheme.handle_handoff(); - } -+ Err(err) => { -+ eprintln!("fbbootlogd: error while reading events: {err}"); -+ break; -+ } - } - } - } -diff --git a/drivers/graphics/fbbootlogd/src/scheme.rs b/drivers/graphics/fbbootlogd/src/scheme.rs -index 812c4a5b..9e1869c3 100644 ---- a/drivers/graphics/fbbootlogd/src/scheme.rs -+++ b/drivers/graphics/fbbootlogd/src/scheme.rs -@@ -26,7 +26,13 @@ pub struct FbbootlogScheme { - impl FbbootlogScheme { - pub fn new() -> FbbootlogScheme { - let mut scheme = FbbootlogScheme { -- input_handle: ConsumerHandle::bootlog_vt().expect("fbbootlogd: Failed to open vt"), -+ input_handle: match ConsumerHandle::bootlog_vt() { -+ Ok(handle) => handle, -+ Err(err) => { -+ eprintln!("fbbootlogd: failed to open vt: {err}"); -+ std::process::exit(1); -+ } -+ }, - display_map: None, - text_screen: console_draw::TextScreen::new(), - text_buffer: console_draw::TextBuffer::new(1000), -@@ -42,7 +48,13 @@ impl FbbootlogScheme { - - pub fn handle_handoff(&mut self) { - let new_display_handle = match self.input_handle.open_display_v2() { -- Ok(display) => V2GraphicsHandle::from_file(display).unwrap(), -+ Ok(display) => match V2GraphicsHandle::from_file(display) { -+ Ok(handle) => handle, -+ Err(err) => { -+ eprintln!("fbbootlogd: failed to create graphics handle: {err}"); -+ return; -+ } -+ }, - Err(err) => { - eprintln!("fbbootlogd: No display present yet: {err}"); - return; -@@ -140,7 +152,9 @@ impl FbbootlogScheme { - total_damage = total_damage.merge(damage); - } - } -- map.dirty_fb(total_damage).unwrap(); -+ if let Err(err) = map.dirty_fb(total_damage) { -+ eprintln!("fbbootlogd: failed to flush scrollback damage: {err}"); -+ } - } - - fn handle_resize(map: &mut V2DisplayMap, text_screen: &mut TextScreen) { -@@ -234,7 +248,9 @@ impl SchemeSync for FbbootlogScheme { - let damage = self.text_screen.write(map, buf, &mut VecDeque::new()); - - if let Some(map) = &mut self.display_map { -- map.dirty_fb(damage).unwrap(); -+ if let Err(err) = map.dirty_fb(damage) { -+ eprintln!("fbbootlogd: failed to flush write damage: {err}"); -+ } - } - } - } -diff --git a/drivers/graphics/fbcond/src/display.rs b/drivers/graphics/fbcond/src/display.rs -index eb09b97e..957a6d88 100644 ---- a/drivers/graphics/fbcond/src/display.rs -+++ b/drivers/graphics/fbcond/src/display.rs -@@ -31,7 +31,13 @@ impl Display { - return; - } - }; -- let new_display_handle = V2GraphicsHandle::from_file(display_file).unwrap(); -+ let new_display_handle = match V2GraphicsHandle::from_file(display_file) { -+ Ok(h) => h, -+ Err(err) => { -+ log::error!("fbcond: failed to create display handle: {err}"); -+ return; -+ } -+ }; - - log::debug!("fbcond: Opened new display"); - -@@ -77,7 +83,9 @@ impl Display { - - pub fn sync_rect(&mut self, damage: Damage) { - if let Some(map) = &mut self.map { -- map.dirty_fb(damage).unwrap(); -+ if let Err(err) = map.dirty_fb(damage) { -+ log::error!("fbcond: failed to sync display rect: {err}"); -+ } - } - } - } -diff --git a/drivers/graphics/fbcond/src/main.rs b/drivers/graphics/fbcond/src/main.rs -index eb4f9add..7acc488f 100644 ---- a/drivers/graphics/fbcond/src/main.rs -+++ b/drivers/graphics/fbcond/src/main.rs -@@ -21,7 +21,15 @@ fn main() { - fn daemon(daemon: daemon::SchemeDaemon) -> ! { - let vt_ids = env::args() - .skip(1) -- .map(|arg| arg.parse().expect("invalid vt number")) -+ .filter_map(|arg| { -+ match arg.parse() { -+ Ok(v) => Some(v), -+ Err(_) => { -+ eprintln!("fbcond: invalid vt number '{}', skipping", arg); -+ None -+ } -+ } -+ }) - .collect::>(); - - common::setup_logging( -@@ -31,18 +39,31 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! { - common::output_level(), - common::file_level(), - ); -- let mut event_queue = EventQueue::new().expect("fbcond: failed to create event queue"); -+ let mut event_queue = match EventQueue::new() { -+ Ok(eq) => eq, -+ Err(err) => { -+ eprintln!("fbcond: failed to create event queue: {}", err); -+ std::process::exit(1); -+ } -+ }; - - // FIXME listen for resize events from inputd and handle them - -- let mut socket = Socket::nonblock().expect("fbcond: failed to create fbcon scheme"); -- event_queue -- .subscribe( -- socket.inner().raw(), -- VtIndex::SCHEMA_SENTINEL, -- event::EventFlags::READ, -- ) -- .expect("fbcond: failed to subscribe to scheme events"); -+ let mut socket = match Socket::nonblock() { -+ Ok(s) => s, -+ Err(err) => { -+ eprintln!("fbcond: failed to create fbcon scheme: {}", err); -+ std::process::exit(1); -+ } -+ }; -+ if let Err(err) = event_queue.subscribe( -+ socket.inner().raw(), -+ VtIndex::SCHEMA_SENTINEL, -+ event::EventFlags::READ, -+ ) { -+ eprintln!("fbcond: failed to subscribe to scheme events: {}", err); -+ std::process::exit(1); -+ } - - let mut state = SchemeState::new(); - let mut scheme = FbconScheme::new(&vt_ids, &mut event_queue); -@@ -51,7 +72,6 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! { - - // This is not possible for now as fbcond needs to open new displays at runtime for graphics - // driver handoff. In the future inputd may directly pass a handle to the display instead. -- // libredox::call::setrens(0, 0).expect("fbcond: failed to enter null namespace"); - - let mut blocked = Vec::new(); - -@@ -68,7 +88,13 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! { - } - - for event in event_queue { -- let event = event.expect("fbcond: failed to read event from event queue"); -+ let event = match event { -+ Ok(ev) => ev, -+ Err(err) => { -+ eprintln!("fbcond: failed to read event from event queue: {}", err); -+ continue; -+ } -+ }; - handle_event( - &mut socket, - &mut scheme, -@@ -99,7 +125,10 @@ fn handle_event( - Err(err) if err.errno == EAGAIN => { - break; - } -- Err(err) => panic!("fbcond: failed to read display scheme: {err}"), -+ Err(err) => { -+ eprintln!("fbcond: failed to read display scheme: {err}"); -+ break; -+ } - }; - - match request.kind() { -@@ -108,12 +137,12 @@ fn handle_event( - let mut op = match req.op() { - Ok(op) => op, - Err(req) => { -- let _ = socket -- .write_response( -- Response::err(EOPNOTSUPP, req), -- SignalBehavior::Restart, -- ) -- .expect("fbcond: failed to write responses to fbcon scheme"); -+ if let Err(err) = socket.write_response( -+ Response::err(EOPNOTSUPP, req), -+ SignalBehavior::Restart, -+ ) { -+ eprintln!("fbcond: failed to write response: {}", err); -+ } - continue; - } - }; -@@ -125,25 +154,27 @@ fn handle_event( - blocked.push((op, caller)); - } - SchemeResponse::Regular(r) => { -- let _ = socket -+ if let Err(err) = socket - .write_response(Response::new(r, op), SignalBehavior::Restart) -- .expect("fbcond: failed to write responses to fbcon scheme"); -+ { -+ eprintln!("fbcond: failed to write response: {}", err); -+ } - } - SchemeResponse::Opened(o) => { -- let _ = socket -- .write_response( -- Response::open_dup_like(o, op), -- SignalBehavior::Restart, -- ) -- .expect("fbcond: failed to write responses to fbcon scheme"); -+ if let Err(err) = socket.write_response( -+ Response::open_dup_like(o, op), -+ SignalBehavior::Restart, -+ ) { -+ eprintln!("fbcond: failed to write response: {}", err); -+ } - } - SchemeResponse::RegularAndNotifyOnDetach(status) => { -- let _ = socket -- .write_response( -- Response::new_notify_on_detach(status, op), -- SignalBehavior::Restart, -- ) -- .expect("fbcond: failed to write scheme"); -+ if let Err(err) = socket.write_response( -+ Response::new_notify_on_detach(status, op), -+ SignalBehavior::Restart, -+ ) { -+ eprintln!("fbcond: failed to write response: {}", err); -+ } - } - } - } -@@ -157,25 +188,32 @@ fn handle_event( - { - let (blocked_req, _) = blocked.remove(i); - let resp = Response::err(EINTR, blocked_req); -- socket -- .write_response(resp, SignalBehavior::Restart) -- .expect("vesad: failed to write display scheme"); -+ if let Err(err) = -+ socket.write_response(resp, SignalBehavior::Restart) -+ { -+ eprintln!("fbcond: failed to write cancellation response: {}", err); -+ } - } - } - _ => {} - } - }, - vt_i => { -- let vt = scheme.vts.get_mut(&vt_i).unwrap(); -+ let Some(vt) = scheme.vts.get_mut(&vt_i) else { -+ eprintln!("fbcond: unknown vt index {:?}", vt_i); -+ return; -+ }; - - let mut events = [Event::new(); 16]; - loop { -- match vt -- .display -- .input_handle -- .read_events(&mut events) -- .expect("fbcond: Error while reading events") -- { -+ let read_result = match vt.display.input_handle.read_events(&mut events) { -+ Ok(r) => r, -+ Err(err) => { -+ eprintln!("fbcond: error while reading events: {}", err); -+ break; -+ } -+ }; -+ match read_result { - ConsumerHandleEvent::Events(&[]) => break, - - ConsumerHandleEvent::Events(events) => { -@@ -193,9 +231,9 @@ fn handle_event( - { - let mut i = 0; - while i < blocked.len() { -- let (op, caller) = blocked -- .get_mut(i) -- .expect("vesad: Failed to get blocked request"); -+ let Some((op, caller)) = blocked.get_mut(i) else { -+ break; -+ }; - let resp = match op.handle_sync_dont_consume(&caller, scheme, state) { - SchemeResponse::Opened(Err(e)) | SchemeResponse::Regular(Err(e)) - if libredox::error::Error::from(e).is_wouldblock() -@@ -217,9 +255,9 @@ fn handle_event( - Response::new_notify_on_detach(status, op) - } - }; -- let _ = socket -- .write_response(resp, SignalBehavior::Restart) -- .expect("vesad: failed to write display scheme"); -+ if let Err(err) = socket.write_response(resp, SignalBehavior::Restart) { -+ eprintln!("fbcond: failed to write blocked response: {}", err); -+ } - } - } - -@@ -242,9 +280,9 @@ fn handle_event( - if !handle.notified_read { - handle.notified_read = true; - let response = Response::post_fevent(*handle_id, EVENT_READ.bits()); -- socket -- .write_response(response, SignalBehavior::Restart) -- .expect("fbcond: failed to write display event"); -+ if let Err(err) = socket.write_response(response, SignalBehavior::Restart) { -+ eprintln!("fbcond: failed to write display event: {}", err); -+ } - } - } else { - handle.notified_read = false; -diff --git a/drivers/graphics/fbcond/src/scheme.rs b/drivers/graphics/fbcond/src/scheme.rs -index 1bee134e..973ff31e 100644 ---- a/drivers/graphics/fbcond/src/scheme.rs -+++ b/drivers/graphics/fbcond/src/scheme.rs -@@ -6,7 +6,7 @@ use redox_scheme::scheme::SchemeSync; - use redox_scheme::{CallerCtx, OpenResult}; - use scheme_utils::{FpathWriter, HandleMap}; - use syscall::schemev2::NewFdFlags; --use syscall::{Error, EventFlags, Result, EACCES, EAGAIN, EBADF, ENOENT, O_NONBLOCK}; -+use syscall::{Error, EventFlags, Result, EACCES, EAGAIN, EBADF, EINVAL, ENOENT, O_NONBLOCK}; - - use crate::display::Display; - use crate::text::TextScreen; -@@ -50,14 +50,21 @@ impl FbconScheme { - let mut vts = BTreeMap::new(); - - for &vt_i in vt_ids { -- let display = Display::open_new_vt().expect("Failed to open display for vt"); -- event_queue -- .subscribe( -- display.input_handle.event_handle().as_raw_fd() as usize, -- VtIndex(vt_i), -- event::EventFlags::READ, -- ) -- .expect("Failed to subscribe to input events for vt"); -+ let display = match Display::open_new_vt() { -+ Ok(d) => d, -+ Err(err) => { -+ eprintln!("fbcond: failed to open display for vt {}: {}", vt_i, err); -+ continue; -+ } -+ }; -+ if let Err(err) = event_queue.subscribe( -+ display.input_handle.event_handle().as_raw_fd() as usize, -+ VtIndex(vt_i), -+ event::EventFlags::READ, -+ ) { -+ eprintln!("fbcond: failed to subscribe to input events for vt {}: {}", vt_i, err); -+ continue; -+ } - vts.insert(VtIndex(vt_i), TextScreen::new(display)); - } - -@@ -127,7 +134,7 @@ impl SchemeSync for FbconScheme { - fn fpath(&mut self, id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> Result { - FpathWriter::with_legacy(buf, "fbcon", |w| { - let handle = self.get_vt_handle_mut(id)?; -- write!(w, "{}", handle.vt_i.0).unwrap(); -+ write!(w, "{}", handle.vt_i.0).map_err(|_| Error::new(EINVAL))?; - Ok(()) - }) - } -diff --git a/drivers/graphics/fbcond/src/text.rs b/drivers/graphics/fbcond/src/text.rs -index 8a24bbeb..c7272ab7 100644 ---- a/drivers/graphics/fbcond/src/text.rs -+++ b/drivers/graphics/fbcond/src/text.rs -@@ -113,7 +113,7 @@ impl TextScreen { - let mut i = 0; - - while i < buf.len() && !self.input.is_empty() { -- buf[i] = self.input.pop_front().unwrap(); -+ buf[i] = self.input.pop_front().unwrap_or(0); - i += 1; - } - -diff --git a/drivers/graphics/graphics-ipc/src/lib.rs b/drivers/graphics/graphics-ipc/src/lib.rs -index 285b3043..7451c90a 100644 ---- a/drivers/graphics/graphics-ipc/src/lib.rs -+++ b/drivers/graphics/graphics-ipc/src/lib.rs -@@ -29,12 +29,16 @@ impl drm::control::Device for V2GraphicsHandle {} - impl V2GraphicsHandle { - pub fn from_file(file: File) -> io::Result { - let handle = V2GraphicsHandle { file }; -- assert!(handle.get_driver_capability(DriverCapability::DumbBuffer)? == 1); -+ if handle.get_driver_capability(DriverCapability::DumbBuffer)? != 1 { -+ return Err(io::Error::other( -+ "graphics device does not support dumb buffers", -+ )); -+ } - Ok(handle) - } - - pub fn first_display(&self) -> io::Result { -- for &connector in self.resource_handles().unwrap().connectors() { -+ for &connector in self.resource_handles()?.connectors() { - if self.get_connector(connector, true)?.state() == State::Connected { - return Ok(connector); - } -@@ -95,13 +99,28 @@ impl CpuBackedBuffer { - return; // No shadow buffer; all writes are already propagated to the GPU. - }; - -- assert!(x.checked_add(width).unwrap() <= self.buffer.size().0); -- assert!(y.checked_add(height).unwrap() <= self.buffer.size().1); -+ let Some(x_end) = x.checked_add(width) else { -+ return; -+ }; -+ let Some(y_end) = y.checked_add(height) else { -+ return; -+ }; -+ if x_end > self.buffer.size().0 || y_end > self.buffer.size().1 { -+ return; -+ } - -- let start_x: usize = x.try_into().unwrap(); -- let start_y: usize = y.try_into().unwrap(); -- let w: usize = width.try_into().unwrap(); -- let h: usize = height.try_into().unwrap(); -+ let Ok(start_x) = usize::try_from(x) else { -+ return; -+ }; -+ let Ok(start_y) = usize::try_from(y) else { -+ return; -+ }; -+ let Ok(w) = usize::try_from(width) else { -+ return; -+ }; -+ let Ok(h) = usize::try_from(height) else { -+ return; -+ }; - - let offscreen_ptr = shadow.as_ptr().cast::(); - let onscreen_ptr = self.map.as_mut_ptr().cast::(); -diff --git a/drivers/graphics/ihdgd/config.toml b/drivers/graphics/ihdgd/config.toml -index acbb4e78..210731ae 100644 ---- a/drivers/graphics/ihdgd/config.toml -+++ b/drivers/graphics/ihdgd/config.toml -@@ -51,5 +51,26 @@ ids = { 0x8086 = [ - 0x56B3, # Pro A60 - 0x56C0, # GPU Flex 170 - 0x56C1, # GPU Flex 140 -+ # Alder Lake-S Desktop -+ 0x4680, 0x4682, 0x4688, 0x468A, 0x468B, -+ 0x4690, 0x4692, 0x4693, -+ # Alder Lake-P Mobile -+ 0x46A0, 0x46A1, 0x46A2, 0x46A3, 0x46A6, -+ 0x46A8, 0x46AA, 0x462A, 0x4626, 0x4628, -+ 0x46B0, 0x46B1, 0x46B2, 0x46B3, -+ 0x46C0, 0x46C1, 0x46C2, 0x46C3, -+ # Alder Lake-N Low-Power -+ 0x46D0, 0x46D1, 0x46D2, 0x46D3, 0x46D4, -+ # Raptor Lake-S Desktop -+ 0xA780, 0xA781, 0xA782, 0xA783, -+ 0xA788, 0xA789, 0xA78A, 0xA78B, -+ # Raptor Lake-P Mobile -+ 0xA720, 0xA7A0, 0xA7A8, 0xA7AA, 0xA7AB, -+ # Raptor Lake-U Mobile -+ 0xA721, 0xA7A1, 0xA7A9, 0xA7AC, 0xA7AD, -+ # Meteor Lake -+ 0x7D40, 0x7D45, 0x7D55, 0x7D60, 0x7DD5, -+ # Arrow Lake-H -+ 0x7D51, 0x7DD1, - ] } - command = ["ihdgd"] -diff --git a/drivers/graphics/ihdgd/src/device/ddi.rs b/drivers/graphics/ihdgd/src/device/ddi.rs -index ac4ce1bd..b851d169 100644 ---- a/drivers/graphics/ihdgd/src/device/ddi.rs -+++ b/drivers/graphics/ihdgd/src/device/ddi.rs -@@ -347,9 +347,12 @@ impl Ddi { - - // Last setting is the default - //TODO: get correct setting index from BIOS -- let setting = settings.last().unwrap(); -+ let Some(setting) = settings.last() else { -+ log::error!("no voltage swing settings available"); -+ return Err(Error::new(EIO)); -+ }; - -- // This allows unwraps on port functions below without panic -+ // All port registers below require port_base to be set (checked above) - if self.port_base.is_none() { - log::error!("HDMI voltage swing procedure only implemented on combo DDI"); - return Err(Error::new(EIO)); -@@ -358,9 +361,15 @@ impl Ddi { - // Clear cmnkeeper_enable for HDMI - { - // It is not possible to read from GRP register, so use LN0 as template -- let pcs_dw1_ln0 = self.port_pcs(PortPcsReg::Dw1, PortLane::Ln0).unwrap(); -- let mut pcs_dw1_grp = -- WriteOnly::new(self.port_pcs(PortPcsReg::Dw1, PortLane::Grp).unwrap()); -+ let Some(pcs_dw1_ln0) = self.port_pcs(PortPcsReg::Dw1, PortLane::Ln0) else { -+ log::error!("failed to get PCS_DW1_LN0 for DDI {}", self.name); -+ return Err(Error::new(EIO)); -+ }; -+ let Some(pcs_dw1_grp_raw) = self.port_pcs(PortPcsReg::Dw1, PortLane::Grp) else { -+ log::error!("failed to get PCS_DW1_GRP for DDI {}", self.name); -+ return Err(Error::new(EIO)); -+ }; -+ let mut pcs_dw1_grp = WriteOnly::new(pcs_dw1_grp_raw); - let mut v = pcs_dw1_ln0.read(); - v &= !PORT_PCS_DW1_CMNKEEPER_ENABLE; - pcs_dw1_grp.write(v); -@@ -369,28 +378,50 @@ impl Ddi { - // Program loadgen select - //TODO: this assumes bit rate <= 6 GHz and 4 lanes enabled - { -- let mut tx_dw4_ln0 = self.port_tx(PortTxReg::Dw4, PortLane::Ln0).unwrap(); -+ let Some(mut tx_dw4_ln0) = self.port_tx(PortTxReg::Dw4, PortLane::Ln0) else { -+ log::error!("failed to get TX_DW4_LN0 for DDI {}", self.name); -+ return Err(Error::new(EIO)); -+ }; - tx_dw4_ln0.writef(PORT_TX_DW4_SELECT, false); - -- let mut tx_dw4_ln1 = self.port_tx(PortTxReg::Dw4, PortLane::Ln1).unwrap(); -+ let Some(mut tx_dw4_ln1) = self.port_tx(PortTxReg::Dw4, PortLane::Ln1) else { -+ log::error!("failed to get TX_DW4_LN1 for DDI {}", self.name); -+ return Err(Error::new(EIO)); -+ }; - tx_dw4_ln1.writef(PORT_TX_DW4_SELECT, true); - -- let mut tx_dw4_ln2 = self.port_tx(PortTxReg::Dw4, PortLane::Ln2).unwrap(); -+ let Some(mut tx_dw4_ln2) = self.port_tx(PortTxReg::Dw4, PortLane::Ln2) else { -+ log::error!("failed to get TX_DW4_LN2 for DDI {}", self.name); -+ return Err(Error::new(EIO)); -+ }; - tx_dw4_ln2.writef(PORT_TX_DW4_SELECT, true); - -- let mut tx_dw4_ln3 = self.port_tx(PortTxReg::Dw4, PortLane::Ln3).unwrap(); -+ let Some(mut tx_dw4_ln3) = self.port_tx(PortTxReg::Dw4, PortLane::Ln3) else { -+ log::error!("failed to get TX_DW4_LN3 for DDI {}", self.name); -+ return Err(Error::new(EIO)); -+ }; - tx_dw4_ln3.writef(PORT_TX_DW4_SELECT, true); - } - - // Set PORT_CL_DW5 sus clock config to 11b - { -- let mut cl_dw5 = self.port_cl(PortClReg::Dw5).unwrap(); -+ let Some(mut cl_dw5) = self.port_cl(PortClReg::Dw5) else { -+ log::error!("failed to get CL_DW5 for DDI {}", self.name); -+ return Err(Error::new(EIO)); -+ }; - cl_dw5.writef(PORT_CL_DW5_SUS_CLOCK_MASK, true); - } - - // Clear training enable to change swing values -- let tx_dw5_ln0 = self.port_tx(PortTxReg::Dw5, PortLane::Ln0).unwrap(); -- let mut tx_dw5_grp = WriteOnly::new(self.port_tx(PortTxReg::Dw5, PortLane::Grp).unwrap()); -+ let Some(tx_dw5_ln0) = self.port_tx(PortTxReg::Dw5, PortLane::Ln0) else { -+ log::error!("failed to get TX_DW5_LN0 for DDI {}", self.name); -+ return Err(Error::new(EIO)); -+ }; -+ let Some(tx_dw5_grp_raw) = self.port_tx(PortTxReg::Dw5, PortLane::Grp) else { -+ log::error!("failed to get TX_DW5_GRP for DDI {}", self.name); -+ return Err(Error::new(EIO)); -+ }; -+ let mut tx_dw5_grp = WriteOnly::new(tx_dw5_grp_raw); - { - let mut v = tx_dw5_ln0.read(); - v &= !PORT_TX_DW5_TRAINING_ENABLE; -@@ -400,7 +431,10 @@ impl Ddi { - // Program swing and de-emphasis - - // Disable eDP bits in PORT_CL_DW10 -- let mut cl_dw10 = self.port_cl(PortClReg::Dw10).unwrap(); -+ let Some(mut cl_dw10) = self.port_cl(PortClReg::Dw10) else { -+ log::error!("failed to get CL_DW10 for DDI {}", self.name); -+ return Err(Error::new(EIO)); -+ }; - cl_dw10.writef( - PORT_CL_DW10_EDP4K2K_MODE_OVRD_EN | PORT_CL_DW10_EDP4K2K_MODE_OVRD_VAL, - false, -@@ -435,7 +469,10 @@ impl Ddi { - // - Set swing sel from settings - // - Set rcomp scalar to 0x98 - for lane in lanes { -- let mut tx_dw2 = self.port_tx(PortTxReg::Dw2, lane).unwrap(); -+ let Some(mut tx_dw2) = self.port_tx(PortTxReg::Dw2, lane) else { -+ log::error!("failed to get TX_DW2 for {:?} on DDI {}", lane, self.name); -+ continue; -+ }; - let mut v = tx_dw2.read(); - v &= !(PORT_TX_DW2_SWING_SEL_UPPER_MASK - | PORT_TX_DW2_SWING_SEL_LOWER_MASK -@@ -451,7 +488,10 @@ impl Ddi { - // - Set post cursor 2 to 0x0 - // - Set cursor coeff from settings - for lane in lanes { -- let mut tx_dw4 = self.port_tx(PortTxReg::Dw4, lane).unwrap(); -+ let Some(mut tx_dw4) = self.port_tx(PortTxReg::Dw4, lane) else { -+ log::error!("failed to get TX_DW4 for {:?} on DDI {}", lane, self.name); -+ continue; -+ }; - let mut v = tx_dw4.read(); - v &= !(PORT_TX_DW4_POST_CURSOR_1_MASK - | PORT_TX_DW4_POST_CURSOR_2_MASK -@@ -464,7 +504,10 @@ impl Ddi { - // For PORT_TX_DW7: - // - Set n scalar from settings - for lane in lanes { -- let mut tx_dw7 = self.port_tx(PortTxReg::Dw7, lane).unwrap(); -+ let Some(mut tx_dw7) = self.port_tx(PortTxReg::Dw7, lane) else { -+ log::error!("failed to get TX_DW7 for {:?} on DDI {}", lane, self.name); -+ continue; -+ }; - // All other bits are spare - tx_dw7.write(setting.dw7_n_scalar << PORT_TX_DW7_N_SCALAR_SHIFT); - } -diff --git a/drivers/graphics/ihdgd/src/device/ggtt.rs b/drivers/graphics/ihdgd/src/device/ggtt.rs -index 5e39827a..0ec5358b 100644 ---- a/drivers/graphics/ihdgd/src/device/ggtt.rs -+++ b/drivers/graphics/ihdgd/src/device/ggtt.rs -@@ -3,7 +3,7 @@ use std::{mem, ptr}; - - use pcid_interface::PciFunctionHandle; - use range_alloc::RangeAllocator; --use syscall::{Error, EIO}; -+use syscall::{Error, EIO, EINVAL}; - - use crate::device::MmioRegion; - -@@ -88,20 +88,36 @@ impl GlobalGtt { - } - } - -- pub fn reserve(&mut self, surf: u32, surf_size: u32) { -- assert!(surf.is_multiple_of(GTT_PAGE_SIZE)); -- assert!(surf_size.is_multiple_of(GTT_PAGE_SIZE)); -+ pub fn reserve(&mut self, surf: u32, surf_size: u32) -> syscall::Result<()> { -+ if !surf.is_multiple_of(GTT_PAGE_SIZE) { -+ log::error!( -+ "reserve: surface address 0x{:x} is not aligned to GTT page size", -+ surf -+ ); -+ return Err(Error::new(EINVAL)); -+ } -+ if !surf_size.is_multiple_of(GTT_PAGE_SIZE) { -+ log::error!( -+ "reserve: surface size 0x{:x} is not aligned to GTT page size", -+ surf_size -+ ); -+ return Err(Error::new(EINVAL)); -+ } - -- self.gm_alloc -- .allocate_exact_range( -- surf / GTT_PAGE_SIZE..surf / GTT_PAGE_SIZE + surf_size / GTT_PAGE_SIZE, -- ) -- .unwrap_or_else(|err| { -- panic!( -+ match self.gm_alloc.allocate_exact_range( -+ surf / GTT_PAGE_SIZE..surf / GTT_PAGE_SIZE + surf_size / GTT_PAGE_SIZE, -+ ) { -+ Ok(_range) => Ok(()), -+ Err(err) => { -+ log::error!( - "failed to allocate pre-existing surface at 0x{:x} of size {}: {:?}", -- surf, surf_size, err -+ surf, -+ surf_size, -+ err - ); -- }); -+ Err(Error::new(EIO)) -+ } -+ } - } - - pub fn alloc_phys_mem(&mut self, size: u32) -> syscall::Result { -diff --git a/drivers/graphics/ihdgd/src/device/mod.rs b/drivers/graphics/ihdgd/src/device/mod.rs -index ced9dd56..fc2a1108 100644 ---- a/drivers/graphics/ihdgd/src/device/mod.rs -+++ b/drivers/graphics/ihdgd/src/device/mod.rs -@@ -51,8 +51,9 @@ impl<'a, T, F: FnOnce(&mut T)> CallbackGuard<'a, T, F> { - - impl<'a, T, F: FnOnce(&mut T)> Drop for CallbackGuard<'a, T, F> { - fn drop(&mut self) { -- let fini = self.fini.take().unwrap(); -- fini(&mut self.value); -+ if let Some(fini) = self.fini.take() { -+ fini(&mut self.value); -+ } - } - } - -@@ -246,7 +247,9 @@ impl Device { - }; - - let gttmm = { -- let (phys, size) = func.bars[0].expect_mem(); -+ let (phys, size) = func.bars[0] -+ .try_mem() -+ .map_err(|_| Error::new(ENODEV))?; - Arc::new(MmioRegion::new( - phys, - size, -@@ -255,7 +258,9 @@ impl Device { - }; - log::info!("GTTMM {:X?}", gttmm); - let gm = { -- let (phys, size) = func.bars[2].expect_mem(); -+ let (phys, size) = func.bars[2] -+ .try_mem() -+ .map_err(|_| Error::new(ENODEV))?; - MmioRegion::new(phys, size, common::MemoryType::WriteCombining)? - }; - log::info!("GM {:X?}", gm); -@@ -453,7 +458,12 @@ impl Device { - // Probe all DDIs - let ddi_names: Vec<&str> = self.ddis.iter().map(|ddi| ddi.name).collect(); - for ddi_name in ddi_names { -- self.probe_ddi(ddi_name).expect("failed to probe DDI"); -+ match self.probe_ddi(ddi_name) { -+ Ok(_) => {} -+ Err(err) => { -+ log::error!("failed to probe DDI {}: {}", ddi_name, err); -+ } -+ } - } - - self.dump(); -diff --git a/drivers/graphics/ihdgd/src/device/pipe.rs b/drivers/graphics/ihdgd/src/device/pipe.rs -index 0e99ffe4..779e4c5f 100644 ---- a/drivers/graphics/ihdgd/src/device/pipe.rs -+++ b/drivers/graphics/ihdgd/src/device/pipe.rs -@@ -76,14 +76,12 @@ impl Plane { - let buf_cfg = self.buf_cfg.read(); - let buffer_start = buf_cfg & 0x7FF; - let buffer_end = (buf_cfg >> 16) & 0x7FF; -- alloc_buffers -- .allocate_exact_range(buffer_start..(buffer_end + 1)) -- .unwrap_or_else(|err| { -- panic!( -- "failed to allocate pre-existing buffer blocks {} to {}: {:?}", -- buffer_start, buffer_end, err -- ); -- }); -+ if let Err(err) = alloc_buffers.allocate_exact_range(buffer_start..(buffer_end + 1)) { -+ log::error!( -+ "failed to allocate pre-existing buffer blocks {} to {}: {:?}", -+ buffer_start, buffer_end, err -+ ); -+ } - } - - pub fn modeset(&mut self, alloc_buffers: &mut RangeAllocator) -> syscall::Result<()> { -@@ -122,7 +120,13 @@ impl Plane { - let surf = self.surf.read() & 0xFFFFF000; - //TODO: read bits per pixel - let surf_size = (stride * height).next_multiple_of(4096); -- ggtt.reserve(surf, surf_size); -+ ggtt.reserve(surf, surf_size).unwrap_or_else(|err| { -+ log::warn!( -+ "failed to reserve GTT entries for existing framebuffer at 0x{:x}: {}", -+ surf, -+ err -+ ); -+ }); - - unsafe { DeviceFb::new(gm, surf, width, height, stride, true) } - } -diff --git a/drivers/graphics/ihdgd/src/device/scheme.rs b/drivers/graphics/ihdgd/src/device/scheme.rs -index 95db5bbf..3554a35e 100644 ---- a/drivers/graphics/ihdgd/src/device/scheme.rs -+++ b/drivers/graphics/ihdgd/src/device/scheme.rs -@@ -68,7 +68,20 @@ impl GraphicsAdapter for Device { - } - - fn probe_connector(&mut self, objects: &mut KmsObjects, id: KmsObjectId) { -- let mut connector = objects.get_connector(id).unwrap().lock().unwrap(); -+ let connector_guard = match objects.get_connector(id) { -+ Ok(guard) => guard, -+ Err(e) => { -+ log::error!("probe_connector: connector {:?} not found: {}", id, e); -+ return; -+ } -+ }; -+ let mut connector = match connector_guard.lock() { -+ Ok(guard) => guard, -+ Err(err) => { -+ log::error!("probe_connector: failed to lock connector {:?}: {}", id, err); -+ return; -+ } -+ }; - let framebuffer = &self.framebuffers[connector.driver_data.framebuffer_id]; - connector.connection = KmsConnectorStatus::Connected; - connector.update_from_size(framebuffer.width as u32, framebuffer.height as u32); -@@ -94,7 +107,10 @@ impl GraphicsAdapter for Device { - state: KmsCrtcState, - damage: Damage, - ) -> syscall::Result<()> { -- let mut crtc = crtc.lock().unwrap(); -+ let mut crtc = crtc.lock().map_err(|err| { -+ log::error!("set_crtc: failed to lock crtc: {}", err); -+ syscall::Error::new(EINVAL) -+ })?; - let buffer = state - .fb_id - .map(|fb_id| objects.get_framebuffer(fb_id)) -@@ -102,7 +118,13 @@ impl GraphicsAdapter for Device { - crtc.state = state; - - for connector in objects.connectors() { -- let connector = connector.lock().unwrap(); -+ let connector = match connector.lock() { -+ Ok(c) => c, -+ Err(err) => { -+ log::error!("set_crtc: failed to lock connector: {}", err); -+ continue; -+ } -+ }; - - if connector.state.crtc_id != objects.crtc_ids()[crtc.crtc_index as usize] { - continue; -@@ -161,9 +183,9 @@ impl DumbFb { - fn layout(len: usize) -> Layout { - // optimizes to an integer mul - Layout::array::(len) -- .unwrap() -+ .unwrap_or_else(|_| Layout::from_size_align(len * 4, PAGE_SIZE).unwrap_or(Layout::new::())) - .align_to(PAGE_SIZE) -- .unwrap() -+ .unwrap_or_else(|_| Layout::new::().align_to(PAGE_SIZE).unwrap_or(Layout::new::())) - } - } - -@@ -182,15 +204,38 @@ impl Buffer for DumbFb { - - impl DumbFb { - fn sync(&self, framebuffer: &mut DeviceFb, sync_rect: Damage) { -- let sync_rect = sync_rect.clip( -- self.width.try_into().unwrap(), -- self.height.try_into().unwrap(), -- ); -- -- let start_x: usize = sync_rect.x.try_into().unwrap(); -- let start_y: usize = sync_rect.y.try_into().unwrap(); -- let w: usize = sync_rect.width.try_into().unwrap(); -- let h: usize = sync_rect.height.try_into().unwrap(); -+ let fb_w: u32 = match self.width.try_into() { -+ Ok(v) => v, -+ Err(_) => { -+ log::error!("sync: framebuffer width {} overflow", self.width); -+ return; -+ } -+ }; -+ let fb_h: u32 = match self.height.try_into() { -+ Ok(v) => v, -+ Err(_) => { -+ log::error!("sync: framebuffer height {} overflow", self.height); -+ return; -+ } -+ }; -+ let sync_rect = sync_rect.clip(fb_w, fb_h); -+ -+ let start_x: usize = match sync_rect.x.try_into() { -+ Ok(v) => v, -+ Err(_) => return, -+ }; -+ let start_y: usize = match sync_rect.y.try_into() { -+ Ok(v) => v, -+ Err(_) => return, -+ }; -+ let w: usize = match sync_rect.width.try_into() { -+ Ok(v) => v, -+ Err(_) => return, -+ }; -+ let h: usize = match sync_rect.height.try_into() { -+ Ok(v) => v, -+ Err(_) => return, -+ }; - - let offscreen_ptr = self.ptr.as_ptr() as *mut u32; - let onscreen_ptr = framebuffer.buffer.virt.cast::(); -diff --git a/drivers/graphics/ihdgd/src/main.rs b/drivers/graphics/ihdgd/src/main.rs -index a8b6cc60..84d58a3e 100644 ---- a/drivers/graphics/ihdgd/src/main.rs -+++ b/drivers/graphics/ihdgd/src/main.rs -@@ -1,6 +1,6 @@ - use driver_graphics::GraphicsScheme; - use event::{user_data, EventQueue}; --use pcid_interface::{irq_helpers::pci_allocate_interrupt_vector, PciFunctionHandle}; -+use pcid_interface::{irq_helpers::try_pci_allocate_interrupt_vector, PciFunctionHandle}; - use std::{ - io::{Read, Write}, - os::fd::AsRawFd, -@@ -29,16 +29,32 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - - log::info!("IHDG {}", pci_config.func.display()); - -- let device = Device::new(&mut pcid_handle, &pci_config.func) -- .expect("ihdgd: failed to initialize device"); -+ let device = match Device::new(&mut pcid_handle, &pci_config.func) { -+ Ok(device) => device, -+ Err(err) => { -+ log::error!("ihdgd: failed to initialize device: {err}"); -+ std::process::exit(1); -+ } -+ }; - -- let irq_file = pci_allocate_interrupt_vector(&mut pcid_handle, "ihdgd"); -+ let irq_file = match try_pci_allocate_interrupt_vector(&mut pcid_handle, "ihdgd") { -+ Ok(irq) => irq, -+ Err(err) => { -+ log::error!("ihdgd: failed to allocate interrupt vector: {err}"); -+ std::process::exit(1); -+ } -+ }; - - // Needs to be before GraphicsScheme::new to avoid a deadlock due to initnsmgr blocking on - // /scheme/event as it is already blocked on opening /scheme/display.ihdg.*. - // FIXME change the initnsmgr to not block on openat for the target scheme. -- let event_queue: EventQueue = -- EventQueue::new().expect("ihdgd: failed to create event queue"); -+ let event_queue: EventQueue = match EventQueue::new() { -+ Ok(eq) => eq, -+ Err(err) => { -+ log::error!("ihdgd: failed to create event queue: {err}"); -+ std::process::exit(1); -+ } -+ }; - - let mut scheme = GraphicsScheme::new(device, format!("display.ihdg.{}", name), false); - -@@ -50,53 +66,69 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - } - } - -- event_queue -- .subscribe( -- scheme.inputd_event_handle().as_raw_fd() as usize, -- Source::Input, -- event::EventFlags::READ, -- ) -- .unwrap(); -- event_queue -- .subscribe( -- irq_file.irq_handle().as_raw_fd() as usize, -- Source::Irq, -- event::EventFlags::READ, -- ) -- .unwrap(); -- event_queue -- .subscribe( -- scheme.event_handle().raw(), -- Source::Scheme, -- event::EventFlags::READ, -- ) -- .unwrap(); -- -- libredox::call::setrens(0, 0).expect("ihdgd: failed to enter null namespace"); -+ if let Err(err) = event_queue.subscribe( -+ scheme.inputd_event_handle().as_raw_fd() as usize, -+ Source::Input, -+ event::EventFlags::READ, -+ ) { -+ log::error!("ihdgd: failed to subscribe to input events: {err}"); -+ } -+ if let Err(err) = event_queue.subscribe( -+ irq_file.irq_handle().as_raw_fd() as usize, -+ Source::Irq, -+ event::EventFlags::READ, -+ ) { -+ log::error!("ihdgd: failed to subscribe to IRQ events: {err}"); -+ } -+ if let Err(err) = event_queue.subscribe( -+ scheme.event_handle().raw(), -+ Source::Scheme, -+ event::EventFlags::READ, -+ ) { -+ log::error!("ihdgd: failed to subscribe to scheme events: {err}"); -+ } -+ -+ if let Err(err) = libredox::call::setrens(0, 0) { -+ log::error!("ihdgd: failed to enter null namespace: {err}"); -+ std::process::exit(1); -+ } - - daemon.ready(); - - let all = [Source::Input, Source::Irq, Source::Scheme]; -- for event in all -- .into_iter() -- .chain(event_queue.map(|e| e.expect("ihdgd: failed to get next event").user_data)) -- { -+ for event in all.into_iter().chain( -+ event_queue.filter_map(|e| match e { -+ Ok(event) => Some(event.user_data), -+ Err(err) => { -+ log::error!("ihdgd: failed to get next event: {err}"); -+ None -+ } -+ }), -+ ) { - match event { - Source::Input => scheme.handle_vt_events(), - Source::Irq => { - let mut irq = [0; 8]; -- irq_file.irq_handle().read(&mut irq).unwrap(); -+ if irq_file.irq_handle().read(&mut irq).is_err() { -+ log::error!("ihdgd: failed to read IRQ"); -+ continue; -+ } - if scheme.adapter_mut().handle_irq() { -- irq_file.irq_handle().write(&mut irq).unwrap(); -+ if let Err(err) = irq_file.irq_handle().write(&mut irq) { -+ log::error!("ihdgd: failed to write IRQ: {err}"); -+ continue; -+ } - - scheme.adapter_mut().handle_events(); -- scheme.tick().unwrap(); -+ if let Err(err) = scheme.tick() { -+ log::error!("ihdgd: failed to handle display events after IRQ: {err}"); -+ } - } - } - Source::Scheme => { -- scheme -- .tick() -- .expect("ihdgd: failed to handle scheme events"); -+ if let Err(err) = scheme.tick() { -+ log::error!("ihdgd: failed to handle scheme events: {err}"); -+ } - } - } - } -diff --git a/drivers/graphics/vesad/src/main.rs b/drivers/graphics/vesad/src/main.rs -index a4c07d1e..41faa0e2 100644 ---- a/drivers/graphics/vesad/src/main.rs -+++ b/drivers/graphics/vesad/src/main.rs -@@ -23,25 +23,49 @@ fn daemon(daemon: daemon::Daemon) -> ! { - } - - let width = usize::from_str_radix( -- &env::var("FRAMEBUFFER_WIDTH").expect("FRAMEBUFFER_WIDTH not set"), -+ &env::var("FRAMEBUFFER_WIDTH").unwrap_or_else(|_| { -+ eprintln!("vesad: FRAMEBUFFER_WIDTH not set"); -+ std::process::exit(1); -+ }), - 16, - ) -- .expect("failed to parse FRAMEBUFFER_WIDTH"); -+ .unwrap_or_else(|err| { -+ eprintln!("vesad: failed to parse FRAMEBUFFER_WIDTH: {}", err); -+ std::process::exit(1); -+ }); - let height = usize::from_str_radix( -- &env::var("FRAMEBUFFER_HEIGHT").expect("FRAMEBUFFER_HEIGHT not set"), -+ &env::var("FRAMEBUFFER_HEIGHT").unwrap_or_else(|_| { -+ eprintln!("vesad: FRAMEBUFFER_HEIGHT not set"); -+ std::process::exit(1); -+ }), - 16, - ) -- .expect("failed to parse FRAMEBUFFER_HEIGHT"); -+ .unwrap_or_else(|err| { -+ eprintln!("vesad: failed to parse FRAMEBUFFER_HEIGHT: {}", err); -+ std::process::exit(1); -+ }); - let phys = usize::from_str_radix( -- &env::var("FRAMEBUFFER_ADDR").expect("FRAMEBUFFER_ADDR not set"), -+ &env::var("FRAMEBUFFER_ADDR").unwrap_or_else(|_| { -+ eprintln!("vesad: FRAMEBUFFER_ADDR not set"); -+ std::process::exit(1); -+ }), - 16, - ) -- .expect("failed to parse FRAMEBUFFER_ADDR"); -+ .unwrap_or_else(|err| { -+ eprintln!("vesad: failed to parse FRAMEBUFFER_ADDR: {}", err); -+ std::process::exit(1); -+ }); - let stride = usize::from_str_radix( -- &env::var("FRAMEBUFFER_STRIDE").expect("FRAMEBUFFER_STRIDE not set"), -+ &env::var("FRAMEBUFFER_STRIDE").unwrap_or_else(|_| { -+ eprintln!("vesad: FRAMEBUFFER_STRIDE not set"); -+ std::process::exit(1); -+ }), - 16, - ) -- .expect("failed to parse FRAMEBUFFER_STRIDE"); -+ .unwrap_or_else(|err| { -+ eprintln!("vesad: failed to parse FRAMEBUFFER_STRIDE: {}", err); -+ std::process::exit(1); -+ }); - - println!( - "vesad: {}x{} stride {} at 0x{:X}", -@@ -57,14 +81,20 @@ fn daemon(daemon: daemon::Daemon) -> ! { - let mut framebuffers = vec![unsafe { FrameBuffer::new(phys, width, height, stride) }]; - - //TODO: ideal maximum number of outputs? -- let bootloader_env = std::fs::read_to_string("/scheme/sys/env") -- .expect("failed to read env") -- .lines() -- .map(|line| { -- let (env, value) = line.split_once('=').unwrap(); -- (env.to_owned(), value.to_owned()) -- }) -- .collect::>(); -+ let bootloader_env: HashMap = -+ match std::fs::read_to_string("/scheme/sys/env") { -+ Ok(content) => content -+ .lines() -+ .filter_map(|line| { -+ let (env, value) = line.split_once('=')?; -+ Some((env.to_owned(), value.to_owned())) -+ }) -+ .collect(), -+ Err(err) => { -+ eprintln!("vesad: failed to read bootloader env: {}", err); -+ HashMap::new() -+ } -+ }; - for i in 1..1024 { - match bootloader_env.get(&format!("FRAMEBUFFER{}", i)) { - Some(var) => match unsafe { FrameBuffer::parse(&var) } { -@@ -93,38 +123,51 @@ fn daemon(daemon: daemon::Daemon) -> ! { - } - } - -- let event_queue: EventQueue = -- EventQueue::new().expect("vesad: failed to create event queue"); -- event_queue -- .subscribe( -- scheme.inputd_event_handle().as_raw_fd() as usize, -- Source::Input, -- event::EventFlags::READ, -- ) -- .unwrap(); -- event_queue -- .subscribe( -- scheme.event_handle().raw(), -- Source::Scheme, -- event::EventFlags::READ, -- ) -- .unwrap(); -- -- libredox::call::setrens(0, 0).expect("vesad: failed to enter null namespace"); -+ let event_queue: EventQueue = match EventQueue::new() { -+ Ok(eq) => eq, -+ Err(err) => { -+ eprintln!("vesad: failed to create event queue: {}", err); -+ daemon.ready(); -+ std::process::exit(1); -+ } -+ }; -+ if let Err(err) = event_queue.subscribe( -+ scheme.inputd_event_handle().as_raw_fd() as usize, -+ Source::Input, -+ event::EventFlags::READ, -+ ) { -+ eprintln!("vesad: failed to subscribe to input events: {}", err); -+ } -+ if let Err(err) = event_queue.subscribe( -+ scheme.event_handle().raw(), -+ Source::Scheme, -+ event::EventFlags::READ, -+ ) { -+ eprintln!("vesad: failed to subscribe to scheme events: {}", err); -+ } -+ -+ if let Err(err) = libredox::call::setrens(0, 0) { -+ eprintln!("vesad: failed to enter null namespace: {}", err); -+ } - - daemon.ready(); - - let all = [Source::Input, Source::Scheme]; -- for event in all -- .into_iter() -- .chain(event_queue.map(|e| e.expect("vesad: failed to get next event").user_data)) -- { -+ for event in all.into_iter().chain(event_queue.filter_map(|e| { -+ match e { -+ Ok(ev) => Some(ev.user_data), -+ Err(err) => { -+ eprintln!("vesad: failed to get next event: {}", err); -+ None -+ } -+ } -+ })) { - match event { - Source::Input => scheme.handle_vt_events(), - Source::Scheme => { -- scheme -- .tick() -- .expect("vesad: failed to handle scheme events"); -+ if let Err(err) = scheme.tick() { -+ eprintln!("vesad: failed to handle scheme events: {}", err); -+ } - } - } - } -diff --git a/drivers/graphics/vesad/src/scheme.rs b/drivers/graphics/vesad/src/scheme.rs -index 5bf2be91..20d755d2 100644 ---- a/drivers/graphics/vesad/src/scheme.rs -+++ b/drivers/graphics/vesad/src/scheme.rs -@@ -74,7 +74,17 @@ impl GraphicsAdapter for FbAdapter { - } - - fn probe_connector(&mut self, objects: &mut KmsObjects, id: KmsObjectId) { -- let mut connector = objects.get_connector(id).unwrap().lock().unwrap(); -+ let connector_mutex = match objects.get_connector(id) { -+ Ok(c) => c, -+ Err(err) => { -+ eprintln!("vesad: probe_connector: connector {:?} not found: {}", id, err); -+ return; -+ } -+ }; -+ let mut connector = connector_mutex.lock().unwrap_or_else(|e| { -+ eprintln!("vesad: probe_connector: connector lock poisoned, recovering"); -+ e.into_inner() -+ }); - let connector = &mut *connector; - connector.connection = KmsConnectorStatus::Connected; - connector.update_from_size(connector.driver_data.width, connector.driver_data.height); -@@ -102,7 +112,10 @@ impl GraphicsAdapter for FbAdapter { - state: KmsCrtcState, - damage: Damage, - ) -> syscall::Result<()> { -- let mut crtc = crtc.lock().unwrap(); -+ let mut crtc = crtc.lock().unwrap_or_else(|e| { -+ eprintln!("vesad: set_crtc: crtc lock poisoned, recovering"); -+ e.into_inner() -+ }); - let buffer = state - .fb_id - .map(|fb_id| objects.get_framebuffer(fb_id)) -@@ -110,7 +123,10 @@ impl GraphicsAdapter for FbAdapter { - crtc.state = state; - - for connector in objects.connectors() { -- let connector = connector.lock().unwrap(); -+ let connector = connector.lock().unwrap_or_else(|e| { -+ eprintln!("vesad: set_crtc: connector lock poisoned, recovering"); -+ e.into_inner() -+ }); - - if connector.state.crtc_id != objects.crtc_ids()[crtc.crtc_index as usize] { - continue; -@@ -159,7 +175,7 @@ pub struct FrameBuffer { - impl FrameBuffer { - pub unsafe fn new(phys: usize, width: usize, height: usize, stride: usize) -> Self { - let size = stride * height; -- let virt = common::physmap( -+ let virt = match common::physmap( - phys, - size * 4, - common::Prot { -@@ -167,8 +183,13 @@ impl FrameBuffer { - write: true, - }, - common::MemoryType::WriteCombining, -- ) -- .expect("vesad: failed to map framebuffer") as *mut u32; -+ ) { -+ Ok(v) => v as *mut u32, -+ Err(err) => { -+ eprintln!("vesad: failed to map framebuffer at 0x{:X}: {}", phys, err); -+ std::process::exit(1); -+ } -+ }; - - let onscreen = ptr::slice_from_raw_parts_mut(virt, size); - -@@ -228,9 +249,11 @@ impl GraphicScreen { - fn layout(len: usize) -> Layout { - // optimizes to an integer mul - Layout::array::(len) -- .unwrap() -- .align_to(PAGE_SIZE) -- .unwrap() -+ .and_then(|l| l.align_to(PAGE_SIZE)) -+ .unwrap_or_else(|err| { -+ eprintln!("vesad: failed to compute buffer layout (len={}): {}", len, err); -+ std::process::exit(1); -+ }) - } - } - -@@ -249,15 +272,26 @@ impl Buffer for GraphicScreen { - - impl GraphicScreen { - fn sync(&self, framebuffer: &mut FrameBuffer, sync_rect: Damage) { -- let sync_rect = sync_rect.clip( -- self.width.try_into().unwrap(), -- self.height.try_into().unwrap(), -- ); -- -- let start_x: usize = sync_rect.x.try_into().unwrap(); -- let start_y: usize = sync_rect.y.try_into().unwrap(); -- let w: usize = sync_rect.width.try_into().unwrap(); -- let h: usize = sync_rect.height.try_into().unwrap(); -+ let Ok(fb_width) = u32::try_from(self.width) else { -+ return; -+ }; -+ let Ok(fb_height) = u32::try_from(self.height) else { -+ return; -+ }; -+ let sync_rect = sync_rect.clip(fb_width, fb_height); -+ -+ let Ok(start_x): Result = sync_rect.x.try_into() else { -+ return; -+ }; -+ let Ok(start_y): Result = sync_rect.y.try_into() else { -+ return; -+ }; -+ let Ok(w): Result = sync_rect.width.try_into() else { -+ return; -+ }; -+ let Ok(h): Result = sync_rect.height.try_into() else { -+ return; -+ }; - - let offscreen_ptr = self.ptr.as_ptr() as *mut u32; - let onscreen_ptr = framebuffer.onscreen as *mut u32; // FIXME use as_mut_ptr once stable -diff --git a/drivers/graphics/virtio-gpud/src/main.rs b/drivers/graphics/virtio-gpud/src/main.rs -index b27f4c56..f3514c8e 100644 ---- a/drivers/graphics/virtio-gpud/src/main.rs -+++ b/drivers/graphics/virtio-gpud/src/main.rs -@@ -482,8 +482,11 @@ fn main() { - } - - fn daemon_runner(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { -- deamon(daemon, pcid_handle).unwrap(); -- unreachable!(); -+ deamon(daemon, pcid_handle).unwrap_or_else(|err| { -+ log::error!("virtio-gpud: daemon failed: {err}"); -+ std::process::exit(1); -+ }); -+ std::process::exit(0); - } - - fn deamon(deamon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> anyhow::Result<()> { -@@ -500,7 +503,10 @@ fn deamon(deamon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> anyhow: - // 0x1050 - virtio-gpu - let pci_config = pcid_handle.config(); - -- assert_eq!(pci_config.func.full_device_id.device_id, 0x1050); -+ if pci_config.func.full_device_id.device_id != 0x1050 { -+ log::error!("virtio-gpud: unexpected device ID {:#06x}, expected 0x1050", pci_config.func.full_device_id.device_id); -+ std::process::exit(1); -+ } - log::info!("virtio-gpu: initiating startup sequence :^)"); - - let device = DEVICE.try_call_once(|| virtio_core::probe_device(&mut pcid_handle))?; -@@ -531,7 +537,10 @@ fn deamon(deamon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> anyhow: - // /scheme/event as it is already blocked on opening /scheme/display.virtio-gpu. - // FIXME change the initnsmgr to not block on openat for the target scheme. - let event_queue: EventQueue = -- EventQueue::new().expect("virtio-gpud: failed to create event queue"); -+ EventQueue::new().unwrap_or_else(|err| { -+ log::error!("virtio-gpud: failed to create event queue: {err}"); -+ std::process::exit(1); -+ }); - - let mut scheme = scheme::GpuScheme::new( - config, -@@ -556,33 +565,48 @@ fn deamon(deamon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> anyhow: - Source::Input, - event::EventFlags::READ, - ) -- .unwrap(); -+ .unwrap_or_else(|err| { -+ log::error!("virtio-gpud: failed to subscribe to input events: {err}"); -+ std::process::exit(1); -+ }); - event_queue - .subscribe( - scheme.event_handle().raw(), - Source::Scheme, - event::EventFlags::READ, - ) -- .unwrap(); -+ .unwrap_or_else(|err| { -+ log::error!("virtio-gpud: failed to subscribe to scheme events: {err}"); -+ std::process::exit(1); -+ }); - event_queue - .subscribe( - device.irq_handle.as_raw_fd() as usize, - Source::Interrupt, - event::EventFlags::READ, - ) -- .unwrap(); -+ .unwrap_or_else(|err| { -+ log::error!("virtio-gpud: failed to subscribe to interrupt events: {err}"); -+ std::process::exit(1); -+ }); - - let all = [Source::Input, Source::Scheme, Source::Interrupt]; - for event in all - .into_iter() -- .chain(event_queue.map(|e| e.expect("virtio-gpud: failed to get next event").user_data)) -+ .chain(event_queue.filter_map(|e| match e { -+ Ok(ev) => Some(ev.user_data), -+ Err(err) => { -+ log::error!("virtio-gpud: failed to get next event: {err}"); -+ None -+ } -+ })) - { - match event { - Source::Input => scheme.handle_vt_events(), - Source::Scheme => { -- scheme -- .tick() -- .expect("virtio-gpud: failed to process scheme events"); -+ if let Err(err) = scheme.tick() { -+ log::error!("virtio-gpud: failed to process scheme events: {err}"); -+ } - } - Source::Interrupt => loop { - let before_gen = device.transport.config_generation(); -@@ -591,7 +615,11 @@ fn deamon(deamon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> anyhow: - - if events & VIRTIO_GPU_EVENT_DISPLAY != 0 { - let (adapter, objects) = scheme.adapter_and_kms_objects_mut(); -- futures::executor::block_on(async { adapter.update_displays().await.unwrap() }); -+ futures::executor::block_on(async { -+ if let Err(err) = adapter.update_displays().await { -+ log::error!("virtio-gpud: failed to update displays: {err}"); -+ } -+ }); - for connector_id in objects.connector_ids().to_vec() { - adapter.probe_connector(objects, connector_id); - } -diff --git a/drivers/graphics/virtio-gpud/src/scheme.rs b/drivers/graphics/virtio-gpud/src/scheme.rs -index 22a985ee..c60facfd 100644 ---- a/drivers/graphics/virtio-gpud/src/scheme.rs -+++ b/drivers/graphics/virtio-gpud/src/scheme.rs -@@ -64,10 +64,23 @@ impl DrmBuffer for VirtGpuFramebuffer<'_> { - - impl Drop for VirtGpuFramebuffer<'_> { - fn drop(&mut self) { -- futures::executor::block_on(async { -- let request = Dma::new(ResourceUnref::new(self.id)).unwrap(); -+ let request = match Dma::new(ResourceUnref::new(self.id)) { -+ Ok(r) => r, -+ Err(err) => { -+ log::error!("virtio-gpud: failed to allocate unref request DMA: {err}"); -+ return; -+ } -+ }; -+ -+ let header = match Dma::new(ControlHeader::default()) { -+ Ok(h) => h, -+ Err(err) => { -+ log::error!("virtio-gpud: failed to allocate unref header DMA: {err}"); -+ return; -+ } -+ }; - -- let header = Dma::new(ControlHeader::default()).unwrap(); -+ futures::executor::block_on(async { - let command = ChainBuilder::new() - .chain(Buffer::new(&request)) - .chain(Buffer::new(&header).flags(DescriptorFlags::WRITE_ONLY)) -@@ -182,7 +195,9 @@ impl VirtGpuAdapter<'_> { - .build(); - - self.control_queue.send(command).await; -- assert!(response.header.ty == CommandTy::RespOkDisplayInfo); -+ if response.header.ty != CommandTy::RespOkDisplayInfo { -+ return Err(Error::Probe("unexpected display info response type")); -+ } - - Ok(response) - } -@@ -197,7 +212,9 @@ impl VirtGpuAdapter<'_> { - .build(); - - self.control_queue.send(command).await; -- assert!(response.header.ty == CommandTy::RespOkEdid); -+ if response.header.ty != CommandTy::RespOkEdid { -+ return Err(Error::Probe("unexpected EDID response type")); -+ } - - Ok(response) - } -@@ -212,7 +229,7 @@ impl VirtGpuAdapter<'_> { - ) { - //Transfering cursor resource to host - futures::executor::block_on(async { -- let transfer_request = Dma::new(XferToHost2d::new( -+ let transfer_request = match Dma::new(XferToHost2d::new( - cursor.id, - GpuRect { - x: 0, -@@ -221,14 +238,33 @@ impl VirtGpuAdapter<'_> { - height: 64, - }, - 0, -- )) -- .unwrap(); -- let header = self.send_request_fenced(transfer_request).await.unwrap(); -- assert_eq!(header.ty, CommandTy::RespOkNodata); -+ )) { -+ Ok(r) => r, -+ Err(err) => { -+ log::error!("virtio-gpud: failed to allocate cursor transfer DMA: {err}"); -+ return; -+ } -+ }; -+ let header = match self.send_request_fenced(transfer_request).await { -+ Ok(h) => h, -+ Err(err) => { -+ log::error!("virtio-gpud: failed to send cursor transfer: {err}"); -+ return; -+ } -+ }; -+ if header.ty != CommandTy::RespOkNodata { -+ log::error!("virtio-gpud: cursor transfer returned {:?}", header.ty); -+ } - }); - - //Update the cursor position -- let request = Dma::new(UpdateCursor::update_cursor(x, y, hot_x, hot_y, cursor.id)).unwrap(); -+ let request = match Dma::new(UpdateCursor::update_cursor(x, y, hot_x, hot_y, cursor.id)) { -+ Ok(r) => r, -+ Err(err) => { -+ log::error!("virtio-gpud: failed to allocate cursor update DMA: {err}"); -+ return; -+ } -+ }; - futures::executor::block_on(async { - let command = ChainBuilder::new().chain(Buffer::new(&request)).build(); - self.cursor_queue.send(command).await; -@@ -236,7 +272,13 @@ impl VirtGpuAdapter<'_> { - } - - fn move_cursor(&mut self, x: i32, y: i32) { -- let request = Dma::new(MoveCursor::move_cursor(x, y)).unwrap(); -+ let request = match Dma::new(MoveCursor::move_cursor(x, y)) { -+ Ok(r) => r, -+ Err(err) => { -+ log::error!("virtio-gpud: failed to allocate move cursor DMA: {err}"); -+ return; -+ } -+ }; - - futures::executor::block_on(async { - let command = ChainBuilder::new().chain(Buffer::new(&request)).build(); -@@ -246,7 +288,7 @@ impl VirtGpuAdapter<'_> { - - fn disable_cursor(&mut self) { - if self.hidden_cursor.is_none() { -- let (width, height) = self.hw_cursor_size().unwrap(); -+ let (width, height) = self.hw_cursor_size().unwrap_or((64, 64)); - let (cursor, stride) = self.create_dumb_buffer(width, height); - unsafe { - core::ptr::write_bytes( -@@ -257,7 +299,10 @@ impl VirtGpuAdapter<'_> { - } - self.hidden_cursor = Some(Arc::new(cursor)); - } -- let hidden_cursor = self.hidden_cursor.as_ref().unwrap().clone(); -+ let hidden_cursor = self.hidden_cursor.as_ref().unwrap_or_else(|| { -+ log::error!("virtio-gpud: hidden_cursor missing after initialization"); -+ std::process::exit(1); -+ }).clone(); - - self.update_cursor(&hidden_cursor, 0, 0, 0, 0); - } -@@ -280,7 +325,9 @@ impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> { - - fn init(&mut self, objects: &mut KmsObjects) { - futures::executor::block_on(async { -- self.update_displays().await.unwrap(); -+ if let Err(err) = self.update_displays().await { -+ log::error!("virtio-gpud: failed to update displays during init: {err}"); -+ } - }); - - for display_id in 0..self.config.num_scanouts.get() { -@@ -310,7 +357,13 @@ impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> { - - fn probe_connector(&mut self, objects: &mut KmsObjects, id: KmsObjectId) { - futures::executor::block_on(async { -- let mut connector = objects.get_connector(id).unwrap().lock().unwrap(); -+ let mut connector = match objects.get_connector(id) { -+ Ok(c) => c.lock().unwrap(), -+ Err(err) => { -+ log::error!("virtio-gpud: connector {:?} not found: {}", id, err); -+ return; -+ } -+ }; - let display = &self.displays[connector.driver_data.display_id as usize]; - - connector.connection = if display.enabled { -@@ -325,7 +378,10 @@ impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> { - drop(connector); - - let blob = objects.add_blob(display.edid.clone()); -- objects.get_connector(id).unwrap().lock().unwrap().edid = blob; -+ match objects.get_connector(id) { -+ Ok(c) => c.lock().unwrap().edid = blob, -+ Err(err) => log::error!("virtio-gpud: connector {:?} not found on second access: {}", id, err), -+ } - } else { - connector.update_from_size(display.width, display.height); - } -@@ -336,7 +392,13 @@ impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> { - futures::executor::block_on(async { - let bpp = 32; - let fb_size = width as usize * height as usize * bpp / 8; -- let sgl = sgl::Sgl::new(fb_size).unwrap(); -+ let sgl = match sgl::Sgl::new(fb_size) { -+ Ok(s) => s, -+ Err(err) => { -+ log::error!("virtio-gpud: failed to allocate SGL: {err}"); -+ std::process::exit(1); -+ } -+ }; - - unsafe { - core::ptr::write_bytes(sgl.as_ptr() as *mut u8, 255, fb_size); -@@ -345,22 +407,43 @@ impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> { - let res_id = ResourceId::alloc(); - - // Create a host resource using `VIRTIO_GPU_CMD_RESOURCE_CREATE_2D`. -- let request = Dma::new(ResourceCreate2d::new( -+ let request = match Dma::new(ResourceCreate2d::new( - res_id, - ResourceFormat::Bgrx, - width, - height, -- )) -- .unwrap(); -+ )) { -+ Ok(r) => r, -+ Err(err) => { -+ log::error!("virtio-gpud: failed to allocate create 2d DMA: {err}"); -+ std::process::exit(1); -+ } -+ }; - -- let header = self.send_request(request).await.unwrap(); -- assert_eq!(header.ty, CommandTy::RespOkNodata); -+ let header = match self.send_request(request).await { -+ Ok(h) => h, -+ Err(err) => { -+ log::error!("virtio-gpud: failed to send create 2d: {err}"); -+ std::process::exit(1); -+ } -+ }; -+ if header.ty != CommandTy::RespOkNodata { -+ log::error!("virtio-gpud: create 2d returned {:?}", header.ty); -+ std::process::exit(1); -+ } - - // Use the allocated framebuffer from the guest ram, and attach it as backing - // storage to the resource just created, using `VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING`. - -- let mut mem_entries = -- unsafe { Dma::zeroed_slice(sgl.chunks().len()).unwrap().assume_init() }; -+ let mut mem_entries = unsafe { -+ match Dma::zeroed_slice(sgl.chunks().len()) { -+ Ok(dma) => dma.assume_init(), -+ Err(err) => { -+ log::error!("virtio-gpud: failed to allocate mem entries DMA: {err}"); -+ std::process::exit(1); -+ } -+ } -+ }; - for (entry, chunk) in mem_entries.iter_mut().zip(sgl.chunks().iter()) { - *entry = MemEntry { - address: chunk.phys as u64, -@@ -369,9 +452,20 @@ impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> { - }; - } - -- let attach_request = -- Dma::new(AttachBacking::new(res_id, mem_entries.len() as u32)).unwrap(); -- let header = Dma::new(ControlHeader::default()).unwrap(); -+ let attach_request = match Dma::new(AttachBacking::new(res_id, mem_entries.len() as u32)) { -+ Ok(r) => r, -+ Err(err) => { -+ log::error!("virtio-gpud: failed to allocate attach backing DMA: {err}"); -+ std::process::exit(1); -+ } -+ }; -+ let header = match Dma::new(ControlHeader::default()) { -+ Ok(h) => h, -+ Err(err) => { -+ log::error!("virtio-gpud: failed to allocate attach header DMA: {err}"); -+ std::process::exit(1); -+ } -+ }; - let command = ChainBuilder::new() - .chain(Buffer::new(&attach_request)) - .chain(Buffer::new_unsized(&mem_entries)) -@@ -379,7 +473,9 @@ impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> { - .build(); - - self.control_queue.send(command).await; -- assert_eq!(header.ty, CommandTy::RespOkNodata); -+ if header.ty != CommandTy::RespOkNodata { -+ log::error!("virtio-gpud: attach backing returned {:?}", header.ty); -+ } - - ( - VirtGpuFramebuffer { -@@ -427,19 +523,27 @@ impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> { - let display_id = connector.driver_data.display_id; - - let Some(framebuffer) = framebuffer else { -- let scanout_request = Dma::new(SetScanout::new( -+ let scanout_request = match Dma::new(SetScanout::new( - display_id, - ResourceId::NONE, - GpuRect::new(0, 0, 0, 0), -- )) -- .unwrap(); -- let header = self.send_request(scanout_request).await.unwrap(); -- assert_eq!(header.ty, CommandTy::RespOkNodata); -+ )) { -+ Ok(r) => r, -+ Err(err) => { -+ log::error!("virtio-gpud: failed to allocate scanout clear DMA: {err}"); -+ return Ok(()); -+ } -+ }; -+ match self.send_request(scanout_request).await { -+ Ok(header) if header.ty == CommandTy::RespOkNodata => {} -+ Ok(header) => log::error!("virtio-gpud: scanout clear returned {:?}", header.ty), -+ Err(err) => log::error!("virtio-gpud: failed to send scanout clear: {err}"), -+ } - self.displays[display_id as usize].active_resource = None; - return Ok(()); - }; - -- let req = Dma::new(XferToHost2d::new( -+ let req = match Dma::new(XferToHost2d::new( - framebuffer.buffer.id, - GpuRect { - x: 0, -@@ -448,22 +552,38 @@ impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> { - height: framebuffer.height, - }, - 0, -- )) -- .unwrap(); -- let header = self.send_request(req).await.unwrap(); -- assert_eq!(header.ty, CommandTy::RespOkNodata); -+ )) { -+ Ok(r) => r, -+ Err(err) => { -+ log::error!("virtio-gpud: failed to allocate xfer DMA: {err}"); -+ return Ok(()); -+ } -+ }; -+ match self.send_request(req).await { -+ Ok(header) if header.ty == CommandTy::RespOkNodata => {} -+ Ok(header) => log::error!("virtio-gpud: xfer returned {:?}", header.ty), -+ Err(err) => log::error!("virtio-gpud: failed to send xfer: {err}"), -+ } - - // FIXME once we support resizing we also need to check that the current and target size match - if self.displays[display_id as usize].active_resource != Some(framebuffer.buffer.id) - { -- let scanout_request = Dma::new(SetScanout::new( -+ let scanout_request = match Dma::new(SetScanout::new( - display_id, - framebuffer.buffer.id, - GpuRect::new(0, 0, framebuffer.width, framebuffer.height), -- )) -- .unwrap(); -- let header = self.send_request(scanout_request).await.unwrap(); -- assert_eq!(header.ty, CommandTy::RespOkNodata); -+ )) { -+ Ok(r) => r, -+ Err(err) => { -+ log::error!("virtio-gpud: failed to allocate scanout DMA: {err}"); -+ return Ok(()); -+ } -+ }; -+ match self.send_request(scanout_request).await { -+ Ok(header) if header.ty == CommandTy::RespOkNodata => {} -+ Ok(header) => log::error!("virtio-gpud: scanout returned {:?}", header.ty), -+ Err(err) => log::error!("virtio-gpud: failed to send scanout: {err}"), -+ } - self.displays[display_id as usize].active_resource = - Some(framebuffer.buffer.id); - } -@@ -472,8 +592,18 @@ impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> { - framebuffer.buffer.id, - damage.clip(framebuffer.width, framebuffer.height).into(), - ); -- let header = self.send_request(Dma::new(flush).unwrap()).await.unwrap(); -- assert_eq!(header.ty, CommandTy::RespOkNodata); -+ let flush_dma = match Dma::new(flush) { -+ Ok(d) => d, -+ Err(err) => { -+ log::error!("virtio-gpud: failed to allocate flush DMA: {err}"); -+ return Ok(()); -+ } -+ }; -+ match self.send_request(flush_dma).await { -+ Ok(header) if header.ty == CommandTy::RespOkNodata => {} -+ Ok(header) => log::error!("virtio-gpud: flush returned {:?}", header.ty), -+ Err(err) => log::error!("virtio-gpud: failed to send flush: {err}"), -+ } - } - - Ok(()) -diff --git a/drivers/hwd/Cargo.toml b/drivers/hwd/Cargo.toml -index 3d37cfb3..40b51a1b 100644 ---- a/drivers/hwd/Cargo.toml -+++ b/drivers/hwd/Cargo.toml -@@ -6,6 +6,7 @@ edition = "2018" - - [dependencies] - fdt.workspace = true -+libc.workspace = true - log.workspace = true - ron.workspace = true - libredox = { workspace = true, default-features = false, features = ["std", "call"] } -diff --git a/drivers/hwd/src/backend/acpi.rs b/drivers/hwd/src/backend/acpi.rs -index 3da41d63..12d26261 100644 ---- a/drivers/hwd/src/backend/acpi.rs -+++ b/drivers/hwd/src/backend/acpi.rs -@@ -1,27 +1,36 @@ - use amlserde::{AmlSerde, AmlSerdeValue}; --use std::{error::Error, fs, process::Command}; -+use std::{error::Error, fs}; - - use super::Backend; - - pub struct AcpiBackend { -- rxsdt: Vec, -+ _rxsdt: Vec, - } - - impl Backend for AcpiBackend { - fn new() -> Result> { - let rxsdt = fs::read("/scheme/kernel.acpi/rxsdt")?; - -- // Spawn acpid -- //TODO: pass rxsdt data to acpid? -- #[allow(deprecated, reason = "we can't yet move this to init")] -- daemon::Daemon::spawn(Command::new("acpid")); -- -- Ok(Self { rxsdt }) -+ Ok(Self { _rxsdt: rxsdt }) - } - - fn probe(&mut self) -> Result<(), Box> { -+ let mut boot_critical_input_candidates = 0usize; -+ let mut thc_candidates = 0usize; -+ let mut non_hid_i2c_candidates = 0usize; -+ - // Read symbols from acpi scheme -- let entries = fs::read_dir("/scheme/acpi/symbols")?; -+ let entries = match fs::read_dir("/scheme/acpi/symbols") { -+ Ok(entries) => entries, -+ Err(err) -+ if err.kind() == std::io::ErrorKind::WouldBlock -+ || err.raw_os_error() == Some(11) => -+ { -+ log::debug!("hwd: ACPI symbols are not ready yet"); -+ return Ok(()); -+ } -+ Err(err) => return Err(Box::new(err)), -+ }; - // TODO: Reimplement with getdents? - let symbols_fd = libredox::Fd::open( - "/scheme/acpi/symbols", -@@ -100,12 +109,103 @@ impl Backend for AcpiBackend { - "PNP0C0F" => "PCI interrupt link", - "PNP0C50" => "I2C HID", - "PNP0F13" => "PS/2 port for PS/2-style mouse", -+ "80860F41" | "808622C1" => "DesignWare I2C controller", -+ "AMDI0010" | "AMDI0019" | "AMDI0510" => "AMD laptop I2C controller", -+ "INT33C2" | "INT33C3" | "INT3432" | "INT3433" | "INTC10EF" => { -+ "Intel LPSS/SerialIO I2C controller" -+ } -+ "INT34C5" | "INTC1055" => "Intel GPIO controller", -+ "INTC1050" | "INTC1051" | "INTC1080" | "INTC1081" | "INTC1082" => { -+ "Intel THC companion (QuickI2C/QuickSPI path)" -+ } -+ _ if is_elan_touchpad_id(&id) => "ELAN touchpad (I2C/SMBus path)", -+ _ if is_cypress_touchpad_id(&id) => "Cypress/Trackpad (non-HID I2C path)", -+ _ if is_synaptics_rmi_id(&id) => "Synaptics RMI touchpad (I2C/SMBus path)", - _ => "?", - }; - log::debug!("{}: {} ({})", name, id, what); -+ if is_boot_critical_i2c_surface(&id) { -+ boot_critical_input_candidates += 1; -+ log::info!("{}: {} is boot-critical for laptop input path", name, id); -+ } -+ if is_thc_companion(&id) { -+ thc_candidates += 1; -+ log::warn!( -+ "{}: {} indicates Intel THC path; DMA/report fast-path is not complete yet", -+ name, -+ id -+ ); -+ } -+ if is_non_hid_i2c_input_id(&id) { -+ non_hid_i2c_candidates += 1; -+ } - } - } - } -+ -+ if boot_critical_input_candidates == 0 { -+ log::warn!( -+ "hwd: no ACPI boot-critical I2C input candidates found; built-in laptop input may require additional controller/device support" -+ ); -+ } else { -+ log::info!( -+ "hwd: ACPI input candidates: total={} thc={} non_hid_i2c={}", -+ boot_critical_input_candidates, -+ thc_candidates, -+ non_hid_i2c_candidates -+ ); -+ } -+ - Ok(()) - } - } -+ -+fn is_boot_critical_i2c_surface(id: &str) -> bool { -+ matches!( -+ id, -+ "PNP0C50" -+ | "ACPI0C50" -+ | "80860F41" -+ | "808622C1" -+ | "AMDI0010" -+ | "AMDI0019" -+ | "AMDI0510" -+ | "INT33C2" -+ | "INT33C3" -+ | "INT3432" -+ | "INT3433" -+ | "INTC10EF" -+ | "INT34C5" -+ | "INTC1055" -+ | "INTC1050" -+ | "INTC1051" -+ | "INTC1080" -+ | "INTC1081" -+ | "INTC1082" -+ ) || is_elan_touchpad_id(id) -+ || is_cypress_touchpad_id(id) -+ || is_synaptics_rmi_id(id) -+} -+ -+fn is_thc_companion(id: &str) -> bool { -+ matches!( -+ id, -+ "INTC1050" | "INTC1051" | "INTC1080" | "INTC1081" | "INTC1082" -+ ) -+} -+ -+fn is_elan_touchpad_id(id: &str) -> bool { -+ id.starts_with("ELAN") -+} -+ -+fn is_cypress_touchpad_id(id: &str) -> bool { -+ id.starts_with("CYAP") -+} -+ -+fn is_synaptics_rmi_id(id: &str) -> bool { -+ id.starts_with("SYNA") -+} -+ -+fn is_non_hid_i2c_input_id(id: &str) -> bool { -+ is_elan_touchpad_id(id) || is_cypress_touchpad_id(id) || is_synaptics_rmi_id(id) -+} -diff --git a/drivers/hwd/src/main.rs b/drivers/hwd/src/main.rs -index 79360e34..a0462f51 100644 ---- a/drivers/hwd/src/main.rs -+++ b/drivers/hwd/src/main.rs -@@ -1,3 +1,5 @@ -+use std::os::fd::AsRawFd; -+use std::os::unix::process::CommandExt; - use std::process; - - mod backend; -@@ -37,8 +39,34 @@ fn daemon(daemon: daemon::Daemon) -> ! { - - //TODO: launch pcid based on backend information? - // Must launch after acpid but before probe calls /scheme/acpi/symbols -- #[allow(deprecated, reason = "we can't yet move this to init")] -- daemon::Daemon::spawn(process::Command::new("pcid")); -+ // Fire-and-forget: daemon::Daemon::spawn blocks until pcid signals readiness, -+ // but pcid only signals after full PCI enumeration. If enumeration hangs on -+ // real hardware (unresponsive device, complex AML), hwd deadlocks initfs. -+ { -+ match std::io::pipe() { -+ Ok((_read_end, write_end)) => { -+ let write_fd: std::os::fd::OwnedFd = write_end.into(); -+ let raw_fd = write_fd.as_raw_fd(); -+ let mut cmd = std::process::Command::new("pcid"); -+ cmd.env("INIT_NOTIFY", raw_fd.to_string()); -+ unsafe { -+ cmd.pre_exec(move || { -+ if libc::fcntl(raw_fd, libc::F_SETFD, 0) == -1 { -+ return Err(std::io::Error::last_os_error()); -+ } -+ Ok(()) -+ }); -+ } -+ match cmd.spawn() { -+ Ok(_) => {} -+ Err(err) => log::error!("hwd: failed to spawn pcid: {}", err), -+ } -+ } -+ Err(err) => { -+ log::error!("hwd: failed to create pcid notification pipe: {}", err); -+ } -+ } -+ } - - daemon.ready(); - -diff --git a/drivers/input/ps2d/src/controller.rs b/drivers/input/ps2d/src/controller.rs -index d7af4cba..638b7cc1 100644 ---- a/drivers/input/ps2d/src/controller.rs -+++ b/drivers/input/ps2d/src/controller.rs -@@ -97,6 +97,14 @@ enum KeyboardCommandData { - const DEFAULT_TIMEOUT: u64 = 50_000; - // Reset timeout in microseconds - const RESET_TIMEOUT: u64 = 1_000_000; -+// Maximum bytes to drain during flush (Linux: I8042_BUFFER_SIZE) -+const FLUSH_LIMIT: usize = 4096; -+// Controller self-test pass value (Linux: I8042_RET_CTL_TEST) -+const SELFTEST_PASS: u8 = 0x55; -+// Controller self-test retries (Linux: 5 attempts) -+const SELFTEST_RETRIES: usize = 5; -+// AUX port test pass value (Linux returns 0x00 on success) -+const AUX_TEST_PASS: u8 = 0x00; - - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - pub struct Ps2 { -@@ -271,6 +279,50 @@ impl Ps2 { - } - } - -+ /// Drain all pending bytes from the controller output buffer. -+ /// Borrowed from Linux i8042_flush(): stale firmware/BIOS bytes can be -+ /// misinterpreted as device responses during initialization. -+ fn flush(&mut self) -> usize { -+ let mut count = 0; -+ while self.status().contains(StatusFlags::OUTPUT_FULL) { -+ if count >= FLUSH_LIMIT { -+ warn!("flush: exceeded limit, controller may be stuck"); -+ break; -+ } -+ let data = self.data.read(); -+ trace!("flush: discarded {:02X}", data); -+ count += 1; -+ } -+ if count > 0 { -+ debug!("flushed {} stale bytes from controller", count); -+ } -+ count -+ } -+ -+ /// Test the AUX (mouse) port via controller command 0xA9. -+ /// Borrowed from Linux: verifies electrical connectivity before -+ /// attempting to talk to the mouse. Returns true if the port passed. -+ fn test_aux_port(&mut self) -> bool { -+ if let Err(err) = self.command(Command::TestSecond) { -+ warn!("aux port test command failed: {:?}", err); -+ return false; -+ } -+ match self.read() { -+ Ok(AUX_TEST_PASS) => { -+ debug!("aux port test passed"); -+ true -+ } -+ Ok(val) => { -+ warn!("aux port test failed: {:02X}", val); -+ false -+ } -+ Err(err) => { -+ warn!("aux port test read timeout: {:?}", err); -+ false -+ } -+ } -+ } -+ - pub fn init_keyboard(&mut self) -> Result<(), Error> { - let mut b; - -@@ -308,66 +360,125 @@ impl Ps2 { - } - - pub fn init(&mut self) -> Result<(), Error> { -+ // Linux i8042_controller_check(): verify controller is present by -+ // flushing any stale data. A stuck output buffer means no controller. -+ self.flush(); -+ -+ // Bare-metal controllers may be slow after firmware handoff. -+ // Give the controller a moment to finish POST before sending commands. -+ std::thread::sleep(std::time::Duration::from_millis(50)); -+ - { -- // Disable devices -- self.command(Command::DisableFirst)?; -- self.command(Command::DisableSecond)?; -+ // Disable both ports first — use retry because the controller -+ // may still be settling or temporarily unresponsive. -+ // Failure here is non-fatal: we continue and attempt the rest -+ // of initialization. A truly absent controller will fail later -+ // at self-test or keyboard reset. -+ if let Err(err) = self.retry( -+ format_args!("disable first port"), -+ 3, -+ |x| x.command(Command::DisableFirst), -+ ) { -+ warn!("disable first port failed: {:?}", err); -+ } -+ if let Err(err) = self.retry( -+ format_args!("disable second port"), -+ 3, -+ |x| x.command(Command::DisableSecond), -+ ) { -+ warn!("disable second port failed: {:?}", err); -+ } - } - -- // Disable clocks, disable interrupts, and disable translate -+ // Flush again after disabling — firmware may have queued more bytes -+ self.flush(); -+ -+ // Linux i8042_controller_init() step 1: write a known-safe config -+ // (interrupts off, both ports disabled) so stale config can't cause -+ // spurious interrupts during the rest of init. - { -- // Since the default config may have interrupts enabled, and the kernel may eat up -- // our data in that case, we will write a config without reading the current one - let config = ConfigFlags::POST_PASSED - | ConfigFlags::FIRST_DISABLED - | ConfigFlags::SECOND_DISABLED; - self.set_config(config)?; - } - -- // The keyboard seems to still collect bytes even when we disable -- // the port, so we must disable the keyboard too -+ // Linux i8042_controller_selftest(): retry up to 5 times with delay. -+ // "On some really fragile systems this does not take the first time." -+ { -+ let mut passed = false; -+ for attempt in 0..SELFTEST_RETRIES { -+ if let Err(err) = self.command(Command::TestController) { -+ warn!("self-test command failed (attempt {}): {:?}", attempt + 1, err); -+ continue; -+ } -+ match self.read() { -+ Ok(SELFTEST_PASS) => { -+ passed = true; -+ break; -+ } -+ Ok(val) => { -+ warn!( -+ "self-test unexpected value {:02X} (attempt {}/{})", -+ val, -+ attempt + 1, -+ SELFTEST_RETRIES -+ ); -+ } -+ Err(err) => { -+ warn!("self-test read timeout (attempt {}): {:?}", attempt + 1, err); -+ } -+ } -+ // Linux: msleep(50) between retries -+ std::thread::sleep(std::time::Duration::from_millis(50)); -+ } -+ if !passed { -+ // Linux on x86: "giving up on controller selftest, continuing anyway" -+ warn!("controller self-test did not pass after {} attempts, continuing", SELFTEST_RETRIES); -+ } -+ } -+ -+ // Flush any bytes the self-test may have left behind -+ self.flush(); -+ -+ // Linux i8042_controller_init() step 2: set keyboard defaults -+ // (disable scanning so keyboard doesn't send scancodes during init) - self.retry(format_args!("keyboard defaults"), 4, |x| { -- // Set defaults and disable scanning - let b = x.keyboard_command(KeyboardCommand::SetDefaultsDisable)?; - if b != 0xFA { - error!("keyboard failed to set defaults: {:02X}", b); - return Err(Error::CommandRetry); - } -- - Ok(b) - })?; - -- { -- // Perform the self test -- self.command(Command::TestController)?; -- let r = self.read()?; -- if r != 0x55 { -- warn!("self test unexpected value: {:02X}", r); -- } -- } -- - // Initialize keyboard - if let Err(err) = self.init_keyboard() { - error!("failed to initialize keyboard: {:?}", err); - return Err(err); - } - -- // Enable second device -- let enable_mouse = match self.command(Command::EnableSecond) { -- Ok(()) => true, -- Err(err) => { -- error!("failed to initialize mouse: {:?}", err); -- false -+ // Linux: test AUX port (command 0xA9) before enabling. -+ // Skips mouse init entirely if the port is not electrically present. -+ let aux_ok = self.test_aux_port(); -+ -+ // Enable second device (mouse) only if AUX port tested OK -+ let enable_mouse = if aux_ok { -+ match self.command(Command::EnableSecond) { -+ Ok(()) => true, -+ Err(err) => { -+ warn!("failed to enable aux port after test passed: {:?}", err); -+ false -+ } - } -+ } else { -+ info!("skipping mouse init: aux port test did not pass"); -+ false - }; - - { -- // Enable keyboard data reporting -- // Use inner function to prevent retries -- // Response is ignored since scanning is now on - if let Err(err) = self.keyboard_command_inner(KeyboardCommand::EnableReporting as u8) { - error!("failed to initialize keyboard reporting: {:?}", err); -- //TODO: fix by using interrupts? - } - } - -diff --git a/drivers/input/ps2d/src/main.rs b/drivers/input/ps2d/src/main.rs -index db17de2a..1ae055e4 100644 ---- a/drivers/input/ps2d/src/main.rs -+++ b/drivers/input/ps2d/src/main.rs -@@ -11,7 +11,7 @@ use std::process; - - use common::acquire_port_io_rights; - use event::{user_data, EventQueue}; --use inputd::ProducerHandle; -+use inputd::InputProducer; - - use crate::state::Ps2d; - -@@ -31,7 +31,10 @@ fn daemon(daemon: daemon::Daemon) -> ! { - - acquire_port_io_rights().expect("ps2d: failed to get I/O permission"); - -- let input = ProducerHandle::new().expect("ps2d: failed to open input producer"); -+ let keyboard_input = InputProducer::new_named_or_fallback("ps2-keyboard") -+ .expect("ps2d: failed to open input producer"); -+ let mouse_input = InputProducer::new_named_or_fallback("ps2-mouse") -+ .expect("ps2d: failed to open input producer"); - - user_data! { - enum Source { -@@ -93,7 +96,7 @@ fn daemon(daemon: daemon::Daemon) -> ! { - - daemon.ready(); - -- let mut ps2d = Ps2d::new(input, time_file); -+ let mut ps2d = Ps2d::new(keyboard_input, mouse_input, time_file); - - let mut data = [0; 256]; - for event in event_queue.map(|e| e.expect("ps2d: failed to get next event").user_data) { -diff --git a/drivers/input/ps2d/src/mouse.rs b/drivers/input/ps2d/src/mouse.rs -index 9e95ab88..8087c8c4 100644 ---- a/drivers/input/ps2d/src/mouse.rs -+++ b/drivers/input/ps2d/src/mouse.rs -@@ -5,6 +5,11 @@ pub const RESET_RETRIES: usize = 10; - pub const RESET_TIMEOUT: Duration = Duration::from_millis(1000); - pub const COMMAND_TIMEOUT: Duration = Duration::from_millis(100); - -+const CMD_ACK: u8 = 0xFA; -+const CMD_RESEND: u8 = 0xFE; -+const BAT_COMPLETE: u8 = 0xAA; -+const BAT_FAIL: u8 = 0xFC; -+ - #[derive(Clone, Copy, Debug)] - #[repr(u8)] - #[allow(dead_code)] -@@ -58,9 +63,11 @@ impl MouseTx { - - fn handle(&mut self, data: u8, ps2: &mut Ps2) -> Result { - if self.write_i < self.write.len() { -- if data == 0xFA { -+ if data == CMD_ACK { - self.write_i += 1; - self.try_write(ps2)?; -+ } else if data == CMD_RESEND { -+ self.try_write(ps2)?; - } else { - log::error!("unknown mouse response {:02X}", data); - return Err(()); -@@ -251,25 +258,43 @@ impl MouseState { - MouseResult::None - } - MouseState::Reset => { -- if data == 0xFA { -- log::debug!("mouse reset ok"); -+ if data == CMD_ACK { -+ log::debug!("mouse reset ack"); - MouseResult::Timeout(RESET_TIMEOUT) -- } else if data == 0xAA { -+ } else if data == BAT_COMPLETE { - log::debug!("BAT completed"); - *self = MouseState::Bat; - MouseResult::Timeout(COMMAND_TIMEOUT) -+ } else if data == CMD_RESEND { -+ // Device asks us to resend the reset command (0xFF). -+ // Resend WITHOUT incrementing the retry counter — 0xFE is -+ // a normal protocol response, not a failure. -+ log::debug!("mouse requests resend during reset, resending 0xFF"); -+ match ps2.mouse_command_async(MouseCommand::Reset as u8) { -+ Ok(()) => MouseResult::Timeout(RESET_TIMEOUT), -+ Err(err) => { -+ log::error!("failed to resend mouse reset: {:?}", err); -+ self.reset(ps2) -+ } -+ } -+ } else if data == BAT_FAIL { -+ log::warn!("mouse BAT failed (0xFC)"); -+ self.reset(ps2) - } else { - log::warn!("unknown mouse response {:02X} after reset", data); - self.reset(ps2) - } - } - MouseState::Bat => { -- if data == MouseId::Base as u8 { -- // Enable intellimouse features -+ if data == CMD_RESEND { -+ // 0xFE after BAT is unusual — the device may be re-issuing -+ // BAT. Wait for the next byte (device ID or another BAT). -+ log::debug!("mouse resend (0xFE) during BAT, waiting"); -+ MouseResult::Timeout(COMMAND_TIMEOUT) -+ } else if data == MouseId::Base as u8 { - log::debug!("BAT mouse id {:02X} (base)", data); - self.identify_touchpad(ps2) - } else if data == MouseId::Intellimouse1 as u8 { -- // Extra packet already enabled - log::debug!("BAT mouse id {:02X} (intellimouse)", data); - self.enable_reporting(data, ps2) - } else { -@@ -320,10 +345,17 @@ impl MouseState { - } - } - MouseState::DeviceId => { -- if data == 0xFA { -- // Command OK response -- //TODO: handle this separately? -+ if data == CMD_ACK { - MouseResult::Timeout(COMMAND_TIMEOUT) -+ } else if data == CMD_RESEND { -+ log::debug!("mouse resend during DeviceId, resending GetDeviceId"); -+ match ps2.mouse_command_async(MouseCommand::GetDeviceId as u8) { -+ Ok(()) => MouseResult::Timeout(COMMAND_TIMEOUT), -+ Err(err) => { -+ log::error!("failed to resend GetDeviceId: {:?}", err); -+ self.reset(ps2) -+ } -+ } - } else if data == MouseId::Base as u8 || data == MouseId::Intellimouse1 as u8 { - log::debug!("mouse id {:02X}", data); - self.enable_reporting(data, ps2) -@@ -333,10 +365,28 @@ impl MouseState { - } - } - MouseState::EnableReporting { id } => { -- log::debug!("mouse id {:02X} enable reporting {:02X}", id, data); -- //TODO: handle response ok/error -- *self = MouseState::Streaming { id }; -- MouseResult::None -+ if data == CMD_ACK { -+ log::debug!("mouse id {:02X} reporting enabled", id); -+ *self = MouseState::Streaming { id }; -+ MouseResult::None -+ } else if data == CMD_RESEND { -+ log::debug!("mouse resend during EnableReporting, resending 0xF4"); -+ match ps2.mouse_command_async(MouseCommand::EnableReporting as u8) { -+ Ok(()) => MouseResult::Timeout(COMMAND_TIMEOUT), -+ Err(err) => { -+ log::error!("failed to resend EnableReporting: {:?}", err); -+ *self = MouseState::Streaming { id }; -+ MouseResult::None -+ } -+ } -+ } else { -+ log::warn!( -+ "unexpected mouse response {:02X} during enable reporting, streaming anyway", -+ data -+ ); -+ *self = MouseState::Streaming { id }; -+ MouseResult::None -+ } - } - MouseState::Streaming { id } => { - MouseResult::Packet(data, id == MouseId::Intellimouse1 as u8) -diff --git a/drivers/input/ps2d/src/state.rs b/drivers/input/ps2d/src/state.rs -index 9018dc6b..da304e05 100644 ---- a/drivers/input/ps2d/src/state.rs -+++ b/drivers/input/ps2d/src/state.rs -@@ -1,4 +1,4 @@ --use inputd::ProducerHandle; -+use inputd::InputProducer; - use log::{error, warn}; - use orbclient::{ButtonEvent, KeyEvent, MouseEvent, MouseRelativeEvent, ScrollEvent}; - use std::{ -@@ -44,7 +44,8 @@ pub struct Ps2d { - ps2: Ps2, - vmmouse: bool, - vmmouse_relative: bool, -- input: ProducerHandle, -+ keyboard_input: InputProducer, -+ mouse_input: InputProducer, - time_file: File, - extended: bool, - mouse_x: i32, -@@ -59,9 +60,11 @@ pub struct Ps2d { - } - - impl Ps2d { -- pub fn new(input: ProducerHandle, time_file: File) -> Self { -+ pub fn new(keyboard_input: InputProducer, mouse_input: InputProducer, time_file: File) -> Self { - let mut ps2 = Ps2::new(); -- ps2.init().expect("failed to initialize"); -+ if let Err(err) = ps2.init() { -+ log::error!("ps2d: controller init failed: {:?}", err); -+ } - - // FIXME add an option for orbital to disable this when an app captures the mouse. - let vmmouse_relative = false; -@@ -77,7 +80,8 @@ impl Ps2d { - ps2, - vmmouse, - vmmouse_relative, -- input, -+ keyboard_input, -+ mouse_input, - time_file, - extended: false, - mouse_x: 0, -@@ -273,7 +277,7 @@ impl Ps2d { - }; - - if scancode != 0 { -- self.input -+ self.keyboard_input - .write_event( - KeyEvent { - character: '\0', -@@ -304,7 +308,7 @@ impl Ps2d { - - if self.vmmouse_relative { - if dx != 0 || dy != 0 { -- self.input -+ self.mouse_input - .write_event( - MouseRelativeEvent { - dx: dx as i32, -@@ -320,14 +324,14 @@ impl Ps2d { - if x != self.mouse_x || y != self.mouse_y { - self.mouse_x = x; - self.mouse_y = y; -- self.input -+ self.mouse_input - .write_event(MouseEvent { x, y }.to_event()) - .expect("ps2d: failed to write mouse event"); - } - }; - - if dz != 0 { -- self.input -+ self.mouse_input - .write_event( - ScrollEvent { - x: 0, -@@ -348,7 +352,7 @@ impl Ps2d { - self.mouse_left = left; - self.mouse_middle = middle; - self.mouse_right = right; -- self.input -+ self.mouse_input - .write_event( - ButtonEvent { - left, -@@ -441,13 +445,13 @@ impl Ps2d { - } - - if dx != 0 || dy != 0 { -- self.input -+ self.mouse_input - .write_event(MouseRelativeEvent { dx, dy }.to_event()) - .expect("ps2d: failed to write mouse event"); - } - - if dz != 0 { -- self.input -+ self.mouse_input - .write_event(ScrollEvent { x: 0, y: dz }.to_event()) - .expect("ps2d: failed to write scroll event"); - } -@@ -462,7 +466,7 @@ impl Ps2d { - self.mouse_left = left; - self.mouse_middle = middle; - self.mouse_right = right; -- self.input -+ self.mouse_input - .write_event( - ButtonEvent { - left, -diff --git a/drivers/input/usbhidd/src/main.rs b/drivers/input/usbhidd/src/main.rs -index 15c5b778..706c4008 100644 ---- a/drivers/input/usbhidd/src/main.rs -+++ b/drivers/input/usbhidd/src/main.rs -@@ -1,7 +1,7 @@ - use anyhow::{Context, Result}; - use std::{env, thread, time}; - --use inputd::ProducerHandle; -+use inputd::InputProducer; - use orbclient::KeyEvent as OrbKeyEvent; - use rehid::{ - report_desc::{ReportTy, REPORT_DESC_TY}, -@@ -15,7 +15,7 @@ use xhcid_interface::{ - - mod reqs; - --fn send_key_event(display: &mut ProducerHandle, usage_page: u16, usage: u16, pressed: bool) { -+fn send_key_event(display: &mut InputProducer, usage_page: u16, usage: u16, pressed: bool) { - let scancode = match usage_page { - 0x07 => match usage { - 0x04 => orbclient::K_A, -@@ -272,7 +272,9 @@ fn main() -> Result<()> { - let report_ty = ReportTy::Input; - let report_id = 0; - -- let mut display = ProducerHandle::new().context("Failed to open input socket")?; -+ let producer_name = format!("usb-{}-if{}", port, interface_num); -+ let mut display = InputProducer::new_named_or_fallback(&producer_name) -+ .context("Failed to open input socket")?; - let mut endpoint_opt = match endp_desc_opt { - Some((endp_num, _endp_desc)) => match handle.open_endpoint(endp_num as u8) { - Ok(ok) => Some(ok), -diff --git a/drivers/inputd/src/lib.rs b/drivers/inputd/src/lib.rs -index b68e8211..f07a411d 100644 ---- a/drivers/inputd/src/lib.rs -+++ b/drivers/inputd/src/lib.rs -@@ -64,25 +64,53 @@ impl ConsumerHandle { - let fd = self.0.as_raw_fd(); - let written = libredox::call::fpath(fd as usize, &mut buffer)?; - -- assert!(written <= buffer.len()); -- -- let mut display_path = PathBuf::from( -- std::str::from_utf8(&buffer[..written]) -- .expect("init: display path UTF-8 check failed") -- .to_owned(), -- ); -- display_path.set_file_name(format!( -- "v2/{}", -- display_path.file_name().unwrap().to_str().unwrap() -- )); -- let display_path = display_path.to_str().unwrap(); -+ if written > buffer.len() { -+ return Err(io::Error::new( -+ io::ErrorKind::InvalidData, -+ "inputd: display path exceeded buffer size", -+ )); -+ } -+ -+ let path_str = std::str::from_utf8(&buffer[..written]).map_err(|e| { -+ io::Error::new( -+ io::ErrorKind::InvalidData, -+ format!("inputd: display path is not valid UTF-8: {e}"), -+ ) -+ })?; -+ let mut display_path = PathBuf::from(path_str.to_owned()); -+ -+ let file_name = display_path -+ .file_name() -+ .and_then(|n| n.to_str()) -+ .ok_or_else(|| { -+ io::Error::new( -+ io::ErrorKind::InvalidData, -+ format!( -+ "inputd: display path has no valid file name: {}", -+ display_path.display() -+ ), -+ ) -+ })?; -+ display_path.set_file_name(format!("v2/{file_name}")); -+ let display_path_str = display_path.to_str().ok_or_else(|| { -+ io::Error::new( -+ io::ErrorKind::InvalidData, -+ format!( -+ "inputd: constructed display path is not valid UTF-8: {}", -+ display_path.display() -+ ), -+ ) -+ })?; - - let display_file = -- libredox::call::open(display_path, (O_CLOEXEC | O_NONBLOCK | O_RDWR) as _, 0) -+ libredox::call::open(display_path_str, (O_CLOEXEC | O_NONBLOCK | O_RDWR) as _, 0) - .map(|socket| unsafe { File::from_raw_fd(socket as RawFd) }) -- .unwrap_or_else(|err| { -- panic!("failed to open display {}: {}", display_path, err); -- }); -+ .map_err(|err| { -+ io::Error::new( -+ io::ErrorKind::Other, -+ format!("inputd: failed to open display {display_path_str}: {err}"), -+ ) -+ })?; - - Ok(display_file) - } -@@ -152,8 +180,12 @@ impl DisplayHandle { - - if nread == 0 { - Ok(None) -+ } else if nread != size_of::() { -+ Err(io::Error::new( -+ io::ErrorKind::InvalidData, -+ format!("inputd: partial vt event read: got {nread}, expected {}", size_of::()), -+ )) - } else { -- assert_eq!(nread, size_of::()); - Ok(Some(event)) - } - } -@@ -171,13 +203,11 @@ impl ControlHandle { - Ok(Self(File::open(path)?)) - } - -- /// Sent to Handle::Display - pub fn activate_vt(&mut self, vt: usize) -> io::Result { - let cmd = ControlEvent::from(VtActivate { vt }); - self.0.write(unsafe { any_as_u8_slice(&cmd) }) - } - -- /// Sent to Handle::Producer - pub fn activate_keymap(&mut self, keymap: usize) -> io::Result { - let cmd = ControlEvent::from(KeymapActivate { keymap }); - self.0.write(unsafe { any_as_u8_slice(&cmd) }) -@@ -209,3 +239,195 @@ impl ProducerHandle { - Ok(()) - } - } -+ -+pub struct NamedProducerHandle(File); -+ -+impl NamedProducerHandle { -+ pub fn new(name: &str) -> io::Result { -+ let path = format!("/scheme/input/producer/{name}"); -+ Ok(Self(File::open(path)?)) -+ } -+ -+ pub fn write_event(&mut self, event: orbclient::Event) -> io::Result<()> { -+ self.0.write(&event)?; -+ Ok(()) -+ } -+} -+ -+/// Convenience wrapper that tries a named producer first, -+/// falling back to the legacy anonymous producer on failure. -+pub enum InputProducer { -+ Named(NamedProducerHandle), -+ Legacy(ProducerHandle), -+} -+ -+impl InputProducer { -+ /// Open a named producer (`/scheme/input/producer/{name}`). -+ /// If the named path is unavailable, fall back to the legacy -+ /// `/scheme/input/producer` path so the driver keeps working on -+ /// older inputd builds or degraded schemes. -+ pub fn new_named_or_fallback(name: &str) -> io::Result { -+ match NamedProducerHandle::new(name) { -+ Ok(named) => Ok(InputProducer::Named(named)), -+ Err(named_err) => { -+ log::debug!( -+ "inputd: named producer '{}' unavailable ({}), falling back to legacy", -+ name, -+ named_err -+ ); -+ ProducerHandle::new().map(InputProducer::Legacy) -+ } -+ } -+ } -+ -+ /// Open the legacy anonymous producer directly. -+ pub fn new_legacy() -> io::Result { -+ ProducerHandle::new().map(InputProducer::Legacy) -+ } -+ -+ pub fn write_event(&mut self, event: orbclient::Event) -> io::Result<()> { -+ match self { -+ InputProducer::Named(h) => h.write_event(event), -+ InputProducer::Legacy(h) => h.write_event(event), -+ } -+ } -+} -+ -+pub struct DeviceConsumerHandle(File); -+ -+pub enum DeviceConsumerHandleEvent<'a> { -+ Events(&'a [Event]), -+} -+ -+impl DeviceConsumerHandle { -+ pub fn new(device_name: &str) -> io::Result { -+ let path = format!("/scheme/input/{device_name}"); -+ Ok(Self(File::open(path)?)) -+ } -+ -+ pub fn event_handle(&self) -> BorrowedFd<'_> { -+ self.0.as_fd() -+ } -+ -+ pub fn read_events<'a>( -+ &self, -+ events: &'a mut [Event], -+ ) -> io::Result> { -+ match read_to_slice(self.0.as_fd(), events) { -+ Ok(count) => Ok(DeviceConsumerHandleEvent::Events(&events[..count])), -+ Err(err) => Err(err.into()), -+ } -+ } -+} -+ -+#[derive(Debug, Clone)] -+#[repr(C)] -+pub struct HotplugEventHeader { -+ pub kind: u32, -+ pub device_id: u32, -+ pub name_len: u32, -+ pub reserved: u32, -+} -+ -+#[derive(Debug, Clone)] -+pub struct HotplugEvent { -+ pub kind: u32, -+ pub device_id: u32, -+ pub name: String, -+} -+ -+pub struct HotplugHandle { -+ file: File, -+ partial: Vec, -+} -+ -+impl HotplugHandle { -+ pub fn new() -> io::Result { -+ let file = File::open("/scheme/input/events")?; -+ Ok(Self { -+ file, -+ partial: Vec::new(), -+ }) -+ } -+ -+ pub fn event_handle(&self) -> BorrowedFd<'_> { -+ self.file.as_fd() -+ } -+ -+ pub fn read_event(&mut self) -> io::Result> { -+ let mut tmp = [0u8; 256]; -+ match self.file.read(&mut tmp) { -+ Ok(0) => {} -+ Ok(n) => self.partial.extend_from_slice(&tmp[..n]), -+ Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {} -+ Err(e) => return Err(e), -+ } -+ -+ if self.partial.len() < 16 { -+ return Ok(None); -+ } -+ -+ let header = HotplugEventHeader { -+ kind: u32::from_ne_bytes(self.partial[0..4].try_into().map_err(|_| { -+ io::Error::new(io::ErrorKind::InvalidData, "header parse failed") -+ })?), -+ device_id: u32::from_ne_bytes(self.partial[4..8].try_into().map_err(|_| { -+ io::Error::new(io::ErrorKind::InvalidData, "header parse failed") -+ })?), -+ name_len: u32::from_ne_bytes(self.partial[8..12].try_into().map_err(|_| { -+ io::Error::new(io::ErrorKind::InvalidData, "header parse failed") -+ })?), -+ reserved: 0, -+ }; -+ -+ let total_len = 16 + header.name_len as usize; -+ if self.partial.len() < total_len { -+ return Ok(None); -+ } -+ -+ let name = String::from_utf8(self.partial[16..total_len].to_vec()).map_err(|e| { -+ io::Error::new(io::ErrorKind::InvalidData, format!("invalid UTF-8: {e}")) -+ })?; -+ -+ self.partial.drain(..total_len); -+ -+ Ok(Some(HotplugEvent { -+ kind: header.kind, -+ device_id: header.device_id, -+ name, -+ })) -+ } -+} -+ -+pub const RESERVED_DEVICE_NAMES: &[&str] = &[ -+ "producer", -+ "consumer", -+ "consumer_bootlog", -+ "events", -+ "handle", -+ "handle_early", -+ "control", -+]; -+ -+pub struct InputDeviceLister; -+ -+impl InputDeviceLister { -+ pub fn list() -> io::Result> { -+ let mut dir = std::fs::read_dir("/scheme/input/")?; -+ let mut devices = Vec::new(); -+ loop { -+ match dir.next() { -+ Some(Ok(entry)) => { -+ if let Some(name) = entry.file_name().to_str() { -+ if !RESERVED_DEVICE_NAMES.contains(&name) { -+ devices.push(name.to_owned()); -+ } -+ } -+ } -+ Some(Err(e)) => return Err(e), -+ None => break, -+ } -+ } -+ Ok(devices) -+ } -+} -diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs -index 07aa943e..89018568 100644 ---- a/drivers/inputd/src/main.rs -+++ b/drivers/inputd/src/main.rs -@@ -13,7 +13,7 @@ - - use core::mem::size_of; - use std::borrow::Cow; --use std::collections::BTreeSet; -+use std::collections::{BTreeMap, BTreeSet}; - use std::mem::transmute; - use std::ops::ControlFlow; - use std::sync::atomic::{AtomicUsize, Ordering}; -@@ -26,8 +26,9 @@ use redox_scheme::{CallerCtx, OpenResult, Response, SignalBehavior, Socket}; - - use orbclient::{Event, EventOption}; - use scheme_utils::{Blocking, FpathWriter, HandleMap}; -+use syscall::dirent::{DirEntry, DirentBuf, DirentKind}; - use syscall::schemev2::NewFdFlags; --use syscall::{Error as SysError, EventFlags, EACCES, EBADF, EEXIST, EINVAL}; -+use syscall::{Error as SysError, EventFlags, EACCES, EBADF, EEXIST, EINVAL, ENOENT, ENOTDIR}; - - pub mod keymap; - -@@ -35,8 +36,57 @@ use keymap::KeymapKind; - - use crate::keymap::KeymapData; - -+const DEVICE_ADD: u32 = 1; -+const DEVICE_REMOVE: u32 = 2; -+ -+fn validate_producer_name(name: &str) -> Result<(), SysError> { -+ if name.is_empty() || name.contains('/') { -+ return Err(SysError::new(EINVAL)); -+ } -+ if inputd::RESERVED_DEVICE_NAMES.contains(&name) { -+ return Err(SysError::new(EINVAL)); -+ } -+ Ok(()) -+} -+ -+fn serialize_hotplug(kind: u32, device_id: u32, name: &str) -> Vec { -+ let name_bytes = name.as_bytes(); -+ let header = HotplugHeader { -+ kind, -+ device_id, -+ name_len: name_bytes.len() as u32, -+ _reserved: 0, -+ }; -+ let mut out = Vec::with_capacity(16 + name_bytes.len()); -+ out.extend_from_slice(&header.to_bytes()); -+ out.extend_from_slice(name_bytes); -+ out -+} -+ -+#[repr(C)] -+struct HotplugHeader { -+ kind: u32, -+ device_id: u32, -+ name_len: u32, -+ _reserved: u32, -+} -+ -+impl HotplugHeader { -+ fn to_bytes(&self) -> [u8; 16] { -+ let mut buf = [0u8; 16]; -+ buf[0..4].copy_from_slice(&self.kind.to_ne_bytes()); -+ buf[4..8].copy_from_slice(&self.device_id.to_ne_bytes()); -+ buf[8..12].copy_from_slice(&self.name_len.to_ne_bytes()); -+ buf[12..16].copy_from_slice(&self._reserved.to_ne_bytes()); -+ buf -+ } -+} -+ - enum Handle { - Producer, -+ NamedProducer { -+ name: String, -+ }, - Consumer { - events: EventFlags, - pending: Vec, -@@ -46,6 +96,17 @@ enum Handle { - notified: bool, - vt: usize, - }, -+ DeviceConsumer { -+ device_name: String, -+ events: EventFlags, -+ pending: Vec, -+ notified: bool, -+ }, -+ HotplugEvents { -+ events: EventFlags, -+ pending: Vec, -+ notified: bool, -+ }, - Display { - events: EventFlags, - pending: Vec, -@@ -72,6 +133,9 @@ struct InputScheme { - rshift: bool, - - has_new_events: bool, -+ -+ devices: BTreeMap, -+ next_device_id: AtomicUsize, - } - - impl InputScheme { -@@ -90,9 +154,28 @@ impl InputScheme { - lshift: false, - rshift: false, - has_new_events: false, -+ -+ devices: BTreeMap::new(), -+ next_device_id: AtomicUsize::new(1), - } - } - -+ fn emit_hotplug(&mut self, kind: u32, device_id: u32, name: &str) { -+ let record = serialize_hotplug(kind, device_id, name); -+ for handle in self.handles.values_mut() { -+ if let Handle::HotplugEvents { -+ pending, -+ notified, -+ .. -+ } = handle -+ { -+ pending.extend_from_slice(&record); -+ *notified = false; -+ } -+ } -+ self.has_new_events = true; -+ } -+ - fn switch_vt(&mut self, new_active: usize) { - if let Some(active_vt) = self.active_vt { - if new_active == active_vt { -@@ -146,6 +229,43 @@ impl InputScheme { - - self.active_keymap = KeymapData::new(new_active.into()); - } -+ -+ fn deliver_to_legacy_consumers(&mut self, buf: &[u8]) { -+ if let Some(active_vt) = self.active_vt { -+ for handle in self.handles.values_mut() { -+ if let Handle::Consumer { -+ pending, -+ notified, -+ vt, -+ .. -+ } = handle -+ { -+ if *vt != active_vt { -+ continue; -+ } -+ pending.extend_from_slice(buf); -+ *notified = false; -+ } -+ } -+ } -+ } -+ -+ fn deliver_to_device_consumers(&mut self, name: &str, buf: &[u8]) { -+ for handle in self.handles.values_mut() { -+ if let Handle::DeviceConsumer { -+ device_name, -+ pending, -+ notified, -+ .. -+ } = handle -+ { -+ if device_name == name { -+ pending.extend_from_slice(buf); -+ *notified = false; -+ } -+ } -+ } -+ } - } - - impl SchemeSync for InputScheme { -@@ -170,7 +290,23 @@ impl SchemeSync for InputScheme { - let command = path_parts.next().ok_or(SysError::new(EINVAL))?; - - let handle_ty = match command { -- "producer" => Handle::Producer, -+ "producer" => { -+ if let Some(name) = path_parts.next() { -+ validate_producer_name(name)?; -+ if self.devices.contains_key(name) { -+ return Err(SysError::new(EEXIST)); -+ } -+ let device_id = self.next_device_id.fetch_add(1, Ordering::SeqCst) as u32; -+ self.devices.insert(name.to_owned(), device_id); -+ let handle = Handle::NamedProducer { -+ name: name.to_owned(), -+ }; -+ self.emit_hotplug(DEVICE_ADD, device_id, name); -+ handle -+ } else { -+ Handle::Producer -+ } -+ } - "consumer" => { - let vt = self.next_vt_id.fetch_add(1, Ordering::Relaxed); - self.vts.insert(vt); -@@ -253,9 +389,23 @@ impl SchemeSync for InputScheme { - } - "control" => Handle::Control, - -- _ => { -- log::error!("invalid path '{path}'"); -- return Err(SysError::new(EINVAL)); -+ "events" => Handle::HotplugEvents { -+ events: EventFlags::empty(), -+ pending: Vec::new(), -+ notified: false, -+ }, -+ -+ // dynamic device consumer: must be a currently registered device -+ name => { -+ if !self.devices.contains_key(name) { -+ return Err(SysError::new(ENOENT)); -+ } -+ Handle::DeviceConsumer { -+ device_name: name.to_owned(), -+ events: EventFlags::empty(), -+ pending: Vec::new(), -+ notified: false, -+ } - } - }; - -@@ -274,7 +424,7 @@ impl SchemeSync for InputScheme { - let handle = self.handles.get(id)?; - - if let Handle::Consumer { vt, .. } = handle { -- write!(w, "{vt}").unwrap(); -+ write!(w, "{vt}").map_err(|_| SysError::new(EINVAL))?; - Ok(()) - } else { - Err(SysError::new(EINVAL)) -@@ -282,6 +432,50 @@ impl SchemeSync for InputScheme { - }) - } - -+ fn getdents<'buf>( -+ &mut self, -+ id: usize, -+ mut buf: DirentBuf<&'buf mut [u8]>, -+ opaque_offset: u64, -+ ) -> syscall::Result> { -+ let handle = self.handles.get(id)?; -+ if !matches!(handle, Handle::SchemeRoot) { -+ return Err(SysError::new(ENOTDIR)); -+ } -+ -+ let static_entries: &[&str] = &[ -+ "producer", -+ "consumer", -+ "consumer_bootlog", -+ "events", -+ "handle", -+ "handle_early", -+ "control", -+ ]; -+ -+ let device_names: Vec<&str> = self.devices.keys().map(|s| s.as_str()).collect(); -+ -+ let all_entries: Vec<(&str, DirentKind)> = static_entries -+ .iter() -+ .map(|&name| (name, DirentKind::Directory)) -+ .chain(device_names.iter().map(|&name| (name, DirentKind::Unspecified))) -+ .collect(); -+ -+ for (idx, (name, kind)) in all_entries -+ .iter() -+ .enumerate() -+ .skip(opaque_offset as usize) -+ { -+ buf.entry(DirEntry { -+ inode: 0, -+ next_opaque_id: idx as u64 + 1, -+ name, -+ kind: *kind, -+ })?; -+ } -+ Ok(buf) -+ } -+ - fn read( - &mut self, - id: usize, -@@ -313,6 +507,22 @@ impl SchemeSync for InputScheme { - Ok(copy) - } - -+ Handle::DeviceConsumer { pending, .. } => { -+ let copy = core::cmp::min(pending.len(), buf.len()); -+ for (i, byte) in pending.drain(..copy).enumerate() { -+ buf[i] = byte; -+ } -+ Ok(copy) -+ } -+ -+ Handle::HotplugEvents { pending, .. } => { -+ let copy = core::cmp::min(pending.len(), buf.len()); -+ for (i, byte) in pending.drain(..copy).enumerate() { -+ buf[i] = byte; -+ } -+ Ok(copy) -+ } -+ - Handle::Display { pending, .. } => { - if buf.len() % size_of::() == 0 { - let copy = core::cmp::min(pending.len(), buf.len() / size_of::()); -@@ -334,6 +544,10 @@ impl SchemeSync for InputScheme { - log::error!("producer tried to read"); - return Err(SysError::new(EINVAL)); - } -+ Handle::NamedProducer { .. } => { -+ log::error!("named producer tried to read"); -+ return Err(SysError::new(EINVAL)); -+ } - Handle::Control => { - log::error!("control tried to read"); - return Err(SysError::new(EINVAL)); -@@ -379,11 +593,20 @@ impl SchemeSync for InputScheme { - log::error!("consumer tried to write"); - return Err(SysError::new(EINVAL)); - } -+ Handle::DeviceConsumer { .. } => { -+ log::error!("device consumer tried to write"); -+ return Err(SysError::new(EINVAL)); -+ } -+ Handle::HotplugEvents { .. } => { -+ log::error!("hotplug events tried to write"); -+ return Err(SysError::new(EINVAL)); -+ } - Handle::Display { .. } => { - log::error!("display tried to write"); - return Err(SysError::new(EINVAL)); - } - Handle::Producer => {} -+ Handle::NamedProducer { .. } => {} - Handle::SchemeRoot => return Err(SysError::new(EBADF)), - } - -@@ -397,6 +620,11 @@ impl SchemeSync for InputScheme { - buf.len() / size_of::(), - ) - }); -+ let producer_name = match self.handles.get(id)? { -+ Handle::NamedProducer { ref name } => Some(name.clone()), -+ Handle::Producer => None, -+ _ => return Err(SysError::new(EBADF)), -+ }; - - for i in 0..events.len() { - let mut new_active_opt = None; -@@ -437,38 +665,21 @@ impl SchemeSync for InputScheme { - } - } - -- let handle = self.handles.get_mut(id)?; -- assert!(matches!(handle, Handle::Producer)); -- -- let buf = unsafe { -+ let serialized = unsafe { - core::slice::from_raw_parts( - (events.as_ptr()) as *const u8, - events.len() * size_of::(), - ) - }; - -- if let Some(active_vt) = self.active_vt { -- for handle in self.handles.values_mut() { -- match handle { -- Handle::Consumer { -- pending, -- notified, -- vt, -- .. -- } => { -- if *vt != active_vt { -- continue; -- } -- -- pending.extend_from_slice(buf); -- *notified = false; -- } -- _ => continue, -- } -- } -+ if let Some(ref name) = producer_name { -+ self.deliver_to_device_consumers(name, serialized); - } - -- Ok(buf.len()) -+ // named producers also feed the legacy path; legacy producers only feed legacy -+ self.deliver_to_legacy_consumers(serialized); -+ -+ Ok(serialized.len()) - } - - fn fevent( -@@ -487,6 +698,24 @@ impl SchemeSync for InputScheme { - *notified = false; - Ok(EventFlags::empty()) - } -+ Handle::DeviceConsumer { -+ ref mut events, -+ ref mut notified, -+ .. -+ } => { -+ *events = flags; -+ *notified = false; -+ Ok(EventFlags::empty()) -+ } -+ Handle::HotplugEvents { -+ ref mut events, -+ ref mut notified, -+ .. -+ } => { -+ *events = flags; -+ *notified = false; -+ Ok(EventFlags::empty()) -+ } - Handle::Display { - ref mut events, - ref mut notified, -@@ -496,7 +725,7 @@ impl SchemeSync for InputScheme { - *notified = false; - Ok(EventFlags::empty()) - } -- Handle::Producer | Handle::Control => { -+ Handle::Producer | Handle::NamedProducer { .. } | Handle::Control => { - log::error!("producer or control tried to use an event queue"); - Err(SysError::new(EINVAL)) - } -@@ -505,8 +734,8 @@ impl SchemeSync for InputScheme { - } - - fn on_close(&mut self, id: usize) { -- match self.handles.remove(id).unwrap() { -- Handle::Consumer { vt, .. } => { -+ match self.handles.remove(id) { -+ Some(Handle::Consumer { vt, .. }) => { - self.vts.remove(&vt); - if self.active_vt == Some(vt) { - if let Some(&new_vt) = self.vts.last() { -@@ -516,7 +745,15 @@ impl SchemeSync for InputScheme { - } - } - } -- _ => {} -+ Some(Handle::NamedProducer { name, .. }) => { -+ if let Some(device_id) = self.devices.remove(&name) { -+ self.emit_hotplug(DEVICE_REMOVE, device_id, &name); -+ } -+ } -+ Some(_) => {} -+ None => { -+ log::warn!("inputd: on_close called with unknown handle id {id}"); -+ } - } - } - } -@@ -564,6 +801,39 @@ fn deamon(daemon: daemon::SchemeDaemon) -> anyhow::Result<()> { - - *notified = true; - } -+ Handle::DeviceConsumer { -+ events, -+ pending, -+ ref mut notified, -+ .. -+ } => { -+ if pending.is_empty() || *notified || !events.contains(EventFlags::EVENT_READ) { -+ continue; -+ } -+ -+ socket_file.write_response( -+ Response::post_fevent(*id, EventFlags::EVENT_READ.bits()), -+ SignalBehavior::Restart, -+ )?; -+ -+ *notified = true; -+ } -+ Handle::HotplugEvents { -+ events, -+ pending, -+ ref mut notified, -+ } => { -+ if pending.is_empty() || *notified || !events.contains(EventFlags::EVENT_READ) { -+ continue; -+ } -+ -+ socket_file.write_response( -+ Response::post_fevent(*id, EventFlags::EVENT_READ.bits()), -+ SignalBehavior::Restart, -+ )?; -+ -+ *notified = true; -+ } - Handle::Display { - events, - pending, -@@ -589,8 +859,11 @@ fn deamon(daemon: daemon::SchemeDaemon) -> anyhow::Result<()> { - } - - fn daemon_runner(daemon: daemon::SchemeDaemon) -> ! { -- deamon(daemon).unwrap(); -- unreachable!(); -+ if let Err(err) = deamon(daemon) { -+ log::error!("inputd: scheme daemon failed: {err}"); -+ std::process::exit(1); -+ } -+ unreachable!() - } - - const HELP: &str = r#" -@@ -608,13 +881,26 @@ fn main() { - match val.as_ref() { - // Activates a VT. - "-A" => { -- let vt = args.next().unwrap().parse::().unwrap(); -+ let vt_str = args.next().unwrap_or_else(|| { -+ eprintln!("inputd: -A requires a VT number argument"); -+ std::process::exit(1); -+ }); -+ let vt = vt_str.parse::().unwrap_or_else(|_| { -+ eprintln!("inputd: invalid VT number: {vt_str}"); -+ std::process::exit(1); -+ }); - -- let mut handle = -- inputd::ControlHandle::new().expect("inputd: failed to open control handle"); -- handle -- .activate_vt(vt) -- .expect("inputd: failed to activate VT"); -+ let mut handle = match inputd::ControlHandle::new() { -+ Ok(h) => h, -+ Err(e) => { -+ eprintln!("inputd: failed to open control handle: {e}"); -+ std::process::exit(1); -+ } -+ }; -+ if let Err(e) = handle.activate_vt(vt) { -+ eprintln!("inputd: failed to activate VT {vt}: {e}"); -+ std::process::exit(1); -+ } - } - // Activates a keymap. - "-K" => { -@@ -630,11 +916,17 @@ fn main() { - std::process::exit(1); - }); - -- let mut handle = -- inputd::ControlHandle::new().expect("inputd: failed to open control handle"); -- handle -- .activate_keymap(vt as usize) -- .expect("inputd: failed to activate keymap"); -+ let mut handle = match inputd::ControlHandle::new() { -+ Ok(h) => h, -+ Err(e) => { -+ eprintln!("inputd: failed to open control handle: {e}"); -+ std::process::exit(1); -+ } -+ }; -+ if let Err(e) = handle.activate_keymap(vt as usize) { -+ eprintln!("inputd: failed to activate keymap: {e}"); -+ std::process::exit(1); -+ } - } - // List available keymaps - "--keymaps" => { -@@ -647,7 +939,10 @@ fn main() { - println!("{}", HELP); - } - -- _ => panic!("inputd: invalid argument: {}", val), -+ _ => { -+ eprintln!("inputd: invalid argument: {val}"); -+ std::process::exit(1); -+ } - } - } else { - common::setup_logging( -diff --git a/drivers/net/e1000d/src/device.rs b/drivers/net/e1000d/src/device.rs -index 4c518f30..0e42d72b 100644 ---- a/drivers/net/e1000d/src/device.rs -+++ b/drivers/net/e1000d/src/device.rs -@@ -3,7 +3,7 @@ use std::{cmp, mem, ptr, slice, thread, time}; - - use driver_network::NetworkAdapter; - --use syscall::error::Result; -+use syscall::error::{Error, Result, EIO}; - - use common::dma::Dma; - -@@ -207,11 +207,10 @@ impl NetworkAdapter for Intel8254x { - } - - fn dma_array() -> Result<[Dma; N]> { -- Ok((0..N) -+ let vec: Vec> = (0..N) - .map(|_| Ok(unsafe { Dma::zeroed()?.assume_init() })) -- .collect::>>()? -- .try_into() -- .unwrap_or_else(|_| unreachable!())) -+ .collect::>>()?; -+ vec.try_into().map_err(|_| Error::new(EIO)) - } - impl Intel8254x { - pub unsafe fn new(base: usize) -> Result { -diff --git a/drivers/net/e1000d/src/main.rs b/drivers/net/e1000d/src/main.rs -index 373ea9b3..8ff57b33 100644 ---- a/drivers/net/e1000d/src/main.rs -+++ b/drivers/net/e1000d/src/main.rs -@@ -1,5 +1,6 @@ - use std::io::{Read, Write}; - use std::os::unix::io::AsRawFd; -+use std::process; - - use driver_network::NetworkScheme; - use event::{user_data, EventQueue}; -@@ -25,10 +26,13 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - common::file_level(), - ); - -- let irq = pci_config -- .func -- .legacy_interrupt_line -- .expect("e1000d: no legacy interrupts supported"); -+ let irq = match pci_config.func.legacy_interrupt_line { -+ Some(irq) => irq, -+ None => { -+ log::error!("e1000d: no legacy interrupts supported"); -+ process::exit(1); -+ } -+ }; - - log::info!("E1000 {}", pci_config.func.display()); - -@@ -38,7 +42,10 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - - let mut scheme = NetworkScheme::new( - move || unsafe { -- device::Intel8254x::new(address).expect("e1000d: failed to allocate device") -+ device::Intel8254x::new(address).unwrap_or_else(|err| { -+ log::error!("e1000d: failed to allocate device: {err}"); -+ process::exit(1); -+ }) - }, - daemon, - format!("network.{name}"), -@@ -51,7 +58,10 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - } - } - -- let event_queue = EventQueue::::new().expect("e1000d: failed to create event queue"); -+ let mut event_queue = EventQueue::::new().unwrap_or_else(|err| { -+ log::error!("e1000d: failed to create event queue: {err}"); -+ process::exit(1); -+ }); - - event_queue - .subscribe( -@@ -59,32 +69,65 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - Source::Irq, - event::EventFlags::READ, - ) -- .expect("e1000d: failed to subscribe to IRQ fd"); -+ .unwrap_or_else(|err| { -+ log::error!("e1000d: failed to subscribe to IRQ fd: {err}"); -+ process::exit(1); -+ }); - event_queue - .subscribe( - scheme.event_handle().raw(), - Source::Scheme, - event::EventFlags::READ, - ) -- .expect("e1000d: failed to subscribe to scheme fd"); -- -- libredox::call::setrens(0, 0).expect("e1000d: failed to enter null namespace"); -- -- scheme.tick().unwrap(); -+ .unwrap_or_else(|err| { -+ log::error!("e1000d: failed to subscribe to scheme fd: {err}"); -+ process::exit(1); -+ }); -+ -+ libredox::call::setrens(0, 0).unwrap_or_else(|err| { -+ log::error!("e1000d: failed to enter null namespace: {err}"); -+ process::exit(1); -+ }); -+ -+ if let Err(err) = scheme.tick() { -+ log::error!("e1000d: failed initial scheme tick: {err}"); -+ process::exit(1); -+ } - -- for event in event_queue.map(|e| e.expect("e1000d: failed to get event")) { -+ loop { -+ let event = match event_queue.next() { -+ Some(Ok(event)) => event, -+ Some(Err(err)) => { -+ log::error!("e1000d: failed to get event: {err}"); -+ continue; -+ } -+ None => break, -+ }; - match event.user_data { - Source::Irq => { - let mut irq = [0; 8]; -- irq_file.read(&mut irq).unwrap(); -+ if let Err(err) = irq_file.read(&mut irq) { -+ log::error!("e1000d: failed to read IRQ: {err}"); -+ continue; -+ } - if unsafe { scheme.adapter().irq() } { -- irq_file.write(&mut irq).unwrap(); -- -- scheme.tick().expect("e1000d: failed to handle IRQ") -+ if let Err(err) = irq_file.write(&mut irq) { -+ log::error!("e1000d: failed to write IRQ: {err}"); -+ continue; -+ } -+ -+ if let Err(err) = scheme.tick() { -+ log::error!("e1000d: failed to handle IRQ: {err}"); -+ } -+ } -+ } -+ Source::Scheme => { -+ if let Err(err) = scheme.tick() { -+ log::error!("e1000d: failed to handle scheme op: {err}"); - } - } -- Source::Scheme => scheme.tick().expect("e1000d: failed to handle scheme op"), - } - } -- unreachable!() -+ -+ process::exit(0); - } -diff --git a/drivers/net/ixgbed/Cargo.toml b/drivers/net/ixgbed/Cargo.toml -index d97ff398..fcaf4b19 100644 ---- a/drivers/net/ixgbed/Cargo.toml -+++ b/drivers/net/ixgbed/Cargo.toml -@@ -7,6 +7,7 @@ edition = "2021" - [dependencies] - bitflags.workspace = true - libredox.workspace = true -+log.workspace = true - redox_event.workspace = true - redox_syscall.workspace = true - -diff --git a/drivers/net/ixgbed/src/device.rs b/drivers/net/ixgbed/src/device.rs -index 0d59b46d..fc7c009f 100644 ---- a/drivers/net/ixgbed/src/device.rs -+++ b/drivers/net/ixgbed/src/device.rs -@@ -3,7 +3,7 @@ use std::time::{Duration, Instant}; - use std::{cmp, mem, ptr, slice, thread}; - - use driver_network::NetworkAdapter; --use syscall::error::Result; -+use syscall::error::{Error, Result, EIO}; - - use common::dma::Dma; - -@@ -45,7 +45,12 @@ impl NetworkAdapter for Intel8259x { - - if (status & IXGBE_RXDADV_STAT_DD) != 0 { - if (status & IXGBE_RXDADV_STAT_EOP) == 0 { -- panic!("increase buffer size or decrease MTU") -+ log::error!("ixgbed: received fragmented packet, skipping descriptor"); -+ desc.read.pkt_addr = self.receive_buffer[self.receive_index].physical() as u64; -+ desc.read.hdr_addr = 0; -+ self.write_reg(IXGBE_RDT(0), self.receive_index as u32); -+ self.receive_index = wrap_ring(self.receive_index, self.receive_ring.len()); -+ return Ok(None); - } - - let data = unsafe { -@@ -132,13 +137,25 @@ impl Intel8259x { - .map(|_| Ok(unsafe { Dma::zeroed()?.assume_init() })) - .collect::>>()? - .try_into() -- .unwrap_or_else(|_| unreachable!()), -+ .map_err(|v: Vec<_>| { -+ log::error!( -+ "ixgbed: internal error: DMA buffer array conversion failed (got {} items, expected 32)", -+ v.len() -+ ); -+ Error::new(EIO) -+ })?, - receive_ring: unsafe { Dma::zeroed()?.assume_init() }, - transmit_buffer: (0..32) - .map(|_| Ok(unsafe { Dma::zeroed()?.assume_init() })) - .collect::>>()? - .try_into() -- .unwrap_or_else(|_| unreachable!()), -+ .map_err(|v: Vec<_>| { -+ log::error!( -+ "ixgbed: internal error: DMA buffer array conversion failed (got {} items, expected 32)", -+ v.len() -+ ); -+ Error::new(EIO) -+ })?, - receive_index: 0, - transmit_ring: unsafe { Dma::zeroed()?.assume_init() }, - transmit_ring_free: 32, -@@ -166,7 +183,7 @@ impl Intel8259x { - - if (status & IXGBE_RXDADV_STAT_DD) != 0 { - if (status & IXGBE_RXDADV_STAT_EOP) == 0 { -- panic!("increase buffer size or decrease MTU") -+ log::error!("ixgbed: received fragmented packet, buffer too small"); - } - - return unsafe { desc.wb.upper.length as usize }; -@@ -205,13 +222,8 @@ impl Intel8259x { - self.mac_address = mac; - } - -- /// Returns the register at `self.base` + `register`. -- /// -- /// # Panics -- /// -- /// Panics if `self.base` + `register` does not belong to the mapped memory of the PCIe device. - fn read_reg(&self, register: u32) -> u32 { -- assert!( -+ debug_assert!( - register as usize <= self.size - 4 as usize, - "MMIO access out of bounds" - ); -@@ -219,13 +231,8 @@ impl Intel8259x { - unsafe { ptr::read_volatile((self.base + register as usize) as *mut u32) } - } - -- /// Sets the register at `self.base` + `register`. -- /// -- /// # Panics -- /// -- /// Panics if `self.base` + `register` does not belong to the mapped memory of the PCIe device. - fn write_reg(&self, register: u32, data: u32) -> u32 { -- assert!( -+ debug_assert!( - register as usize <= self.size - 4 as usize, - "MMIO access out of bounds" - ); -@@ -279,7 +286,7 @@ impl Intel8259x { - - let mac = self.get_mac_addr(); - -- println!( -+ log::info!( - " - MAC: {:>02X}:{:>02X}:{:>02X}:{:>02X}:{:>02X}:{:>02X}", - mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] - ); -@@ -438,13 +445,11 @@ impl Intel8259x { - } - - /// Sets the rx queues` descriptors and enables the queues. -- /// -- /// # Panics -- /// Panics if length of `self.receive_ring` is not a power of 2. - fn start_rx_queue(&mut self, queue_id: u16) { -- if self.receive_ring.len() & (self.receive_ring.len() - 1) != 0 { -- panic!("number of receive queue entries must be a power of 2"); -- } -+ debug_assert!( -+ self.receive_ring.len() & (self.receive_ring.len() - 1) == 0, -+ "number of receive queue entries must be a power of 2" -+ ); - - for i in 0..self.receive_ring.len() { - self.receive_ring[i].read.pkt_addr = self.receive_buffer[i].physical() as u64; -@@ -466,13 +471,11 @@ impl Intel8259x { - } - - /// Enables the tx queues. -- /// -- /// # Panics -- /// Panics if length of `self.transmit_ring` is not a power of 2. - fn start_tx_queue(&mut self, queue_id: u16) { -- if self.transmit_ring.len() & (self.transmit_ring.len() - 1) != 0 { -- panic!("number of receive queue entries must be a power of 2"); -- } -+ debug_assert!( -+ self.transmit_ring.len() & (self.transmit_ring.len() - 1) == 0, -+ "number of transmit queue entries must be a power of 2" -+ ); - - for i in 0..self.transmit_ring.len() { - self.transmit_ring[i].read.buffer_addr = self.transmit_buffer[i].physical() as u64; -@@ -506,14 +509,14 @@ impl Intel8259x { - - /// Waits for the link to come up. - fn wait_for_link(&self) { -- println!(" - waiting for link"); -+ log::info!(" - waiting for link"); - let time = Instant::now(); - let mut speed = self.get_link_speed(); - while speed == 0 && time.elapsed().as_secs() < 10 { - thread::sleep(Duration::from_millis(100)); - speed = self.get_link_speed(); - } -- println!(" - link speed is {} Mbit/s", self.get_link_speed()); -+ log::info!(" - link speed is {} Mbit/s", self.get_link_speed()); - } - - /// Enables or disables promisc mode of this device. -diff --git a/drivers/net/ixgbed/src/main.rs b/drivers/net/ixgbed/src/main.rs -index 4a6ce74d..855d339d 100644 ---- a/drivers/net/ixgbed/src/main.rs -+++ b/drivers/net/ixgbed/src/main.rs -@@ -1,5 +1,6 @@ - use std::io::{Read, Write}; - use std::os::unix::io::AsRawFd; -+use std::process; - - use driver_network::NetworkScheme; - use event::{user_data, EventQueue}; -@@ -19,12 +20,23 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - let mut name = pci_config.func.name(); - name.push_str("_ixgbe"); - -- let irq = pci_config -- .func -- .legacy_interrupt_line -- .expect("ixgbed: no legacy interrupts supported"); -+ common::setup_logging( -+ "net", -+ "pci", -+ &name, -+ common::output_level(), -+ common::file_level(), -+ ); -+ -+ let irq = match pci_config.func.legacy_interrupt_line { -+ Some(irq) => irq, -+ None => { -+ log::error!("ixgbed: no legacy interrupts supported"); -+ process::exit(1); -+ } -+ }; - -- println!(" + IXGBE {}", pci_config.func.display()); -+ log::info!("IXGBE {}", pci_config.func.display()); - - let mut irq_file = irq.irq_handle("ixgbed"); - -@@ -34,8 +46,10 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - - let mut scheme = NetworkScheme::new( - move || { -- device::Intel8259x::new(address as usize, size) -- .expect("ixgbed: failed to allocate device") -+ device::Intel8259x::new(address as usize, size).unwrap_or_else(|err| { -+ log::error!("ixgbed: failed to allocate device: {err}"); -+ process::exit(1); -+ }) - }, - daemon, - format!("network.{name}"), -@@ -48,41 +62,77 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - } - } - -- let event_queue = EventQueue::::new().expect("ixgbed: Could not create event queue."); -+ let mut event_queue = EventQueue::::new().unwrap_or_else(|err| { -+ log::error!("ixgbed: failed to create event queue: {err}"); -+ process::exit(1); -+ }); -+ - event_queue - .subscribe( - irq_file.as_raw_fd() as usize, - Source::Irq, - event::EventFlags::READ, - ) -- .unwrap(); -+ .unwrap_or_else(|err| { -+ log::error!("ixgbed: failed to subscribe to IRQ fd: {err}"); -+ process::exit(1); -+ }); - event_queue - .subscribe( - scheme.event_handle().raw(), - Source::Scheme, - event::EventFlags::READ, - ) -- .unwrap(); -- -- libredox::call::setrens(0, 0).expect("ixgbed: failed to enter null namespace"); -+ .unwrap_or_else(|err| { -+ log::error!("ixgbed: failed to subscribe to scheme fd: {err}"); -+ process::exit(1); -+ }); -+ -+ libredox::call::setrens(0, 0).unwrap_or_else(|err| { -+ log::error!("ixgbed: failed to enter null namespace: {err}"); -+ process::exit(1); -+ }); -+ -+ if let Err(err) = scheme.tick() { -+ log::error!("ixgbed: failed initial scheme tick: {err}"); -+ process::exit(1); -+ } - -- scheme.tick().unwrap(); -+ loop { -+ let event = match event_queue.next() { -+ Some(Ok(event)) => event, -+ Some(Err(err)) => { -+ log::error!("ixgbed: failed to get event: {err}"); -+ continue; -+ } -+ None => break, -+ }; - -- for event in event_queue.map(|e| e.expect("ixgbed: failed to get next event")) { - match event.user_data { - Source::Irq => { - let mut irq = [0; 8]; -- irq_file.read(&mut irq).unwrap(); -+ if let Err(err) = irq_file.read(&mut irq) { -+ log::error!("ixgbed: failed to read IRQ: {err}"); -+ continue; -+ } - if scheme.adapter().irq() { -- irq_file.write(&mut irq).unwrap(); -- -- scheme.tick().unwrap(); -+ if let Err(err) = irq_file.write(&mut irq) { -+ log::error!("ixgbed: failed to write IRQ: {err}"); -+ continue; -+ } -+ -+ if let Err(err) = scheme.tick() { -+ log::error!("ixgbed: failed to handle IRQ: {err}"); -+ } - } - } - Source::Scheme => { -- scheme.tick().unwrap(); -+ if let Err(err) = scheme.tick() { -+ log::error!("ixgbed: failed to handle scheme op: {err}"); -+ } - } - } - } -- unreachable!() -+ -+ process::exit(0); - } -diff --git a/drivers/net/rtl8139d/src/device.rs b/drivers/net/rtl8139d/src/device.rs -index 37167ee2..d7428132 100644 ---- a/drivers/net/rtl8139d/src/device.rs -+++ b/drivers/net/rtl8139d/src/device.rs -@@ -215,7 +215,7 @@ impl Rtl8139 { - .map(|_| Ok(Dma::zeroed()?.assume_init())) - .collect::>>()? - .try_into() -- .unwrap_or_else(|_| unreachable!()), -+ .map_err(|_| Error::new(EIO))?, - transmit_i: 0, - mac_address: [0; 6], - }; -diff --git a/drivers/net/rtl8139d/src/main.rs b/drivers/net/rtl8139d/src/main.rs -index d470e814..64335a23 100644 ---- a/drivers/net/rtl8139d/src/main.rs -+++ b/drivers/net/rtl8139d/src/main.rs -@@ -1,5 +1,6 @@ - use std::io::{Read, Write}; - use std::os::unix::io::AsRawFd; -+use std::process; - - use driver_network::NetworkScheme; - use event::{user_data, EventQueue}; -@@ -32,7 +33,8 @@ fn map_bar(pcid_handle: &mut PciFunctionHandle) -> *mut u8 { - other => log::warn!("BAR {} is {:?} instead of memory BAR", barnum, other), - } - } -- panic!("rtl8139d: failed to find BAR"); -+ log::error!("rtl8139d: failed to find BAR"); -+ process::exit(1); - } - - fn main() { -@@ -61,7 +63,10 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - - let mut scheme = NetworkScheme::new( - move || unsafe { -- device::Rtl8139::new(bar as usize).expect("rtl8139d: failed to allocate device") -+ device::Rtl8139::new(bar as usize).unwrap_or_else(|err| { -+ log::error!("rtl8139d: failed to allocate device: {err}"); -+ process::exit(1); -+ }) - }, - daemon, - format!("network.{name}"), -@@ -74,42 +79,76 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - } - } - -- let event_queue = EventQueue::::new().expect("rtl8139d: Could not create event queue."); -+ let mut event_queue = EventQueue::::new().unwrap_or_else(|err| { -+ log::error!("rtl8139d: failed to create event queue: {err}"); -+ process::exit(1); -+ }); - event_queue - .subscribe( - irq_file.irq_handle().as_raw_fd() as usize, - Source::Irq, - event::EventFlags::READ, - ) -- .unwrap(); -+ .unwrap_or_else(|err| { -+ log::error!("rtl8139d: failed to subscribe to IRQ fd: {err}"); -+ process::exit(1); -+ }); - event_queue - .subscribe( - scheme.event_handle().raw(), - Source::Scheme, - event::EventFlags::READ, - ) -- .unwrap(); -- -- libredox::call::setrens(0, 0).expect("rtl8139d: failed to enter null namespace"); -- -- scheme.tick().unwrap(); -+ .unwrap_or_else(|err| { -+ log::error!("rtl8139d: failed to subscribe to scheme fd: {err}"); -+ process::exit(1); -+ }); -+ -+ libredox::call::setrens(0, 0).unwrap_or_else(|err| { -+ log::error!("rtl8139d: failed to enter null namespace: {err}"); -+ process::exit(1); -+ }); -+ -+ if let Err(err) = scheme.tick() { -+ log::error!("rtl8139d: failed initial scheme tick: {err}"); -+ process::exit(1); -+ } - -- for event in event_queue.map(|e| e.expect("rtl8139d: failed to get next event")) { -+ loop { -+ let event = match event_queue.next() { -+ Some(Ok(event)) => event, -+ Some(Err(err)) => { -+ log::error!("rtl8139d: failed to get next event: {err}"); -+ continue; -+ } -+ None => break, -+ }; - match event.user_data { - Source::Irq => { - let mut irq = [0; 8]; -- irq_file.irq_handle().read(&mut irq).unwrap(); -+ if let Err(err) = irq_file.irq_handle().read(&mut irq) { -+ log::error!("rtl8139d: failed to read IRQ: {err}"); -+ continue; -+ } - //TODO: This may be causing spurious interrupts - if unsafe { scheme.adapter_mut().irq() } { -- irq_file.irq_handle().write(&mut irq).unwrap(); -- -- scheme.tick().unwrap(); -+ if let Err(err) = irq_file.irq_handle().write(&mut irq) { -+ log::error!("rtl8139d: failed to write IRQ: {err}"); -+ continue; -+ } -+ -+ if let Err(err) = scheme.tick() { -+ log::error!("rtl8139d: failed to handle IRQ tick: {err}"); -+ } - } - } - Source::Scheme => { -- scheme.tick().unwrap(); -+ if let Err(err) = scheme.tick() { -+ log::error!("rtl8139d: failed to handle scheme op: {err}"); -+ } - } - } - } -- unreachable!() -+ -+ process::exit(0); - } -diff --git a/drivers/net/rtl8168d/src/device.rs b/drivers/net/rtl8168d/src/device.rs -index ae545ec4..7229a52d 100644 ---- a/drivers/net/rtl8168d/src/device.rs -+++ b/drivers/net/rtl8168d/src/device.rs -@@ -177,7 +177,7 @@ impl Rtl8168 { - .map(|_| Ok(Dma::zeroed()?.assume_init())) - .collect::>>()? - .try_into() -- .unwrap_or_else(|_| unreachable!()), -+ .map_err(|_| Error::new(EIO))?, - - receive_ring: Dma::zeroed()?.assume_init(), - receive_i: 0, -@@ -185,7 +185,7 @@ impl Rtl8168 { - .map(|_| Ok(Dma::zeroed()?.assume_init())) - .collect::>>()? - .try_into() -- .unwrap_or_else(|_| unreachable!()), -+ .map_err(|_| Error::new(EIO))?, - transmit_ring: Dma::zeroed()?.assume_init(), - transmit_i: 0, - transmit_buffer_h: [Dma::zeroed()?.assume_init()], -diff --git a/drivers/net/rtl8168d/src/main.rs b/drivers/net/rtl8168d/src/main.rs -index 1d9963a3..bd2fcb1a 100644 ---- a/drivers/net/rtl8168d/src/main.rs -+++ b/drivers/net/rtl8168d/src/main.rs -@@ -1,5 +1,6 @@ - use std::io::{Read, Write}; - use std::os::unix::io::AsRawFd; -+use std::process; - - use driver_network::NetworkScheme; - use event::{user_data, EventQueue}; -@@ -32,7 +33,8 @@ fn map_bar(pcid_handle: &mut PciFunctionHandle) -> *mut u8 { - other => log::warn!("BAR {} is {:?} instead of memory BAR", barnum, other), - } - } -- panic!("rtl8168d: failed to find BAR"); -+ log::error!("rtl8168d: failed to find BAR"); -+ process::exit(1); - } - - fn main() { -@@ -61,7 +63,10 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - - let mut scheme = NetworkScheme::new( - move || unsafe { -- device::Rtl8168::new(bar as usize).expect("rtl8168d: failed to allocate device") -+ device::Rtl8168::new(bar as usize).unwrap_or_else(|err| { -+ log::error!("rtl8168d: failed to allocate device: {err}"); -+ process::exit(1); -+ }) - }, - daemon, - format!("network.{name}"), -@@ -74,42 +79,76 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - } - } - -- let event_queue = EventQueue::::new().expect("rtl8168d: Could not create event queue."); -+ let mut event_queue = EventQueue::::new().unwrap_or_else(|err| { -+ log::error!("rtl8168d: failed to create event queue: {err}"); -+ process::exit(1); -+ }); - event_queue - .subscribe( - irq_file.irq_handle().as_raw_fd() as usize, - Source::Irq, - event::EventFlags::READ, - ) -- .unwrap(); -+ .unwrap_or_else(|err| { -+ log::error!("rtl8168d: failed to subscribe to IRQ fd: {err}"); -+ process::exit(1); -+ }); - event_queue - .subscribe( - scheme.event_handle().raw(), - Source::Scheme, - event::EventFlags::READ, - ) -- .unwrap(); -- -- libredox::call::setrens(0, 0).expect("rtl8168d: failed to enter null namespace"); -- -- scheme.tick().unwrap(); -+ .unwrap_or_else(|err| { -+ log::error!("rtl8168d: failed to subscribe to scheme fd: {err}"); -+ process::exit(1); -+ }); -+ -+ libredox::call::setrens(0, 0).unwrap_or_else(|err| { -+ log::error!("rtl8168d: failed to enter null namespace: {err}"); -+ process::exit(1); -+ }); -+ -+ if let Err(err) = scheme.tick() { -+ log::error!("rtl8168d: failed initial scheme tick: {err}"); -+ process::exit(1); -+ } - -- for event in event_queue.map(|e| e.expect("rtl8168d: failed to get next event")) { -+ loop { -+ let event = match event_queue.next() { -+ Some(Ok(event)) => event, -+ Some(Err(err)) => { -+ log::error!("rtl8168d: failed to get next event: {err}"); -+ continue; -+ } -+ None => break, -+ }; - match event.user_data { - Source::Irq => { - let mut irq = [0; 8]; -- irq_file.irq_handle().read(&mut irq).unwrap(); -+ if let Err(err) = irq_file.irq_handle().read(&mut irq) { -+ log::error!("rtl8168d: failed to read IRQ: {err}"); -+ continue; -+ } - //TODO: This may be causing spurious interrupts - if unsafe { scheme.adapter_mut().irq() } { -- irq_file.irq_handle().write(&mut irq).unwrap(); -- -- scheme.tick().unwrap(); -+ if let Err(err) = irq_file.irq_handle().write(&mut irq) { -+ log::error!("rtl8168d: failed to write IRQ: {err}"); -+ continue; -+ } -+ -+ if let Err(err) = scheme.tick() { -+ log::error!("rtl8168d: failed to handle IRQ tick: {err}"); -+ } - } - } - Source::Scheme => { -- scheme.tick().unwrap(); -+ if let Err(err) = scheme.tick() { -+ log::error!("rtl8168d: failed to handle scheme op: {err}"); -+ } - } - } - } -- unreachable!() -+ -+ process::exit(0); - } -diff --git a/drivers/net/virtio-netd/src/main.rs b/drivers/net/virtio-netd/src/main.rs -index 17d168ef..adbd1086 100644 ---- a/drivers/net/virtio-netd/src/main.rs -+++ b/drivers/net/virtio-netd/src/main.rs -@@ -3,6 +3,7 @@ mod scheme; - use std::fs::File; - use std::io::{Read, Write}; - use std::mem; -+use std::process; - - use driver_network::NetworkScheme; - use pcid_interface::PciFunctionHandle; -@@ -31,8 +32,11 @@ fn main() { - } - - fn daemon_runner(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { -- deamon(daemon, pcid_handle).unwrap(); -- unreachable!(); -+ deamon(daemon, pcid_handle).unwrap_or_else(|err| { -+ log::error!("virtio-netd: daemon failed: {err}"); -+ process::exit(1); -+ }); -+ process::exit(0); - } - - fn deamon( -@@ -52,7 +56,10 @@ fn deamon( - // 0x1000 - virtio-net - let pci_config = pcid_handle.config(); - -- assert_eq!(pci_config.func.full_device_id.device_id, 0x1000); -+ if pci_config.func.full_device_id.device_id != 0x1000 { -+ log::error!("virtio-netd: unexpected device ID {:#06x}, expected 0x1000", pci_config.func.full_device_id.device_id); -+ process::exit(1); -+ } - log::info!("virtio-net: initiating startup sequence :^)"); - - let device = virtio_core::probe_device(&mut pcid_handle)?; -@@ -84,7 +91,8 @@ fn deamon( - device.transport.ack_driver_feature(VIRTIO_NET_F_MAC); - mac - } else { -- unimplemented!() -+ log::error!("virtio-netd: device does not support MAC feature"); -+ return Err("virtio-netd: VIRTIO_NET_F_MAC not supported".into()); - }; - - device.transport.finalize_features(); -@@ -126,12 +134,23 @@ fn deamon( - data: 0, - })?; - -- libredox::call::setrens(0, 0).expect("virtio-netd: failed to enter null namespace"); -+ libredox::call::setrens(0, 0).unwrap_or_else(|err| { -+ log::error!("virtio-netd: failed to enter null namespace: {err}"); -+ process::exit(1); -+ }); - -- scheme.tick()?; -+ if let Err(err) = scheme.tick() { -+ log::error!("virtio-netd: failed initial scheme tick: {err}"); -+ process::exit(1); -+ } - - loop { -- event_queue.read(&mut [0; mem::size_of::()])?; // Wait for event -- scheme.tick()?; -+ if let Err(err) = event_queue.read(&mut [0; mem::size_of::()]) { -+ log::error!("virtio-netd: failed to read event: {err}"); -+ continue; -+ } -+ if let Err(err) = scheme.tick() { -+ log::error!("virtio-netd: failed to handle scheme event: {err}"); -+ } - } - } -diff --git a/drivers/net/virtio-netd/src/scheme.rs b/drivers/net/virtio-netd/src/scheme.rs -index 59b3b93e..d0acb2ba 100644 ---- a/drivers/net/virtio-netd/src/scheme.rs -+++ b/drivers/net/virtio-netd/src/scheme.rs -@@ -27,11 +27,16 @@ impl<'a> VirtioNet<'a> { - // Populate all of the `rx_queue` with buffers to maximize performence. - let mut rx_buffers = vec![]; - for i in 0..(rx.descriptor_len() as usize) { -- rx_buffers.push(unsafe { -- Dma::<[u8]>::zeroed_slice(MAX_BUFFER_LEN) -- .unwrap() -- .assume_init() -- }); -+ let buf = unsafe { -+ match Dma::<[u8]>::zeroed_slice(MAX_BUFFER_LEN) { -+ Ok(dma) => dma.assume_init(), -+ Err(err) => { -+ log::error!("virtio-netd: failed to allocate rx buffer: {err}"); -+ continue; -+ } -+ } -+ }; -+ rx_buffers.push(buf); - - let chain = ChainBuilder::new() - .chain(Buffer::new_unsized(&rx_buffers[i]).flags(DescriptorFlags::WRITE_ONLY)) -diff --git a/drivers/pcid-spawner/src/main.rs b/drivers/pcid-spawner/src/main.rs -index a968f4d4..bfff05c3 100644 ---- a/drivers/pcid-spawner/src/main.rs -+++ b/drivers/pcid-spawner/src/main.rs -@@ -1,11 +1,41 @@ -+use std::env; - use std::fs; - use std::process::Command; -+use std::thread; -+use std::time::Duration; - - use anyhow::{anyhow, Context, Result}; - - use pcid_interface::config::Config; - use pcid_interface::PciFunctionHandle; - -+fn strict_usb_boot() -> bool { -+ matches!( -+ env::var("REDBEAR_STRICT_USB_BOOT") -+ .ok() -+ .as_deref() -+ .map(str::to_ascii_lowercase) -+ .as_deref(), -+ Some("1" | "true" | "yes" | "on") -+ ) -+} -+ -+fn should_detach_in_initfs(initfs: bool, class: u8, subclass: u8, strict_usb_boot: bool) -> bool { -+ if !initfs { -+ return false; -+ } -+ -+ if class == 0x01 { -+ return false; -+ } -+ -+ if strict_usb_boot && class == 0x0C && subclass == 0x03 { -+ return false; -+ } -+ -+ true -+} -+ - fn main() -> Result<()> { - let mut args = pico_args::Arguments::from_env(); - let initfs = args.contains("--initfs"); -@@ -30,12 +59,33 @@ fn main() -> Result<()> { - } - - let config: Config = toml::from_str(&config_data)?; - + let strict_usb_boot = strict_usb_boot(); - + - + log::info!( - + "pcid-spawner: starting (initfs={}, strict_usb_boot={})", - + initfs, strict_usb_boot - + ); -+ -+ let pci_dir = { -+ let mut attempts = 0u32; -+ loop { -+ match fs::read_dir("/scheme/pci") { -+ Ok(dir) => break dir, -+ Err(e) if e.raw_os_error() == Some(19) => { -+ attempts += 1; -+ if attempts > 50 { -+ return Err(e).context("pcid-spawner: /scheme/pci never appeared after 5 s"); -+ } -+ log::warn!( -+ "pcid-spawner: /scheme/pci not ready yet (ENODEV, attempt {}/50), waiting 100 ms", -+ attempts -+ ); -+ thread::sleep(Duration::from_millis(100)); -+ } -+ Err(e) => return Err(e).context("pcid-spawner: failed to read /scheme/pci"), -+ } -+ } -+ }; - -- for entry in fs::read_dir("/scheme/pci")? { -+ for entry in pci_dir { - let entry = entry.context("failed to get entry")?; -@@ -55,10 +90,11 @@ fn main() -> Result<()> { - }; - - let full_device_id = handle.config().func.full_device_id; -+ let device_addr = handle.config().func.addr; - - log::debug!( - "pcid-spawner enumerated: PCI {} {}", -- handle.config().func.addr, -+ device_addr, - full_device_id.display() - ); - -@@ -67,7 +103,7 @@ fn main() -> Result<()> { - .iter() - .find(|driver| driver.match_function(&full_device_id)) - else { -- log::debug!("no driver for {}, continuing", handle.config().func.addr); -+ log::debug!("no driver for {}, continuing", device_addr); - continue; - }; - -@@ -85,16 +121,105 @@ fn main() -> Result<()> { - let mut command = Command::new(program); - command.args(args); - -- log::info!("pcid-spawner: spawn {:?}", command); -+ log::info!( -+ "pcid-spawner: matched {} to driver {:?}", -+ device_addr, -+ driver.command -+ ); -+ log::info!("pcid-spawner: enabling {} before spawn", device_addr); -+ -+ if let Err(err) = handle.try_enable_device() { -+ log::error!( -+ "pcid-spawner: failed to enable {} before spawn: {}", -+ device_addr, -+ err -+ ); -+ continue; -+ } - -- handle.enable_device(); -+ let irq_info = handle.config().func.legacy_interrupt_line; -+ let irq_desc = match irq_info { -+ Some(irq) => format!("INTx#{irq}"), -+ None => "MSI/MSI-X only".to_string(), -+ }; -+ log::info!( -+ "pcid-spawner: {} enabled (interrupt: {}, driver: {:?})", -+ device_addr, -+ irq_desc, -+ driver.command, -+ ); - - let channel_fd = handle.into_inner_fd(); - command.env("PCID_CLIENT_CHANNEL", channel_fd.to_string()); - -+ log::info!("pcid-spawner: spawn {:?}", command); - #[allow(deprecated, reason = "we can't yet move this to init")] -- daemon::Daemon::spawn(command); -- syscall::close(channel_fd as usize).unwrap(); -+ if should_detach_in_initfs( -+ initfs, -+ full_device_id.class, -+ full_device_id.subclass, -+ strict_usb_boot, -+ ) { -+ log::warn!( -+ "pcid-spawner: detached initfs spawn for {} to avoid blocking early boot", -+ device_addr -+ ); -+ -+ let device_addr = device_addr.to_string(); -+ thread::spawn(move || { -+ #[allow(deprecated, reason = "we can't yet move this to init")] -+ if let Err(err) = daemon::Daemon::spawn(command) { -+ log::error!( -+ "pcid-spawner: spawn/readiness failed for {}: {}", -+ device_addr, -+ err -+ ); -+ log::error!( -+ "pcid-spawner: {} remains enabled without a confirmed ready driver", -+ device_addr -+ ); -+ } -+ if let Err(err) = syscall::close(channel_fd as usize) { -+ log::error!( -+ "pcid-spawner: failed to close channel fd {} for {}: {}", -+ channel_fd, -+ device_addr, -+ err -+ ); -+ } -+ }); -+ } else { -+ log::info!( -+ "pcid-spawner: blocking on storage driver spawn for {} (class={:#04x})", -+ device_addr, -+ full_device_id.class -+ ); -+ #[allow(deprecated, reason = "we can't yet move this to init")] -+ if let Err(err) = daemon::Daemon::spawn(command) { -+ log::error!( -+ "pcid-spawner: spawn/readiness failed for {}: {}", -+ device_addr, -+ err -+ ); -+ log::error!( -+ "pcid-spawner: {} remains enabled without a confirmed ready driver", -+ device_addr -+ ); -+ } else { -+ log::info!( -+ "pcid-spawner: storage driver ready for {}", -+ device_addr -+ ); -+ } -+ if let Err(err) = syscall::close(channel_fd as usize) { -+ log::error!( -+ "pcid-spawner: failed to close channel fd {} for {}: {}", -+ channel_fd, -+ device_addr, -+ err -+ ); -+ } -+ } - } - - Ok(()) -diff --git a/drivers/pcid/src/cfg_access/fallback.rs b/drivers/pcid/src/cfg_access/fallback.rs -index 671d17f7..ea8f69f8 100644 ---- a/drivers/pcid/src/cfg_access/fallback.rs -+++ b/drivers/pcid/src/cfg_access/fallback.rs -@@ -33,7 +33,12 @@ impl Pci { - "PCI: couldn't find or access PCIe extended configuration, \ - and thus falling back to PCI 3.0 io ports" - ); -- common::acquire_port_io_rights().expect("pcid: failed to get IO port rights"); -+ common::acquire_port_io_rights() -+ .map_err(|err| { -+ log::error!("pcid: failed to get IO port rights: {err}"); -+ err -+ }) -+ .expect("pcid: IO port rights required for PCI 3.0 fallback"); - } - }); - } -@@ -61,8 +66,9 @@ impl ConfigRegionAccess for Pci { - - Self::set_iopl(); - -- let offset = -- u8::try_from(offset).expect("offset too large for PCI 3.0 configuration space"); -+ let Ok(offset) = u8::try_from(offset) else { -+ return 0xFFFFFFFF; -+ }; - let address = Self::address(address, offset); - - Pio::::new(0xCF8).write(address); -@@ -74,8 +80,9 @@ impl ConfigRegionAccess for Pci { - - Self::set_iopl(); - -- let offset = -- u8::try_from(offset).expect("offset too large for PCI 3.0 configuration space"); -+ let Ok(offset) = u8::try_from(offset) else { -+ return; -+ }; - let address = Self::address(address, offset); - - Pio::::new(0xCF8).write(address); -diff --git a/drivers/pcid/src/cfg_access/mod.rs b/drivers/pcid/src/cfg_access/mod.rs -index c2552448..0fe215a6 100644 ---- a/drivers/pcid/src/cfg_access/mod.rs -+++ b/drivers/pcid/src/cfg_access/mod.rs -@@ -38,42 +38,57 @@ fn locate_ecam_dtb( - ) - })?; - -- let address = node.reg().unwrap().next().unwrap().starting_address as u64; -+ let mut reg = node.reg().ok_or_else(|| { -+ io::Error::new(io::ErrorKind::NotFound, "pci-host-ecam-generic missing 'reg' property") -+ })?; -+ let address = reg.next().ok_or_else(|| { -+ io::Error::new(io::ErrorKind::NotFound, "pci-host-ecam-generic 'reg' has no entries") -+ })?.starting_address as u64; - -- let bus_range = node.property("bus-range").unwrap(); -- assert_eq!(bus_range.value.len(), 8); -- let start_bus = u32::from_be_bytes(<[u8; 4]>::try_from(&bus_range.value[0..4]).unwrap()); -- let end_bus = u32::from_be_bytes(<[u8; 4]>::try_from(&bus_range.value[4..8]).unwrap()); -+ let bus_range = node.property("bus-range").ok_or_else(|| { -+ io::Error::new(io::ErrorKind::NotFound, "pci-host-ecam-generic missing 'bus-range' property") -+ })?; -+ if bus_range.value.len() != 8 { -+ return Err(io::Error::new(io::ErrorKind::InvalidData, "pci-host-ecam-generic 'bus-range' not 8 bytes")); -+ } -+ let start_bus = u32::from_be_bytes(<[u8; 4]>::try_from(&bus_range.value[0..4]).map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "bus-range start parse failed"))?); -+ let end_bus = u32::from_be_bytes(<[u8; 4]>::try_from(&bus_range.value[4..8]).map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "bus-range end parse failed"))?); - -- // address-cells == 3, size-cells == 2, interrupt-cells == 1 -- let mut interrupt_map_data = node -- .property("interrupt-map") -- .unwrap() -+ let interrupt_map_prop = node.property("interrupt-map").ok_or_else(|| { -+ io::Error::new(io::ErrorKind::NotFound, "pci-host-ecam-generic missing 'interrupt-map' property") -+ })?; -+ let mut interrupt_map_data = interrupt_map_prop - .value - .chunks_exact(4) -- .map(|x| u32::from_be_bytes(<[u8; 4]>::try_from(x).unwrap())); -+ .map(|x| u32::from_be_bytes(<[u8; 4]>::try_from(x).unwrap_or([0, 0, 0, 0]))); - let mut interrupt_map = Vec::::new(); - while let Ok([addr1, addr2, addr3, int1, phandle]) = interrupt_map_data.next_chunk::<5>() { -- let parent = dt.find_phandle(phandle).unwrap(); -- let parent_address_cells = u32::from_be_bytes( -- parent.property("#address-cells").unwrap().value[..4] -- .try_into() -- .unwrap(), -- ); -+ let Some(parent) = dt.find_phandle(phandle) else { -+ log::warn!("pcid: DTB interrupt-map references phandle {phandle} not found, skipping"); -+ continue; -+ }; -+ let parent_address_cells = match parent.property("#address-cells") { -+ Some(prop) => u32::from_be_bytes( -+ prop.value[..4] -+ .try_into() -+ .unwrap_or([0, 0, 0, 0]), -+ ), -+ None => 0, -+ }; - match parent_address_cells { - 0 => {} - 1 => { -- assert_eq!(interrupt_map_data.next().unwrap(), 0); -+ let _ = interrupt_map_data.next(); - } - 2 => { -- assert_eq!(interrupt_map_data.next_chunk::<2>().unwrap(), [0, 0]); -+ let _ = interrupt_map_data.next_chunk::<2>(); - } - 3 => { -- assert_eq!(interrupt_map_data.next_chunk::<3>().unwrap(), [0, 0, 0]); -+ let _ = interrupt_map_data.next_chunk::<3>(); - } - _ => break, - }; -- let parent_interrupt_cells = parent.interrupt_cells().unwrap(); -+ let parent_interrupt_cells = parent.interrupt_cells().unwrap_or(1); - let parent_interrupt = match parent_interrupt_cells { - 1 if let Some(a) = interrupt_map_data.next() => [a, 0, 0], - 2 if let Ok([a, b]) = interrupt_map_data.next_chunk::<2>() => [a, b, 0], -@@ -94,8 +109,8 @@ fn locate_ecam_dtb( - let mut cells = interrupt_mask_node - .value - .chunks_exact(4) -- .map(|x| u32::from_be_bytes(<[u8; 4]>::try_from(x).unwrap())); -- cells.next_chunk::<4>().unwrap().to_owned() -+ .map(|x| u32::from_be_bytes(<[u8; 4]>::try_from(x).unwrap_or([0, 0, 0, 0]))); -+ cells.next_chunk::<4>().unwrap_or([u32::MAX, u32::MAX, u32::MAX, u32::MAX]).to_owned() - } else { - [u32::MAX, u32::MAX, u32::MAX, u32::MAX] - }; -@@ -104,8 +119,8 @@ fn locate_ecam_dtb( - PcieAllocs(&[PcieAlloc { - base_addr: address, - seg_group_num: 0, -- start_bus: start_bus.try_into().unwrap(), -- end_bus: end_bus.try_into().unwrap(), -+ start_bus: start_bus.try_into().map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "start_bus overflow"))?, -+ end_bus: end_bus.try_into().map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "end_bus overflow"))?, - _rsvd: [0; 4], - }]), - interrupt_map, -@@ -165,7 +180,10 @@ impl Mcfg { - // crashing. `as_encoded_bytes()` returns some superset - // of ASCII, so directly comparing it with an ASCII name - // is fine. -- let table_filename = table_path.file_name().unwrap().as_encoded_bytes(); -+ let table_filename = match table_path.file_name() { -+ Some(name) => name.as_encoded_bytes(), -+ None => continue, -+ }; - if table_filename.get(0..4) == Some(&MCFG_NAME) { - let bytes = fs::read(table_path)?.into_boxed_slice(); - match Mcfg::parse(&*bytes) { -diff --git a/drivers/pcid/src/driver_handler.rs b/drivers/pcid/src/driver_handler.rs -index f70a7f6d..64701f6c 100644 ---- a/drivers/pcid/src/driver_handler.rs -+++ b/drivers/pcid/src/driver_handler.rs -@@ -48,8 +48,18 @@ impl<'a> DriverHandler<'a> { - self.capabilities - .iter() - .filter_map(|capability| match capability { -- PciCapability::Vendor(addr) => unsafe { -- Some(VendorSpecificCapability::parse(*addr, self.pcie)) -+ PciCapability::Vendor(addr) => match unsafe { -+ VendorSpecificCapability::try_parse(*addr, self.pcie) -+ } { -+ Ok(capability) => Some(capability), -+ Err(err) => { -+ log::warn!( -+ "pcid: skipping malformed vendor capability at {:#x}: {}", -+ addr.offset, -+ err -+ ); -+ None -+ } - }, - _ => None, - }) -@@ -230,10 +240,14 @@ impl<'a> DriverHandler<'a> { - } - info.set_message_info( - message_addr, -- message_addr_and_data -- .data -- .try_into() -- .expect("pcid: MSI message data too big"), -+ match message_addr_and_data.data.try_into() { -+ Ok(d) => d, -+ Err(_) => { -+ return PcidClientResponse::Error( -+ PcidServerResponseError::InvalidBitPattern, -+ ) -+ } -+ }, - self.pcie, - ); - } -@@ -266,7 +280,7 @@ impl<'a> DriverHandler<'a> { - ); - } - } -- _ => unreachable!(), -+ _ => PcidClientResponse::Error(PcidServerResponseError::UnrecognizedRequest), - }, - PcidClientRequest::ReadConfig(offset) => { - let value = unsafe { self.pcie.read(self.func.addr, offset) }; -@@ -278,7 +292,7 @@ impl<'a> DriverHandler<'a> { - } - return PcidClientResponse::WriteConfig; - } -- _ => unreachable!(), -+ _ => PcidClientResponse::Error(PcidServerResponseError::UnrecognizedRequest), - } - } - } -diff --git a/drivers/pcid/src/driver_interface/bar.rs b/drivers/pcid/src/driver_interface/bar.rs -index b2c1d35b..3a83bb4d 100644 ---- a/drivers/pcid/src/driver_interface/bar.rs -+++ b/drivers/pcid/src/driver_interface/bar.rs -@@ -1,7 +1,38 @@ - use std::convert::TryInto; -+use std::fmt; -+use std::process; - - use serde::{Deserialize, Serialize}; - -+#[derive(Clone, Copy, Debug, Eq, PartialEq)] -+pub enum PciBarError { -+ Missing, -+ ExpectedPortFoundMemory, -+ ExpectedMemoryFoundPort, -+ AddressTooLarge, -+ SizeTooLarge, -+} -+ -+impl fmt::Display for PciBarError { -+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -+ match self { -+ PciBarError::Missing => write!(f, "expected BAR to exist"), -+ PciBarError::ExpectedPortFoundMemory => { -+ write!(f, "expected port BAR, found memory BAR") -+ } -+ PciBarError::ExpectedMemoryFoundPort => { -+ write!(f, "expected memory BAR, found port BAR") -+ } -+ PciBarError::AddressTooLarge => { -+ write!(f, "conversion from 64-bit BAR address to usize failed") -+ } -+ PciBarError::SizeTooLarge => { -+ write!(f, "conversion from 64-bit BAR size to usize failed") -+ } -+ } -+ } -+} -+ - // This type is used instead of [pci_types::Bar] in the driver interface as the - // latter can't be serialized and is missing the convenience functions of [PciBar]. - #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] -@@ -30,26 +61,88 @@ impl PciBar { - } - - pub fn expect_port(&self) -> u16 { -+ match self.try_port() { -+ Ok(port) => port, -+ Err(err) => { -+ log::error!("{err}"); -+ process::exit(1); -+ } -+ } -+ } -+ -+ pub fn try_port(&self) -> Result { - match *self { -- PciBar::Port(port) => port, -+ PciBar::Port(port) => Ok(port), - PciBar::Memory32 { .. } | PciBar::Memory64 { .. } => { -- panic!("expected port BAR, found memory BAR"); -+ Err(PciBarError::ExpectedPortFoundMemory) - } -- PciBar::None => panic!("expected BAR to exist"), -+ PciBar::None => Err(PciBarError::Missing), - } - } - - pub fn expect_mem(&self) -> (usize, usize) { -+ match self.try_mem() { -+ Ok(result) => result, -+ Err(err) => { -+ log::error!("{err}"); -+ process::exit(1); -+ } -+ } -+ } -+ -+ pub fn try_mem(&self) -> Result<(usize, usize), PciBarError> { - match *self { -- PciBar::Memory32 { addr, size } => (addr as usize, size as usize), -- PciBar::Memory64 { addr, size } => ( -- addr.try_into() -- .expect("conversion from 64bit BAR to usize failed"), -- size.try_into() -- .expect("conversion from 64bit BAR size to usize failed"), -- ), -- PciBar::Port(_) => panic!("expected memory BAR, found port BAR"), -- PciBar::None => panic!("expected BAR to exist"), -+ PciBar::Memory32 { addr, size } => Ok((addr as usize, size as usize)), -+ PciBar::Memory64 { addr, size } => Ok(( -+ addr.try_into().map_err(|_| PciBarError::AddressTooLarge)?, -+ size.try_into().map_err(|_| PciBarError::SizeTooLarge)?, -+ )), -+ PciBar::Port(_) => Err(PciBarError::ExpectedMemoryFoundPort), -+ PciBar::None => Err(PciBarError::Missing), - } - } - } -+ -+#[cfg(test)] -+mod tests { -+ use super::{PciBar, PciBarError}; -+ -+ #[test] -+ fn try_port_accepts_port_bar() { -+ assert_eq!(PciBar::Port(0x1234).try_port(), Ok(0x1234)); -+ } -+ -+ #[test] -+ fn try_port_rejects_non_port_bars() { -+ assert_eq!( -+ PciBar::Memory32 { -+ addr: 0x1000, -+ size: 0x100, -+ } -+ .try_port(), -+ Err(PciBarError::ExpectedPortFoundMemory) -+ ); -+ assert_eq!(PciBar::None.try_port(), Err(PciBarError::Missing)); -+ } -+ -+ #[test] -+ fn try_mem_accepts_memory_bars() { -+ assert_eq!( -+ PciBar::Memory32 { -+ addr: 0x1000, -+ size: 0x200, -+ } -+ .try_mem(), -+ Ok((0x1000, 0x200)) -+ ); -+ } -+ -+ #[test] -+ fn try_mem_rejects_non_memory_bars() { -+ assert_eq!( -+ PciBar::Port(0x1234).try_mem(), -+ Err(PciBarError::ExpectedMemoryFoundPort) -+ ); -+ assert_eq!(PciBar::None.try_mem(), Err(PciBarError::Missing)); -+ } -+} -diff --git a/drivers/pcid/src/driver_interface/cap.rs b/drivers/pcid/src/driver_interface/cap.rs -index 19521608..17c26c0c 100644 ---- a/drivers/pcid/src/driver_interface/cap.rs -+++ b/drivers/pcid/src/driver_interface/cap.rs -@@ -1,14 +1,44 @@ - use pci_types::capability::PciCapabilityAddress; - use pci_types::ConfigRegionAccess; - use serde::{Deserialize, Serialize}; -+use std::fmt; -+use std::process; - - #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] - pub struct VendorSpecificCapability { - pub data: Vec, - } - -+#[derive(Clone, Copy, Debug, Eq, PartialEq)] -+pub enum VendorSpecificCapabilityError { -+ InvalidLength(u16), -+} -+ -+impl fmt::Display for VendorSpecificCapabilityError { -+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -+ match self { -+ VendorSpecificCapabilityError::InvalidLength(length) => { -+ write!(f, "invalid vendor capability length: {length}") -+ } -+ } -+ } -+} -+ - impl VendorSpecificCapability { - pub unsafe fn parse(addr: PciCapabilityAddress, access: &dyn ConfigRegionAccess) -> Self { -+ match Self::try_parse(addr, access) { -+ Ok(cap) => cap, -+ Err(err) => { -+ log::error!("{err}"); -+ process::exit(1); -+ } -+ } -+ } -+ -+ pub unsafe fn try_parse( -+ addr: PciCapabilityAddress, -+ access: &dyn ConfigRegionAccess, -+ ) -> Result { - let dword = access.read(addr.address, addr.offset); - let length = ((dword >> 16) & 0xFF) as u16; - // let next = (dword >> 8) & 0xFF; -@@ -17,11 +47,9 @@ impl VendorSpecificCapability { - // addr.offset - // ); - let data = if length > 0 { -- assert!( -- length > 3 && length % 4 == 0, -- "invalid range length: {}", -- length -- ); -+ if !(length > 3 && length % 4 == 0) { -+ return Err(VendorSpecificCapabilityError::InvalidLength(length)); -+ } - let mut raw_data = { - (addr.offset..addr.offset + length) - .step_by(4) -@@ -33,6 +61,75 @@ impl VendorSpecificCapability { - log::warn!("Vendor specific capability is invalid"); - Vec::new() - }; -- VendorSpecificCapability { data } -+ Ok(VendorSpecificCapability { data }) -+ } -+} -+ -+#[cfg(test)] -+mod tests { -+ use super::{VendorSpecificCapability, VendorSpecificCapabilityError}; -+ use pci_types::capability::PciCapabilityAddress; -+ use pci_types::{ConfigRegionAccess, PciAddress}; -+ use std::collections::BTreeMap; -+ use std::sync::Mutex; -+ -+ #[derive(Default)] -+ struct MockConfigRegionAccess { -+ values: Mutex>, -+ } -+ -+ impl MockConfigRegionAccess { -+ fn with_read(address: PciAddress, offset: u16, value: u32) -> Self { -+ let mut map = BTreeMap::new(); -+ map.insert((address, offset), value); -+ Self { -+ values: Mutex::new(map), -+ } -+ } -+ } -+ -+ impl ConfigRegionAccess for MockConfigRegionAccess { -+ unsafe fn read(&self, address: PciAddress, offset: u16) -> u32 { -+ self.values -+ .lock() -+ .expect("mock config lock poisoned") -+ .get(&(address, offset)) -+ .copied() -+ .unwrap_or_default() -+ } -+ -+ unsafe fn write(&self, _address: PciAddress, _offset: u16, _value: u32) {} -+ } -+ -+ #[test] -+ fn try_parse_accepts_valid_vendor_capability() { -+ let address = PciAddress::new(0, 0, 1, 0); -+ let capability = PciCapabilityAddress { -+ address, -+ offset: 0x40, -+ }; -+ let access = MockConfigRegionAccess::with_read(address, 0x40, 0x0010_0000); -+ -+ let capability = unsafe { VendorSpecificCapability::try_parse(capability, &access) }; -+ assert_eq!( -+ capability -+ .expect("valid vendor capability should parse") -+ .data -+ .len(), -+ 13 -+ ); -+ } -+ -+ #[test] -+ fn try_parse_rejects_invalid_length() { -+ let address = PciAddress::new(0, 0, 1, 0); -+ let capability = PciCapabilityAddress { -+ address, -+ offset: 0x40, -+ }; -+ let access = MockConfigRegionAccess::with_read(address, 0x40, 0x0005_0000); -+ -+ let error = unsafe { VendorSpecificCapability::try_parse(capability, &access) }.unwrap_err(); -+ assert_eq!(error, VendorSpecificCapabilityError::InvalidLength(5)); - } - } -diff --git a/drivers/pcid/src/driver_interface/config.rs b/drivers/pcid/src/driver_interface/config.rs -index e148b26c..041f0ced 100644 ---- a/drivers/pcid/src/driver_interface/config.rs -+++ b/drivers/pcid/src/driver_interface/config.rs -@@ -47,7 +47,13 @@ impl DriverConfig { - let mut device_found = false; - for (vendor, devices) in ids { - let vendor_without_prefix = vendor.trim_start_matches("0x"); -- let vendor = i64::from_str_radix(vendor_without_prefix, 16).unwrap() as u16; -+ let Ok(vendor_val) = i64::from_str_radix(vendor_without_prefix, 16) else { -+ log::warn!( -+ "invalid hex vendor ID '{vendor_without_prefix}' in driver config, skipping" -+ ); -+ continue; -+ }; -+ let vendor = vendor_val as u16; - - if vendor != id.vendor_id { - continue; -diff --git a/drivers/pcid/src/driver_interface/irq_helpers.rs b/drivers/pcid/src/driver_interface/irq_helpers.rs -index 28ca077a..bff35650 100644 ---- a/drivers/pcid/src/driver_interface/irq_helpers.rs -+++ b/drivers/pcid/src/driver_interface/irq_helpers.rs -@@ -7,6 +7,7 @@ use std::convert::TryFrom; - use std::fs::{self, File}; - use std::io::{self, prelude::*}; - use std::num::NonZeroU8; -+use std::process; - - use crate::driver_interface::msi::{MsiAddrAndData, MsixTableEntry}; - -@@ -24,11 +25,13 @@ pub fn read_bsp_apic_id() -> io::Result { - buffer[0], buffer[1], buffer[2], buffer[3], - ])) - } else { -- panic!( -- "`/scheme/irq` scheme responded with {} bytes, expected {}", -- bytes_read, -- std::mem::size_of::() -- ); -+ return Err(io::Error::new( -+ io::ErrorKind::InvalidData, -+ format!( -+ "`/scheme/irq` scheme responded with {bytes_read} bytes, expected {}", -+ std::mem::size_of::() -+ ), -+ )); - }) - .or(Err(io::Error::new( - io::ErrorKind::InvalidData, -@@ -83,7 +86,12 @@ pub fn allocate_aligned_interrupt_vectors( - alignment: NonZeroU8, - count: u8, - ) -> io::Result)>> { -- let cpu_id = u8::try_from(cpu_id).expect("usize cpu ids not implemented yet"); -+ let cpu_id = u8::try_from(cpu_id).map_err(|_| { -+ io::Error::new( -+ io::ErrorKind::InvalidInput, -+ format!("CPU id {cpu_id} too large for u8 (usize cpu ids not supported)"), -+ ) -+ })?; - if count == 0 { - return Ok(None); - } -@@ -163,7 +171,7 @@ pub fn allocate_aligned_interrupt_vectors( - /// Allocate at most `count` interrupt vectors, which can start at any offset. Unless MSI is used - /// and an entire aligned range of vectors is needed, this function should be used. - pub fn allocate_interrupt_vectors(cpu_id: usize, count: u8) -> io::Result)>> { -- allocate_aligned_interrupt_vectors(cpu_id, NonZeroU8::new(1).unwrap(), count) -+ allocate_aligned_interrupt_vectors(cpu_id, NonZeroU8::MIN, count) - } - - /// Allocate a single interrupt vector, returning both the vector number (starting from 32 up to -@@ -176,44 +184,66 @@ pub fn allocate_single_interrupt_vector(cpu_id: usize) -> io::Result return Err(err), - }; - assert_eq!(files.len(), 1); -- Ok(Some((base, files.pop().unwrap()))) -+ let handle = files.pop().ok_or_else(|| { -+ io::Error::new( -+ io::ErrorKind::Other, -+ "allocate_interrupt_vectors returned empty file list despite count=1", -+ ) -+ })?; -+ Ok(Some((base, handle))) - } - - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] --pub fn allocate_single_interrupt_vector_for_msi(cpu_id: usize) -> (MsiAddrAndData, File) { -+pub fn try_allocate_single_interrupt_vector_for_msi( -+ cpu_id: usize, -+) -> io::Result<(MsiAddrAndData, File)> { - use crate::driver_interface::msi::x86 as x86_msix; - -- // FIXME for cpu_id >255 we need to use the IOMMU to use IRQ remapping -- let lapic_id = u8::try_from(cpu_id).expect("CPU id couldn't fit inside u8"); -+ let lapic_id = u8::try_from(cpu_id).map_err(|_| { -+ io::Error::new( -+ io::ErrorKind::InvalidInput, -+ format!("CPU id {cpu_id} could not fit inside u8"), -+ ) -+ })?; - let rh = false; - let dm = false; - let addr = x86_msix::message_address(lapic_id, rh, dm); - -- let (vector, interrupt_handle) = allocate_single_interrupt_vector(cpu_id) -- .expect("failed to allocate interrupt vector") -- .expect("no interrupt vectors left"); -+ let (vector, interrupt_handle) = allocate_single_interrupt_vector(cpu_id)? -+ .ok_or_else(|| io::Error::other("no interrupt vectors left"))?; - let msg_data = x86_msix::message_data_edge_triggered(x86_msix::DeliveryMode::Fixed, vector); - -- ( -+ Ok(( - MsiAddrAndData { - addr, - data: msg_data, - }, - interrupt_handle, -- ) -+ )) - } - - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] --pub fn allocate_first_msi_interrupt_on_bsp( -+pub fn allocate_single_interrupt_vector_for_msi(cpu_id: usize) -> (MsiAddrAndData, File) { -+ match try_allocate_single_interrupt_vector_for_msi(cpu_id) { -+ Ok(result) => result, -+ Err(err) => { -+ log::error!("failed to allocate MSI interrupt vector: {err}"); -+ process::exit(1); -+ } -+ } -+} -+ -+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -+pub fn try_allocate_first_msi_interrupt_on_bsp( - pcid_handle: &mut crate::driver_interface::PciFunctionHandle, --) -> File { -+) -> Result { - use crate::driver_interface::{MsiSetFeatureInfo, PciFeature, SetFeatureInfo}; - -- // TODO: Allow allocation of up to 32 vectors. -- -- let destination_id = read_bsp_apic_id().expect("failed to read BSP apic id"); -- let (msg_addr_and_data, interrupt_handle) = -- allocate_single_interrupt_vector_for_msi(destination_id); -+ let destination_id = read_bsp_apic_id().map_err(InterruptVectorError::ApicId)?; -+ let (msg_addr_and_data, interrupt_handle) = try_allocate_single_interrupt_vector_for_msi( -+ destination_id, -+ ) -+ .map_err(InterruptVectorError::Allocate)?; - - let set_feature_info = MsiSetFeatureInfo { - multi_message_enable: Some(0), -@@ -222,10 +252,25 @@ pub fn allocate_first_msi_interrupt_on_bsp( - }; - pcid_handle.set_feature_info(SetFeatureInfo::Msi(set_feature_info)); - -- pcid_handle.enable_feature(PciFeature::Msi); -+ pcid_handle -+ .try_enable_feature(PciFeature::Msi) -+ .map_err(InterruptVectorError::IrqHandle)?; - log::debug!("Enabled MSI"); - -- interrupt_handle -+ Ok(interrupt_handle) -+} -+ -+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -+pub fn allocate_first_msi_interrupt_on_bsp( -+ pcid_handle: &mut crate::driver_interface::PciFunctionHandle, -+) -> File { -+ match try_allocate_first_msi_interrupt_on_bsp(pcid_handle) { -+ Ok(handle) => handle, -+ Err(err) => { -+ log::error!("failed to allocate first MSI interrupt on BSP: {err}"); -+ process::exit(1); -+ } -+ } - } - - pub struct InterruptVector { -@@ -234,6 +279,39 @@ pub struct InterruptVector { - kind: InterruptVectorKind, - } - -+#[derive(Debug)] -+pub enum InterruptVectorError { -+ MissingMsixFeature, -+ MissingLegacyInterrupt, -+ ApicId(io::Error), -+ Allocate(io::Error), -+ IrqHandle(io::Error), -+ MsixMap(super::msi::MsixMapError), -+} -+ -+impl std::fmt::Display for InterruptVectorError { -+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { -+ match self { -+ InterruptVectorError::MissingMsixFeature => { -+ write!(f, "missing MSI-X feature information") -+ } -+ InterruptVectorError::MissingLegacyInterrupt => { -+ write!(f, "no interrupts supported at all") -+ } -+ InterruptVectorError::ApicId(err) => write!(f, "failed to read BSP APIC ID: {err}"), -+ InterruptVectorError::Allocate(err) => { -+ write!(f, "failed to allocate interrupt vector: {err}") -+ } -+ InterruptVectorError::IrqHandle(err) => { -+ write!(f, "failed to open IRQ handle: {err}") -+ } -+ InterruptVectorError::MsixMap(err) => { -+ write!(f, "failed to map MSI-X registers: {err}") -+ } -+ } -+ } -+} -+ - enum InterruptVectorKind { - Legacy, - Msi, -@@ -266,10 +344,10 @@ impl InterruptVector { - // FIXME allow allocating multiple interrupt vectors - // FIXME move MSI-X IRQ allocation to pcid - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] --pub fn pci_allocate_interrupt_vector( -+pub fn try_pci_allocate_interrupt_vector( - pcid_handle: &mut crate::driver_interface::PciFunctionHandle, - driver: &str, --) -> InterruptVector { -+) -> Result { - let features = pcid_handle.fetch_all_features(); - - let has_msi = features.iter().any(|feature| feature.is_msi()); -@@ -278,57 +356,89 @@ pub fn pci_allocate_interrupt_vector( - if has_msix { - let msix_info = match pcid_handle.feature_info(super::PciFeature::MsiX) { - super::PciFeatureInfo::MsiX(msix) => msix, -- _ => unreachable!(), -+ _ => return Err(InterruptVectorError::MissingMsixFeature), - }; -- let mut info = unsafe { msix_info.map_and_mask_all(pcid_handle) }; -+ let mut info = unsafe { msix_info.try_map_and_mask_all(pcid_handle) } -+ .map_err(InterruptVectorError::MsixMap)?; - - pcid_handle.enable_feature(crate::driver_interface::PciFeature::MsiX); - - let entry = info.table_entry_pointer(0); - -- let bsp_cpu_id = read_bsp_apic_id() -- .unwrap_or_else(|err| panic!("{driver}: failed to read BSP APIC ID: {err}")); -- let (msg_addr_and_data, irq_handle) = allocate_single_interrupt_vector_for_msi(bsp_cpu_id); -+ let bsp_cpu_id = read_bsp_apic_id().map_err(InterruptVectorError::ApicId)?; -+ let (msg_addr_and_data, irq_handle) = -+ try_allocate_single_interrupt_vector_for_msi(bsp_cpu_id) -+ .map_err(InterruptVectorError::Allocate)?; - entry.write_addr_and_data(msg_addr_and_data); - entry.unmask(); - -- InterruptVector { -+ Ok(InterruptVector { - irq_handle, - vector: 0, - kind: InterruptVectorKind::MsiX { table_entry: entry }, -- } -+ }) - } else if has_msi { -- InterruptVector { -+ Ok(InterruptVector { - irq_handle: allocate_first_msi_interrupt_on_bsp(pcid_handle), - vector: 0, - kind: InterruptVectorKind::Msi, -- } -+ }) - } else if let Some(irq) = pcid_handle.config().func.legacy_interrupt_line { -- // INTx# pin based interrupts. -- InterruptVector { -- irq_handle: irq.irq_handle(driver), -+ Ok(InterruptVector { -+ irq_handle: irq -+ .try_irq_handle(driver) -+ .map_err(InterruptVectorError::IrqHandle)?, - vector: 0, - kind: InterruptVectorKind::Legacy, -- } -+ }) - } else { -- panic!("{driver}: no interrupts supported at all") -+ Err(InterruptVectorError::MissingLegacyInterrupt) - } - } - --// FIXME support MSI on non-x86 systems --#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] -+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - pub fn pci_allocate_interrupt_vector( - pcid_handle: &mut crate::driver_interface::PciFunctionHandle, - driver: &str, - ) -> InterruptVector { -+ match try_pci_allocate_interrupt_vector(pcid_handle, driver) { -+ Ok(vec) => vec, -+ Err(err) => { -+ log::error!("{driver}: {err}"); -+ process::exit(1); -+ } -+ } -+} -+ -+// FIXME support MSI on non-x86 systems -+#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] -+pub fn try_pci_allocate_interrupt_vector( -+ pcid_handle: &mut crate::driver_interface::PciFunctionHandle, -+ driver: &str, -+) -> Result { - if let Some(irq) = pcid_handle.config().func.legacy_interrupt_line { -- // INTx# pin based interrupts. -- InterruptVector { -- irq_handle: irq.irq_handle(driver), -+ Ok(InterruptVector { -+ irq_handle: irq -+ .try_irq_handle(driver) -+ .map_err(InterruptVectorError::IrqHandle)?, - vector: 0, - kind: InterruptVectorKind::Legacy, -- } -+ }) - } else { -- panic!("{driver}: no interrupts supported at all") -+ Err(InterruptVectorError::MissingLegacyInterrupt) -+ } -+} -+ -+#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] -+pub fn pci_allocate_interrupt_vector( -+ pcid_handle: &mut crate::driver_interface::PciFunctionHandle, -+ driver: &str, -+) -> InterruptVector { -+ match try_pci_allocate_interrupt_vector(pcid_handle, driver) { -+ Ok(vec) => vec, -+ Err(err) => { -+ log::error!("{driver}: {err}"); -+ process::exit(1); -+ } - } - } -diff --git a/drivers/pcid/src/driver_interface/mod.rs b/drivers/pcid/src/driver_interface/mod.rs -index bbc7304e..9d7172b9 100644 ---- a/drivers/pcid/src/driver_interface/mod.rs -+++ b/drivers/pcid/src/driver_interface/mod.rs -@@ -30,7 +30,7 @@ pub struct LegacyInterruptLine { - - impl LegacyInterruptLine { - /// Get an IRQ handle for this interrupt line. -- pub fn irq_handle(self, driver: &str) -> File { -+ pub fn try_irq_handle(self, _driver: &str) -> io::Result { - if let Some((phandle, addr, cells)) = self.phandled { - let path = match cells { - 1 => format!("/scheme/irq/phandle-{}/{}", phandle, addr[0]), -@@ -39,15 +39,28 @@ impl LegacyInterruptLine { - "/scheme/irq/phandle-{}/{},{},{}", - phandle, addr[0], addr[1], addr[2] - ), -- _ => panic!( -- "unexpected number of IRQ description cells for phandle {phandle}: {cells}" -- ), -+ _ => { -+ return Err(io::Error::new( -+ io::ErrorKind::InvalidData, -+ format!( -+ "unexpected number of IRQ description cells for phandle {phandle}: {cells}" -+ ), -+ )) -+ } - }; - File::create(path) -- .unwrap_or_else(|err| panic!("{driver}: failed to open IRQ file: {err}")) - } else { - File::open(format!("/scheme/irq/{}", self.irq)) -- .unwrap_or_else(|err| panic!("{driver}: failed to open IRQ file: {err}")) -+ } -+ } -+ -+ pub fn irq_handle(self, driver: &str) -> File { -+ match self.try_irq_handle(driver) { -+ Ok(handle) => handle, -+ Err(err) => { -+ log::error!("{driver}: failed to open IRQ file: {err}"); -+ process::exit(1); -+ } - } - } - } -@@ -59,8 +72,10 @@ impl fmt::Display for LegacyInterruptLine { - 1 => write!(f, "(phandle {}, {:?})", phandle, addr[0]), - 2 => write!(f, "(phandle {}, {:?},{:?})", phandle, addr[0], addr[1]), - 3 => write!(f, "(phandle {}, {:?})", phandle, addr), -- _ => panic!( -- "unexpected number of IRQ description cells for phandle {phandle}: {cells}" -+ _ => write!( -+ f, -+ "(phandle {}, invalid IRQ description cells: {cells})", -+ phandle, - ), - } - } else { -@@ -247,6 +262,7 @@ pub enum PcidClientRequest { - pub enum PcidServerResponseError { - NonexistentFeature(PciFeature), - InvalidBitPattern, -+ UnrecognizedRequest, - } - - #[derive(Debug, Serialize, Deserialize)] -@@ -278,33 +294,51 @@ pub struct PciFunctionHandle { - } - - fn send(w: &mut File, message: &T) { -- let mut data = Vec::new(); -- bincode::serialize_into(&mut data, message).expect("couldn't serialize pcid message"); -- match w.write(&data) { -- Ok(len) => assert_eq!(len, data.len()), -+ if let Err(err) = send_result(w, message) { -+ log::error!("pcid send failed: {err}"); -+ process::exit(1); -+ } -+} -+fn recv(r: &mut File) -> T { -+ match recv_result(r) { -+ Ok(value) => value, - Err(err) => { -- log::error!("writing pcid request failed: {err}"); -+ log::error!("pcid recv failed: {err}"); - process::exit(1); - } - } - } --fn recv(r: &mut File) -> T { -- let mut length_bytes = [0u8; 8]; -- if let Err(err) = r.read_exact(&mut length_bytes) { -- log::error!("reading pcid response length failed: {err}"); -- process::exit(1); -+ -+fn send_result(w: &mut File, message: &T) -> io::Result<()> { -+ let mut data = Vec::new(); -+ bincode::serialize_into(&mut data, message) -+ .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?; -+ -+ let len = w.write(&data)?; -+ if len == data.len() { -+ Ok(()) -+ } else { -+ Err(io::Error::new( -+ io::ErrorKind::WriteZero, -+ format!("short pcid request write: wrote {len} of {} bytes", data.len()), -+ )) - } -+} -+ -+fn recv_result(r: &mut File) -> io::Result { -+ let mut length_bytes = [0u8; 8]; -+ r.read_exact(&mut length_bytes)?; - let length = u64::from_le_bytes(length_bytes); - if length > 0x100_000 { -- panic!("pcid_interface: buffer too large"); -+ return Err(io::Error::new( -+ io::ErrorKind::InvalidData, -+ format!("pcid_interface: buffer too large ({length} bytes)"), -+ )); - } - let mut data = vec![0u8; length as usize]; -- if let Err(err) = r.read_exact(&mut data) { -- log::error!("reading pcid response failed: {err}"); -- process::exit(1); -- } -- -- bincode::deserialize_from(&data[..]).expect("couldn't deserialize pcid message") -+ r.read_exact(&mut data)?; -+ bincode::deserialize_from(&data[..]) -+ .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err)) - } - - impl PciFunctionHandle { -@@ -327,11 +361,14 @@ impl PciFunctionHandle { - } - - pub fn connect_by_path(device_path: &Path) -> io::Result { -- let channel_fd = libredox::call::open( -- device_path.join("channel").to_str().unwrap(), -- libredox::flag::O_RDWR, -- 0, -- )?; -+ let channel_path = device_path.join("channel"); -+ let channel_str = channel_path.to_str().ok_or_else(|| { -+ io::Error::new( -+ io::ErrorKind::InvalidData, -+ format!("device path contains invalid UTF-8: {}", device_path.display()), -+ ) -+ })?; -+ let channel_fd = libredox::call::open(channel_str, libredox::flag::O_RDWR, 0)?; - Ok(Self::connect_common(channel_fd as RawFd)) - } - -@@ -369,55 +406,99 @@ impl PciFunctionHandle { - self.config.clone() - } - -+ pub fn try_enable_device(&mut self) -> io::Result<()> { -+ send_result(&mut self.channel, &PcidClientRequest::EnableDevice)?; -+ match recv_result(&mut self.channel)? { -+ PcidClientResponse::EnabledDevice => Ok(()), -+ other => Err(io::Error::new( -+ io::ErrorKind::InvalidData, -+ format!("received wrong pcid response while enabling device: {other:?}"), -+ )), -+ } -+ } -+ - pub fn enable_device(&mut self) { -- self.send(&PcidClientRequest::EnableDevice); -- match self.recv() { -- PcidClientResponse::EnabledDevice => {} -- other => { -- log::error!("received wrong pcid response: {other:?}"); -- process::exit(1); -- } -+ if let Err(err) = self.try_enable_device() { -+ log::error!("failed to enable PCI device: {err}"); -+ process::exit(1); - } - } - - pub fn get_vendor_capabilities(&mut self) -> Vec { -- self.send(&PcidClientRequest::RequestVendorCapabilities); -- match self.recv() { -- PcidClientResponse::VendorCapabilities(a) => a, -- other => { -- log::error!("received wrong pcid response: {other:?}"); -+ match self.try_get_vendor_capabilities() { -+ Ok(capabilities) => capabilities, -+ Err(err) => { -+ log::error!("failed to fetch vendor capabilities: {err}"); - process::exit(1); - } - } - } - -+ pub fn try_get_vendor_capabilities(&mut self) -> io::Result> { -+ send_result(&mut self.channel, &PcidClientRequest::RequestVendorCapabilities)?; -+ match recv_result(&mut self.channel)? { -+ PcidClientResponse::VendorCapabilities(capabilities) => Ok(capabilities), -+ other => Err(io::Error::new( -+ io::ErrorKind::InvalidData, -+ format!( -+ "received wrong pcid response while requesting vendor capabilities: {other:?}" -+ ), -+ )), -+ } -+ } -+ - // FIXME turn into struct with bool fields -+ pub fn try_fetch_all_features(&mut self) -> io::Result> { -+ send_result(&mut self.channel, &PcidClientRequest::RequestFeatures)?; -+ match recv_result(&mut self.channel)? { -+ PcidClientResponse::AllFeatures(features) => Ok(features), -+ other => Err(io::Error::new( -+ io::ErrorKind::InvalidData, -+ format!("received wrong pcid response while fetching features: {other:?}"), -+ )), -+ } -+ } -+ - pub fn fetch_all_features(&mut self) -> Vec { -- self.send(&PcidClientRequest::RequestFeatures); -- match self.recv() { -- PcidClientResponse::AllFeatures(a) => a, -- other => { -- log::error!("received wrong pcid response: {other:?}"); -+ match self.try_fetch_all_features() { -+ Ok(features) => features, -+ Err(err) => { -+ log::error!("failed to fetch PCI features: {err}"); - process::exit(1); - } - } - } -+ pub fn try_enable_feature(&mut self, feature: PciFeature) -> io::Result<()> { -+ send_result(&mut self.channel, &PcidClientRequest::EnableFeature(feature))?; -+ match recv_result(&mut self.channel)? { -+ PcidClientResponse::FeatureEnabled(feat) if feat == feature => Ok(()), -+ other => Err(io::Error::new( -+ io::ErrorKind::InvalidData, -+ format!("received wrong pcid response while enabling feature: {other:?}"), -+ )), -+ } -+ } - pub fn enable_feature(&mut self, feature: PciFeature) { -- self.send(&PcidClientRequest::EnableFeature(feature)); -- match self.recv() { -- PcidClientResponse::FeatureEnabled(feat) if feat == feature => {} -- other => { -- log::error!("received wrong pcid response: {other:?}"); -- process::exit(1); -- } -+ if let Err(err) = self.try_enable_feature(feature) { -+ log::error!("failed to enable PCI feature {feature:?}: {err}"); -+ process::exit(1); -+ } -+ } -+ pub fn try_feature_info(&mut self, feature: PciFeature) -> io::Result { -+ send_result(&mut self.channel, &PcidClientRequest::FeatureInfo(feature))?; -+ match recv_result(&mut self.channel)? { -+ PcidClientResponse::FeatureInfo(feat, info) if feat == feature => Ok(info), -+ other => Err(io::Error::new( -+ io::ErrorKind::InvalidData, -+ format!("received wrong pcid response while reading feature info: {other:?}"), -+ )), - } - } - pub fn feature_info(&mut self, feature: PciFeature) -> PciFeatureInfo { -- self.send(&PcidClientRequest::FeatureInfo(feature)); -- match self.recv() { -- PcidClientResponse::FeatureInfo(feat, info) if feat == feature => info, -- other => { -- log::error!("received wrong pcid response: {other:?}"); -+ match self.try_feature_info(feature) { -+ Ok(info) => info, -+ Err(err) => { -+ log::error!("failed to fetch PCI feature info for {feature:?}: {err}"); - process::exit(1); - } - } -@@ -433,33 +514,50 @@ impl PciFunctionHandle { - } - } - pub unsafe fn read_config(&mut self, offset: u16) -> u32 { -- self.send(&PcidClientRequest::ReadConfig(offset)); -- match self.recv() { -- PcidClientResponse::ReadConfig(value) => value, -- other => { -- log::error!("received wrong pcid response: {other:?}"); -+ match self.try_read_config(offset) { -+ Ok(value) => value, -+ Err(err) => { -+ log::error!("failed to read PCI config dword at {offset:#x}: {err}"); - process::exit(1); - } - } - } -+ pub unsafe fn try_read_config(&mut self, offset: u16) -> io::Result { -+ send_result(&mut self.channel, &PcidClientRequest::ReadConfig(offset))?; -+ match recv_result(&mut self.channel)? { -+ PcidClientResponse::ReadConfig(value) => Ok(value), -+ other => Err(io::Error::new( -+ io::ErrorKind::InvalidData, -+ format!("received wrong pcid response while reading config: {other:?}"), -+ )), -+ } -+ } - pub unsafe fn write_config(&mut self, offset: u16, value: u32) { -- self.send(&PcidClientRequest::WriteConfig(offset, value)); -- match self.recv() { -- PcidClientResponse::WriteConfig => {} -- other => { -- log::error!("received wrong pcid response: {other:?}"); -- process::exit(1); -- } -+ if let Err(err) = self.try_write_config(offset, value) { -+ log::error!("failed to write PCI config dword at {offset:#x}: {err}"); -+ process::exit(1); - } - } -- pub unsafe fn map_bar(&mut self, bir: u8) -> &MappedBar { -+ pub unsafe fn try_write_config(&mut self, offset: u16, value: u32) -> io::Result<()> { -+ send_result(&mut self.channel, &PcidClientRequest::WriteConfig(offset, value))?; -+ match recv_result(&mut self.channel)? { -+ PcidClientResponse::WriteConfig => Ok(()), -+ other => Err(io::Error::new( -+ io::ErrorKind::InvalidData, -+ format!("received wrong pcid response while writing config: {other:?}"), -+ )), -+ } -+ } -+ pub unsafe fn try_map_bar(&mut self, bir: u8) -> io::Result<&MappedBar> { - let mapped_bar = &mut self.mapped_bars[bir as usize]; - if let Some(mapped_bar) = mapped_bar { -- mapped_bar -+ Ok(mapped_bar) - } else { -- let (bar, bar_size) = self.config.func.bars[bir as usize].expect_mem(); -+ let (bar, bar_size) = self.config.func.bars[bir as usize] -+ .try_mem() -+ .map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err.to_string()))?; - -- let ptr = match unsafe { -+ let ptr = unsafe { - common::physmap( - bar, - bar_size, -@@ -467,18 +565,25 @@ impl PciFunctionHandle { - // FIXME once the kernel supports this use write-through for prefetchable BAR - common::MemoryType::Uncacheable, - ) -- } { -- Ok(ptr) => ptr, -- Err(err) => { -- log::error!("failed to map BAR at {bar:016X}: {err}"); -- process::exit(1); -- } -- }; -+ } -+ .map_err(|err| io::Error::other(format!("failed to map BAR at {bar:016X}: {err}")))?; - -- mapped_bar.insert(MappedBar { -- ptr: NonNull::new(ptr.cast::()).expect("Mapping a BAR resulted in a nullptr"), -+ Ok(mapped_bar.insert(MappedBar { -+ ptr: NonNull::new(ptr.cast::()).ok_or_else(|| { -+ io::Error::new(io::ErrorKind::Other, "mapping a BAR resulted in a null pointer") -+ })?, - bar_size, -- }) -+ })) -+ } -+ } -+ -+ pub unsafe fn map_bar(&mut self, bir: u8) -> &MappedBar { -+ match self.try_map_bar(bir) { -+ Ok(bar) => bar, -+ Err(err) => { -+ log::error!("failed to map BAR {bir}: {err}"); -+ process::exit(1); -+ } - } - } - } -diff --git a/drivers/pcid/src/driver_interface/msi.rs b/drivers/pcid/src/driver_interface/msi.rs -index 0ca68ec5..cd2fd701 100644 ---- a/drivers/pcid/src/driver_interface/msi.rs -+++ b/drivers/pcid/src/driver_interface/msi.rs -@@ -1,6 +1,8 @@ - use std::fmt; - use std::ptr::NonNull; -+use std::process; - -+use crate::driver_interface::bar::PciBarError; - use crate::driver_interface::PciBar; - use crate::PciFunctionHandle; - -@@ -33,9 +35,74 @@ pub struct MsixInfo { - pub pba_offset: u32, - } - -+#[derive(Debug)] -+pub enum MsixMapError { -+ ReservedBir(u8), -+ InvalidBar { -+ which: &'static str, -+ source: PciBarError, -+ }, -+ TableOutsideBar { -+ offset: usize, -+ end: usize, -+ bar_size: usize, -+ }, -+ PbaOutsideBar { -+ offset: usize, -+ end: usize, -+ bar_size: usize, -+ }, -+ NullPointer, -+} -+ -+impl fmt::Display for MsixMapError { -+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -+ match self { -+ MsixMapError::ReservedBir(bir) => { -+ write!(f, "MSI-X BIR contained a reserved value: {bir}") -+ } -+ MsixMapError::InvalidBar { which, source } => { -+ write!(f, "MSI-X {which} BAR is invalid: {source}") -+ } -+ MsixMapError::TableOutsideBar { -+ offset, -+ end, -+ bar_size, -+ } => write!( -+ f, -+ "MSI-X table {offset:#x}:{end:#x} outside BAR with length {bar_size:#x}" -+ ), -+ MsixMapError::PbaOutsideBar { -+ offset, -+ end, -+ bar_size, -+ } => write!( -+ f, -+ "MSI-X PBA {offset:#x}:{end:#x} outside BAR with length {bar_size:#x}" -+ ), -+ MsixMapError::NullPointer => { -+ write!(f, "MSI-X BAR mapping resulted in null pointer") -+ }, -+ } -+ } -+} -+ - impl MsixInfo { - pub unsafe fn map_and_mask_all(self, pcid_handle: &mut PciFunctionHandle) -> MappedMsixRegs { -- self.validate(pcid_handle.config().func.bars); -+ match self.try_map_and_mask_all(pcid_handle) { -+ Ok(regs) => regs, -+ Err(err) => { -+ log::error!("{err}"); -+ process::exit(1); -+ } -+ } -+ } -+ -+ pub unsafe fn try_map_and_mask_all( -+ self, -+ pcid_handle: &mut PciFunctionHandle, -+ ) -> Result { -+ self.try_validate(pcid_handle.config().func.bars)?; - - let virt_table_base = unsafe { - pcid_handle -@@ -46,7 +113,8 @@ impl MsixInfo { - }; - - let mut info = MappedMsixRegs { -- virt_table_base: NonNull::new(virt_table_base.cast::()).unwrap(), -+ virt_table_base: NonNull::new(virt_table_base.cast::()) -+ .ok_or(MsixMapError::NullPointer)?, - info: self, - }; - -@@ -56,21 +124,15 @@ impl MsixInfo { - info.table_entry_pointer(i.into()).mask(); - } - -- info -+ Ok(info) - } - -- fn validate(&self, bars: [PciBar; 6]) { -+ pub fn try_validate(&self, bars: [PciBar; 6]) -> Result<(), MsixMapError> { - if self.table_bar > 5 { -- panic!( -- "MSI-X Table BIR contained a reserved enum value: {}", -- self.table_bar -- ); -+ return Err(MsixMapError::ReservedBir(self.table_bar)); - } - if self.pba_bar > 5 { -- panic!( -- "MSI-X PBA BIR contained a reserved enum value: {}", -- self.pba_bar -- ); -+ return Err(MsixMapError::ReservedBir(self.pba_bar)); - } - - let table_size = self.table_size; -@@ -80,28 +142,38 @@ impl MsixInfo { - let pba_offset = self.pba_offset as usize; - let pba_min_length = table_size.div_ceil(8); - -- let (_, table_bar_size) = bars[self.table_bar as usize].expect_mem(); -- let (_, pba_bar_size) = bars[self.pba_bar as usize].expect_mem(); -+ let (_, table_bar_size) = bars[self.table_bar as usize] -+ .try_mem() -+ .map_err(|source| MsixMapError::InvalidBar { -+ which: "table", -+ source, -+ })?; -+ let (_, pba_bar_size) = bars[self.pba_bar as usize] -+ .try_mem() -+ .map_err(|source| MsixMapError::InvalidBar { -+ which: "PBA", -+ source, -+ })?; - - // Ensure that the table and PBA are within the BAR. - - if !(0..table_bar_size as u64).contains(&(table_offset as u64 + table_min_length as u64)) { -- panic!( -- "Table {:#x}:{:#x} outside of BAR with length {:#x}", -- table_offset, -- table_offset + table_min_length as usize, -- table_bar_size -- ); -+ return Err(MsixMapError::TableOutsideBar { -+ offset: table_offset, -+ end: table_offset + table_min_length as usize, -+ bar_size: table_bar_size, -+ }); - } - - if !(0..pba_bar_size as u64).contains(&(pba_offset as u64 + pba_min_length as u64)) { -- panic!( -- "PBA {:#x}:{:#x} outside of BAR with length {:#x}", -- pba_offset, -- pba_offset + pba_min_length as usize, -- pba_bar_size -- ); -+ return Err(MsixMapError::PbaOutsideBar { -+ offset: pba_offset, -+ end: pba_offset + pba_min_length as usize, -+ bar_size: pba_bar_size, -+ }); - } -+ -+ Ok(()) - } - } - -@@ -120,6 +192,68 @@ impl MappedMsixRegs { - } - } - -+#[cfg(test)] -+mod tests { -+ use super::{MsixInfo, MsixMapError}; -+ use crate::driver_interface::PciBar; -+ -+ #[test] -+ fn try_validate_accepts_in_range_table_and_pba() { -+ let info = MsixInfo { -+ table_bar: 0, -+ table_offset: 0x100, -+ table_size: 4, -+ pba_bar: 1, -+ pba_offset: 0x80, -+ }; -+ let mut bars = [PciBar::None; 6]; -+ bars[0] = PciBar::Memory32 { -+ addr: 0x1000, -+ size: 0x400, -+ }; -+ bars[1] = PciBar::Memory32 { -+ addr: 0x2000, -+ size: 0x200, -+ }; -+ -+ assert!(info.try_validate(bars).is_ok()); -+ } -+ -+ #[test] -+ fn try_validate_rejects_reserved_bir() { -+ let info = MsixInfo { -+ table_bar: 6, -+ table_offset: 0, -+ table_size: 1, -+ pba_bar: 0, -+ pba_offset: 0, -+ }; -+ -+ assert!(matches!(info.try_validate([PciBar::None; 6]), Err(MsixMapError::ReservedBir(6)))); -+ } -+ -+ #[test] -+ fn try_validate_rejects_out_of_range_table() { -+ let info = MsixInfo { -+ table_bar: 0, -+ table_offset: 0x100, -+ table_size: 16, -+ pba_bar: 0, -+ pba_offset: 0, -+ }; -+ let mut bars = [PciBar::None; 6]; -+ bars[0] = PciBar::Memory32 { -+ addr: 0x1000, -+ size: 0x80, -+ }; -+ -+ assert!(matches!( -+ info.try_validate(bars), -+ Err(MsixMapError::TableOutsideBar { .. }) -+ )); -+ } -+} -+ - #[repr(C, packed)] - pub struct MsixTableEntry { - pub addr_lo: Mmio, -diff --git a/drivers/pcid/src/main.rs b/drivers/pcid/src/main.rs -index 61cd9a78..8840e141 100644 ---- a/drivers/pcid/src/main.rs -+++ b/drivers/pcid/src/main.rs -@@ -12,6 +12,7 @@ use pci_types::{ - }; - use redox_scheme::scheme::register_sync_scheme; - use scheme_utils::Blocking; -+use syscall::{sendfd, SendFdFlags}; - - use crate::cfg_access::Pcie; - use pcid_interface::{FullDeviceId, LegacyInterruptLine, PciBar, PciFunction, PciRom}; -@@ -42,7 +43,15 @@ fn handle_parsed_header( - continue; - } - match endpoint_header.bar(i, pcie) { -- Some(TyBar::Io { port }) => bars[i as usize] = PciBar::Port(port.try_into().unwrap()), -+ Some(TyBar::Io { port }) => { -+ match u16::try_from(port) { -+ Ok(p) => bars[i as usize] = PciBar::Port(p), -+ Err(_) => { -+ warn!("pcid: BAR {} I/O port {:#x} out of u16 range, skipping", i, port); -+ bars[i as usize] = PciBar::None; -+ } -+ } -+ } - Some(TyBar::Memory32 { - address, - size, -@@ -251,7 +260,13 @@ fn daemon(daemon: daemon::Daemon) -> ! { - info!("PCI SG-BS:DV.F VEND:DEVI CL.SC.IN.RV"); - - let mut scheme = scheme::PciScheme::new(pcie); -- let socket = redox_scheme::Socket::create().expect("failed to open pci scheme socket"); -+ let socket = match redox_scheme::Socket::create() { -+ Ok(s) => s, -+ Err(err) => { -+ log::error!("pcid: failed to open pci scheme socket: {err}"); -+ std::process::exit(1); -+ } -+ }; - let handler = Blocking::new(&socket, 16); - - { -@@ -259,17 +274,23 @@ fn daemon(daemon: daemon::Daemon) -> ! { - Ok(register_pci) => { - let access_id = scheme.access(); - -- let access_fd = socket -+ let access_fd = match socket - .create_this_scheme_fd(0, access_id, syscall::O_RDWR, 0) -- .expect("failed to issue this resource"); -- let access_bytes = access_fd.to_ne_bytes(); -- let _ = register_pci -- .call_wo( -- &access_bytes, -- syscall::CallFlags::WRITE | syscall::CallFlags::FD, -- &[], -- ) -- .expect("failed to send pci_fd to acpid"); -+ { -+ Ok(fd) => fd, -+ Err(err) => { -+ warn!("pcid: failed to issue pci access resource to acpid: {err}. Running without ACPI integration."); -+ 0 -+ } -+ }; -+ if let Err(err) = sendfd( -+ register_pci.raw(), -+ access_fd as usize, -+ SendFdFlags::empty().bits(), -+ 0, -+ ) { -+ warn!("pcid: failed to send pci_fd to acpid: {err}. Running without ACPI integration."); -+ } - } - Err(err) => { - if err.errno() == libredox::errno::ENODEV { -@@ -302,16 +323,24 @@ fn daemon(daemon: daemon::Daemon) -> ! { - ); - } - } -- debug!("Enumeration complete, now starting pci scheme"); -+ info!( -+ "PCI enumeration complete: {} devices, {} buses", -+ scheme.tree.len(), -+ bus_nums.len() -+ ); - -- register_sync_scheme(&socket, "pci", &mut scheme) -- .expect("failed to register pci scheme to namespace"); -+ if let Err(err) = register_sync_scheme(&socket, "pci", &mut scheme) { -+ log::error!("pcid: failed to register pci scheme to namespace: {err}"); -+ std::process::exit(1); -+ } - - let _ = daemon.ready(); - -- handler -- .process_requests_blocking(scheme) -- .expect("pcid: failed to process requests"); -+ if let Err(err) = handler.process_requests_blocking(scheme) { -+ log::error!("pcid: failed to process requests: {err}"); -+ std::process::exit(1); -+ } -+ loop {} - } - - fn scan_device( -@@ -350,16 +379,16 @@ fn scan_device( - - match header.header_type(pcie) { - HeaderType::Endpoint => { -- handle_parsed_header( -- pcie, -- tree, -- EndpointHeader::from_header(header, pcie).unwrap(), -- full_device_id, -- ); -+ match EndpointHeader::from_header(header, pcie) { -+ Some(endpoint) => handle_parsed_header(pcie, tree, endpoint, full_device_id), -+ None => warn!("pcid: failed to parse endpoint header for {}.{}.{}", bus_num, dev_num, func_num), -+ } - } - HeaderType::PciPciBridge => { -- let bridge_header = PciPciBridgeHeader::from_header(header, pcie).unwrap(); -- bus_nums.push(bridge_header.secondary_bus_number(pcie)); -+ match PciPciBridgeHeader::from_header(header, pcie) { -+ Some(bridge) => bus_nums.push(bridge.secondary_bus_number(pcie)), -+ None => warn!("pcid: failed to parse PCI-PCI bridge header for {}.{}.{}", bus_num, dev_num, func_num), -+ } - } - ty => { - warn!("pcid: unknown header type: {ty:?}"); -diff --git a/drivers/pcid/src/scheme.rs b/drivers/pcid/src/scheme.rs -index bb9f39a3..df026ab4 100644 ---- a/drivers/pcid/src/scheme.rs -+++ b/drivers/pcid/src/scheme.rs -@@ -21,6 +21,7 @@ enum Handle { - TopLevel { entries: Vec }, - Access, - Device, -+ Config { addr: PciAddress }, - Channel { addr: PciAddress, st: ChannelState }, - SchemeRoot, - } -@@ -30,14 +31,20 @@ struct HandleWrapper { - } - impl Handle { - fn is_file(&self) -> bool { -- matches!(self, Self::Access | Self::Channel { .. }) -+ matches!( -+ self, -+ Self::Access | Self::Config { .. } | Self::Channel { .. } -+ ) - } - fn is_dir(&self) -> bool { - !self.is_file() - } - // TODO: capability rather than root - fn requires_root(&self) -> bool { -- matches!(self, Self::Access | Self::Channel { .. }) -+ matches!( -+ self, -+ Self::Access | Self::Config { .. } | Self::Channel { .. } -+ ) - } - fn is_scheme_root(&self) -> bool { - matches!(self, Self::SchemeRoot) -@@ -132,6 +139,7 @@ impl SchemeSync for PciScheme { - let (len, mode) = match handle.inner { - Handle::TopLevel { ref entries } => (entries.len(), MODE_DIR | 0o755), - Handle::Device => (DEVICE_CONTENTS.len(), MODE_DIR | 0o755), -+ Handle::Config { .. } => (256, MODE_CHR | 0o600), - Handle::Access | Handle::Channel { .. } => (0, MODE_CHR | 0o600), - Handle::SchemeRoot => return Err(Error::new(EBADF)), - }; -@@ -156,6 +164,18 @@ impl SchemeSync for PciScheme { - match handle.inner { - Handle::TopLevel { .. } => Err(Error::new(EISDIR)), - Handle::Device => Err(Error::new(EISDIR)), -+ Handle::Config { addr } => { -+ let offset = _offset as u16; -+ let dword_offset = offset & !0x3; -+ let byte_offset = (offset & 0x3) as usize; -+ let bytes_to_read = buf.len().min(4 - byte_offset); -+ -+ let dword = unsafe { self.pcie.read(addr, dword_offset) }; -+ let bytes = dword.to_le_bytes(); -+ buf[..bytes_to_read] -+ .copy_from_slice(&bytes[byte_offset..byte_offset + bytes_to_read]); -+ Ok(bytes_to_read) -+ } - Handle::Channel { - addr: _, - ref mut st, -@@ -193,7 +213,9 @@ impl SchemeSync for PciScheme { - return Ok(buf); - } - Handle::Device => DEVICE_CONTENTS, -- Handle::Access | Handle::Channel { .. } => return Err(Error::new(ENOTDIR)), -+ Handle::Access | Handle::Config { .. } | Handle::Channel { .. } => { -+ return Err(Error::new(ENOTDIR)); -+ } - Handle::SchemeRoot => return Err(Error::new(EBADF)), - }; - -@@ -223,6 +245,20 @@ impl SchemeSync for PciScheme { - } - - match handle.inner { -+ Handle::Config { addr } => { -+ let offset = _offset as u16; -+ let dword_offset = offset & !0x3; -+ let byte_offset = (offset & 0x3) as usize; -+ let bytes_to_write = buf.len().min(4 - byte_offset); -+ -+ let mut dword = unsafe { self.pcie.read(addr, dword_offset) }; -+ let mut bytes = dword.to_le_bytes(); -+ bytes[byte_offset..byte_offset + bytes_to_write] -+ .copy_from_slice(&buf[..bytes_to_write]); -+ dword = u32::from_le_bytes(bytes); -+ unsafe { self.pcie.write(addr, dword_offset, dword) }; -+ Ok(buf.len()) -+ } - Handle::Channel { addr, ref mut st } => { - Self::write_channel(&self.pcie, &mut self.tree, addr, st, buf) - } -@@ -316,6 +352,10 @@ impl SchemeSync for PciScheme { - func.enabled = false; - } - } -+ Some(HandleWrapper { -+ inner: Handle::Config { .. }, -+ .. -+ }) => {} - _ => {} - } - } -@@ -341,6 +381,7 @@ impl PciScheme { - let path = &after[1..]; - - match path { -+ "config" => Handle::Config { addr }, - "channel" => { - if func.enabled { - return Err(Error::new(ENOLCK)); -@@ -387,7 +428,7 @@ impl PciScheme { - match *state { - ChannelState::AwaitingResponseRead(_) => return Err(Error::new(EINVAL)), - ChannelState::AwaitingData => { -- let func = tree.get_mut(&addr).unwrap(); -+ let func = tree.get_mut(&addr).ok_or(Error::new(ENOENT))?; - - let request = bincode::deserialize_from(buf).map_err(|_| Error::new(EINVAL))?; - let response = crate::driver_handler::DriverHandler::new( -diff --git a/drivers/storage/ahcid/src/ahci/disk_ata.rs b/drivers/storage/ahcid/src/ahci/disk_ata.rs -index 4f83c51d..7423603b 100644 ---- a/drivers/storage/ahcid/src/ahci/disk_ata.rs -+++ b/drivers/storage/ahcid/src/ahci/disk_ata.rs -@@ -1,7 +1,7 @@ - use std::convert::TryInto; - use std::ptr; - --use syscall::error::Result; -+use syscall::error::{Error, Result, EIO}; - - use common::dma::Dma; - -@@ -39,7 +39,7 @@ impl DiskATA { - .map(|_| Ok(unsafe { Dma::zeroed()?.assume_init() })) - .collect::>>()? - .try_into() -- .unwrap_or_else(|_| unreachable!()); -+ .map_err(|_| Error::new(EIO))?; - - let mut fb = unsafe { Dma::zeroed()?.assume_init() }; - let buf = unsafe { Dma::zeroed()?.assume_init() }; -diff --git a/drivers/storage/ahcid/src/ahci/disk_atapi.rs b/drivers/storage/ahcid/src/ahci/disk_atapi.rs -index a0e75c09..8fbdfbef 100644 ---- a/drivers/storage/ahcid/src/ahci/disk_atapi.rs -+++ b/drivers/storage/ahcid/src/ahci/disk_atapi.rs -@@ -37,7 +37,7 @@ impl DiskATAPI { - .map(|_| Ok(unsafe { Dma::zeroed()?.assume_init() })) - .collect::>>()? - .try_into() -- .unwrap_or_else(|_| unreachable!()); -+ .map_err(|_| Error::new(EBADF))?; - - let mut fb = unsafe { Dma::zeroed()?.assume_init() }; - let mut buf = unsafe { Dma::zeroed()?.assume_init() }; -diff --git a/drivers/storage/ahcid/src/ahci/hba.rs b/drivers/storage/ahcid/src/ahci/hba.rs -index bea8792c..11a3d4ae 100644 ---- a/drivers/storage/ahcid/src/ahci/hba.rs -+++ b/drivers/storage/ahcid/src/ahci/hba.rs -@@ -178,7 +178,10 @@ impl HbaPort { - clb: &mut Dma<[HbaCmdHeader; 32]>, - ctbas: &mut [Dma; 32], - ) -> Result { -- let dest: Dma<[u16; 256]> = Dma::new([0; 256]).unwrap(); -+ let dest: Dma<[u16; 256]> = Dma::new([0; 256]).map_err(|err| { -+ error!("ahcid: failed to allocate DMA buffer: {err}"); -+ Error::new(EIO) -+ })?; - - let slot = self - .ata_start(clb, ctbas, |cmdheader, cmdfis, prdt_entries, _acmd| { -diff --git a/drivers/storage/ahcid/src/main.rs b/drivers/storage/ahcid/src/main.rs -index 1f130a29..cccd2980 100644 ---- a/drivers/storage/ahcid/src/main.rs -+++ b/drivers/storage/ahcid/src/main.rs -@@ -2,6 +2,7 @@ - - use std::io::{Read, Write}; - use std::os::fd::AsRawFd; -+use std::process; - use std::usize; - - use common::io::Io; -@@ -23,10 +24,13 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - let mut name = pci_config.func.name(); - name.push_str("_ahci"); - -- let irq = pci_config -- .func -- .legacy_interrupt_line -- .expect("ahcid: no legacy interrupts supported"); -+ let irq = match pci_config.func.legacy_interrupt_line { -+ Some(irq) => irq, -+ None => { -+ error!("ahcid: no legacy interrupts supported"); -+ process::exit(1); -+ } -+ }; - - common::setup_logging( - "disk", -@@ -57,46 +61,71 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - let mut irq_file = irq.irq_handle("ahcid"); - let irq_fd = irq_file.as_raw_fd() as usize; - -- let event_queue = RawEventQueue::new().expect("ahcid: failed to create event queue"); -+ let event_queue = RawEventQueue::new().unwrap_or_else(|err| { -+ error!("ahcid: failed to create event queue: {err}"); -+ process::exit(1); -+ }); - -- libredox::call::setrens(0, 0).expect("ahcid: failed to enter null namespace"); -+ libredox::call::setrens(0, 0).unwrap_or_else(|err| { -+ error!("ahcid: failed to enter null namespace: {err}"); -+ process::exit(1); -+ }); - - event_queue - .subscribe(scheme.event_handle().raw(), 1, EventFlags::READ) -- .expect("ahcid: failed to event scheme socket"); -+ .unwrap_or_else(|err| { -+ error!("ahcid: failed to event scheme socket: {err}"); -+ process::exit(1); -+ }); - event_queue - .subscribe(irq_fd, 1, EventFlags::READ) -- .expect("ahcid: failed to event irq scheme"); -+ .unwrap_or_else(|err| { -+ error!("ahcid: failed to event irq scheme: {err}"); -+ process::exit(1); -+ }); - - for event in event_queue { -- let event = event.unwrap(); -+ let event = match event { -+ Ok(event) => event, -+ Err(err) => { -+ error!("ahcid: failed to get event: {err}"); -+ continue; -+ } -+ }; - if event.fd == scheme.event_handle().raw() { -- FuturesExecutor.block_on(scheme.tick()).unwrap(); -+ if let Err(err) = FuturesExecutor.block_on(scheme.tick()) { -+ error!("ahcid: failed to handle scheme event: {err}"); -+ } - } else if event.fd == irq_fd { - let mut irq = [0; 8]; -- if irq_file -- .read(&mut irq) -- .expect("ahcid: failed to read irq file") -- >= irq.len() -- { -- let is = hba_mem.is.read(); -- if is > 0 { -- let pi = hba_mem.pi.read(); -- let pi_is = pi & is; -- for i in 0..hba_mem.ports.len() { -- if pi_is & 1 << i > 0 { -- let port = &mut hba_mem.ports[i]; -- let is = port.is.read(); -- port.is.write(is); -- } -+ match irq_file.read(&mut irq) { -+ Ok(count) if count >= irq.len() => {} -+ Ok(_) => continue, -+ Err(err) => { -+ error!("ahcid: failed to read irq file: {err}"); -+ continue; -+ } -+ } -+ let is = hba_mem.is.read(); -+ if is > 0 { -+ let pi = hba_mem.pi.read(); -+ let pi_is = pi & is; -+ for i in 0..hba_mem.ports.len() { -+ if pi_is & 1 << i > 0 { -+ let port = &mut hba_mem.ports[i]; -+ let is = port.is.read(); -+ port.is.write(is); - } -- hba_mem.is.write(is); -+ } -+ hba_mem.is.write(is); - -- irq_file -- .write(&irq) -- .expect("ahcid: failed to write irq file"); -+ if let Err(err) = irq_file.write(&irq) { -+ error!("ahcid: failed to write irq file: {err}"); -+ continue; -+ } - -- FuturesExecutor.block_on(scheme.tick()).unwrap(); -+ if let Err(err) = FuturesExecutor.block_on(scheme.tick()) { -+ error!("ahcid: failed to handle IRQ tick: {err}"); - } - } - } else { -@@ -105,5 +134,5 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - } - } - -- std::process::exit(0); -+ process::exit(0); - } -diff --git a/drivers/storage/ided/src/ide.rs b/drivers/storage/ided/src/ide.rs -index 5faf3250..094e5889 100644 ---- a/drivers/storage/ided/src/ide.rs -+++ b/drivers/storage/ided/src/ide.rs -@@ -184,10 +184,10 @@ impl Disk for AtaDisk { - let block = start_block + (count as u64) / 512; - - //TODO: support other LBA modes -- assert!(block < 0x1_0000_0000_0000); -+ debug_assert!(block < 0x1_0000_0000_0000); - - let sectors = (chunk.len() + 511) / 512; -- assert!(sectors <= 128); -+ debug_assert!(sectors <= 128); - - log::trace!( - "IDE read chan {} dev {} block {:#x} count {:#x}", -@@ -205,7 +205,7 @@ impl Disk for AtaDisk { - // Make PRDT EOT match chunk size - for i in 0..sectors { - chan.prdt[i] = PrdtEntry { -- phys: (chan.buf.physical() + i * 512).try_into().unwrap(), -+ phys: (chan.buf.physical() + i * 512).try_into().map_err(|_| Error::new(EIO))?, - size: 512, - flags: if i + 1 == sectors { - 1 << 15 // End of table -@@ -216,7 +216,7 @@ impl Disk for AtaDisk { - } - // Set PRDT - let prdt = chan.prdt.physical(); -- chan.busmaster_prdt.write(prdt.try_into().unwrap()); -+ chan.busmaster_prdt.write(prdt.try_into().map_err(|_| Error::new(EIO))?); - // Set to read - chan.busmaster_command.writef(1 << 3, true); - // Clear interrupt and error bits -@@ -325,10 +325,10 @@ impl Disk for AtaDisk { - let block = start_block + (count as u64) / 512; - - //TODO: support other LBA modes -- assert!(block < 0x1_0000_0000_0000); -+ debug_assert!(block < 0x1_0000_0000_0000); - - let sectors = (chunk.len() + 511) / 512; -- assert!(sectors <= 128); -+ debug_assert!(sectors <= 128); - - log::trace!( - "IDE write chan {} dev {} block {:#x} count {:#x}", -@@ -346,7 +346,7 @@ impl Disk for AtaDisk { - // Make PRDT EOT match chunk size - for i in 0..sectors { - chan.prdt[i] = PrdtEntry { -- phys: (chan.buf.physical() + i * 512).try_into().unwrap(), -+ phys: (chan.buf.physical() + i * 512).try_into().map_err(|_| Error::new(EIO))?, - size: 512, - flags: if i + 1 == sectors { - 1 << 15 // End of table -@@ -357,7 +357,7 @@ impl Disk for AtaDisk { - } - // Set PRDT - let prdt = chan.prdt.physical(); -- chan.busmaster_prdt.write(prdt.try_into().unwrap()); -+ chan.busmaster_prdt.write(prdt.try_into().map_err(|_| Error::new(EIO))?); - // Set to write - chan.busmaster_command.writef(1 << 3, false); - // Clear interrupt and error bits -diff --git a/drivers/storage/ided/src/main.rs b/drivers/storage/ided/src/main.rs -index 4197217d..03174554 100644 ---- a/drivers/storage/ided/src/main.rs -+++ b/drivers/storage/ided/src/main.rs -@@ -8,6 +8,7 @@ use std::{ - fs::File, - io::{Read, Write}, - os::unix::io::{FromRawFd, RawFd}, -+ process, - sync::{Arc, Mutex}, - thread::{self, sleep}, - time::Duration, -@@ -45,17 +46,34 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { - - let busmaster_base = pci_config.func.bars[4].expect_port(); - let (primary, primary_irq) = if pci_config.func.full_device_id.interface & 1 != 0 { -- panic!("TODO: IDE primary channel is PCI native"); -+ error!("ided: IDE primary channel PCI native mode not supported"); -+ process::exit(1); - } else { -- (Channel::primary_compat(busmaster_base).unwrap(), 14) -+ ( -+ Channel::primary_compat(busmaster_base).unwrap_or_else(|err| { -+ error!("ided: failed to init primary channel: {err}"); -+ process::exit(1); -+ }), -+ 14, -+ ) - }; - let (secondary, secondary_irq) = if pci_config.func.full_device_id.interface & 1 != 0 { -- panic!("TODO: IDE secondary channel is PCI native"); -+ error!("ided: IDE secondary channel PCI native mode not supported"); -+ process::exit(1); - } else { -- (Channel::secondary_compat(busmaster_base + 8).unwrap(), 15) -+ ( -+ Channel::secondary_compat(busmaster_base + 8).unwrap_or_else(|err| { -+ error!("ided: failed to init secondary channel: {err}"); -+ process::exit(1); -+ }), -+ 15, -+ ) - }; - -- common::acquire_port_io_rights().expect("ided: failed to get I/O privilege"); -+ common::acquire_port_io_rights().unwrap_or_else(|err| { -+ error!("ided: failed to get I/O privilege: {err}"); -+ process::exit(1); -+ }); - - //TODO: move this to ide.rs? - let chans = vec![ -@@ -87,13 +105,13 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { - for (chan_i, chan_lock) in chans.iter().enumerate() { - let mut chan = chan_lock.lock().unwrap(); - -- println!(" - channel {}", chan_i); -+ log::info!(" - channel {}", chan_i); - - // Disable IRQs - chan.control.write(2); - - for dev in 0..=1 { -- println!(" - device {}", dev); -+ log::info!(" - device {}", dev); - - // Select device - chan.device_select.write(0xA0 | (dev << 4)); -@@ -105,7 +123,7 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { - - // Check if device exists - if chan.status.read() == 0 { -- println!(" not found"); -+ log::info!(" not found"); - continue; - } - -@@ -125,7 +143,7 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { - - //TODO: probe ATAPI - if error { -- println!(" error"); -+ log::info!(" error"); - continue; - } - -@@ -189,12 +207,12 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { - 48 - }; - -- println!(" Serial: {}", serial.trim()); -- println!(" Firmware: {}", firmware.trim()); -- println!(" Model: {}", model.trim()); -- println!(" Size: {} MB", sectors / 2048); -- println!(" DMA: {}", dma); -- println!(" {}-bit LBA", lba_bits); -+ log::info!(" Serial: {}", serial.trim()); -+ log::info!(" Firmware: {}", firmware.trim()); -+ log::info!(" Model: {}", model.trim()); -+ log::info!(" Size: {} MB", sectors / 2048); -+ log::info!(" DMA: {}", dma); -+ log::info!(" {}-bit LBA", lba_bits); - - disks.push(AnyDisk::Ata(AtaDisk { - chan: chan_lock.clone(), -@@ -227,7 +245,10 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { - flag::O_RDWR | flag::O_NONBLOCK, - 0, - ) -- .expect("ided: failed to open irq file"); -+ .unwrap_or_else(|err| { -+ error!("ided: failed to open primary irq file: {err}"); -+ process::exit(1); -+ }); - let mut primary_irq_file = unsafe { File::from_raw_fd(primary_irq_fd as RawFd) }; - - let secondary_irq_fd = libredox::call::open( -@@ -235,70 +256,107 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { - flag::O_RDWR | flag::O_NONBLOCK, - 0, - ) -- .expect("ided: failed to open irq file"); -+ .unwrap_or_else(|err| { -+ error!("ided: failed to open secondary irq file: {err}"); -+ process::exit(1); -+ }); - let mut secondary_irq_file = unsafe { File::from_raw_fd(secondary_irq_fd as RawFd) }; - -- let event_queue = RawEventQueue::new().expect("ided: failed to open event file"); -+ let event_queue = RawEventQueue::new().unwrap_or_else(|err| { -+ error!("ided: failed to open event file: {err}"); -+ process::exit(1); -+ }); - -- libredox::call::setrens(0, 0).expect("ided: failed to enter null namespace"); -+ libredox::call::setrens(0, 0).unwrap_or_else(|err| { -+ error!("ided: failed to enter null namespace: {err}"); -+ process::exit(1); -+ }); - - event_queue - .subscribe(scheme.event_handle().raw(), 0, EventFlags::READ) -- .expect("ided: failed to event disk scheme"); -+ .unwrap_or_else(|err| { -+ error!("ided: failed to event disk scheme: {err}"); -+ process::exit(1); -+ }); - - event_queue - .subscribe(primary_irq_fd, 0, EventFlags::READ) -- .expect("ided: failed to event irq scheme"); -+ .unwrap_or_else(|err| { -+ error!("ided: failed to event primary irq: {err}"); -+ process::exit(1); -+ }); - - event_queue - .subscribe(secondary_irq_fd, 0, EventFlags::READ) -- .expect("ided: failed to event irq scheme"); -+ .unwrap_or_else(|err| { -+ error!("ided: failed to event secondary irq: {err}"); -+ process::exit(1); -+ }); - - for event in event_queue { -- let event = event.unwrap(); -+ let event = match event { -+ Ok(event) => event, -+ Err(err) => { -+ error!("ided: failed to get event: {err}"); -+ continue; -+ } -+ }; - if event.fd == scheme.event_handle().raw() { -- FuturesExecutor.block_on(scheme.tick()).unwrap(); -+ if let Err(err) = FuturesExecutor.block_on(scheme.tick()) { -+ error!("ided: failed to handle scheme event: {err}"); -+ } - } else if event.fd == primary_irq_fd { - let mut irq = [0; 8]; -- if primary_irq_file -- .read(&mut irq) -- .expect("ided: failed to read irq file") -- >= irq.len() -- { -- let _chan = chans[0].lock().unwrap(); -- //TODO: check chan for irq -+ match primary_irq_file.read(&mut irq) { -+ Ok(count) if count >= irq.len() => {} -+ Ok(_) => continue, -+ Err(err) => { -+ error!("ided: failed to read primary irq file: {err}"); -+ continue; -+ } -+ } -+ let _chan = chans[0].lock().unwrap(); -+ //TODO: check chan for irq - -- primary_irq_file -- .write(&irq) -- .expect("ided: failed to write irq file"); -+ if let Err(err) = primary_irq_file.write(&irq) { -+ error!("ided: failed to write primary irq file: {err}"); -+ continue; -+ } - -- FuturesExecutor.block_on(scheme.tick()).unwrap(); -+ if let Err(err) = FuturesExecutor.block_on(scheme.tick()) { -+ error!("ided: failed to handle primary IRQ tick: {err}"); - } - } else if event.fd == secondary_irq_fd { - let mut irq = [0; 8]; -- if secondary_irq_file -- .read(&mut irq) -- .expect("ided: failed to read irq file") -- >= irq.len() -- { -- let _chan = chans[1].lock().unwrap(); -- //TODO: check chan for irq -+ match secondary_irq_file.read(&mut irq) { -+ Ok(count) if count >= irq.len() => {} -+ Ok(_) => continue, -+ Err(err) => { -+ error!("ided: failed to read secondary irq file: {err}"); -+ continue; -+ } -+ } -+ let _chan = chans[1].lock().unwrap(); -+ //TODO: check chan for irq - -- secondary_irq_file -- .write(&irq) -- .expect("ided: failed to write irq file"); -+ if let Err(err) = secondary_irq_file.write(&irq) { -+ error!("ided: failed to write secondary irq file: {err}"); -+ continue; -+ } - -- FuturesExecutor.block_on(scheme.tick()).unwrap(); -+ if let Err(err) = FuturesExecutor.block_on(scheme.tick()) { -+ error!("ided: failed to handle secondary IRQ tick: {err}"); - } - } else { - error!("Unknown event {}", event.fd); - } - } - -- std::process::exit(0); -+ process::exit(0); - } - - #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] - fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { -- unimplemented!() -+ log::error!("ided: unsupported architecture"); -+ process::exit(1); - } -diff --git a/drivers/storage/nvmed/src/main.rs b/drivers/storage/nvmed/src/main.rs -index beb1b689..3772f4e5 100644 ---- a/drivers/storage/nvmed/src/main.rs -+++ b/drivers/storage/nvmed/src/main.rs -@@ -2,6 +2,7 @@ use std::cell::RefCell; - use std::fs::File; - use std::io::{self, Read, Write}; - use std::os::fd::AsRawFd; -+use std::process; - use std::rc::Rc; - use std::sync::Arc; - use std::usize; -@@ -22,7 +23,10 @@ struct NvmeDisk { - - impl Disk for NvmeDisk { - fn block_size(&self) -> u32 { -- self.ns.block_size.try_into().unwrap() -+ self.ns.block_size.try_into().unwrap_or_else(|_| { -+ log::error!("nvmed: block size {} does not fit in u32", self.ns.block_size); -+ process::exit(1); -+ }) - } - - fn size(&self) -> u64 { -@@ -79,26 +83,43 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - - let interrupt_vector = irq_helpers::pci_allocate_interrupt_vector(&mut pcid_handle, "nvmed"); - let iv = interrupt_vector.vector(); -- let irq_handle = interrupt_vector.irq_handle().try_clone().unwrap(); -+ let irq_handle = interrupt_vector.irq_handle().try_clone().unwrap_or_else(|err| { -+ log::error!("nvmed: failed to clone IRQ handle: {err}"); -+ process::exit(1); -+ }); - - let mut nvme = Nvme::new(address.as_ptr() as usize, interrupt_vector, pcid_handle) -- .expect("nvmed: failed to allocate driver data"); -- -- unsafe { nvme.init().expect("nvmed: failed to init") } -+ .unwrap_or_else(|err| { -+ log::error!("nvmed: failed to allocate driver data: {err}"); -+ process::exit(1); -+ }); -+ -+ unsafe { -+ nvme.init().unwrap_or_else(|err| { -+ log::error!("nvmed: failed to init: {err}"); -+ process::exit(1); -+ }); -+ } - log::debug!("Finished base initialization"); - let nvme = Arc::new(nvme); - - let executor = nvme::executor::init(Arc::clone(&nvme), iv, false /* FIXME */, irq_handle); - - let mut time_handle = File::open(&format!("/scheme/time/{}", libredox::flag::CLOCK_MONOTONIC)) -- .expect("failed to open time handle"); -+ .unwrap_or_else(|err| { -+ log::error!("nvmed: failed to open time handle: {err}"); -+ process::exit(1); -+ }); - - let mut time_events = Box::pin( - executor.register_external_event(time_handle.as_raw_fd() as usize, event::EventFlags::READ), - ); - - // Try to init namespaces for 5 seconds -- time_arm(&mut time_handle, 5).expect("failed to arm timer"); -+ time_arm(&mut time_handle, 5).unwrap_or_else(|err| { -+ log::error!("nvmed: failed to arm timer: {err}"); -+ process::exit(1); -+ }); - let namespaces = executor.block_on(async { - let namespaces_future = nvme.init_with_queues(); - let time_future = time_events.as_mut().next(); -@@ -106,7 +127,10 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - futures::pin_mut!(time_future); - match futures::future::select(namespaces_future, time_future).await { - futures::future::Either::Left((namespaces, _)) => namespaces, -- futures::future::Either::Right(_) => panic!("timeout on init"), -+ futures::future::Either::Right(_) => { -+ log::error!("nvmed: timeout on init"); -+ process::exit(1); -+ } - } - }); - log::debug!("Initialized!"); -@@ -134,7 +158,10 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - event::EventFlags::READ, - )); - -- libredox::call::setrens(0, 0).expect("nvmed: failed to enter null namespace"); -+ libredox::call::setrens(0, 0).unwrap_or_else(|err| { -+ log::error!("nvmed: failed to enter null namespace: {err}"); -+ process::exit(1); -+ }); - - log::debug!("Starting to listen for scheme events"); - -@@ -150,5 +177,5 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - - //TODO: destroy NVMe stuff - -- std::process::exit(0); -+ process::exit(0); - } -diff --git a/drivers/storage/nvmed/src/nvme/executor.rs b/drivers/storage/nvmed/src/nvme/executor.rs -index 6242fa98..c1435e88 100644 ---- a/drivers/storage/nvmed/src/nvme/executor.rs -+++ b/drivers/storage/nvmed/src/nvme/executor.rs -@@ -34,7 +34,12 @@ impl Hardware for NvmeHw { - &VTABLE - } - fn current() -> std::rc::Rc> { -- THE_EXECUTOR.with(|exec| Rc::clone(exec.borrow().as_ref().unwrap())) -+ THE_EXECUTOR.with(|exec| { -+ Rc::clone(exec.borrow().as_ref().unwrap_or_else(|| { -+ log::error!("nvmed: internal error: executor not initialized"); -+ std::process::exit(1); -+ })) -+ }) - } - fn try_submit( - nvme: &Arc, -diff --git a/drivers/storage/nvmed/src/nvme/identify.rs b/drivers/storage/nvmed/src/nvme/identify.rs -index 05e5b9b2..b1b6e959 100644 ---- a/drivers/storage/nvmed/src/nvme/identify.rs -+++ b/drivers/storage/nvmed/src/nvme/identify.rs -@@ -126,7 +126,7 @@ impl LbaFormat { - 0b01 => RelativePerformance::Better, - 0b10 => RelativePerformance::Good, - 0b11 => RelativePerformance::Degraded, -- _ => unreachable!(), -+ _ => RelativePerformance::Degraded, - } - } - pub fn is_available(&self) -> bool { -@@ -153,7 +153,14 @@ impl Nvme { - /// Returns the serial number, model, and firmware, in that order. - pub async fn identify_controller(&self) { - // TODO: Use same buffer -- let data: Dma = unsafe { Dma::zeroed().unwrap().assume_init() }; -+ let data: Dma = unsafe { -+ Dma::zeroed() -+ .map(|dma| dma.assume_init()) -+ .unwrap_or_else(|err| { -+ log::error!("nvmed: failed to allocate identify DMA: {err}"); -+ std::process::exit(1); -+ }) -+ }; - - // println!(" - Attempting to identify controller"); - let comp = self -@@ -182,7 +189,14 @@ impl Nvme { - } - pub async fn identify_namespace_list(&self, base: u32) -> Vec { - // TODO: Use buffer -- let data: Dma<[u32; 1024]> = unsafe { Dma::zeroed().unwrap().assume_init() }; -+ let data: Dma<[u32; 1024]> = unsafe { -+ Dma::zeroed() -+ .map(|dma| dma.assume_init()) -+ .unwrap_or_else(|err| { -+ log::error!("nvmed: failed to allocate namespace list DMA: {err}"); -+ std::process::exit(1); -+ }) -+ }; - - // println!(" - Attempting to retrieve namespace ID list"); - let comp = self -@@ -198,7 +212,14 @@ impl Nvme { - } - pub async fn identify_namespace(&self, nsid: u32) -> NvmeNamespace { - //TODO: Use buffer -- let data: Dma = unsafe { Dma::zeroed().unwrap().assume_init() }; -+ let data: Dma = unsafe { -+ Dma::zeroed() -+ .map(|dma| dma.assume_init()) -+ .unwrap_or_else(|err| { -+ log::error!("nvmed: failed to allocate namespace DMA: {err}"); -+ std::process::exit(1); -+ }) -+ }; - - log::debug!("Attempting to identify namespace {nsid}"); - let comp = self -@@ -216,7 +237,10 @@ impl Nvme { - let block_size = data - .formatted_lba_size() - .lba_data_size() -- .expect("nvmed: error: size outside 512-2^64 range"); -+ .unwrap_or_else(|| { -+ log::error!("nvmed: error: size outside 512-2^64 range"); -+ std::process::exit(1); -+ }); - log::debug!("NVME block size: {}", block_size); - - NvmeNamespace { -diff --git a/drivers/storage/nvmed/src/nvme/mod.rs b/drivers/storage/nvmed/src/nvme/mod.rs -index 682ee933..90a25d5b 100644 ---- a/drivers/storage/nvmed/src/nvme/mod.rs -+++ b/drivers/storage/nvmed/src/nvme/mod.rs -@@ -160,7 +160,15 @@ impl Nvme { - } - fn cur_thread_ctxt(&self) -> Arc> { - // TODO: multi-threading -- Arc::clone(self.thread_ctxts.read().get(&0).unwrap()) -+ Arc::clone( -+ self.thread_ctxts -+ .read() -+ .get(&0) -+ .unwrap_or_else(|| { -+ log::error!("nvmed: internal error: thread context 0 missing"); -+ std::process::exit(1); -+ }), -+ ) - } - - pub unsafe fn submission_queue_tail(&self, qid: u16, tail: u16) { -@@ -208,10 +216,22 @@ impl Nvme { - } - - for (qid, iv) in self.cq_ivs.get_mut().iter_mut() { -- let ctxt = thread_ctxts.get(&0).unwrap().lock(); -+ let ctxt = match thread_ctxts.get(&0) { -+ Some(c) => c.lock(), -+ None => { -+ log::error!("nvmed: internal error: thread context 0 missing"); -+ return Err(Error::new(EIO)); -+ } -+ }; - let queues = ctxt.queues.borrow(); - -- let &(ref cq, ref sq) = queues.get(qid).unwrap(); -+ let (cq, sq) = match queues.get(qid) { -+ Some(pair) => pair, -+ None => { -+ log::error!("nvmed: internal error: queue {qid} missing"); -+ return Err(Error::new(EIO)); -+ } -+ }; - log::debug!( - "iv {iv} [cq {qid}: {:X}, {}] [sq {qid}: {:X}, {}]", - cq.data.physical(), -@@ -222,7 +242,13 @@ impl Nvme { - } - - { -- let main_ctxt = thread_ctxts.get(&0).unwrap().lock(); -+ let main_ctxt = match thread_ctxts.get(&0) { -+ Some(c) => c.lock(), -+ None => { -+ log::error!("nvmed: internal error: thread context 0 missing"); -+ return Err(Error::new(EIO)); -+ } -+ }; - - for (i, prp) in main_ctxt.buffer_prp.borrow_mut().iter_mut().enumerate() { - *prp = (main_ctxt.buffer.borrow_mut().physical() + i * 4096) as u64; -@@ -231,7 +257,13 @@ impl Nvme { - let regs = self.regs.get_mut(); - - let mut queues = main_ctxt.queues.borrow_mut(); -- let (asq, acq) = queues.get_mut(&0).unwrap(); -+ let (asq, acq) = match queues.get_mut(&0) { -+ Some(pair) => pair, -+ None => { -+ log::error!("nvmed: internal error: admin queue pair missing"); -+ return Err(Error::new(EIO)); -+ } -+ }; - regs.aqa - .write(((acq.data.len() as u32 - 1) << 16) | (asq.data.len() as u32 - 1)); - regs.asq_low.write(asq.data.physical() as u32); -@@ -281,14 +313,14 @@ impl Nvme { - let vector = vector as u8; - - if masked { -- assert_ne!( -+ debug_assert_ne!( - to_clear & (1 << vector), - (1 << vector), - "nvmed: internal error: cannot both mask and set" - ); - to_mask |= 1 << vector; - } else { -- assert_ne!( -+ debug_assert_ne!( - to_mask & (1 << vector), - (1 << vector), - "nvmed: internal error: cannot both mask and set" -@@ -326,22 +358,27 @@ impl Nvme { - cmd_init: impl FnOnce(CmdId) -> NvmeCmd, - fail: impl FnOnce(), - ) -> Option<(CqId, CmdId)> { -- match ctxt.queues.borrow_mut().get_mut(&sq_id).unwrap() { -- (sq, _cq) => { -- if sq.is_full() { -- fail(); -- return None; -- } -- let cmd_id = sq.tail; -- let tail = sq.submit_unchecked(cmd_init(cmd_id)); -- -- // TODO: Submit in bulk -- unsafe { -- self.submission_queue_tail(sq_id, tail); -- } -- Some((sq_id, cmd_id)) -+ let mut queues_ref = ctxt.queues.borrow_mut(); -+ let (sq, _cq) = match queues_ref.get_mut(&sq_id) { -+ Some(pair) => pair, -+ None => { -+ log::error!("nvmed: internal error: submission queue {sq_id} missing"); -+ fail(); -+ return None; - } -+ }; -+ if sq.is_full() { -+ fail(); -+ return None; -+ } -+ let cmd_id = sq.tail; -+ let tail = sq.submit_unchecked(cmd_init(cmd_id)); -+ -+ // TODO: Submit in bulk -+ unsafe { -+ self.submission_queue_tail(sq_id, tail); - } -+ Some((sq_id, cmd_id)) - } - - pub async fn create_io_completion_queue( -@@ -349,13 +386,19 @@ impl Nvme { - io_cq_id: CqId, - vector: Option, - ) -> NvmeCompQueue { -- let queue = NvmeCompQueue::new().expect("nvmed: failed to allocate I/O completion queue"); -- -- let len = u16::try_from(queue.data.len()) -- .expect("nvmed: internal error: I/O CQ longer than 2^16 entries"); -- let raw_len = len -- .checked_sub(1) -- .expect("nvmed: internal error: CQID 0 for I/O CQ"); -+ let queue = NvmeCompQueue::new().unwrap_or_else(|err| { -+ log::error!("nvmed: failed to allocate I/O completion queue: {err}"); -+ std::process::exit(1); -+ }); -+ -+ let len = u16::try_from(queue.data.len()).unwrap_or_else(|_| { -+ log::error!("nvmed: internal error: I/O CQ longer than 2^16 entries"); -+ std::process::exit(1); -+ }); -+ let raw_len = len.checked_sub(1).unwrap_or_else(|| { -+ log::error!("nvmed: internal error: CQID 0 for I/O CQ"); -+ std::process::exit(1); -+ }); - - let comp = self - .submit_and_complete_admin_command(|cid| { -@@ -370,22 +413,28 @@ impl Nvme { - .await; - - /*match comp.status.specific { -- 1 => panic!("invalid queue identifier"), -- 2 => panic!("invalid queue size"), -- 8 => panic!("invalid interrupt vector"), -+ 1 => { log::error!("nvmed: invalid queue identifier"); std::process::exit(1); } -+ 2 => { log::error!("nvmed: invalid queue size"); std::process::exit(1); } -+ 8 => { log::error!("nvmed: invalid interrupt vector"); std::process::exit(1); } - _ => (), - }*/ - - queue - } - pub async fn create_io_submission_queue(&self, io_sq_id: SqId, io_cq_id: CqId) -> NvmeCmdQueue { -- let q = NvmeCmdQueue::new().expect("failed to create submission queue"); -- -- let len = u16::try_from(q.data.len()) -- .expect("nvmed: internal error: I/O SQ longer than 2^16 entries"); -- let raw_len = len -- .checked_sub(1) -- .expect("nvmed: internal error: SQID 0 for I/O SQ"); -+ let q = NvmeCmdQueue::new().unwrap_or_else(|err| { -+ log::error!("nvmed: failed to create submission queue: {err}"); -+ std::process::exit(1); -+ }); -+ -+ let len = u16::try_from(q.data.len()).unwrap_or_else(|_| { -+ log::error!("nvmed: internal error: I/O SQ longer than 2^16 entries"); -+ std::process::exit(1); -+ }); -+ let raw_len = len.checked_sub(1).unwrap_or_else(|| { -+ log::error!("nvmed: internal error: SQID 0 for I/O SQ"); -+ std::process::exit(1); -+ }); - - let comp = self - .submit_and_complete_admin_command(|cid| { -@@ -399,9 +448,9 @@ impl Nvme { - }) - .await; - /*match comp.status.specific { -- 0 => panic!("completion queue invalid"), -- 1 => panic!("invalid queue identifier"), -- 2 => panic!("invalid queue size"), -+ 0 => { log::error!("nvmed: completion queue invalid"); std::process::exit(1); } -+ 1 => { log::error!("nvmed: invalid queue identifier"); std::process::exit(1); } -+ 2 => { log::error!("nvmed: invalid queue size"); std::process::exit(1); } - _ => (), - }*/ - -@@ -431,7 +480,10 @@ impl Nvme { - self.thread_ctxts - .read() - .get(&0) -- .unwrap() -+ .unwrap_or_else(|| { -+ log::error!("nvmed: internal error: thread context 0 missing"); -+ std::process::exit(1); -+ }) - .lock() - .queues - .borrow_mut() -@@ -497,8 +549,8 @@ impl Nvme { - for chunk in buf.chunks_mut(/* TODO: buf len */ 8192) { - let blocks = (chunk.len() + block_size - 1) / block_size; - -- assert!(blocks > 0); -- assert!(blocks <= 0x1_0000); -+ debug_assert!(blocks > 0); -+ debug_assert!(blocks <= 0x1_0000); - - self.namespace_rw(&*ctxt, namespace, lba, (blocks - 1) as u16, false) - .await?; -@@ -525,8 +577,8 @@ impl Nvme { - for chunk in buf.chunks(/* TODO: buf len */ 8192) { - let blocks = (chunk.len() + block_size - 1) / block_size; - -- assert!(blocks > 0); -- assert!(blocks <= 0x1_0000); -+ debug_assert!(blocks > 0); -+ debug_assert!(blocks <= 0x1_0000); - - ctxt.buffer.borrow_mut()[..chunk.len()].copy_from_slice(chunk); - -diff --git a/drivers/storage/nvmed/src/nvme/queues.rs b/drivers/storage/nvmed/src/nvme/queues.rs -index a3712aeb..438c905c 100644 ---- a/drivers/storage/nvmed/src/nvme/queues.rs -+++ b/drivers/storage/nvmed/src/nvme/queues.rs -@@ -145,7 +145,7 @@ impl Status { - 3 => Self::PathRelatedStatus(code), - 4..=6 => Self::Rsvd(code), - 7 => Self::Vendor(code), -- _ => unreachable!(), -+ _ => Self::Vendor(code), - } - } - } -diff --git a/drivers/storage/virtio-blkd/src/main.rs b/drivers/storage/virtio-blkd/src/main.rs -index d21236b3..2b777937 100644 ---- a/drivers/storage/virtio-blkd/src/main.rs -+++ b/drivers/storage/virtio-blkd/src/main.rs -@@ -1,6 +1,7 @@ - #![deny(trivial_numeric_casts, unused_allocation)] - - use std::collections::BTreeMap; -+use std::process; - use std::sync::{Arc, Weak}; - - use driver_block::DiskScheme; -@@ -59,14 +60,23 @@ impl BlockDeviceConfig { - T: Sized + TryFrom, - >::Error: std::fmt::Debug, - { -- let transport = self.0.upgrade().unwrap(); -+ let transport = self.0.upgrade().unwrap_or_else(|| { -+ log::error!("virtio-blkd: transport handle dropped"); -+ process::exit(1); -+ }); - - let size = core::mem::size_of::() - .try_into() -- .expect("load_config: invalid size"); -+ .unwrap_or_else(|_| { -+ log::error!("virtio-blkd: load_config: invalid size"); -+ process::exit(1); -+ }); - - let value = transport.load_config(ty as u8, size); -- T::try_from(value).unwrap() -+ T::try_from(value).unwrap_or_else(|_| { -+ log::error!("virtio-blkd: load_config: invalid config value"); -+ process::exit(1); -+ }) - } - - /// Returns the capacity of the block device in bytes. -@@ -103,8 +113,11 @@ fn main() { - } - - fn daemon_runner(redox_daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { -- daemon(redox_daemon, pcid_handle).unwrap(); -- unreachable!(); -+ daemon(redox_daemon, pcid_handle).unwrap_or_else(|err| { -+ log::error!("virtio-blkd: daemon failed: {err}"); -+ process::exit(1); -+ }); -+ process::exit(0); - } - - fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> anyhow::Result<()> { -@@ -121,7 +134,10 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> anyhow: - // 0x1001 - virtio-blk - let pci_config = pcid_handle.config(); - -- assert_eq!(pci_config.func.full_device_id.device_id, 0x1001); -+ if pci_config.func.full_device_id.device_id != 0x1001 { -+ log::error!("virtio-blkd: unexpected device ID {:#06x}, expected 0x1001", pci_config.func.full_device_id.device_id); -+ process::exit(1); -+ } - log::info!("virtio-blk: initiating startup sequence :^)"); - - let device = virtio_core::probe_device(&mut pcid_handle)?; -@@ -147,7 +163,10 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> anyhow: - - let scheme_name = format!("disk.{}", name); - -- let event_queue = event::EventQueue::new().unwrap(); -+ let mut event_queue = event::EventQueue::new().unwrap_or_else(|err| { -+ log::error!("virtio-blkd: failed to create event queue: {err}"); -+ process::exit(1); -+ }); - - event::user_data! { - enum Event { -@@ -162,7 +181,10 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> anyhow: - &driver_block::FuturesExecutor, - ); - -- libredox::call::setrens(0, 0).expect("nvmed: failed to enter null namespace"); -+ libredox::call::setrens(0, 0).unwrap_or_else(|err| { -+ log::error!("virtio-blkd: failed to enter null namespace: {err}"); -+ process::exit(1); -+ }); - - event_queue - .subscribe( -@@ -170,11 +192,26 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> anyhow: - Event::Scheme, - event::EventFlags::READ, - ) -- .unwrap(); -- -- for event in event_queue { -- match event.unwrap().user_data { -- Event::Scheme => futures::executor::block_on(scheme.tick()).unwrap(), -+ .unwrap_or_else(|err| { -+ log::error!("virtio-blkd: failed to subscribe to scheme events: {err}"); -+ process::exit(1); -+ }); -+ -+ loop { -+ let event = match event_queue.next() { -+ Some(Ok(event)) => event, -+ Some(Err(err)) => { -+ log::error!("virtio-blkd: failed to get event: {err}"); -+ continue; -+ } -+ None => break, -+ }; -+ match event.user_data { -+ Event::Scheme => { -+ if let Err(err) = futures::executor::block_on(scheme.tick()) { -+ log::error!("virtio-blkd: failed to handle scheme event: {err}"); -+ } -+ } - } - } - -diff --git a/drivers/storage/virtio-blkd/src/scheme.rs b/drivers/storage/virtio-blkd/src/scheme.rs -index ec4ecf73..39fb24a8 100644 ---- a/drivers/storage/virtio-blkd/src/scheme.rs -+++ b/drivers/storage/virtio-blkd/src/scheme.rs -@@ -15,19 +15,34 @@ trait BlkExtension { - - impl BlkExtension for Queue<'_> { - async fn read(&self, block: u64, target: &mut [u8]) -> usize { -- let req = Dma::new(BlockVirtRequest { -+ let req = match Dma::new(BlockVirtRequest { - ty: BlockRequestTy::In, - reserved: 0, - sector: block, -- }) -- .unwrap(); -+ }) { -+ Ok(req) => req, -+ Err(err) => { -+ log::error!("virtio-blkd: failed to allocate read request DMA: {err}"); -+ return 0; -+ } -+ }; - - let result = unsafe { -- Dma::<[u8]>::zeroed_slice(target.len()) -- .unwrap() -- .assume_init() -+ match Dma::<[u8]>::zeroed_slice(target.len()) { -+ Ok(dma) => dma.assume_init(), -+ Err(err) => { -+ log::error!("virtio-blkd: failed to allocate read buffer DMA: {err}"); -+ return 0; -+ } -+ } -+ }; -+ let status = match Dma::new(u8::MAX) { -+ Ok(s) => s, -+ Err(err) => { -+ log::error!("virtio-blkd: failed to allocate read status DMA: {err}"); -+ return 0; -+ } - }; -- let status = Dma::new(u8::MAX).unwrap(); - - let chain = ChainBuilder::new() - .chain(Buffer::new(&req)) -@@ -37,28 +52,46 @@ impl BlkExtension for Queue<'_> { - - // XXX: Subtract 1 because the of status byte. - let written = self.send(chain).await as usize - 1; -- assert_eq!(*status, 0); -+ if *status != 0 { -+ log::error!("virtio-blkd: read failed with status {}", *status); -+ return 0; -+ } - - target[..written].copy_from_slice(&result); - written - } - - async fn write(&self, block: u64, target: &[u8]) -> usize { -- let req = Dma::new(BlockVirtRequest { -+ let req = match Dma::new(BlockVirtRequest { - ty: BlockRequestTy::Out, - reserved: 0, - sector: block, -- }) -- .unwrap(); -+ }) { -+ Ok(req) => req, -+ Err(err) => { -+ log::error!("virtio-blkd: failed to allocate write request DMA: {err}"); -+ return 0; -+ } -+ }; - - let mut result = unsafe { -- Dma::<[u8]>::zeroed_slice(target.len()) -- .unwrap() -- .assume_init() -+ match Dma::<[u8]>::zeroed_slice(target.len()) { -+ Ok(dma) => dma.assume_init(), -+ Err(err) => { -+ log::error!("virtio-blkd: failed to allocate write buffer DMA: {err}"); -+ return 0; -+ } -+ } - }; - result.copy_from_slice(target.as_ref()); - -- let status = Dma::new(u8::MAX).unwrap(); -+ let status = match Dma::new(u8::MAX) { -+ Ok(s) => s, -+ Err(err) => { -+ log::error!("virtio-blkd: failed to allocate write status DMA: {err}"); -+ return 0; -+ } -+ }; - - let chain = ChainBuilder::new() - .chain(Buffer::new(&req)) -@@ -67,7 +100,10 @@ impl BlkExtension for Queue<'_> { - .build(); - - self.send(chain).await as usize; -- assert_eq!(*status, 0); -+ if *status != 0 { -+ log::error!("virtio-blkd: write failed with status {}", *status); -+ return 0; -+ } - - target.len() - } -diff --git a/drivers/usb/usbctl/src/main.rs b/drivers/usb/usbctl/src/main.rs -index 9b5773d9..232f7cfc 100644 ---- a/drivers/usb/usbctl/src/main.rs -+++ b/drivers/usb/usbctl/src/main.rs -@@ -15,6 +15,9 @@ fn main() { - Command::new("port") - .arg(Arg::new("PORT").num_args(1).required(true)) - .subcommand(Command::new("status")) -+ .subcommand(Command::new("pm-state")) -+ .subcommand(Command::new("suspend")) -+ .subcommand(Command::new("resume")) - .subcommand( - Command::new("endpoint") - .arg(Arg::new("ENDPOINT_NUM").num_args(1).required(true)) -@@ -38,6 +41,15 @@ fn main() { - if let Some(_status_scmd_matches) = port_scmd_matches.subcommand_matches("status") { - let state = handle.port_state().expect("Failed to get port state"); - println!("{}", state.as_str()); -+ } else if let Some(_pm_state_scmd_matches) = port_scmd_matches.subcommand_matches("pm-state") { -+ let state = handle -+ .port_pm_state() -+ .expect("Failed to get port power-management state"); -+ println!("{}", state.as_str()); -+ } else if let Some(_suspend_scmd_matches) = port_scmd_matches.subcommand_matches("suspend") { -+ handle.suspend_device().expect("Failed to suspend device"); -+ } else if let Some(_resume_scmd_matches) = port_scmd_matches.subcommand_matches("resume") { -+ handle.resume_device().expect("Failed to resume device"); - } else if let Some(endp_scmd_matches) = port_scmd_matches.subcommand_matches("endpoint") { - let endp_num = endp_scmd_matches - .get_one::("ENDPOINT_NUM") -diff --git a/drivers/usb/xhcid/drivers.toml b/drivers/usb/xhcid/drivers.toml -index 83c90e23..470ec063 100644 ---- a/drivers/usb/xhcid/drivers.toml -+++ b/drivers/usb/xhcid/drivers.toml -@@ -1,9 +1,8 @@ --#TODO: causes XHCI errors --#[[drivers]] --#name = "SCSI over USB" --#class = 8 # Mass Storage class --#subclass = 6 # SCSI transparent command set --#command = ["usbscsid", "$SCHEME", "$PORT", "$IF_PROTO"] -+[[drivers]] -+name = "SCSI over USB" -+class = 8 # Mass Storage class -+subclass = 6 # SCSI transparent command set -+command = ["usbscsid", "$SCHEME", "$PORT", "$IF_PROTO"] - - [[drivers]] - name = "USB HUB" -diff --git a/drivers/usb/xhcid/src/driver_interface.rs b/drivers/usb/xhcid/src/driver_interface.rs -index 727f8d7e..82f839ae 100644 ---- a/drivers/usb/xhcid/src/driver_interface.rs -+++ b/drivers/usb/xhcid/src/driver_interface.rs -@@ -444,6 +444,33 @@ impl str::FromStr for PortState { - } - } - -+#[repr(u8)] -+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -+pub enum PortPmState { -+ Active, -+ Suspended, -+} -+impl PortPmState { -+ pub fn as_str(&self) -> &'static str { -+ match self { -+ Self::Active => "active", -+ Self::Suspended => "suspended", -+ } -+ } -+} -+ -+impl str::FromStr for PortPmState { -+ type Err = Invalid; -+ -+ fn from_str(s: &str) -> result::Result { -+ Ok(match s { -+ "active" => Self::Active, -+ "suspended" => Self::Suspended, -+ _ => return Err(Invalid("read reserved port PM state")), -+ }) -+ } -+} -+ - #[repr(u8)] - #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] - pub enum EndpointStatus { -@@ -560,6 +587,16 @@ impl XhciClientHandle { - let _bytes_written = file.write(&[])?; - Ok(()) - } -+ pub fn suspend_device(&self) -> result::Result<(), XhciClientHandleError> { -+ let file = self.fd.openat("suspend", libredox::flag::O_WRONLY, 0)?; -+ let _bytes_written = file.write(&[])?; -+ Ok(()) -+ } -+ pub fn resume_device(&self) -> result::Result<(), XhciClientHandleError> { -+ let file = self.fd.openat("resume", libredox::flag::O_WRONLY, 0)?; -+ let _bytes_written = file.write(&[])?; -+ Ok(()) -+ } - pub fn get_standard_descs(&self) -> result::Result { - let json = self.read("descriptors")?; - Ok(serde_json::from_slice(&json)?) -@@ -582,6 +619,10 @@ impl XhciClientHandle { - let string = self.read_to_string("state")?; - Ok(string.parse()?) - } -+ pub fn port_pm_state(&self) -> result::Result { -+ let string = self.read_to_string("pm_state")?; -+ Ok(string.parse()?) -+ } - pub fn open_endpoint_ctl(&self, num: u8) -> result::Result { - let path = format!("endpoints/{}/ctl", num); - let fd = self.fd.openat(&path, libredox::flag::O_RDWR, 0)?; -diff --git a/drivers/usb/xhcid/src/main.rs b/drivers/usb/xhcid/src/main.rs -index d345a52f..562c580a 100644 ---- a/drivers/usb/xhcid/src/main.rs -+++ b/drivers/usb/xhcid/src/main.rs -@@ -33,7 +33,7 @@ use std::sync::Arc; - use pcid_interface::irq_helpers::read_bsp_apic_id; - #[cfg(target_arch = "x86_64")] - use pcid_interface::irq_helpers::{ -- allocate_first_msi_interrupt_on_bsp, allocate_single_interrupt_vector_for_msi, -+ try_allocate_first_msi_interrupt_on_bsp, try_allocate_single_interrupt_vector_for_msi, - }; - use pcid_interface::{PciFeature, PciFeatureInfo, PciFunctionHandle}; - -@@ -61,11 +61,24 @@ fn get_int_method(pcid_handle: &mut PciFunctionHandle) -> (Option, Interru - let has_msix = all_pci_features.iter().any(|feature| feature.is_msix()); - - if has_msix { -- let msix_info = match pcid_handle.feature_info(PciFeature::MsiX) { -- PciFeatureInfo::Msi(_) => panic!(), -- PciFeatureInfo::MsiX(s) => s, -+ let msix_info = match pcid_handle.try_feature_info(PciFeature::MsiX) { -+ Ok(PciFeatureInfo::MsiX(s)) => s, -+ Ok(PciFeatureInfo::Msi(_)) => { -+ log::error!("xhcid: invalid MSI-X feature response payload"); -+ return (None, InterruptMethod::Polling); -+ } -+ Err(err) => { -+ log::error!("xhcid: failed to fetch MSI-X feature info: {err}"); -+ return (None, InterruptMethod::Polling); -+ } -+ }; -+ let mut info = match unsafe { msix_info.try_map_and_mask_all(pcid_handle) } { -+ Ok(info) => info, -+ Err(err) => { -+ log::error!("xhcid: failed to map MSI-X registers: {err}"); -+ return (None, InterruptMethod::Polling); -+ } - }; -- let mut info = unsafe { msix_info.map_and_mask_all(pcid_handle) }; - - // Allocate one msi vector. - -@@ -75,27 +88,53 @@ fn get_int_method(pcid_handle: &mut PciFunctionHandle) -> (Option, Interru - - let table_entry_pointer = info.table_entry_pointer(k); - -- let destination_id = read_bsp_apic_id().expect("xhcid: failed to read BSP apic id"); -+ let destination_id = match read_bsp_apic_id() { -+ Ok(id) => id, -+ Err(err) => { -+ log::error!("xhcid: failed to read BSP APIC ID: {err}"); -+ return (None, InterruptMethod::Polling); -+ } -+ }; - let (msg_addr_and_data, interrupt_handle) = -- allocate_single_interrupt_vector_for_msi(destination_id); -+ match try_allocate_single_interrupt_vector_for_msi(destination_id) { -+ Ok(result) => result, -+ Err(err) => { -+ log::error!("xhcid: failed to allocate MSI-X vector: {err}"); -+ return (None, InterruptMethod::Polling); -+ } -+ }; - table_entry_pointer.write_addr_and_data(msg_addr_and_data); - table_entry_pointer.unmask(); - - (Some(interrupt_handle), InterruptMethod::Msi) - }; - -- pcid_handle.enable_feature(PciFeature::MsiX); -+ if let Err(err) = pcid_handle.try_enable_feature(PciFeature::MsiX) { -+ log::error!("xhcid: failed to enable MSI-X: {err}"); -+ return (None, InterruptMethod::Polling); -+ } - log::debug!("Enabled MSI-X"); - - method - } else if has_msi { -- let interrupt_handle = allocate_first_msi_interrupt_on_bsp(pcid_handle); -- (Some(interrupt_handle), InterruptMethod::Msi) -+ match try_allocate_first_msi_interrupt_on_bsp(pcid_handle) { -+ Ok(interrupt_handle) => (Some(interrupt_handle), InterruptMethod::Msi), -+ Err(err) => { -+ log::error!("xhcid: failed to allocate MSI interrupt: {err}"); -+ (None, InterruptMethod::Polling) -+ } -+ } - } else if let Some(irq) = pci_config.func.legacy_interrupt_line { - log::debug!("Legacy IRQ {}", irq); - - // legacy INTx# interrupt pins. -- (Some(irq.irq_handle("xhcid")), InterruptMethod::Intx) -+ match irq.try_irq_handle("xhcid") { -+ Ok(file) => (Some(file), InterruptMethod::Intx), -+ Err(err) => { -+ log::error!("xhcid: failed to open legacy IRQ handle: {err}"); -+ (None, InterruptMethod::Polling) -+ } -+ } - } else { - // no interrupts at all - (None, InterruptMethod::Polling) -@@ -109,7 +148,13 @@ fn get_int_method(pcid_handle: &mut PciFunctionHandle) -> (Option, Interru - - if let Some(irq) = pci_config.func.legacy_interrupt_line { - // legacy INTx# interrupt pins. -- (Some(irq.irq_handle("xhcid")), InterruptMethod::Intx) -+ match irq.try_irq_handle("xhcid") { -+ Ok(file) => (Some(file), InterruptMethod::Intx), -+ Err(err) => { -+ log::error!("xhcid: failed to open legacy IRQ handle: {err}"); -+ (None, InterruptMethod::Polling) -+ } -+ } - } else { - // no interrupts at all - (None, InterruptMethod::Polling) -@@ -136,23 +181,48 @@ fn daemon_with_context_size( - - log::debug!("XHCI PCI CONFIG: {:?}", pci_config); - -- let address = unsafe { pcid_handle.map_bar(0) }.ptr.as_ptr() as usize; -+ let address = match unsafe { pcid_handle.try_map_bar(0) } { -+ Ok(bar) => bar.ptr.as_ptr() as usize, -+ Err(err) => { -+ log::error!("xhcid: failed to map BAR0: {err}"); -+ std::process::exit(1); -+ } -+ }; -+ -+ let (irq_file, interrupt_method) = get_int_method(&mut pcid_handle); - -- let (irq_file, interrupt_method) = (None, InterruptMethod::Polling); //get_int_method(&mut pcid_handle); -- //TODO: Fix interrupts. -+ match interrupt_method { -+ InterruptMethod::Msi => log::info!("xhcid: using MSI/MSI-X interrupt delivery"), -+ InterruptMethod::Intx => log::info!("xhcid: using legacy INTx interrupt delivery"), -+ InterruptMethod::Polling => log::warn!("xhcid: falling back to polling mode"), -+ } - - log::info!("XHCI {}", pci_config.func.display()); - - let scheme_name = format!("usb.{}", name); -- let socket = Socket::create().expect("xhcid: failed to create usb scheme"); -+ let socket = match Socket::create() { -+ Ok(socket) => socket, -+ Err(err) => { -+ log::error!("xhcid: failed to create usb scheme: {err}"); -+ std::process::exit(1); -+ } -+ }; - let handler = Blocking::new(&socket, 16); - - let hci = Arc::new( -- Xhci::::new(scheme_name.clone(), address, interrupt_method, pcid_handle) -- .expect("xhcid: failed to allocate device"), -+ match Xhci::::new(scheme_name.clone(), address, interrupt_method, pcid_handle) { -+ Ok(hci) => hci, -+ Err(err) => { -+ log::error!("xhcid: failed to allocate device: {err}"); -+ std::process::exit(1); -+ } -+ }, - ); - register_sync_scheme(&socket, &scheme_name, &mut &*hci) -- .expect("xhcid: failed to regsiter scheme to namespace"); -+ .unwrap_or_else(|err| { -+ log::error!("xhcid: failed to register scheme to namespace: {err}"); -+ std::process::exit(1); -+ }); - - daemon.ready(); - -@@ -163,7 +233,10 @@ fn daemon_with_context_size( - - handler - .process_requests_blocking(&*hci) -- .expect("xhcid: failed to process requests"); -+ .unwrap_or_else(|err| { -+ log::error!("xhcid: failed to process requests: {err}"); -+ std::process::exit(1); -+ }); - } - - fn main() { -@@ -171,7 +244,13 @@ fn main() { - } - - fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { -- let address = unsafe { pcid_handle.map_bar(0) }.ptr.as_ptr() as usize; -+ let address = match unsafe { pcid_handle.try_map_bar(0) } { -+ Ok(bar) => bar.ptr.as_ptr() as usize, -+ Err(err) => { -+ log::error!("xhcid: failed to map BAR0: {err}"); -+ std::process::exit(1); -+ } -+ }; - let cap = unsafe { &mut *(address as *mut xhci::CapabilityRegs) }; - if cap.csz() { - daemon_with_context_size::<{ xhci::CONTEXT_64 }>(daemon, pcid_handle) -diff --git a/drivers/usb/xhcid/src/xhci/device_enumerator.rs b/drivers/usb/xhcid/src/xhci/device_enumerator.rs -index 74b9f732..493e79df 100644 ---- a/drivers/usb/xhcid/src/xhci/device_enumerator.rs -+++ b/drivers/usb/xhcid/src/xhci/device_enumerator.rs -@@ -4,9 +4,11 @@ use common::io::Io; - use crossbeam_channel; - use log::{debug, info, warn}; - use std::sync::Arc; --use std::time::Duration; -+use std::time::{Duration, Instant}; - use syscall::EAGAIN; - -+const DEFAULT_PORT_RESET_SETTLE_MS: u64 = 16; -+ - pub struct DeviceEnumerationRequest { - pub port_id: PortId, - } -@@ -28,7 +30,11 @@ impl DeviceEnumerator { - let request = match self.request_queue.recv() { - Ok(req) => req, - Err(err) => { -- panic!("Failed to received an enumeration request! error: {}", err) -+ warn!( -+ "device enumerator stopping after request queue closed: {}", -+ err -+ ); -+ break; - } - }; - -@@ -38,7 +44,11 @@ impl DeviceEnumerator { - debug!("Device Enumerator request for port {}", port_id); - - let (len, flags) = { -- let ports = self.hci.ports.lock().unwrap(); -+ let ports = self -+ .hci -+ .ports -+ .lock() -+ .unwrap_or_else(|poisoned| poisoned.into_inner()); - - let len = ports.len(); - -@@ -62,43 +72,52 @@ impl DeviceEnumerator { - //A USB3 port won't generate a Connect Status Change until it's already enabled, so this check - //will always be skipped for USB3 ports - if !flags.contains(PortFlags::PED) { -- let disabled_state = flags.contains(PortFlags::PP) -- && flags.contains(PortFlags::CCS) -- && !flags.contains(PortFlags::PED) -- && !flags.contains(PortFlags::PR); -+ let disabled_state = Self::port_is_disabled(&flags); - - if !disabled_state { -- panic!( -- "Port {} isn't in the disabled state! Current flags: {:?}", -+ warn!( -+ "Port {} never reached the disabled state before reset-driven enumeration; current flags: {:?}", - port_id, flags - ); -+ continue; - } else { - debug!("Port {} has entered the disabled state.", port_id); - } - - //THIS LOCKS THE PORTS. DO NOT LOCK PORTS BEFORE THIS POINT - debug!("Received a device connect on port {}, but it's not enabled. Resetting the port.", port_id); -- let _ = self.hci.reset_port(port_id); -+ if let Err(err) = self.hci.reset_port(port_id) { -+ warn!( -+ "failed to reset port {} before enumeration; skipping attach: {}", -+ port_id, err -+ ); -+ continue; -+ } - -- let mut ports = self.hci.ports.lock().unwrap(); -+ let mut ports = self -+ .hci -+ .ports -+ .lock() -+ .unwrap_or_else(|poisoned| poisoned.into_inner()); - let port = &mut ports[port_array_index]; - - port.clear_prc(); - -- std::thread::sleep(Duration::from_millis(16)); //Some controllers need some extra time to make the transition. -+ drop(ports); - -- let flags = port.flags(); -+ let flags = self.wait_for_port_enabled_state( -+ port_array_index, -+ Duration::from_millis(DEFAULT_PORT_RESET_SETTLE_MS), -+ ); - -- let enabled_state = flags.contains(PortFlags::PP) -- && flags.contains(PortFlags::CCS) -- && flags.contains(PortFlags::PED) -- && !flags.contains(PortFlags::PR); -+ let enabled_state = Self::port_is_enabled(&flags); - - if !enabled_state { - warn!( -- "Port {} isn't in the enabled state! Current flags: {:?}", -+ "Port {} isn't in the enabled state after bounded reset settle; current flags: {:?}", - port_id, flags - ); -+ continue; - } else { - debug!( - "Port {} is in the enabled state. Proceeding with enumeration", -@@ -131,13 +150,60 @@ impl DeviceEnumerator { - Ok(was_connected) => { - if was_connected { - info!("Device on port {} was detached", port_id); -+ } else { -+ debug!( -+ "Ignoring duplicate or out-of-order detach event for unattached port {}", -+ port_id -+ ); - } - } - Err(err) => { -- warn!("processing of device attach request failed! Error: {}", err); -+ warn!("processing of device detach request failed! Error: {}", err); - } - } - } - } - } -+ -+ fn port_is_disabled(flags: &PortFlags) -> bool { -+ flags.contains(PortFlags::PP) -+ && flags.contains(PortFlags::CCS) -+ && !flags.contains(PortFlags::PED) -+ && !flags.contains(PortFlags::PR) -+ } -+ -+ fn port_is_enabled(flags: &PortFlags) -> bool { -+ flags.contains(PortFlags::PP) -+ && flags.contains(PortFlags::CCS) -+ && flags.contains(PortFlags::PED) -+ && !flags.contains(PortFlags::PR) -+ } -+ -+ fn wait_for_port_enabled_state( -+ &self, -+ port_array_index: usize, -+ settle_timeout: Duration, -+ ) -> PortFlags { -+ let start = Instant::now(); -+ -+ loop { -+ let flags = { -+ let ports = self -+ .hci -+ .ports -+ .lock() -+ .unwrap_or_else(|poisoned| poisoned.into_inner()); -+ ports[port_array_index].flags() -+ }; -+ -+ if Self::port_is_enabled(&flags) -+ || !flags.contains(PortFlags::PR) -+ || start.elapsed() >= settle_timeout -+ { -+ return flags; -+ } -+ -+ std::thread::sleep(Duration::from_millis(1)); -+ } -+ } - } -diff --git a/drivers/usb/xhcid/src/xhci/irq_reactor.rs b/drivers/usb/xhcid/src/xhci/irq_reactor.rs -index ac492d5b..310fe51f 100644 ---- a/drivers/usb/xhcid/src/xhci/irq_reactor.rs -+++ b/drivers/usb/xhcid/src/xhci/irq_reactor.rs -@@ -633,7 +633,10 @@ impl Xhci { - pub fn with_ring T>(&self, id: RingId, function: F) -> Option { - use super::RingOrStreams; - -- let slot_state = self.port_states.get(&id.port)?; -+ let slot_state = self -+ .port_states -+ .get(&id.port) -+ .or_else(|| self.staged_port_states.get(&id.port))?; - let endpoint_state = slot_state.endpoint_states.get(&id.endpoint_num)?; - - let ring_ref = match endpoint_state.transfer { -@@ -650,7 +653,10 @@ impl Xhci { - ) -> Option { - use super::RingOrStreams; - -- let mut slot_state = self.port_states.get_mut(&id.port)?; -+ let mut slot_state = self -+ .port_states -+ .get_mut(&id.port) -+ .or_else(|| self.staged_port_states.get_mut(&id.port))?; - let mut endpoint_state = slot_state.endpoint_states.get_mut(&id.endpoint_num)?; - - let ring_ref = match endpoint_state.transfer { -diff --git a/drivers/usb/xhcid/src/xhci/mod.rs b/drivers/usb/xhcid/src/xhci/mod.rs -index f2143676..0d2ec432 100644 ---- a/drivers/usb/xhcid/src/xhci/mod.rs -+++ b/drivers/usb/xhcid/src/xhci/mod.rs -@@ -11,12 +11,13 @@ - //! documents are specified in the crate-level documentation. - use std::collections::BTreeMap; - use std::convert::TryFrom; --use std::fs::File; -+use std::fs::{self, File}; - use std::sync::atomic::AtomicUsize; --use std::sync::{Arc, Mutex}; -+use std::sync::{Arc, Condvar, Mutex}; -+use std::time::Duration; - - use std::{mem, process, slice, thread}; --use syscall::error::{Error, Result, EBADF, EBADMSG, EIO, ENOENT}; -+use syscall::error::{Error, Result, EBADF, EBADMSG, EBUSY, EIO, ENOENT}; - use syscall::{EAGAIN, PAGE_SIZE}; - - use chashmap::CHashMap; -@@ -77,7 +78,52 @@ pub enum InterruptMethod { - Msi, - } - -+const XHCID_TEST_HOOK_PATH: &str = "/tmp/xhcid-test-hook"; -+const XHCID_TEST_HOOK_MAX_DELAY_MS: u64 = 5_000; -+ - impl Xhci { -+ fn read_test_hook_command_from_path(path: &str) -> Option { -+ let contents = fs::read_to_string(path).ok()?; -+ contents -+ .lines() -+ .map(|line| line.trim()) -+ .find(|line| !line.is_empty() && !line.starts_with('#')) -+ .map(|line| line.to_owned()) -+ } -+ -+ fn clear_test_hook_command_path(path: &str) { -+ if let Err(err) = fs::remove_file(path) { -+ if err.kind() != std::io::ErrorKind::NotFound { -+ warn!("failed to remove xhcid test hook file {}: {}", path, err); -+ } -+ } -+ } -+ -+ fn consume_test_hook_from_path(path: &str, expected: &str) -> bool { -+ match Self::read_test_hook_command_from_path(path) { -+ Some(command) if command == expected => { -+ Self::clear_test_hook_command_path(path); -+ true -+ } -+ _ => false, -+ } -+ } -+ -+ fn consume_test_hook_delay_ms_from_path(path: &str, prefix: &str) -> Option { -+ let command = Self::read_test_hook_command_from_path(path)?; -+ let delay_ms = command.strip_prefix(prefix)?.parse::().ok()?; -+ Self::clear_test_hook_command_path(path); -+ Some(delay_ms.min(XHCID_TEST_HOOK_MAX_DELAY_MS)) -+ } -+ -+ pub(crate) fn consume_test_hook(&self, expected: &str) -> bool { -+ Self::consume_test_hook_from_path(XHCID_TEST_HOOK_PATH, expected) -+ } -+ -+ pub(crate) fn consume_test_hook_delay_ms(&self, prefix: &str) -> Option { -+ Self::consume_test_hook_delay_ms_from_path(XHCID_TEST_HOOK_PATH, prefix) -+ } -+ - /// Gets descriptors, before the port state is initiated. - async fn get_desc_raw( - &self, -@@ -104,7 +150,17 @@ impl Xhci { - ); - - let future = { -- let mut port_state = self.port_states.get_mut(&port).ok_or(Error::new(ENOENT))?; -+ let mut published_port_state = self.port_states.get_mut(&port); -+ let mut staged_port_state = if published_port_state.is_none() { -+ self.staged_port_states.get_mut(&port) -+ } else { -+ None -+ }; -+ -+ let port_state = published_port_state -+ .as_deref_mut() -+ .or_else(|| staged_port_state.as_deref_mut()) -+ .ok_or(Error::new(ENOENT))?; - let ring = port_state - .endpoint_states - .get_mut(&0) -@@ -283,6 +339,7 @@ pub struct Xhci { - handles: CHashMap, - next_handle: AtomicUsize, - port_states: CHashMap>, -+ staged_port_states: CHashMap>, - drivers: CHashMap>, - scheme_name: String, - -@@ -308,9 +365,97 @@ struct PortState { - slot: u8, - protocol_speed: &'static ProtocolSpeed, - cfg_idx: Option, -+ active_ifaces: BTreeMap, // iface number → active alternate setting - input_context: Mutex>>, - dev_desc: Option, - endpoint_states: BTreeMap, -+ lifecycle: Arc, -+ pm_state: PortPmState, -+} -+ -+#[derive(Clone, Copy, Debug, Eq, PartialEq)] -+pub(crate) enum PortLifecycleState { -+ Attaching, -+ Attached, -+ Detaching, -+} -+ -+struct PortLifecycleInner { -+ state: PortLifecycleState, -+ active_operations: usize, -+} -+ -+pub(crate) struct PortLifecycle { -+ inner: Mutex, -+ idle: Condvar, -+} -+ -+impl PortLifecycle { -+ pub(crate) fn new_attaching() -> Self { -+ Self { -+ inner: Mutex::new(PortLifecycleInner { -+ state: PortLifecycleState::Attaching, -+ active_operations: 1, -+ }), -+ idle: Condvar::new(), -+ } -+ } -+ -+ fn lock_inner(&self) -> std::sync::MutexGuard<'_, PortLifecycleInner> { -+ self.inner.lock().unwrap_or_else(|err| err.into_inner()) -+ } -+ -+ pub(crate) fn finish_attach_success(&self) -> PortLifecycleState { -+ let mut inner = self.lock_inner(); -+ -+ if inner.state == PortLifecycleState::Attaching { -+ inner.state = PortLifecycleState::Attached; -+ } -+ -+ if inner.active_operations != 0 { -+ inner.active_operations -= 1; -+ } -+ if inner.active_operations == 0 { -+ self.idle.notify_all(); -+ } -+ -+ inner.state -+ } -+ -+ pub(crate) fn finish_attach_failure(&self) { -+ let mut inner = self.lock_inner(); -+ inner.state = PortLifecycleState::Detaching; -+ -+ if inner.active_operations != 0 { -+ inner.active_operations -= 1; -+ } -+ if inner.active_operations == 0 { -+ self.idle.notify_all(); -+ } -+ } -+ -+ pub(crate) fn begin_detaching(&self) { -+ let mut inner = self.lock_inner(); -+ inner.state = PortLifecycleState::Detaching; -+ -+ while inner.active_operations != 0 { -+ inner = self.idle.wait(inner).unwrap_or_else(|err| err.into_inner()); -+ } -+ } -+} -+ -+#[derive(Clone, Copy, Debug, Eq, PartialEq)] -+pub(crate) enum PortPmState { -+ Active, -+ Suspended, -+} -+impl PortPmState { -+ pub fn as_str(&self) -> &'static str { -+ match self { -+ Self::Active => "active", -+ Self::Suspended => "suspended", -+ } -+ } - } - - impl PortState { -@@ -463,6 +608,7 @@ impl Xhci { - handles: CHashMap::new(), - next_handle: AtomicUsize::new(0), - port_states: CHashMap::new(), -+ staged_port_states: CHashMap::new(), - drivers: CHashMap::new(), - scheme_name, - -@@ -793,11 +939,14 @@ impl Xhci { - } - - pub async fn attach_device(&self, port_id: PortId) -> syscall::Result<()> { -- if self.port_states.contains_key(&port_id) { -+ if self.port_states.contains_key(&port_id) || self.staged_port_states.contains_key(&port_id) -+ { - debug!("Already contains port {}", port_id); - return Err(syscall::Error::new(EAGAIN)); - } - -+ info!("xhcid: begin attach for port {}", port_id); -+ - let (data, state, speed, flags) = { - let port = &self.ports.lock().unwrap()[port_id.root_hub_port_index()]; - (port.read(), port.state(), port.speed(), port.flags()) -@@ -808,74 +957,102 @@ impl Xhci { - port_id, data, state, speed, flags - ); - -- if flags.contains(port::PortFlags::CCS) { -- let slot_ty = match self.supported_protocol(port_id) { -- Some(protocol) => protocol.proto_slot_ty(), -- None => { -- warn!("Failed to find supported protocol information for port"); -- 0 -- } -- }; -- -- debug!("Slot type: {}", slot_ty); -- debug!("Enabling slot."); -- let slot = match self.enable_port_slot(slot_ty).await { -- Ok(ok) => ok, -- Err(err) => { -- error!("Failed to enable slot for port {}: {}", port_id, err); -- return Err(err); -- } -- }; -+ if !flags.contains(port::PortFlags::CCS) { -+ warn!("Attempted to attach a device that didnt have CCS=1"); -+ return Ok(()); -+ } - -- debug!("Enabled port {}, which the xHC mapped to {}", port_id, slot); -+ let slot_ty = match self.supported_protocol(port_id) { -+ Some(protocol) => protocol.proto_slot_ty(), -+ None => { -+ warn!("Failed to find supported protocol information for port"); -+ 0 -+ } -+ }; - -- //TODO: get correct speed for child devices -- let protocol_speed = self -- .lookup_psiv(port_id, speed) -- .expect("Failed to retrieve speed ID"); -+ debug!("Slot type: {}", slot_ty); -+ debug!("Enabling slot."); -+ let slot = match self.enable_port_slot(slot_ty).await { -+ Ok(ok) => ok, -+ Err(err) => { -+ error!("Failed to enable slot for port {}: {}", port_id, err); -+ return Err(err); -+ } -+ }; - -- let mut input = unsafe { self.alloc_dma_zeroed::>()? }; -+ debug!("Enabled port {}, which the xHC mapped to {}", port_id, slot); - -- debug!("Attempting to address the device"); -- let mut ring = match self -- .address_device(&mut input, port_id, slot_ty, slot, protocol_speed, speed) -- .await -- { -- Ok(device_ring) => device_ring, -- Err(err) => { -- error!("Failed to address device for port {}: `{}`", port_id, err); -- return Err(err); -+ let protocol_speed = match self.lookup_psiv(port_id, speed) { -+ Some(protocol_speed) => protocol_speed, -+ None => { -+ let err = Error::new(EIO); -+ error!("Failed to retrieve speed ID for port {}", port_id); -+ if let Err(disable_err) = self.disable_port_slot(slot).await { -+ warn!( -+ "Failed to disable slot {} after speed lookup failure on port {}: {}", -+ slot, port_id, disable_err -+ ); - } -- }; -+ return Err(err); -+ } -+ }; - -- debug!("Addressed device"); -+ let mut input = unsafe { self.alloc_dma_zeroed::>()? }; - -- // TODO: Should the descriptors be cached in PortState, or refetched? -+ debug!("Attempting to address the device"); -+ let ring = match self -+ .address_device(&mut input, port_id, slot_ty, slot, protocol_speed, speed) -+ .await -+ { -+ Ok(device_ring) => device_ring, -+ Err(err) => { -+ error!("Failed to address device for port {}: `{}`", port_id, err); -+ if let Err(disable_err) = self.disable_port_slot(slot).await { -+ warn!( -+ "Failed to disable slot {} after address failure on port {}: {}", -+ slot, port_id, disable_err -+ ); -+ } -+ return Err(err); -+ } -+ }; - -- let mut port_state = PortState { -- slot, -- protocol_speed, -- input_context: Mutex::new(input), -- dev_desc: None, -- cfg_idx: None, -- endpoint_states: std::iter::once(( -- 0, -- EndpointState { -- transfer: RingOrStreams::Ring(ring), -- driver_if_state: EndpIfState::Init, -- }, -- )) -- .collect::>(), -- }; -- self.port_states.insert(port_id, port_state); -- debug!("Got port states!"); -+ debug!("Addressed device"); - -- // Ensure correct packet size is used -+ let lifecycle = Arc::new(PortLifecycle::new_attaching()); -+ let port_state = PortState { -+ slot, -+ protocol_speed, -+ input_context: Mutex::new(input), -+ dev_desc: None, -+ cfg_idx: None, -+ active_ifaces: BTreeMap::new(), -+ endpoint_states: std::iter::once(( -+ 0, -+ EndpointState { -+ transfer: RingOrStreams::Ring(ring), -+ driver_if_state: EndpIfState::Init, -+ }, -+ )) -+ .collect::>(), -+ lifecycle: Arc::clone(&lifecycle), -+ pm_state: PortPmState::Active, -+ }; -+ self.staged_port_states.insert(port_id, port_state); -+ debug!("Got staged port state!"); -+ -+ let attach_result = async { - let dev_desc_8_byte = self.fetch_dev_desc_8_byte(port_id, slot).await?; - { -- let mut port_state = self.port_states.get_mut(&port_id).unwrap(); -+ let mut port_state = self -+ .staged_port_states -+ .get_mut(&port_id) -+ .ok_or(Error::new(ENOENT))?; - -- let mut input = port_state.input_context.lock().unwrap(); -+ let mut input = port_state -+ .input_context -+ .lock() -+ .unwrap_or_else(|err| err.into_inner()); - - self.update_max_packet_size(&mut *input, slot, dev_desc_8_byte) - .await?; -@@ -885,97 +1062,175 @@ impl Xhci { - - let dev_desc = self.get_desc(port_id, slot).await?; - debug!("Got the full device descriptor!"); -- self.port_states.get_mut(&port_id).unwrap().dev_desc = Some(dev_desc); -+ self.staged_port_states -+ .get_mut(&port_id) -+ .ok_or(Error::new(ENOENT))? -+ .dev_desc = Some(dev_desc); - - debug!("Got the port states again!"); - { -- let mut port_state = self.port_states.get_mut(&port_id).unwrap(); -- -- let mut input = port_state.input_context.lock().unwrap(); -+ let mut port_state = self -+ .staged_port_states -+ .get_mut(&port_id) -+ .ok_or(Error::new(ENOENT))?; -+ -+ let mut input = port_state -+ .input_context -+ .lock() -+ .unwrap_or_else(|err| err.into_inner()); - debug!("Got the input context!"); -- let dev_desc = port_state.dev_desc.as_ref().unwrap(); -+ let dev_desc = port_state.dev_desc.as_ref().ok_or(Error::new(EIO))?; - - self.update_default_control_pipe(&mut *input, slot, dev_desc) - .await?; - } - - debug!("Updated the default control pipe"); -+ Ok(()) -+ } -+ .await; - -- match self.spawn_drivers(port_id) { -- Ok(()) => (), -- Err(err) => { -- error!("Failed to spawn driver for port {}: `{}`", port_id, err) -+ match attach_result { -+ Ok(()) => { -+ if let Some(delay_ms) = -+ self.consume_test_hook_delay_ms("delay_before_attach_commit_ms=") -+ { -+ info!( -+ "xhcid: test hook delaying attach commit for port {} by {} ms", -+ port_id, delay_ms -+ ); -+ thread::sleep(Duration::from_millis(delay_ms)); - } -+ -+ if lifecycle.finish_attach_success() != PortLifecycleState::Attached { -+ warn!( -+ "attach for port {} completed after detach already started; skipping publication", -+ port_id -+ ); -+ return Err(Error::new(EBUSY)); -+ } -+ -+ let staged_port_state = self -+ .staged_port_states -+ .remove(&port_id) -+ .ok_or(Error::new(ENOENT))?; -+ self.port_states.insert(port_id, staged_port_state); -+ -+ match self.spawn_drivers(port_id) { -+ Ok(()) => (), -+ Err(err) => { -+ error!("Failed to spawn driver for port {}: `{}`", port_id, err) -+ } -+ } -+ -+ info!("xhcid: finished attach for port {}", port_id); -+ Ok(()) -+ } -+ Err(err) => { -+ lifecycle.finish_attach_failure(); -+ if let Err(detach_err) = self.detach_device(port_id).await { -+ warn!( -+ "failed to clean up attach failure on port {}: {}", -+ port_id, detach_err -+ ); -+ } -+ Err(err) - } -- } else { -- warn!("Attempted to attach a device that didnt have CCS=1"); - } -- -- Ok(()) - } - - pub async fn detach_device(&self, port_id: PortId) -> Result { -- if let Some(children) = self.drivers.remove(&port_id) { -- for mut child in children { -- info!("killing driver process {} for port {}", child.id(), port_id); -- match child.kill() { -- Ok(()) => { -- info!("killed driver process {} for port {}", child.id(), port_id); -- match child.try_wait() { -- Ok(status_opt) => match status_opt { -- Some(status) => { -- debug!( -- "driver process {} for port {} exited with status {}", -- child.id(), -- port_id, -- status -- ); -- } -- None => { -- //TODO: kill harder -+ let published_state = self.port_states.get(&port_id); -+ let staged_state = if published_state.is_none() { -+ self.staged_port_states.get(&port_id) -+ } else { -+ None -+ }; -+ -+ let (slot, lifecycle, was_published) = match published_state -+ .as_deref() -+ .or_else(|| staged_state.as_deref()) -+ { -+ Some(state) => (state.slot, Arc::clone(&state.lifecycle), published_state.is_some()), -+ None => { -+ debug!( -+ "Attempted to detach from port {}, which wasn't previously attached.", -+ port_id -+ ); -+ return Ok(false); -+ } -+ }; -+ drop(published_state); -+ drop(staged_state); -+ -+ lifecycle.begin_detaching(); -+ -+ if was_published { -+ if let Some(children) = self.drivers.remove(&port_id) { -+ for mut child in children { -+ info!("killing driver process {} for port {}", child.id(), port_id); -+ match child.kill() { -+ Ok(()) => { -+ info!("killed driver process {} for port {}", child.id(), port_id); -+ match child.try_wait() { -+ Ok(status_opt) => match status_opt { -+ Some(status) => { -+ debug!( -+ "driver process {} for port {} exited with status {}", -+ child.id(), -+ port_id, -+ status -+ ); -+ } -+ None => { -+ warn!( -+ "driver process {} for port {} still running", -+ child.id(), -+ port_id -+ ); -+ } -+ }, -+ Err(err) => { - warn!( -- "driver process {} for port {} still running", -+ "failed to wait for the driver process {} for port {}: {}", - child.id(), -- port_id -+ port_id, -+ err - ); - } -- }, -- Err(err) => { -- warn!( -- "failed to wait for the driver process {} for port {}: {}", -- child.id(), -- port_id, -- err -- ); - } - } -- } -- Err(err) => { -- warn!( -- "failed to kill the driver process {} for port {}: {}", -- child.id(), -- port_id, -- err -- ); -+ Err(err) => { -+ warn!( -+ "failed to kill the driver process {} for port {}: {}", -+ child.id(), -+ port_id, -+ err -+ ); -+ } - } - } - } - } - -- if let Some(state) = self.port_states.remove(&port_id) { -- debug!("disabling port slot {} for port {}", state.slot, port_id); -- let result = self.disable_port_slot(state.slot).await.and(Ok(true)); -- debug!( -- "disabled port slot {} for port {} with result: {:?}", -- state.slot, port_id, result -- ); -- result -- } else { -- debug!( -- "Attempted to detach from port {}, which wasn't previously attached.", -- port_id -- ); -- Ok(false) -+ debug!("disabling port slot {} for port {}", slot, port_id); -+ match self.disable_port_slot(slot).await { -+ Ok(()) => { -+ if was_published { -+ let _ = self.port_states.remove(&port_id); -+ } else { -+ let _ = self.staged_port_states.remove(&port_id); -+ } -+ debug!("disabled port slot {} for port {}", slot, port_id); -+ Ok(true) -+ } -+ Err(err) => { -+ warn!( -+ "failed to disable port slot {} for port {}: {}", -+ slot, port_id, err -+ ); -+ Err(err) -+ } - } - } - -@@ -1246,14 +1501,12 @@ impl Xhci { - let drivers_usercfg: &DriversConfig = &DRIVERS_CONFIG; - - for ifdesc in config_desc.interface_descs.iter() { -- //TODO: support alternate settings -- // This is difficult because the device driver must know which alternate -- // to use, but if alternates can have different classes, then a different -- // device driver may be required for each alternate. For now, we will use -- // only the default alternate setting (0) -+ // Only auto-spawn drivers for the default alternate setting (0). -+ // Non-default alternates are selected later by the device driver -+ // via SET_INTERFACE + configure_endpoints with specific alternate_setting. - if ifdesc.alternate_setting != 0 { -- warn!( -- "ignoring port {} iface {} alternate {} class {}.{} proto {}", -+ debug!( -+ "skipping port {} iface {} alternate {} class {}.{} proto {} (non-default alternate)", - port, - ifdesc.number, - ifdesc.alternate_setting, -@@ -1458,6 +1711,53 @@ pub fn start_device_enumerator(hci: &Arc>) { - })); - } - -+#[cfg(test)] -+mod tests { -+ use std::fs; -+ use std::path::Path; -+ use std::time::{SystemTime, UNIX_EPOCH}; -+ -+ use super::{Xhci, XHCID_TEST_HOOK_MAX_DELAY_MS}; -+ -+ fn unique_test_hook_path() -> String { -+ let unique = SystemTime::now() -+ .duration_since(UNIX_EPOCH) -+ .unwrap() -+ .as_nanos(); -+ format!("/tmp/xhcid-test-hook-{}", unique) -+ } -+ -+ #[test] -+ fn consume_test_hook_only_clears_matching_command() { -+ let path = unique_test_hook_path(); -+ fs::write(&path, "fail_after_set_configuration\n").unwrap(); -+ -+ assert!(!Xhci::<16>::consume_test_hook_from_path( -+ &path, -+ "fail_after_configure_endpoint" -+ )); -+ assert!(Path::new(&path).exists()); -+ -+ assert!(Xhci::<16>::consume_test_hook_from_path( -+ &path, -+ "fail_after_set_configuration" -+ )); -+ assert!(!Path::new(&path).exists()); -+ } -+ -+ #[test] -+ fn consume_test_hook_delay_clamps_and_clears() { -+ let path = unique_test_hook_path(); -+ fs::write(&path, "delay_before_attach_commit_ms=999999\n").unwrap(); -+ -+ assert_eq!( -+ Xhci::<16>::consume_test_hook_delay_ms_from_path(&path, "delay_before_attach_commit_ms="), -+ Some(XHCID_TEST_HOOK_MAX_DELAY_MS) -+ ); -+ assert!(!Path::new(&path).exists()); -+ } -+} -+ - #[derive(Deserialize)] - struct DriverConfig { - name: String, -diff --git a/drivers/usb/xhcid/src/xhci/scheme.rs b/drivers/usb/xhcid/src/xhci/scheme.rs -index ca27b3fe..29437294 100644 ---- a/drivers/usb/xhcid/src/xhci/scheme.rs -+++ b/drivers/usb/xhcid/src/xhci/scheme.rs -@@ -20,6 +20,7 @@ use std::convert::TryFrom; - use std::io::prelude::*; - use std::ops::Deref; - use std::sync::atomic; -+use std::collections::BTreeMap; - use std::{cmp, fmt, io, mem, str}; - - use common::dma::Dma; -@@ -33,9 +34,9 @@ use common::io::Io; - use redox_scheme::{CallerCtx, OpenResult}; - use syscall::schemev2::NewFdFlags; - use syscall::{ -- Error, Result, Stat, EACCES, EBADF, EBADFD, EBADMSG, EINVAL, EIO, EISDIR, ENOENT, ENOSYS, -- ENOTDIR, EOPNOTSUPP, EPROTO, ESPIPE, MODE_CHR, MODE_DIR, MODE_FILE, O_DIRECTORY, O_RDWR, -- O_STAT, O_WRONLY, SEEK_CUR, SEEK_END, SEEK_SET, -+ Error, Result, Stat, EACCES, EBADF, EBADFD, EBADMSG, EBUSY, EINVAL, EIO, EISDIR, ENOENT, -+ ENOSYS, ENOTDIR, EOPNOTSUPP, EPROTO, ESPIPE, MODE_CHR, MODE_DIR, MODE_FILE, O_DIRECTORY, -+ O_RDWR, O_STAT, O_WRONLY, SEEK_CUR, SEEK_END, SEEK_SET, - }; - - use super::{port, usb}; -@@ -61,10 +62,16 @@ lazy_static! { - .expect("Failed to create the regex for the port/attach scheme."); - static ref REGEX_PORT_DETACH: Regex = Regex::new(r"^port([\d\.]+)/detach$") - .expect("Failed to create the regex for the port/detach scheme."); -+ static ref REGEX_PORT_SUSPEND: Regex = Regex::new(r"^port([\d\.]+)/suspend$") -+ .expect("Failed to create the regex for the port/suspend scheme."); -+ static ref REGEX_PORT_RESUME: Regex = Regex::new(r"^port([\d\.]+)/resume$") -+ .expect("Failed to create the regex for the port/resume scheme."); - static ref REGEX_PORT_DESCRIPTORS: Regex = Regex::new(r"^port([\d\.]+)/descriptors$") - .expect("Failed to create the regex for the port/descriptors"); - static ref REGEX_PORT_STATE: Regex = Regex::new(r"^port([\d\.]+)/state$") - .expect("Failed to create the regex for the port/state scheme"); -+ static ref REGEX_PORT_PM_STATE: Regex = Regex::new(r"^port([\d\.]+)/pm_state$") -+ .expect("Failed to create the regex for the port/pm_state scheme"); - static ref REGEX_PORT_REQUEST: Regex = Regex::new(r"^port([\d\.]+)/request$") - .expect("Failed to create the regex for the port/request scheme"); - static ref REGEX_PORT_ENDPOINTS: Regex = Regex::new(r"^port([\d\.]+)/endpoints$") -@@ -138,12 +145,15 @@ pub enum Handle { - Port(PortId, Vec), // port, contents - PortDesc(PortId, Vec), // port, contents - PortState(PortId), // port -+ PortPmState(PortId), // port - PortReq(PortId, PortReqState), // port, state - Endpoints(PortId, Vec), // port, contents - Endpoint(PortId, u8, EndpointHandleTy), // port, endpoint, state - ConfigureEndpoints(PortId), // port - AttachDevice(PortId), // port - DetachDevice(PortId), // port -+ SuspendDevice(PortId), // port -+ ResumeDevice(PortId), // port - SchemeRoot, - } - -@@ -173,6 +183,8 @@ enum SchemeParameters { - PortDesc(PortId), // port number - /// /port/state - PortState(PortId), // port number -+ /// /port/pm_state -+ PortPmState(PortId), // port number - /// /port/request - PortReq(PortId), // port number - /// /port/endpoints -@@ -188,6 +200,10 @@ enum SchemeParameters { - AttachDevice(PortId), // port number - /// /port/detach - DetachDevice(PortId), // port number -+ /// /port/suspend -+ SuspendDevice(PortId), // port number -+ /// /port/resume -+ ResumeDevice(PortId), // port number - } - - impl Handle { -@@ -210,6 +226,9 @@ impl Handle { - Handle::PortState(port_num) => { - format!("port{}/state", port_num) - } -+ Handle::PortPmState(port_num) => { -+ format!("port{}/pm_state", port_num) -+ } - Handle::PortReq(port_num, _) => { - format!("port{}/request", port_num) - } -@@ -236,6 +255,12 @@ impl Handle { - Handle::DetachDevice(port_num) => { - format!("port{}/detach", port_num) - } -+ Handle::SuspendDevice(port_num) => { -+ format!("port{}/suspend", port_num) -+ } -+ Handle::ResumeDevice(port_num) => { -+ format!("port{}/resume", port_num) -+ } - Handle::SchemeRoot => String::from(""), - } - } -@@ -259,10 +284,13 @@ impl Handle { - &Handle::PortReq(_, PortReqState::Tmp) => unreachable!(), - &Handle::PortReq(_, PortReqState::TmpSetup(_)) => unreachable!(), - &Handle::PortState(_) => HandleType::Character, -+ &Handle::PortPmState(_) => HandleType::Character, - &Handle::PortReq(_, _) => HandleType::Character, - &Handle::ConfigureEndpoints(_) => HandleType::Character, - &Handle::AttachDevice(_) => HandleType::Character, - &Handle::DetachDevice(_) => HandleType::Character, -+ &Handle::SuspendDevice(_) => HandleType::Character, -+ &Handle::ResumeDevice(_) => HandleType::Character, - &Handle::Endpoint(_, _, ref st) => match st { - EndpointHandleTy::Data => HandleType::Character, - EndpointHandleTy::Ctl => HandleType::Character, -@@ -290,10 +318,13 @@ impl Handle { - &Handle::PortReq(_, PortReqState::Tmp) => None, - &Handle::PortReq(_, PortReqState::TmpSetup(_)) => None, - &Handle::PortState(_) => None, -+ &Handle::PortPmState(_) => None, - &Handle::PortReq(_, _) => None, - &Handle::ConfigureEndpoints(_) => None, - &Handle::AttachDevice(_) => None, - &Handle::DetachDevice(_) => None, -+ &Handle::SuspendDevice(_) => None, -+ &Handle::ResumeDevice(_) => None, - &Handle::Endpoint(_, _, ref st) => match st { - EndpointHandleTy::Data => None, - EndpointHandleTy::Ctl => None, -@@ -384,6 +415,14 @@ impl SchemeParameters { - let port_num = get_port_id_from_regex(®EX_PORT_DETACH, scheme, 0)?; - - Ok(Self::DetachDevice(port_num)) -+ } else if REGEX_PORT_SUSPEND.is_match(scheme) { -+ let port_num = get_port_id_from_regex(®EX_PORT_SUSPEND, scheme, 0)?; -+ -+ Ok(Self::SuspendDevice(port_num)) -+ } else if REGEX_PORT_RESUME.is_match(scheme) { -+ let port_num = get_port_id_from_regex(®EX_PORT_RESUME, scheme, 0)?; -+ -+ Ok(Self::ResumeDevice(port_num)) - } else if REGEX_PORT_DESCRIPTORS.is_match(scheme) { - let port_num = get_port_id_from_regex(®EX_PORT_DESCRIPTORS, scheme, 0)?; - -@@ -392,6 +431,10 @@ impl SchemeParameters { - let port_num = get_port_id_from_regex(®EX_PORT_STATE, scheme, 0)?; - - Ok(Self::PortState(port_num)) -+ } else if REGEX_PORT_PM_STATE.is_match(scheme) { -+ let port_num = get_port_id_from_regex(®EX_PORT_PM_STATE, scheme, 0)?; -+ -+ Ok(Self::PortPmState(port_num)) - } else if REGEX_PORT_REQUEST.is_match(scheme) { - let port_num = get_port_id_from_regex(®EX_PORT_REQUEST, scheme, 0)?; - -@@ -524,6 +567,39 @@ pub enum AnyDescriptor { - SuperSpeedPlusCompanion(usb::SuperSpeedPlusIsochCmpDescriptor), - } - -+#[derive(Clone, Copy)] -+struct ConfigureContextSnapshot { -+ add_context: u32, -+ drop_context: u32, -+ control: u32, -+ slot_a: u32, -+ slot_b: u32, -+} -+ -+#[derive(Clone, Copy)] -+struct EndpointContextSnapshot { -+ a: u32, -+ b: u32, -+ trl: u32, -+ trh: u32, -+ c: u32, -+} -+ -+impl EndpointContextSnapshot { -+ fn capture_values(a: u32, b: u32, trl: u32, trh: u32, c: u32) -> Self { -+ Self { a, b, trl, trh, c } -+ } -+} -+ -+struct EndpointProgram { -+ endp_num_xhc: u8, -+ a: u32, -+ b: u32, -+ trl: u32, -+ trh: u32, -+ c: u32, -+} -+ - impl AnyDescriptor { - fn parse(bytes: &[u8]) -> Option<(Self, usize)> { - if bytes.len() < 2 { -@@ -640,6 +716,8 @@ impl Xhci { - where - D: FnMut(&mut Trb, bool) -> ControlFlow, - { -+ self.ensure_port_active(port_num)?; -+ - let future = { - let mut port_state = self.port_state_mut(port_num)?; - let slot = port_state.slot; -@@ -710,6 +788,8 @@ impl Xhci { - where - D: FnMut(&mut Trb, bool) -> ControlFlow, - { -+ self.ensure_port_active(port_num)?; -+ - let endp_idx = endp_num.checked_sub(1).ok_or(Error::new(EIO))?; - let mut port_state = self.port_state_mut(port_num)?; - -@@ -835,7 +915,10 @@ impl Xhci { - port, - usb::Setup::set_interface(interface_num, alternate_setting), - ) -- .await -+ .await?; -+ let mut port_state = self.port_states.get_mut(&port).ok_or(Error::new(EBADFD))?; -+ port_state.active_ifaces.insert(interface_num, alternate_setting); -+ Ok(()) - } - - async fn reset_endpoint(&self, port_num: PortId, endp_num: u8, tsp: bool) -> Result<()> { -@@ -950,35 +1033,114 @@ impl Xhci { - self.port_states.get_mut(&port).ok_or(Error::new(EBADF)) - } - -+ fn restore_configure_input_context( -+ &self, -+ port: PortId, -+ snapshot: ConfigureContextSnapshot, -+ endpoint_snapshots: &[(usize, EndpointContextSnapshot)], -+ ) -> Result { -+ let port_state = self.port_states.get(&port).ok_or(Error::new(EBADFD))?; -+ let mut input_context = port_state.input_context.lock().unwrap(); -+ -+ input_context.add_context.write(snapshot.add_context); -+ input_context.drop_context.write(snapshot.drop_context); -+ input_context.control.write(snapshot.control); -+ input_context.device.slot.a.write(snapshot.slot_a); -+ input_context.device.slot.b.write(snapshot.slot_b); -+ -+ for (endp_i, endp_snapshot) in endpoint_snapshots { -+ input_context.device.endpoints[*endp_i].a.write(endp_snapshot.a); -+ input_context.device.endpoints[*endp_i].b.write(endp_snapshot.b); -+ input_context.device.endpoints[*endp_i].trl.write(endp_snapshot.trl); -+ input_context.device.endpoints[*endp_i].trh.write(endp_snapshot.trh); -+ input_context.device.endpoints[*endp_i].c.write(endp_snapshot.c); -+ } -+ -+ Ok(input_context.physical()) -+ } -+ -+ async fn rollback_configure_attempt( -+ &self, -+ port: PortId, -+ slot: u8, -+ configure_snapshot: ConfigureContextSnapshot, -+ endpoint_snapshots: &[(usize, EndpointContextSnapshot)], -+ stage: &str, -+ ) { -+ let rollback_input_context_physical = match self.restore_configure_input_context( -+ port, -+ configure_snapshot, -+ endpoint_snapshots, -+ ) { -+ Ok(physical) => physical, -+ Err(restore_err) => { -+ warn!( -+ "failed to restore configure input context after {}: {:?}", -+ stage, restore_err -+ ); -+ return; -+ } -+ }; -+ -+ let (rollback_event_trb, rollback_command_trb) = self -+ .execute_command(|trb, cycle| { -+ trb.configure_endpoint(slot, rollback_input_context_physical, cycle) -+ }) -+ .await; -+ -+ if let Err(rollback_err) = handle_event_trb( -+ "CONFIGURE_ENDPOINT_ROLLBACK", -+ &rollback_event_trb, -+ &rollback_command_trb, -+ ) { -+ warn!( -+ "failed to roll back CONFIGURE_ENDPOINT after {}: {:?}", -+ stage, rollback_err -+ ); -+ } -+ } -+ - async fn configure_endpoints_once( - &self, - port: PortId, - req: &ConfigureEndpointsReq, - ) -> Result<()> { -- let (endp_desc_count, new_context_entries, configuration_value) = { -- let mut port_state = self.port_states.get_mut(&port).ok_or(Error::new(EBADFD))?; -- -- port_state.cfg_idx = Some(req.config_desc); -+ let (dev_desc, endpoint_descs, new_context_entries, configuration_value, speed_id) = { -+ let port_state = self.port_states.get(&port).ok_or(Error::new(EBADFD))?; -+ let dev_desc = port_state.dev_desc.as_ref().ok_or(Error::new(EBADFD))?.clone(); -+ let speed_id = port_state.protocol_speed; - -- let config_desc = port_state -- .dev_desc -- .as_ref() -- .unwrap() -+ let config_desc = dev_desc - .config_descs - .iter() - .find(|desc| desc.configuration_value == req.config_desc) - .ok_or(Error::new(EBADFD))?; - -- //TODO: USE ENDPOINTS FROM ALL INTERFACES -- let mut endp_desc_count = 0; -- let mut new_context_entries = 1; -- for if_desc in config_desc.interface_descs.iter() { -- for endpoint in if_desc.endpoints.iter() { -- endp_desc_count += 1; -- let entry = Self::endp_num_to_dci(endp_desc_count, endpoint); -- if entry > new_context_entries { -- new_context_entries = entry; -- } -+ let configuration_value = config_desc.configuration_value; -+ -+ let endpoint_descs = if let Some(iface_num) = req.interface_desc { -+ let alt = req.alternate_setting.unwrap_or(0); -+ config_desc -+ .interface_descs -+ .iter() -+ .filter(|if_desc| if_desc.number == iface_num && if_desc.alternate_setting == alt) -+ .flat_map(|if_desc| if_desc.endpoints.iter().copied()) -+ .collect::>() -+ } else { -+ config_desc -+ .interface_descs -+ .iter() -+ .filter(|if_desc| if_desc.alternate_setting == 0) -+ .flat_map(|if_desc| if_desc.endpoints.iter().copied()) -+ .collect::>() -+ }; -+ -+ let endp_desc_count = endpoint_descs.len(); -+ let mut new_context_entries = 1u8; -+ for (endp_idx, endpoint) in endpoint_descs.iter().enumerate() { -+ let entry = Self::endp_num_to_dci(endp_idx as u8 + 1, endpoint); -+ if entry > new_context_entries { -+ new_context_entries = entry; - } - } - new_context_entries += 1; -@@ -989,74 +1151,22 @@ impl Xhci { - } - - ( -- endp_desc_count, -+ dev_desc, -+ endpoint_descs, - new_context_entries, -- config_desc.configuration_value, -+ configuration_value, -+ speed_id, - ) - }; - let lec = self.cap.lec(); - let log_max_psa_size = self.cap.max_psa_size(); - -- let port_speed_id = self.ports.lock().unwrap()[port.root_hub_port_index()].speed(); -- let speed_id: &ProtocolSpeed = self.lookup_psiv(port, port_speed_id).ok_or_else(|| { -- warn!("no speed_id"); -- Error::new(EIO) -- })?; -- -- { -- let port_state = self.port_states.get(&port).ok_or(Error::new(EBADFD))?; -- let mut input_context = port_state.input_context.lock().unwrap(); -- -- // Configure the slot context as well, which holds the last index of the endp descs. -- input_context.add_context.write(1); -- input_context.drop_context.write(0); -- -- const CONTEXT_ENTRIES_MASK: u32 = 0xF800_0000; -- const CONTEXT_ENTRIES_SHIFT: u8 = 27; -- -- const HUB_PORTS_MASK: u32 = 0xFF00_0000; -- const HUB_PORTS_SHIFT: u8 = 24; -- -- let mut current_slot_a = input_context.device.slot.a.read(); -- let mut current_slot_b = input_context.device.slot.b.read(); -- -- // Set context entries -- current_slot_a &= !CONTEXT_ENTRIES_MASK; -- current_slot_a |= -- (u32::from(new_context_entries) << CONTEXT_ENTRIES_SHIFT) & CONTEXT_ENTRIES_MASK; -- -- // Set hub data -- current_slot_a &= !(1 << 26); -- current_slot_b &= !HUB_PORTS_MASK; -- if let Some(hub_ports) = req.hub_ports { -- current_slot_a |= 1 << 26; -- current_slot_b |= (u32::from(hub_ports) << HUB_PORTS_SHIFT) & HUB_PORTS_MASK; -- } -- -- input_context.device.slot.a.write(current_slot_a); -- input_context.device.slot.b.write(current_slot_b); -- -- let control = if self.op.lock().unwrap().cie() { -- (u32::from(req.alternate_setting.unwrap_or(0)) << 16) -- | (u32::from(req.interface_desc.unwrap_or(0)) << 8) -- | u32::from(configuration_value) -- } else { -- 0 -- }; -- input_context.control.write(control); -- } -+ let mut staged_endpoint_states = BTreeMap::new(); -+ let mut endpoint_programs = Vec::new(); - -- for endp_idx in 0..endp_desc_count as u8 { -- let endp_num = endp_idx + 1; -- -- let mut port_state = self.port_states.get_mut(&port).ok_or(Error::new(EBADFD))?; -- let dev_desc = port_state.dev_desc.as_ref().unwrap(); -- let endp_desc = port_state.get_endp_desc(endp_idx).ok_or_else(|| { -- warn!("failed to find endpoint {}", endp_idx); -- Error::new(EIO) -- })?; -- -- let endp_num_xhc = Self::endp_num_to_dci(endp_num, endp_desc); -+ for (endp_idx, endp_desc) in endpoint_descs.iter().copied().enumerate() { -+ let endp_num = endp_idx as u8 + 1; -+ let endp_num_xhc = Self::endp_num_to_dci(endp_num, &endp_desc); - - let usb_log_max_streams = endp_desc.log_max_streams(); - -@@ -1078,20 +1188,20 @@ impl Xhci { - - let mult = endp_desc.isoch_mult(lec); - -- let max_packet_size = Self::endp_ctx_max_packet_size(endp_desc); -- let max_burst_size = Self::endp_ctx_max_burst(speed_id, dev_desc, endp_desc); -+ let max_packet_size = Self::endp_ctx_max_packet_size(&endp_desc); -+ let max_burst_size = Self::endp_ctx_max_burst(speed_id, &dev_desc, &endp_desc); - - let max_esit_payload = Self::endp_ctx_max_esit_payload( - speed_id, -- dev_desc, -- endp_desc, -+ &dev_desc, -+ &endp_desc, - max_packet_size, - max_burst_size, - ); - let max_esit_payload_lo = max_esit_payload as u16; - let max_esit_payload_hi = ((max_esit_payload & 0x00FF_0000) >> 16) as u8; - -- let interval = Self::endp_ctx_interval(speed_id, endp_desc); -+ let interval = Self::endp_ctx_interval(speed_id, &endp_desc); - - let max_error_count = 3; - let ep_ty = endp_desc.xhci_ep_type()?; -@@ -1114,7 +1224,7 @@ impl Xhci { - assert_eq!(max_error_count & 0x3, max_error_count); - assert_ne!(ep_ty, 0); // 0 means invalid. - -- let ring_ptr = if usb_log_max_streams.is_some() { -+ let (endpoint_state, ring_ptr) = if usb_log_max_streams.is_some() { - let mut array = - StreamContextArray::new::(self.cap.ac64(), 1 << (primary_streams + 1))?; - -@@ -1127,15 +1237,13 @@ impl Xhci { - array_ptr, - "stream ctx ptr not aligned to 16 bytes" - ); -- port_state.endpoint_states.insert( -- endp_num, -+ ( - EndpointState { - transfer: super::RingOrStreams::Streams(array), - driver_if_state: EndpIfState::Init, - }, -- ); -- -- array_ptr -+ array_ptr, -+ ) - } else { - let ring = Ring::new::(self.cap.ac64(), 16, true)?; - let ring_ptr = ring.register(); -@@ -1145,68 +1253,205 @@ impl Xhci { - ring_ptr, - "ring pointer not aligned to 16 bytes" - ); -- port_state.endpoint_states.insert( -- endp_num, -+ ( - EndpointState { - transfer: super::RingOrStreams::Ring(ring), - driver_if_state: EndpIfState::Init, - }, -- ); -- ring_ptr -+ ring_ptr, -+ ) - }; - assert_eq!(primary_streams & 0x1F, primary_streams); - -- let mut input_context = port_state.input_context.lock().unwrap(); -- input_context.add_context.writef(1 << endp_num_xhc, true); -- -- let endp_i = endp_num_xhc as usize - 1; -- input_context.device.endpoints[endp_i].a.write( -- u32::from(mult) << 8 -+ staged_endpoint_states.insert(endp_num, endpoint_state); -+ endpoint_programs.push(EndpointProgram { -+ endp_num_xhc, -+ a: u32::from(mult) << 8 - | u32::from(primary_streams) << 10 - | u32::from(linear_stream_array) << 15 - | u32::from(interval) << 16 - | u32::from(max_esit_payload_hi) << 24, -- ); -- input_context.device.endpoints[endp_i].b.write( -- max_error_count << 1 -+ b: max_error_count << 1 - | u32::from(ep_ty) << 3 - | u32::from(host_initiate_disable) << 7 - | u32::from(max_burst_size) << 8 - | u32::from(max_packet_size) << 16, -- ); -- -- input_context.device.endpoints[endp_i] -- .trl -- .write(ring_ptr as u32); -- input_context.device.endpoints[endp_i] -- .trh -- .write((ring_ptr >> 32) as u32); -- -- input_context.device.endpoints[endp_i] -- .c -- .write(u32::from(avg_trb_len) | (u32::from(max_esit_payload_lo) << 16)); -+ trl: ring_ptr as u32, -+ trh: (ring_ptr >> 32) as u32, -+ c: u32::from(avg_trb_len) | (u32::from(max_esit_payload_lo) << 16), -+ }); - -- log::debug!("initialized endpoint {}", endp_num); -+ log::debug!("staged endpoint {}", endp_num); - } - -- { -+ let (configure_snapshot, endpoint_snapshots, input_context_physical) = { - let port_state = self.port_states.get(&port).ok_or(Error::new(EBADFD))?; -- let slot = port_state.slot; -- let input_context_physical = port_state.input_context.lock().unwrap().physical(); -+ let mut input_context = port_state.input_context.lock().unwrap(); -+ -+ let configure_snapshot = ConfigureContextSnapshot { -+ add_context: input_context.add_context.read(), -+ drop_context: input_context.drop_context.read(), -+ control: input_context.control.read(), -+ slot_a: input_context.device.slot.a.read(), -+ slot_b: input_context.device.slot.b.read(), -+ }; - -- let (event_trb, command_trb) = self -- .execute_command(|trb, cycle| { -- trb.configure_endpoint(slot, input_context_physical, cycle) -+ let endpoint_snapshots = endpoint_programs -+ .iter() -+ .map(|program| { -+ let endp_i = program.endp_num_xhc as usize - 1; -+ ( -+ endp_i, -+ EndpointContextSnapshot::capture_values( -+ input_context.device.endpoints[endp_i].a.read(), -+ input_context.device.endpoints[endp_i].b.read(), -+ input_context.device.endpoints[endp_i].trl.read(), -+ input_context.device.endpoints[endp_i].trh.read(), -+ input_context.device.endpoints[endp_i].c.read(), -+ ), -+ ) - }) -- .await; -+ .collect::>(); -+ -+ // Configure the slot context as well, which holds the last index of the endp descs. -+ input_context.add_context.write(1); -+ input_context.drop_context.write(0); - -- //self.event_handler_finished(); -+ const CONTEXT_ENTRIES_MASK: u32 = 0xF800_0000; -+ const CONTEXT_ENTRIES_SHIFT: u8 = 27; -+ -+ const HUB_PORTS_MASK: u32 = 0xFF00_0000; -+ const HUB_PORTS_SHIFT: u8 = 24; -+ -+ let mut current_slot_a = input_context.device.slot.a.read(); -+ let mut current_slot_b = input_context.device.slot.b.read(); -+ -+ current_slot_a &= !CONTEXT_ENTRIES_MASK; -+ current_slot_a |= -+ (u32::from(new_context_entries) << CONTEXT_ENTRIES_SHIFT) & CONTEXT_ENTRIES_MASK; -+ -+ current_slot_a &= !(1 << 26); -+ current_slot_b &= !HUB_PORTS_MASK; -+ if let Some(hub_ports) = req.hub_ports { -+ current_slot_a |= 1 << 26; -+ current_slot_b |= (u32::from(hub_ports) << HUB_PORTS_SHIFT) & HUB_PORTS_MASK; -+ } -+ -+ input_context.device.slot.a.write(current_slot_a); -+ input_context.device.slot.b.write(current_slot_b); -+ -+ let control = if self.op.lock().unwrap().cie() { -+ (u32::from(req.alternate_setting.unwrap_or(0)) << 16) -+ | (u32::from(req.interface_desc.unwrap_or(0)) << 8) -+ | u32::from(configuration_value) -+ } else { -+ 0 -+ }; -+ input_context.control.write(control); -+ -+ for program in &endpoint_programs { -+ let endp_i = program.endp_num_xhc as usize - 1; -+ input_context.add_context.writef(1 << program.endp_num_xhc, true); -+ input_context.device.endpoints[endp_i].a.write(program.a); -+ input_context.device.endpoints[endp_i].b.write(program.b); -+ input_context.device.endpoints[endp_i].trl.write(program.trl); -+ input_context.device.endpoints[endp_i].trh.write(program.trh); -+ input_context.device.endpoints[endp_i].c.write(program.c); -+ } -+ -+ (configure_snapshot, endpoint_snapshots, input_context.physical()) -+ }; -+ -+ let slot = self.port_states.get(&port).ok_or(Error::new(EBADFD))?.slot; -+ -+ let (event_trb, command_trb) = self -+ .execute_command(|trb, cycle| trb.configure_endpoint(slot, input_context_physical, cycle)) -+ .await; -+ -+ if let Err(err) = handle_event_trb("CONFIGURE_ENDPOINT", &event_trb, &command_trb) { -+ self.rollback_configure_attempt( -+ port, -+ slot, -+ configure_snapshot, -+ &endpoint_snapshots, -+ "CONFIGURE_ENDPOINT failure", -+ ) -+ .await; -+ return Err(err); -+ } -+ -+ if self.consume_test_hook("fail_after_configure_endpoint") { -+ info!( -+ "xhcid: test hook injecting failure after CONFIGURE_ENDPOINT for port {}", -+ port -+ ); -+ self.rollback_configure_attempt( -+ port, -+ slot, -+ configure_snapshot, -+ &endpoint_snapshots, -+ "test hook fail_after_configure_endpoint", -+ ) -+ .await; -+ return Err(Error::new(EIO)); -+ } -+ -+ if let Err(err) = self.set_configuration(port, configuration_value).await { -+ self.rollback_configure_attempt( -+ port, -+ slot, -+ configure_snapshot, -+ &endpoint_snapshots, -+ "set_configuration failure", -+ ) -+ .await; -+ return Err(err); -+ } - -- handle_event_trb("CONFIGURE_ENDPOINT", &event_trb, &command_trb)?; -+ if self.consume_test_hook("fail_after_set_configuration") { -+ info!( -+ "xhcid: test hook injecting failure after SET_CONFIGURATION for port {}", -+ port -+ ); -+ self.rollback_configure_attempt( -+ port, -+ slot, -+ configure_snapshot, -+ &endpoint_snapshots, -+ "test hook fail_after_set_configuration", -+ ) -+ .await; -+ return Err(Error::new(EIO)); - } - -- // Tell the device about this configuration. -- self.set_configuration(port, configuration_value).await?; -+ { -+ let mut port_state = self.port_states.get_mut(&port).ok_or(Error::new(EBADFD))?; -+ port_state.cfg_idx = Some(configuration_value); -+ port_state.endpoint_states.retain(|endp_num, _| *endp_num == 0); -+ for (endp_num, endpoint_state) in staged_endpoint_states { -+ port_state.endpoint_states.insert(endp_num, endpoint_state); -+ } -+ if let Some(iface_num) = req.interface_desc { -+ let alt = req.alternate_setting.unwrap_or(0); -+ port_state.active_ifaces.insert(iface_num, alt); -+ } else if port_state.active_ifaces.is_empty() { -+ let default_iface_entries: Vec<(u8, u8)> = port_state -+ .dev_desc -+ .as_ref() -+ .and_then(|dd| dd.config_descs.iter().find(|cd| cd.configuration_value == configuration_value)) -+ .map(|cd| { -+ cd.interface_descs -+ .iter() -+ .filter(|if_desc| if_desc.alternate_setting == 0) -+ .map(|if_desc| (if_desc.number, 0u8)) -+ .collect() -+ }) -+ .unwrap_or_default(); -+ for (iface_num, alt) in default_iface_entries { -+ port_state.active_ifaces.insert(iface_num, alt); -+ } -+ } -+ } - - Ok(()) - } -@@ -1857,7 +2102,7 @@ impl Xhci { - if (flags & O_DIRECTORY != 0) || (flags & O_STAT != 0) { - let mut contents = Vec::new(); - -- write!(contents, "descriptors\nendpoints\n").unwrap(); -+ write!(contents, "descriptors\nendpoints\npm_state\nsuspend\nresume\n").unwrap(); - - if self.slot_state( - self.port_states -@@ -1894,6 +2139,14 @@ impl Xhci { - Ok(Handle::PortState(port_num)) - } - -+ fn open_handle_port_pm_state(&self, port_num: PortId, flags: usize) -> Result { -+ if flags & O_DIRECTORY != 0 && flags & O_STAT == 0 { -+ return Err(Error::new(ENOTDIR)); -+ } -+ -+ Ok(Handle::PortPmState(port_num)) -+ } -+ - /// implements open() for /port/endpoints - /// - /// # Arguments -@@ -2088,6 +2341,30 @@ impl Xhci { - Ok(Handle::DetachDevice(port_num)) - } - -+ fn open_handle_suspend_device(&self, port_num: PortId, flags: usize) -> Result { -+ if flags & O_DIRECTORY != 0 && flags & O_STAT == 0 { -+ return Err(Error::new(ENOTDIR)); -+ } -+ -+ if flags & O_RDWR != O_WRONLY && flags & O_STAT == 0 { -+ return Err(Error::new(EACCES)); -+ } -+ -+ Ok(Handle::SuspendDevice(port_num)) -+ } -+ -+ fn open_handle_resume_device(&self, port_num: PortId, flags: usize) -> Result { -+ if flags & O_DIRECTORY != 0 && flags & O_STAT == 0 { -+ return Err(Error::new(ENOTDIR)); -+ } -+ -+ if flags & O_RDWR != O_WRONLY && flags & O_STAT == 0 { -+ return Err(Error::new(EACCES)); -+ } -+ -+ Ok(Handle::ResumeDevice(port_num)) -+ } -+ - /// implements open() for /port/request - /// - /// # Arguments -@@ -2156,6 +2433,9 @@ impl SchemeSync for &Xhci { - SchemeParameters::PortState(port_number) => { - self.open_handle_port_state(port_number, flags)? - } -+ SchemeParameters::PortPmState(port_number) => { -+ self.open_handle_port_pm_state(port_number, flags)? -+ } - SchemeParameters::PortReq(port_number) => { - self.open_handle_port_request(port_number, flags)? - } -@@ -2174,6 +2454,12 @@ impl SchemeSync for &Xhci { - SchemeParameters::DetachDevice(port_number) => { - self.open_handle_detach_device(port_number, flags)? - } -+ SchemeParameters::SuspendDevice(port_number) => { -+ self.open_handle_suspend_device(port_number, flags)? -+ } -+ SchemeParameters::ResumeDevice(port_number) => { -+ self.open_handle_resume_device(port_number, flags)? -+ } - }; - - let fd = self.next_handle.fetch_add(1, atomic::Ordering::Relaxed); -@@ -2204,7 +2490,11 @@ impl SchemeSync for &Xhci { - - //If we have a handle to the configure scheme, we need to mark it as write only. - match &*guard { -- Handle::ConfigureEndpoints(_) | Handle::AttachDevice(_) | Handle::DetachDevice(_) => { -+ Handle::ConfigureEndpoints(_) -+ | Handle::AttachDevice(_) -+ | Handle::DetachDevice(_) -+ | Handle::SuspendDevice(_) -+ | Handle::ResumeDevice(_) => { - stat.st_mode = stat.st_mode | 0o200; - } - _ => {} -@@ -2254,6 +2544,8 @@ impl SchemeSync for &Xhci { - Handle::ConfigureEndpoints(_) => Err(Error::new(EBADF)), - Handle::AttachDevice(_) => Err(Error::new(EBADF)), - Handle::DetachDevice(_) => Err(Error::new(EBADF)), -+ Handle::SuspendDevice(_) => Err(Error::new(EBADF)), -+ Handle::ResumeDevice(_) => Err(Error::new(EBADF)), - Handle::SchemeRoot => Err(Error::new(EBADF)), - - &mut Handle::Endpoint(port_num, endp_num, ref mut st) => match st { -@@ -2285,6 +2577,10 @@ impl SchemeSync for &Xhci { - - Ok(Xhci::::write_dyn_string(string, buf, offset)) - } -+ &mut Handle::PortPmState(port_num) => { -+ let ps = self.port_states.get(&port_num).ok_or(Error::new(EBADF))?; -+ Ok(Xhci::::write_dyn_string(ps.pm_state.as_str().as_bytes(), buf, offset)) -+ } - &mut Handle::PortReq(port_num, ref mut st) => { - let state = std::mem::replace(st, PortReqState::Tmp); - drop(guard); // release the lock -@@ -2324,6 +2620,14 @@ impl SchemeSync for &Xhci { - block_on(self.detach_device(port_num))?; - Ok(buf.len()) - } -+ &mut Handle::SuspendDevice(port_num) => { -+ block_on(self.suspend_device(port_num))?; -+ Ok(buf.len()) -+ } -+ &mut Handle::ResumeDevice(port_num) => { -+ block_on(self.resume_device(port_num))?; -+ Ok(buf.len()) -+ } - &mut Handle::Endpoint(port_num, endp_num, ref ep_file_ty) => match ep_file_ty { - EndpointHandleTy::Ctl => block_on(self.on_write_endp_ctl(port_num, endp_num, buf)), - EndpointHandleTy::Data => { -@@ -2348,6 +2652,54 @@ impl SchemeSync for &Xhci { - } - - impl Xhci { -+ fn ensure_port_active(&self, port_num: PortId) -> Result<()> { -+ let port_state = self.port_states.get(&port_num).ok_or(Error::new(EBADFD))?; -+ -+ match port_state.pm_state { -+ super::PortPmState::Active => Ok(()), -+ super::PortPmState::Suspended => { -+ info!( -+ "xhcid: port {} rejected routable operation while suspended", -+ port_num -+ ); -+ Err(Error::new(EBUSY)) -+ } -+ } -+ } -+ -+ pub async fn suspend_device(&self, port_num: PortId) -> Result<()> { -+ let mut port_state = self.port_states.get_mut(&port_num).ok_or(Error::new(EBADFD))?; -+ -+ if port_state.pm_state != super::PortPmState::Active { -+ return Err(Error::new(EBUSY)); -+ } -+ -+ port_state.pm_state = super::PortPmState::Suspended; -+ info!("xhcid: suspended port {}", port_num); -+ Ok(()) -+ } -+ -+ pub async fn resume_device(&self, port_num: PortId) -> Result<()> { -+ let mut port_state = self.port_states.get_mut(&port_num).ok_or(Error::new(EBADFD))?; -+ -+ if port_state.pm_state == super::PortPmState::Active { -+ return Ok(()); -+ } -+ -+ let slot_state = self.slot_state(port_state.slot as usize); -+ if slot_state != SlotState::Addressed as u8 && slot_state != SlotState::Configured as u8 { -+ warn!( -+ "refusing to resume port {} while slot {} is in controller state {}", -+ port_num, port_state.slot, slot_state -+ ); -+ return Err(Error::new(EIO)); -+ } -+ -+ port_state.pm_state = super::PortPmState::Active; -+ info!("xhcid: resumed port {}", port_num); -+ Ok(()) -+ } -+ - pub fn get_endp_status(&self, port_num: PortId, endp_num: u8) -> Result { - let port_state = self.port_states.get(&port_num).ok_or(Error::new(EBADFD))?; - -@@ -2398,6 +2750,8 @@ impl Xhci { - endp_num: u8, - clear_feature: bool, - ) -> Result<()> { -+ self.ensure_port_active(port_num)?; -+ - if self.get_endp_status(port_num, endp_num)? != EndpointStatus::Halted { - return Err(Error::new(EPROTO)); - } -diff --git a/drivers/vboxd/src/main.rs b/drivers/vboxd/src/main.rs -index bcb9bb15..b9e42d4a 100644 ---- a/drivers/vboxd/src/main.rs -+++ b/drivers/vboxd/src/main.rs -@@ -199,16 +199,28 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - let mut name = pci_config.func.name(); - name.push_str("_vbox"); - -- let bar0 = pci_config.func.bars[0].expect_port(); -+ let bar0 = match pci_config.func.bars[0].try_port() { -+ Ok(port) => port, -+ Err(err) => { -+ eprintln!("vboxd: invalid BAR0: {err}"); -+ std::process::exit(1); -+ } -+ }; - - let irq = pci_config - .func - .legacy_interrupt_line -- .expect("vboxd: no legacy interrupts supported"); -+ .unwrap_or_else(|| { -+ eprintln!("vboxd: no legacy interrupts supported"); -+ std::process::exit(1); -+ }); - - println!(" + VirtualBox {}", pci_config.func.display()); - -- common::acquire_port_io_rights().expect("vboxd: failed to get I/O permission"); -+ if let Err(err) = common::acquire_port_io_rights() { -+ eprintln!("vboxd: failed to get I/O permission: {err}"); -+ std::process::exit(1); -+ } - - let mut width = 0; - let mut height = 0; -@@ -233,25 +245,55 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - } - } - -- let mut irq_file = irq.irq_handle("vboxd"); -+ let mut irq_file = match irq.try_irq_handle("vboxd") { -+ Ok(file) => file, -+ Err(err) => { -+ eprintln!("vboxd: failed to open IRQ handle: {err}"); -+ std::process::exit(1); -+ } -+ }; - -- let address = unsafe { pcid_handle.map_bar(1) }.ptr.as_ptr(); -+ let address = match unsafe { pcid_handle.try_map_bar(1) } { -+ Ok(bar) => bar.ptr.as_ptr(), -+ Err(err) => { -+ eprintln!("vboxd: failed to map BAR1: {err}"); -+ std::process::exit(1); -+ } -+ }; - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - { - let mut port = common::io::Pio::::new(bar0 as u16); - - let vmmdev = unsafe { &mut *(address as *mut VboxVmmDev) }; - -- let mut guest_info = VboxGuestInfo::new().expect("vboxd: failed to map GuestInfo"); -+ let mut guest_info = match VboxGuestInfo::new() { -+ Ok(value) => value, -+ Err(err) => { -+ eprintln!("vboxd: failed to map GuestInfo: {err}"); -+ std::process::exit(1); -+ } -+ }; - guest_info.version.write(VBOX_VMMDEV_VERSION); - guest_info.ostype.write(0x100); - port.write(guest_info.physical() as u32); - -- let mut guest_caps = VboxGuestCaps::new().expect("vboxd: failed to map GuestCaps"); -+ let mut guest_caps = match VboxGuestCaps::new() { -+ Ok(value) => value, -+ Err(err) => { -+ eprintln!("vboxd: failed to map GuestCaps: {err}"); -+ std::process::exit(1); -+ } -+ }; - guest_caps.caps.write(1 << 2); - port.write(guest_caps.physical() as u32); - -- let mut set_mouse = VboxSetMouse::new().expect("vboxd: failed to map SetMouse"); -+ let mut set_mouse = match VboxSetMouse::new() { -+ Ok(value) => value, -+ Err(err) => { -+ eprintln!("vboxd: failed to map SetMouse: {err}"); -+ std::process::exit(1); -+ } -+ }; - set_mouse.features.write(1 << 4 | 1); - port.write(set_mouse.physical() as u32); - -@@ -265,34 +307,71 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - } - } - -- let event_queue = -- EventQueue::::new().expect("vboxd: Could not create event queue."); -+ let event_queue = match EventQueue::::new() { -+ Ok(queue) => queue, -+ Err(err) => { -+ eprintln!("vboxd: could not create event queue: {err}"); -+ std::process::exit(1); -+ } -+ }; - event_queue - .subscribe( - irq_file.as_raw_fd() as usize, - Source::Irq, - event::EventFlags::READ, - ) -- .unwrap(); -+ .unwrap_or_else(|err| { -+ eprintln!("vboxd: failed to subscribe IRQ fd: {err}"); -+ std::process::exit(1); -+ }); - - daemon.ready(); - -- libredox::call::setrens(0, 0).expect("vboxd: failed to enter null namespace"); -+ if let Err(err) = libredox::call::setrens(0, 0) { -+ eprintln!("vboxd: failed to enter null namespace: {err}"); -+ std::process::exit(1); -+ } - - let mut bga = crate::bga::Bga::new(); -- let get_mouse = VboxGetMouse::new().expect("vboxd: failed to map GetMouse"); -- let display_change = VboxDisplayChange::new().expect("vboxd: failed to map DisplayChange"); -- let ack_events = VboxAckEvents::new().expect("vboxd: failed to map AckEvents"); -+ let get_mouse = match VboxGetMouse::new() { -+ Ok(value) => value, -+ Err(err) => { -+ eprintln!("vboxd: failed to map GetMouse: {err}"); -+ std::process::exit(1); -+ } -+ }; -+ let display_change = match VboxDisplayChange::new() { -+ Ok(value) => value, -+ Err(err) => { -+ eprintln!("vboxd: failed to map DisplayChange: {err}"); -+ std::process::exit(1); -+ } -+ }; -+ let ack_events = match VboxAckEvents::new() { -+ Ok(value) => value, -+ Err(err) => { -+ eprintln!("vboxd: failed to map AckEvents: {err}"); -+ std::process::exit(1); -+ } -+ }; - -- for Source::Irq in iter::once(Source::Irq) -- .chain(event_queue.map(|e| e.expect("vboxd: failed to get next event").user_data)) -- { -+ for Source::Irq in iter::once(Source::Irq).chain(event_queue.map(|e| match e { -+ Ok(event) => event.user_data, -+ Err(err) => { -+ eprintln!("vboxd: failed to get next event: {err}"); -+ std::process::exit(1); -+ } -+ })) { - let mut irq = [0; 8]; -- if irq_file.read(&mut irq).unwrap() >= irq.len() { -+ match irq_file.read(&mut irq) { -+ Ok(read) if read >= irq.len() => { - let host_events = vmmdev.host_events.read(); - if host_events != 0 { - port.write(ack_events.physical() as u32); -- irq_file.write(&irq).unwrap(); -+ if let Err(err) = irq_file.write(&irq) { -+ eprintln!("vboxd: failed to acknowledge IRQ: {err}"); -+ std::process::exit(1); -+ } - - if host_events & VBOX_EVENT_DISPLAY == VBOX_EVENT_DISPLAY { - port.write(display_change.physical() as u32); -@@ -326,8 +405,14 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - } - } - } -+ Ok(_) => {} -+ Err(err) => { -+ eprintln!("vboxd: failed to read IRQ file: {err}"); -+ std::process::exit(1); -+ } -+ } - } - } - -- std::process::exit(0); -+ std::process::exit(1); - } -diff --git a/drivers/virtio-core/src/arch/x86.rs b/drivers/virtio-core/src/arch/x86.rs -index aea86c4a..c5b2767f 100644 ---- a/drivers/virtio-core/src/arch/x86.rs -+++ b/drivers/virtio-core/src/arch/x86.rs -@@ -11,7 +11,10 @@ pub fn enable_msix(pcid_handle: &mut PciFunctionHandle) -> Result { - // Extended message signaled interrupts. - let msix_info = match pcid_handle.feature_info(PciFeature::MsiX) { - PciFeatureInfo::MsiX(capability) => capability, -- _ => unreachable!(), -+ _ => { -+ log::warn!("virtio_core::enable_msix: expected MSI-X feature info"); -+ return Err(Error::Probe("unexpected PCI feature info for MSI-X")); -+ } - }; - let mut info = unsafe { msix_info.map_and_mask_all(pcid_handle) }; - -@@ -21,7 +24,10 @@ pub fn enable_msix(pcid_handle: &mut PciFunctionHandle) -> Result { - let interrupt_handle = { - let table_entry_pointer = info.table_entry_pointer(MSIX_PRIMARY_VECTOR as usize); - -- let destination_id = read_bsp_apic_id().expect("virtio_core: `read_bsp_apic_id()` failed"); -+ let destination_id = read_bsp_apic_id().map_err(|e| { -+ log::warn!("virtio_core::enable_msix: read_bsp_apic_id failed: {e}"); -+ Error::Probe("read_bsp_apic_id failed") -+ })?; - let (msg_addr_and_data, interrupt_handle) = - allocate_single_interrupt_vector_for_msi(destination_id); - table_entry_pointer.write_addr_and_data(msg_addr_and_data); -diff --git a/drivers/virtio-core/src/probe.rs b/drivers/virtio-core/src/probe.rs -index 5631ef67..eaef1b96 100644 ---- a/drivers/virtio-core/src/probe.rs -+++ b/drivers/virtio-core/src/probe.rs -@@ -31,16 +31,16 @@ pub const MSIX_PRIMARY_VECTOR: u16 = 0; - /// before starting the device. - /// * Finally start the device (via [`StandardTransport::run_device`]). At this point, the device - /// is alive. --/// --/// ## Panics --/// This function panics if the device is not a virtio device. - pub fn probe_device(pcid_handle: &mut PciFunctionHandle) -> Result { - let pci_config = pcid_handle.config(); - -- assert_eq!( -- pci_config.func.full_device_id.vendor_id, 6900, -- "virtio_core::probe_device: not a virtio device" -- ); -+ if pci_config.func.full_device_id.vendor_id != 6900 { -+ log::warn!( -+ "virtio_core::probe_device: skipping non-virtio device (vendor ID {:#06x})", -+ pci_config.func.full_device_id.vendor_id -+ ); -+ return Err(Error::Probe("not a virtio device")); -+ } - - let mut common_addr = None; - let mut notify_addr = None; -@@ -55,7 +55,9 @@ pub fn probe_device(pcid_handle: &mut PciFunctionHandle) -> Result continue, - } - -- let (addr, _) = pci_config.func.bars[capability.bar as usize].expect_mem(); -+ let (addr, _) = pci_config.func.bars[capability.bar as usize] -+ .try_mem() -+ .map_err(|_| Error::Probe("BAR is not memory-mapped"))?; - - let address = unsafe { - let addr = addr + capability.offset as usize; -@@ -100,19 +102,23 @@ pub fn probe_device(pcid_handle: &mut PciFunctionHandle) -> Result unreachable!(), -+ _ => continue, - } - } - -- let common_addr = common_addr.expect("virtio common capability missing"); -- let device_addr = device_addr.expect("virtio device capability missing"); -- let (notify_addr, notify_multiplier) = notify_addr.expect("virtio notify capability missing"); -- -- // FIXME this is explicitly allowed by the virtio specification to happen -- assert!( -- notify_multiplier != 0, -- "virtio-core::device_probe: device uses the same Queue Notify addresses for all queues" -- ); -+ let common_addr = common_addr.ok_or(Error::InCapable(CfgType::Common))?; -+ let device_addr = device_addr.ok_or(Error::InCapable(CfgType::Device))?; -+ let (notify_addr, notify_multiplier) = -+ notify_addr.ok_or(Error::InCapable(CfgType::Notify))?; -+ -+ // The virtio specification explicitly allows a zero notify_off_multiplier, -+ // meaning all queues share the same notification address. Handle gracefully. -+ if notify_multiplier == 0 { -+ log::warn!( -+ "virtio_core::probe_device: device uses the same Queue Notify address for all queues" -+ ); -+ return Err(Error::Probe("zero notify_off_multiplier")); -+ } - - let common = unsafe { &mut *(common_addr as *mut CommonCfg) }; - let device_space = unsafe { &mut *(device_addr as *mut u8) }; -@@ -128,8 +134,10 @@ pub fn probe_device(pcid_handle: &mut PciFunctionHandle) -> Result Vec { -- let last_buffer = self.buffers.last_mut().expect("virtio-core: empty chain"); -- last_buffer.flags.remove(DescriptorFlags::NEXT); -- -+ if let Some(last_buffer) = self.buffers.last_mut() { -+ last_buffer.flags.remove(DescriptorFlags::NEXT); -+ } - self.buffers - } - } -diff --git a/drivers/virtio-core/src/transport.rs b/drivers/virtio-core/src/transport.rs -index d3445d2d..99972c95 100644 ---- a/drivers/virtio-core/src/transport.rs -+++ b/drivers/virtio-core/src/transport.rs -@@ -19,6 +19,8 @@ pub enum Error { - SyscallError(#[from] libredox::error::Error), - #[error("the device is incapable of {0:?}")] - InCapable(CfgType), -+ #[error("virtio probe: {0}")] -+ Probe(&'static str), - } - - /// Returns the queue part sizes in bytes. -@@ -59,14 +61,23 @@ pub fn spawn_irq_thread(irq_handle: &File, queue: &Arc>) { - let queue_copy = queue.clone(); - - std::thread::spawn(move || { -- let event_queue = RawEventQueue::new().unwrap(); -+ let event_queue = match RawEventQueue::new() { -+ Ok(eq) => eq, -+ Err(err) => { -+ log::error!("virtio-core: failed to create event queue for IRQ thread: {err}"); -+ return; -+ } -+ }; - -- event_queue -- .subscribe(irq_fd as usize, 0, event::EventFlags::READ) -- .unwrap(); -+ if let Err(err) = event_queue.subscribe(irq_fd as usize, 0, event::EventFlags::READ) { -+ log::error!("virtio-core: failed to subscribe to IRQ fd: {err}"); -+ return; -+ } - -- for _ in event_queue.map(Result::unwrap) { -- // Wake up the tasks waiting on the queue. -+ for event_result in event_queue.map(|res| res) { -+ if event_result.is_err() { -+ break; -+ } - for (_, task) in queue_copy.waker.lock().unwrap().iter() { - task.wake_by_ref(); - } -@@ -604,7 +615,9 @@ impl Transport for StandardTransport<'_> { - // Re-read device status to ensure the `FEATURES_OK` bit is still set: otherwise, - // the device does not support our subset of features and the device is unusable. - let confirm = common.device_status.get(); -- assert!((confirm & DeviceStatusFlags::FEATURES_OK) == DeviceStatusFlags::FEATURES_OK); -+ if (confirm & DeviceStatusFlags::FEATURES_OK) != DeviceStatusFlags::FEATURES_OK { -+ log::error!("virtio-core: device rejected feature set (FEATURES_OK cleared after negotiation)"); -+ } - } - - fn setup_config_notify(&self, vector: u16) { -@@ -640,7 +653,10 @@ impl Transport for StandardTransport<'_> { - - // Set the MSI-X vector. - common.queue_msix_vector.set(vector); -- assert!(common.queue_msix_vector.get() == vector); -+ if common.queue_msix_vector.get() != vector { -+ log::error!("virtio-core: MSI-X vector {vector:#x} was not accepted by device for queue {queue_index}"); -+ return Err(Error::SyscallError(libredox::error::Error::new(libredox::errno::EIO))); -+ } - - // Enable the queue. - common.queue_enable.set(1); -@@ -685,7 +701,9 @@ impl Transport for StandardTransport<'_> { - - // Set the MSI-X vector. - common.queue_msix_vector.set(queue.vector); -- assert!(common.queue_msix_vector.get() == queue.vector); -+ if common.queue_msix_vector.get() != queue.vector { -+ log::error!("virtio-core: MSI-X vector {:#x} was not accepted during reinit for queue {}", queue.vector, queue.queue_index); -+ } - - // Enable the queue. - common.queue_enable.set(1); -diff --git a/init.initfs.d/40_drivers.target b/init.initfs.d/40_drivers.target -index 8ddb4795..029583a1 100644 ---- a/init.initfs.d/40_drivers.target -+++ b/init.initfs.d/40_drivers.target -@@ -7,4 +7,5 @@ requires_weak = [ - "40_bcm2835-sdhcid.service", - "40_hwd.service", - "40_pcid-spawner-initfs.service", -+ "41_acpid.service", - ] -diff --git a/init.initfs.d/40_hwd.service b/init.initfs.d/40_hwd.service -index cba12dde..cf34a51b 100644 ---- a/init.initfs.d/40_hwd.service -+++ b/init.initfs.d/40_hwd.service -@@ -1,6 +1,6 @@ - [unit] - description = "Hardware manager" --requires_weak = ["10_inputd.service", "10_lived.service", "20_graphics.target"] -+requires_weak = ["10_inputd.service", "10_lived.service", "20_graphics.target", "41_acpid.service"] - - [service] - cmd = "hwd" -diff --git a/init.initfs.d/40_pcid-spawner-initfs.service b/init.initfs.d/40_pcid-spawner-initfs.service -index 6945b9ea..ba1ee0bb 100644 ---- a/init.initfs.d/40_pcid-spawner-initfs.service -+++ b/init.initfs.d/40_pcid-spawner-initfs.service -@@ -1,6 +1,6 @@ - [unit] - description = "PCI driver spawner" --requires_weak = ["10_inputd.service", "20_graphics.target", "40_hwd.service"] -+requires_weak = ["10_inputd.service", "20_graphics.target", "40_hwd.service", "41_acpid.service"] - - [service] - cmd = "pcid-spawner" -diff --git a/init/src/main.rs b/init/src/main.rs -index 5682cf44..ed436619 100644 ---- a/init/src/main.rs -+++ b/init/src/main.rs -@@ -117,6 +117,8 @@ fn main() { - let mut unit_store = UnitStore::new(); - let mut scheduler = Scheduler::new(); - -+ eprintln!("init: phase 1 — initfs boot"); -+ - switch_root( - &mut unit_store, - &mut init_config, -@@ -125,6 +127,7 @@ fn main() { - ); - - // Start logd first such that we can pass /scheme/log as stdio to all other services -+ eprintln!("init: starting logd"); - scheduler - .schedule_start_and_report_errors(&mut unit_store, UnitId("00_logd.service".to_owned())); - scheduler.step(&mut unit_store, &mut init_config); -@@ -132,14 +135,18 @@ fn main() { - eprintln!("init: failed to switch stdio to '/scheme/log': {err}"); - } - -+ eprintln!("init: starting runtime target"); - let runtime_target = UnitId("00_runtime.target".to_owned()); - scheduler.schedule_start_and_report_errors(&mut unit_store, runtime_target.clone()); - unit_store.set_runtime_target(runtime_target); - -+ eprintln!("init: starting initfs drivers target"); - scheduler - .schedule_start_and_report_errors(&mut unit_store, UnitId("90_initfs.target".to_owned())); - scheduler.step(&mut unit_store, &mut init_config); -+ eprintln!("init: initfs drivers target step() complete"); - -+ eprintln!("init: phase 2 — switchroot to /usr"); - switch_root( - &mut unit_store, - &mut init_config, -@@ -162,23 +169,64 @@ fn main() { - .collect::>() - .join(", ") - ); -- return; -+ Vec::new() - } - }; -+ eprintln!("init: scheduling {} rootfs units", entries.len()); - for entry in entries { -+ let name = match entry.file_name().and_then(|n| n.to_str()) { -+ Some(name) => name, -+ None => { -+ eprintln!("init: skipping config entry with non-UTF-8 filename"); -+ continue; -+ } -+ }; - scheduler.schedule_start_and_report_errors( - &mut unit_store, -- UnitId(entry.file_name().unwrap().to_str().unwrap().to_owned()), -+ UnitId(name.to_owned()), - ); - } - }; - - scheduler.step(&mut unit_store, &mut init_config); -+ eprintln!("init: phase 3 — rootfs services started"); -+ -+ if let Err(err) = libredox::call::setrens(0, 0) { -+ eprintln!("init: failed to enter null namespace: {err}"); -+ } - -- libredox::call::setrens(0, 0).expect("init: failed to enter null namespace"); -+ eprintln!("init: boot complete — entering waitpid loop"); -+ -+ let mut respawn_map: BTreeMap = BTreeMap::new(); -+ for (unit_id, pid) in scheduler.respawn_pids { -+ respawn_map.insert(pid, unit_id); -+ } - - loop { - let mut status = 0; -- libredox::call::waitpid(0, &mut status, 0).unwrap(); -+ match libredox::call::waitpid(0, &mut status, 0) { -+ Ok(pid) => { -+ if let Some(unit_id) = respawn_map.remove(&(pid as u32)) { -+ eprintln!("init: respawning {} (pid {} exited)", unit_id.0, pid); -+ let mut resp_scheduler = Scheduler::new(); -+ resp_scheduler.schedule_start_and_report_errors( -+ &mut unit_store, -+ unit_id.clone(), -+ ); -+ resp_scheduler.step(&mut unit_store, &mut init_config); -+ for (uid, new_pid) in resp_scheduler.respawn_pids { -+ respawn_map.insert(new_pid, uid); -+ } -+ } -+ } -+ Err(err) => { -+ // EAGAIN is normal (no child exited yet). Other errors are -+ // unexpected but init must never crash — log and continue. -+ if err.errno() != syscall::EAGAIN { -+ eprintln!("init: waitpid error: {err}"); -+ } -+ std::thread::sleep(std::time::Duration::from_millis(100)); -+ } -+ } - } - } -diff --git a/init/src/scheduler.rs b/init/src/scheduler.rs -index d42a4e57..64e64e1e 100644 ---- a/init/src/scheduler.rs -+++ b/init/src/scheduler.rs -@@ -5,6 +5,7 @@ use crate::unit::{Unit, UnitId, UnitKind, UnitStore}; - - pub struct Scheduler { - pending: VecDeque, -+ pub respawn_pids: Vec<(UnitId, u32)>, - } - - struct Job { -@@ -20,6 +21,7 @@ impl Scheduler { - pub fn new() -> Scheduler { - Scheduler { - pending: VecDeque::new(), -+ respawn_pids: Vec::new(), - } - } - -@@ -43,7 +45,10 @@ impl Scheduler { - ) { - let loaded_units = unit_store.load_units(unit_id.clone(), errors); - for unit_id in loaded_units { -- if !unit_store.unit(&unit_id).conditions_met() { -+ let Some(unit) = unit_store.unit(&unit_id) else { -+ continue; -+ }; -+ if !unit.conditions_met() { - continue; - } - -@@ -62,7 +67,10 @@ impl Scheduler { - - match job.kind { - JobKind::Start => { -- let unit = unit_store.unit_mut(&job.unit); -+ let Some(unit) = unit_store.unit_mut(&job.unit) else { -+ eprintln!("init: unit {} not found in store, skipping", job.unit.0); -+ continue 'a; -+ }; - - for dep in &unit.info.requires_weak { - for pending_job in &self.pending { -@@ -73,14 +81,17 @@ impl Scheduler { - } - } - -- run(unit, init_config); -+ let pid = run(unit, init_config); -+ if let Some(pid) = pid { -+ self.respawn_pids.push((job.unit.clone(), pid)); -+ } - } - } - } - } - } - --fn run(unit: &mut Unit, config: &mut InitConfig) { -+fn run(unit: &mut Unit, config: &mut InitConfig) -> Option { - match &unit.kind { - UnitKind::LegacyScript { script } => { - for cmd in script.clone() { -@@ -92,25 +103,30 @@ fn run(unit: &mut Unit, config: &mut InitConfig) { - } - UnitKind::Service { service } => { - if config.skip_cmd.contains(&service.cmd) { -- eprintln!("Skipping '{} {}'", service.cmd, service.args.join(" ")); -- return; -+ eprintln!("init: skipping {} {}", service.cmd, service.args.join(" ")); -+ return None; - } -- if config.log_debug { -+ eprintln!( -+ "init: starting {} ({})", -+ unit.info.description.as_ref().unwrap_or(&unit.id.0), -+ service.cmd, -+ ); -+ let pid = service.spawn(&config.envs); -+ if pid.is_some() { - eprintln!( -- "Starting {} ({})", -+ "init: started {} (pid {})", - unit.info.description.as_ref().unwrap_or(&unit.id.0), -- service.cmd, -+ pid.unwrap_or(0), - ); - } -- service.spawn(&config.envs); -+ return pid; - } - UnitKind::Target {} => { -- if config.log_debug { -- eprintln!( -- "Reached target {}", -- unit.info.description.as_ref().unwrap_or(&unit.id.0), -- ); -- } -+ eprintln!( -+ "init: reached target {}", -+ unit.info.description.as_ref().unwrap_or(&unit.id.0), -+ ); - } - } -+ None - } -diff --git a/init/src/service.rs b/init/src/service.rs -index ed0023e9..cc95d02b 100644 ---- a/init/src/service.rs -+++ b/init/src/service.rs -@@ -22,6 +22,8 @@ pub struct Service { - pub inherit_envs: BTreeSet, - #[serde(rename = "type")] - pub type_: ServiceType, -+ #[serde(default)] -+ pub respawn: bool, - } - - #[derive(Clone, Debug, Default, Deserialize)] -@@ -35,7 +37,7 @@ pub enum ServiceType { - } - - impl Service { -- pub fn spawn(&self, base_envs: &BTreeMap) { -+ pub fn spawn(&self, base_envs: &BTreeMap) -> Option { - let mut command = Command::new(&self.cmd); - command.args(self.args.iter().map(|arg| subst_env(arg))); - command.env_clear(); -@@ -46,20 +48,28 @@ impl Service { - } - command.envs(base_envs).envs(&self.envs); - -- let (mut read_pipe, write_pipe) = io::pipe().unwrap(); -+ let (mut read_pipe, write_pipe) = match io::pipe() { -+ Ok(pair) => pair, -+ Err(err) => { -+ eprintln!("init: failed to create readiness pipe for {:?}: {err}", command); -+ return None; -+ } -+ }; - unsafe { pass_fd(&mut command, "INIT_NOTIFY", write_pipe.into()) }; - - let mut child = match command.spawn() { - Ok(child) => child, - Err(err) => { - eprintln!("init: failed to execute {:?}: {}", command, err); -- return; -+ return None; - } - }; - - match &self.type_ { - ServiceType::Notify => match read_pipe.read_exact(&mut [0]) { -- Ok(()) => {} -+ Ok(()) => { -+ eprintln!("init: {} ready (notify)", self.cmd); -+ } - Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => { - eprintln!("init: {command:?} exited without notifying readiness"); - } -@@ -81,23 +91,34 @@ impl Service { - }) => continue, - Ok(0) => { - eprintln!("init: {command:?} exited without notifying readiness"); -- return; -+ return None; - } - Ok(1) => break, - Ok(n) => { - eprintln!("init: incorrect amount of fds {n} returned"); -- return; -+ return None; - } - Err(err) => { - eprintln!("init: failed to wait for {command:?}: {err}"); -- return; -+ return None; - } - } - } - -- let current_namespace_fd = libredox::call::getns().expect("TODO"); -- libredox::call::register_scheme_to_ns(current_namespace_fd, scheme, new_fd) -- .expect("TODO"); -+ let current_namespace_fd = match libredox::call::getns() { -+ Ok(fd) => fd, -+ Err(err) => { -+ eprintln!("init: failed to get current namespace for {command:?}: {err}"); -+ return None; -+ } -+ }; -+ if let Err(err) = -+ libredox::call::register_scheme_to_ns(current_namespace_fd, scheme, new_fd) -+ { -+ eprintln!("init: failed to register scheme {scheme:?} for {command:?}: {err}"); -+ } else { -+ eprintln!("init: {} ready (scheme {})", self.cmd, scheme); -+ } - } - ServiceType::Oneshot => { - drop(read_pipe); -@@ -105,6 +126,8 @@ impl Service { - Ok(exit_status) => { - if !exit_status.success() { - eprintln!("init: {command:?} failed with {exit_status}"); -+ } else { -+ eprintln!("init: {} done (oneshot)", self.cmd); - } - } - Err(err) => { -@@ -112,8 +135,13 @@ impl Service { - } - } - } -- ServiceType::OneshotAsync => {} -+ ServiceType::OneshotAsync => { -+ if self.respawn { -+ return Some(child.id()); -+ } -+ } - } -+ None - } - } - -diff --git a/init/src/unit.rs b/init/src/unit.rs -index 98053cb2..a58bfb96 100644 ---- a/init/src/unit.rs -+++ b/init/src/unit.rs -@@ -23,8 +23,14 @@ impl UnitStore { - } - - pub fn set_runtime_target(&mut self, unit_id: UnitId) { -- assert!(self.runtime_target.is_none()); -- assert!(self.units.contains_key(&unit_id)); -+ if self.runtime_target.is_some() { -+ eprintln!("init: runtime target already set, ignoring {}", unit_id.0); -+ return; -+ } -+ if !self.units.contains_key(&unit_id) { -+ eprintln!("init: runtime target {} not found in unit store", unit_id.0); -+ return; -+ } - self.runtime_target = Some(unit_id); - } - -@@ -85,8 +91,10 @@ impl UnitStore { - let unit = self.load_single_unit(unit_id, errors); - if let Some(unit) = unit { - loaded_units.push(unit.clone()); -- for dep in &self.unit(&unit).info.requires_weak { -- pending_units.push(dep.clone()); -+ if let Some(u) = self.unit(&unit) { -+ for dep in &u.info.requires_weak { -+ pending_units.push(dep.clone()); -+ } - } - } - } -@@ -94,12 +102,12 @@ impl UnitStore { - loaded_units - } - -- pub fn unit(&self, unit: &UnitId) -> &Unit { -- self.units.get(unit).unwrap() -+ pub fn unit(&self, unit: &UnitId) -> Option<&Unit> { -+ self.units.get(unit) - } - -- pub fn unit_mut(&mut self, unit: &UnitId) -> &mut Unit { -- self.units.get_mut(unit).unwrap() -+ pub fn unit_mut(&mut self, unit: &UnitId) -> Option<&mut Unit> { -+ self.units.get_mut(unit) - } - } - -@@ -180,7 +188,7 @@ impl Unit { - ) -> io::Result { - let config = fs::read_to_string(config_path)?; - -- let Some(ext) = config_path.extension().map(|ext| ext.to_str().unwrap()) else { -+ let Some(ext) = config_path.extension().and_then(|ext| ext.to_str()) else { - let script = Script::from_str(&config, errors)?; - return Ok(Unit { - id, -diff --git a/logd/src/main.rs b/logd/src/main.rs -index 3636f1fa..559d8993 100644 ---- a/logd/src/main.rs -+++ b/logd/src/main.rs -@@ -6,18 +6,30 @@ use crate::scheme::LogScheme; - mod scheme; - - fn daemon(daemon: daemon::SchemeDaemon) -> ! { -- let socket = Socket::create().expect("logd: failed to create log scheme"); -+ let socket = match Socket::create() { -+ Ok(s) => s, -+ Err(e) => { -+ eprintln!("logd: failed to create log scheme: {e}"); -+ std::process::exit(1); -+ } -+ }; - - let mut scheme = LogScheme::new(&socket); - let handler = Blocking::new(&socket, 16); - - let _ = daemon.ready_sync_scheme(&socket, &mut scheme); - -- libredox::call::setrens(0, 0).expect("logd: failed to enter null namespace"); -- -- handler -- .process_requests_blocking(scheme) -- .expect("logd: failed to process requests"); -+ if let Err(e) = libredox::call::setrens(0, 0) { -+ eprintln!("logd: failed to enter null namespace: {e}"); -+ } -+ -+ match handler.process_requests_blocking(scheme) { -+ Ok(never) => match never {}, -+ Err(e) => { -+ eprintln!("logd: failed to process requests: {e}"); -+ std::process::exit(1); -+ } -+ } - } - - fn main() { -diff --git a/logd/src/scheme.rs b/logd/src/scheme.rs -index 070de3d6..ef3e175c 100644 ---- a/logd/src/scheme.rs -+++ b/logd/src/scheme.rs -@@ -22,7 +22,7 @@ pub enum LogHandle { - - pub struct LogScheme<'sock> { - socket: &'sock Socket, -- kernel_debug: File, -+ kernel_debug: Option, - output_tx: Sender, - handles: HandleMap, - } -@@ -34,12 +34,24 @@ enum OutputCmd { - - impl<'sock> LogScheme<'sock> { - pub fn new(socket: &'sock Socket) -> Self { -- let kernel_debug = OpenOptions::new() -+ let kernel_debug = match OpenOptions::new() - .write(true) - .open("/scheme/debug") -- .unwrap(); -+ { -+ Ok(f) => Some(f), -+ Err(e) => { -+ eprintln!("logd: failed to open /scheme/debug: {e}"); -+ None -+ } -+ }; - -- let mut kernel_sys_log = std::fs::File::open("/scheme/sys/log").unwrap(); -+ let kernel_sys_log = match std::fs::File::open("/scheme/sys/log") { -+ Ok(f) => Some(f), -+ Err(e) => { -+ eprintln!("logd: failed to open /scheme/sys/log: {e}"); -+ None -+ } -+ }; - - let (output_tx, output_rx) = mpsc::channel::(); - -@@ -72,20 +84,28 @@ impl<'sock> LogScheme<'sock> { - } - }); - -- let output_tx2 = output_tx.clone(); -- std::thread::spawn(move || { -- let mut handle_buf = vec![]; -- let mut buf = [0; 4096]; -- buf[.."kernel: ".len()].copy_from_slice(b"kernel: "); -- loop { -- let n = kernel_sys_log.read(&mut buf["kernel: ".len()..]).unwrap(); -- if n == 0 { -- // FIXME currently possible as /scheme/log/kernel presents a snapshot of the log queue -- break; -+ if let Some(mut kernel_sys_log) = kernel_sys_log { -+ let output_tx2 = output_tx.clone(); -+ std::thread::spawn(move || { -+ let mut handle_buf = vec![]; -+ let mut buf = [0; 4096]; -+ buf[.."kernel: ".len()].copy_from_slice(b"kernel: "); -+ loop { -+ let n = match kernel_sys_log.read(&mut buf["kernel: ".len()..]) { -+ Ok(n) => n, -+ Err(e) => { -+ eprintln!("logd: error reading kernel log: {e}"); -+ break; -+ } -+ }; -+ if n == 0 { -+ // FIXME currently possible as /scheme/log/kernel presents a snapshot of the log queue -+ break; -+ } -+ Self::write_logs(&output_tx2, &mut handle_buf, "kernel", &buf, None); - } -- Self::write_logs(&output_tx2, &mut handle_buf, "kernel", &buf, None); -- } -- }); -+ }); -+ } - - LogScheme { - socket, -@@ -120,9 +140,9 @@ impl<'sock> LogScheme<'sock> { - let _ = kernel_debug.flush(); - } - -- output_tx -- .send(OutputCmd::Log(mem::take(handle_buf))) -- .unwrap(); -+ if let Err(e) = output_tx.send(OutputCmd::Log(mem::take(handle_buf))) { -+ eprintln!("logd: failed to send log output: {e}"); -+ } - } - - i += 1; -@@ -196,7 +216,7 @@ impl<'sock> SchemeSync for LogScheme<'sock> { - handle_buf, - context, - buf, -- Some(&mut self.kernel_debug), -+ self.kernel_debug.as_mut(), - ); - - Ok(buf.len()) -@@ -217,7 +237,10 @@ impl<'sock> SchemeSync for LogScheme<'sock> { - ) { - return Err(e); - } -- self.output_tx.send(OutputCmd::AddSink(new_fd)).unwrap(); -+ if let Err(e) = self.output_tx.send(OutputCmd::AddSink(new_fd)) { -+ eprintln!("logd: failed to add log sink: {e}"); -+ return Err(Error::new(EIO)); -+ } - - Ok(1) - } -diff --git a/randd/src/main.rs b/randd/src/main.rs -index d68dd732..5c330719 100644 ---- a/randd/src/main.rs -+++ b/randd/src/main.rs -@@ -41,7 +41,11 @@ fn create_rdrand_seed() -> [u8; SEED_BYTES] { - let mut have_seeded = false; - #[cfg(target_arch = "x86_64")] - { -- if CpuId::new().get_feature_info().unwrap().has_rdrand() { -+ if CpuId::new() -+ .get_feature_info() -+ .map(|info| info.has_rdrand()) -+ .unwrap_or(false) -+ { - for i in 0..SEED_BYTES / 8 { - // We get 8 bytes at a time from rdrand instruction - let rand: u64; -@@ -81,7 +85,7 @@ fn create_rdrand_seed() -> [u8; SEED_BYTES] { - } - } // TODO integrate alternative entropy sources - if !have_seeded { -- println!("randd: Seeding failed, no entropy source. Random numbers on this platform are NOT SECURE"); -+ eprintln!("randd: no hardware entropy source, random numbers are NOT SECURE"); - } - rng - } -@@ -450,18 +454,32 @@ impl SchemeSync for RandScheme { - } - - fn daemon(daemon: daemon::SchemeDaemon) -> ! { -- let socket = Socket::create().expect("randd: failed to create rand scheme"); -+ let socket = match Socket::create() { -+ Ok(s) => s, -+ Err(e) => { -+ eprintln!("randd: failed to create rand scheme: {e}"); -+ std::process::exit(1); -+ } -+ }; - - let mut scheme = RandScheme::new(); -- let handler = Blocking::new(&socket, 16); - - let _ = daemon.ready_sync_scheme(&socket, &mut scheme); - -- libredox::call::setrens(0, 0).expect("randd: failed to enter null namespace"); -+ if let Err(e) = libredox::call::setrens(0, 0) { -+ eprintln!("randd: failed to enter null namespace: {e}"); -+ } - -- handler -- .process_requests_blocking(scheme) -- .expect("randd: failed to process events from zero scheme"); -+ loop { -+ let handler = Blocking::new(&socket, 16); -+ match handler.process_requests_blocking(scheme) { -+ Ok(never) => never, -+ Err(e) => { -+ eprintln!("randd: error processing requests: {e}"); -+ scheme = RandScheme::new(); -+ } -+ } -+ } - } - - fn main() { -diff --git a/zerod/src/main.rs b/zerod/src/main.rs -index c9bd1465..59f6b97c 100644 ---- a/zerod/src/main.rs -+++ b/zerod/src/main.rs -@@ -5,6 +5,7 @@ use scheme_utils::Blocking; - - mod scheme; - -+#[derive(Clone, Copy)] - enum Ty { - Null, - Zero, -@@ -15,21 +16,36 @@ fn main() { - } - - fn daemon(daemon: daemon::SchemeDaemon) -> ! { -- let ty = match &*std::env::args().nth(1).unwrap() { -- "null" => Ty::Null, -- "zero" => Ty::Zero, -- _ => panic!("needs to be called with either null or zero as argument"), -+ let ty = match std::env::args().nth(1).as_deref() { -+ Some("null") => Ty::Null, -+ Some("zero") | None => Ty::Zero, -+ Some(other) => { -+ eprintln!("zerod: unknown argument '{other}', use 'null' or 'zero'"); -+ std::process::exit(1); -+ } - }; - -- let socket = Socket::create().expect("zerod: failed to create zero scheme"); -+ let socket = match Socket::create() { -+ Ok(s) => s, -+ Err(e) => { -+ eprintln!("zerod: failed to create zero scheme: {e}"); -+ std::process::exit(1); -+ } -+ }; - let mut zero_scheme = ZeroScheme(ty); -- let zero_handler = Blocking::new(&socket, 16); - - let _ = daemon.ready_sync_scheme(&socket, &mut zero_scheme); - -- libredox::call::setrens(0, 0).expect("zerod: failed to enter null namespace"); -- -- zero_handler -- .process_requests_blocking(zero_scheme) -- .expect("zerod: failed to process events from zero scheme"); -+ if let Err(e) = libredox::call::setrens(0, 0) { -+ eprintln!("zerod: failed to enter null namespace: {e}"); -+ } -+ -+ loop { -+ let zero_handler = Blocking::new(&socket, 16); -+ let scheme = ZeroScheme(ty); -+ match zero_handler.process_requests_blocking(scheme) { -+ Ok(never) => never, -+ Err(e) => eprintln!("zerod: error processing requests: {e}"), -+ } -+ } - } diff --git a/recipes/core/base/P0-bootstrap-workspace-fix.patch b/recipes/core/base/P0-bootstrap-workspace-fix.patch deleted file mode 120000 index 5fef2e17..00000000 --- a/recipes/core/base/P0-bootstrap-workspace-fix.patch +++ /dev/null @@ -1 +0,0 @@ -../../../local/patches/base/P0-bootstrap-workspace-fix.patch \ No newline at end of file diff --git a/recipes/core/base/P0-daemon-fix-init-notify-unwrap.patch b/recipes/core/base/P0-daemon-fix-init-notify-unwrap.patch deleted file mode 100644 index e6638562..00000000 --- a/recipes/core/base/P0-daemon-fix-init-notify-unwrap.patch +++ /dev/null @@ -1,43 +0,0 @@ -diff --git a/daemon/src/lib.rs b/daemon/src/lib.rs -index 9f507221..c57d91dc 100644 ---- a/daemon/src/lib.rs -+++ b/daemon/src/lib.rs -@@ -11,12 +11,23 @@ use redox_scheme::Socket; - use redox_scheme::scheme::{SchemeAsync, SchemeSync}; - - unsafe fn get_fd(var: &str) -> RawFd { -- let fd: RawFd = std::env::var(var).unwrap().parse().unwrap(); -+ let fd: RawFd = match std::env::var(var) -+ .map_err(|e| eprintln!("daemon: env var {var} not set: {e}")) -+ .ok() -+ .and_then(|val| { -+ val.parse() -+ .map_err(|e| eprintln!("daemon: failed to parse {var} as fd: {e}")) -+ .ok() -+ }) { -+ Some(fd) => fd, -+ None => return -1, -+ }; - if unsafe { libc::fcntl(fd, libc::F_SETFD, libc::FD_CLOEXEC) } == -1 { -- panic!( -+ eprintln!( - "daemon: failed to set CLOEXEC flag for {var} fd: {}", - io::Error::last_os_error() - ); -+ return -1; - } - fd - } -@@ -51,7 +62,11 @@ impl Daemon { - - /// Notify the process that the daemon is ready to accept requests. - pub fn ready(mut self) { -- self.write_pipe.write_all(&[0]).unwrap(); -+ if let Err(err) = self.write_pipe.write_all(&[0]) { -+ if err.kind() != io::ErrorKind::BrokenPipe { -+ eprintln!("daemon::ready write failed: {err}"); -+ } -+ } - } - - /// Executes `Command` as a child process. diff --git a/recipes/core/base/P0-daemon-fix-init-notify-unwrap.patch b/recipes/core/base/P0-daemon-fix-init-notify-unwrap.patch new file mode 120000 index 00000000..de5db5fa --- /dev/null +++ b/recipes/core/base/P0-daemon-fix-init-notify-unwrap.patch @@ -0,0 +1 @@ +../../../local/patches/base/P0-daemon-fix-init-notify-unwrap.patch \ No newline at end of file diff --git a/recipes/core/base/P0-dhcpd-auto-iface.patch b/recipes/core/base/P0-dhcpd-auto-iface.patch deleted file mode 120000 index 737f2249..00000000 --- a/recipes/core/base/P0-dhcpd-auto-iface.patch +++ /dev/null @@ -1 +0,0 @@ -../../../local/patches/base/P0-dhcpd-auto-iface.patch \ No newline at end of file diff --git a/recipes/core/base/P0-workspace-add-bootstrap.patch b/recipes/core/base/P0-workspace-add-bootstrap.patch deleted file mode 100644 index 907d9459..00000000 --- a/recipes/core/base/P0-workspace-add-bootstrap.patch +++ /dev/null @@ -1,12 +0,0 @@ -diff --git a/Cargo.toml b/Cargo.toml -index 9e776232..acdb1c97 100644 ---- a/Cargo.toml -+++ b/Cargo.toml -@@ -2,6 +2,7 @@ - resolver = "2" - members = [ - "audiod", -+ "bootstrap", - "config", - "daemon", - "dhcpd", diff --git a/recipes/core/base/P0-workspace-add-bootstrap.patch b/recipes/core/base/P0-workspace-add-bootstrap.patch new file mode 120000 index 00000000..7f0441ad --- /dev/null +++ b/recipes/core/base/P0-workspace-add-bootstrap.patch @@ -0,0 +1 @@ +../../../local/patches/base/P0-workspace-add-bootstrap.patch \ No newline at end of file diff --git a/recipes/core/base/P2-ac97d-ihdad-main.patch b/recipes/core/base/P2-ac97d-ihdad-main.patch deleted file mode 120000 index 825cbc52..00000000 --- a/recipes/core/base/P2-ac97d-ihdad-main.patch +++ /dev/null @@ -1 +0,0 @@ -../../../local/patches/base/P2-ac97d-ihdad-main.patch \ No newline at end of file diff --git a/recipes/core/base/P2-acpi-defer-aml.patch b/recipes/core/base/P2-acpi-defer-aml.patch deleted file mode 120000 index bfb92644..00000000 --- a/recipes/core/base/P2-acpi-defer-aml.patch +++ /dev/null @@ -1 +0,0 @@ -../../../local/patches/base/P2-acpi-defer-aml.patch \ No newline at end of file diff --git a/recipes/core/base/P2-acpi-i2c-resources.patch b/recipes/core/base/P2-acpi-i2c-resources.patch deleted file mode 120000 index d5370a7a..00000000 --- a/recipes/core/base/P2-acpi-i2c-resources.patch +++ /dev/null @@ -1 +0,0 @@ -../../../local/patches/base/P2-acpi-i2c-resources.patch \ No newline at end of file diff --git a/recipes/core/base/P2-acpid-core-refactor.patch b/recipes/core/base/P2-acpid-core-refactor.patch deleted file mode 120000 index c35dc42c..00000000 --- a/recipes/core/base/P2-acpid-core-refactor.patch +++ /dev/null @@ -1 +0,0 @@ -../../../local/patches/base/P2-acpid-core-refactor.patch \ No newline at end of file diff --git a/recipes/core/base/P2-boot-logging.patch b/recipes/core/base/P2-boot-logging.patch deleted file mode 120000 index 11f4da26..00000000 --- a/recipes/core/base/P2-boot-logging.patch +++ /dev/null @@ -1 +0,0 @@ -../../../local/patches/base/P2-boot-logging.patch \ No newline at end of file diff --git a/recipes/core/base/P2-boot-runtime-fixes.patch b/recipes/core/base/P2-boot-runtime-fixes.patch deleted file mode 120000 index 8e76040e..00000000 --- a/recipes/core/base/P2-boot-runtime-fixes.patch +++ /dev/null @@ -1 +0,0 @@ -../../../local/patches/base/P2-boot-runtime-fixes.patch \ No newline at end of file diff --git a/recipes/core/base/P2-daemon-hardening.patch b/recipes/core/base/P2-daemon-hardening.patch deleted file mode 120000 index b22ac90a..00000000 --- a/recipes/core/base/P2-daemon-hardening.patch +++ /dev/null @@ -1 +0,0 @@ -../../../local/patches/base/P2-daemon-hardening.patch \ No newline at end of file diff --git a/recipes/core/base/P2-daemon-ready-graceful.patch b/recipes/core/base/P2-daemon-ready-graceful.patch deleted file mode 120000 index fccefbe7..00000000 --- a/recipes/core/base/P2-daemon-ready-graceful.patch +++ /dev/null @@ -1 +0,0 @@ -../../../local/patches/base/P2-daemon-ready-graceful.patch \ No newline at end of file diff --git a/recipes/core/base/P2-hwd-misc.patch b/recipes/core/base/P2-hwd-misc.patch deleted file mode 120000 index 76a8309f..00000000 --- a/recipes/core/base/P2-hwd-misc.patch +++ /dev/null @@ -1 +0,0 @@ -../../../local/patches/base/P2-hwd-misc.patch \ No newline at end of file diff --git a/recipes/core/base/P2-ihdad-device-refactor.patch b/recipes/core/base/P2-ihdad-device-refactor.patch deleted file mode 120000 index 18404cae..00000000 --- a/recipes/core/base/P2-ihdad-device-refactor.patch +++ /dev/null @@ -1 +0,0 @@ -../../../local/patches/base/P2-ihdad-device-refactor.patch \ No newline at end of file diff --git a/recipes/core/base/P2-ihdad-hda-stream.patch b/recipes/core/base/P2-ihdad-hda-stream.patch deleted file mode 120000 index 02aa7043..00000000 --- a/recipes/core/base/P2-ihdad-hda-stream.patch +++ /dev/null @@ -1 +0,0 @@ -../../../local/patches/base/P2-ihdad-hda-stream.patch \ No newline at end of file diff --git a/recipes/core/base/P2-init-acpid-wiring.patch b/recipes/core/base/P2-init-acpid-wiring.patch deleted file mode 120000 index 996168b4..00000000 --- a/recipes/core/base/P2-init-acpid-wiring.patch +++ /dev/null @@ -1 +0,0 @@ -../../../local/patches/base/P2-init-acpid-wiring.patch \ No newline at end of file diff --git a/recipes/core/base/P2-initfs-pcid-service.patch b/recipes/core/base/P2-initfs-pcid-service.patch deleted file mode 100644 index db0a7060..00000000 --- a/recipes/core/base/P2-initfs-pcid-service.patch +++ /dev/null @@ -1,45 +0,0 @@ -diff --git a/init.initfs.d/40_pcid.service b/init.initfs.d/40_pcid.service -new file mode 100644 ---- /dev/null -+++ b/init.initfs.d/40_pcid.service -@@ -0,0 +1,7 @@ -+[unit] -+description = "PCI daemon" -+requires_weak = ["41_acpid.service"] -+ -+[service] -+cmd = "pcid" -+type = "notify" -diff --git a/init.initfs.d/40_drivers.target b/init.initfs.d/40_drivers.target ---- a/init.initfs.d/40_drivers.target -+++ b/init.initfs.d/40_drivers.target -@@ -3,6 +3,7 @@ description = "Initfs drivers" - requires_weak = [ - "10_lived.service", - "20_graphics.target", -+ "40_pcid.service", - "40_ps2d.service", - "40_bcm2835-sdhcid.service", - "40_hwd.service", -diff --git a/init.initfs.d/40_hwd.service b/init.initfs.d/40_hwd.service ---- a/init.initfs.d/40_hwd.service -+++ b/init.initfs.d/40_hwd.service -@@ -1,6 +1,6 @@ - [unit] - description = "Hardware manager" --requires_weak = ["10_inputd.service", "10_lived.service", "20_graphics.target", "41_acpid.service"] -+requires_weak = ["10_inputd.service", "10_lived.service", "20_graphics.target", "40_pcid.service", "41_acpid.service"] - - [service] - cmd = "hwd" -diff --git a/init.initfs.d/40_pcid-spawner-initfs.service b/init.initfs.d/40_pcid-spawner-initfs.service ---- a/init.initfs.d/40_pcid-spawner-initfs.service -+++ b/init.initfs.d/40_pcid-spawner-initfs.service -@@ -1,6 +1,6 @@ - [unit] - description = "PCI driver spawner" --requires_weak = ["10_inputd.service", "20_graphics.target", "40_hwd.service"] -+requires_weak = ["10_inputd.service", "20_graphics.target", "40_pcid.service"] - - [service] - cmd = "pcid-spawner" diff --git a/recipes/core/base/P2-ixgbed-error-handling.patch b/recipes/core/base/P2-ixgbed-error-handling.patch deleted file mode 120000 index 0eafdc40..00000000 --- a/recipes/core/base/P2-ixgbed-error-handling.patch +++ /dev/null @@ -1 +0,0 @@ -../../../local/patches/base/P2-ixgbed-error-handling.patch \ No newline at end of file diff --git a/recipes/core/base/P2-misc-daemon-fixes.patch b/recipes/core/base/P2-misc-daemon-fixes.patch deleted file mode 120000 index 31e83d90..00000000 --- a/recipes/core/base/P2-misc-daemon-fixes.patch +++ /dev/null @@ -1 +0,0 @@ -../../../local/patches/base/P2-misc-daemon-fixes.patch \ No newline at end of file diff --git a/recipes/core/base/P2-network-driver-mains.patch b/recipes/core/base/P2-network-driver-mains.patch deleted file mode 120000 index 93017413..00000000 --- a/recipes/core/base/P2-network-driver-mains.patch +++ /dev/null @@ -1 +0,0 @@ -../../../local/patches/base/P2-network-driver-mains.patch \ No newline at end of file diff --git a/recipes/core/base/P2-network-error-handling.patch b/recipes/core/base/P2-network-error-handling.patch deleted file mode 120000 index b4954856..00000000 --- a/recipes/core/base/P2-network-error-handling.patch +++ /dev/null @@ -1 +0,0 @@ -../../../local/patches/base/P2-network-error-handling.patch \ No newline at end of file diff --git a/recipes/core/base/P2-pcid-cfg-access.patch b/recipes/core/base/P2-pcid-cfg-access.patch deleted file mode 120000 index 41782524..00000000 --- a/recipes/core/base/P2-pcid-cfg-access.patch +++ /dev/null @@ -1 +0,0 @@ -../../../local/patches/base/P2-pcid-cfg-access.patch \ No newline at end of file diff --git a/recipes/core/base/P2-pcid-driver-interface.patch b/recipes/core/base/P2-pcid-driver-interface.patch deleted file mode 120000 index b2a2d83f..00000000 --- a/recipes/core/base/P2-pcid-driver-interface.patch +++ /dev/null @@ -1 +0,0 @@ -../../../local/patches/base/P2-pcid-driver-interface.patch \ No newline at end of file diff --git a/recipes/core/base/P2-ps2d-improvements.patch b/recipes/core/base/P2-ps2d-improvements.patch deleted file mode 120000 index e4e58dda..00000000 --- a/recipes/core/base/P2-ps2d-improvements.patch +++ /dev/null @@ -1 +0,0 @@ -../../../local/patches/base/P2-ps2d-improvements.patch \ No newline at end of file diff --git a/recipes/core/base/P2-storage-driver-mains.patch b/recipes/core/base/P2-storage-driver-mains.patch deleted file mode 120000 index db707a19..00000000 --- a/recipes/core/base/P2-storage-driver-mains.patch +++ /dev/null @@ -1 +0,0 @@ -../../../local/patches/base/P2-storage-driver-mains.patch \ No newline at end of file diff --git a/recipes/core/base/P2-storage-error-handling.patch b/recipes/core/base/P2-storage-error-handling.patch deleted file mode 120000 index bcb49397..00000000 --- a/recipes/core/base/P2-storage-error-handling.patch +++ /dev/null @@ -1 +0,0 @@ -../../../local/patches/base/P2-storage-error-handling.patch \ No newline at end of file diff --git a/recipes/core/base/P2-usb-pm-and-drivers.patch b/recipes/core/base/P2-usb-pm-and-drivers.patch deleted file mode 120000 index 580be8db..00000000 --- a/recipes/core/base/P2-usb-pm-and-drivers.patch +++ /dev/null @@ -1 +0,0 @@ -../../../local/patches/base/P2-usb-pm-and-drivers.patch \ No newline at end of file diff --git a/recipes/core/base/P2-virtio-core-vbox.patch b/recipes/core/base/P2-virtio-core-vbox.patch deleted file mode 120000 index aebf32c8..00000000 --- a/recipes/core/base/P2-virtio-core-vbox.patch +++ /dev/null @@ -1 +0,0 @@ -../../../local/patches/base/P2-virtio-core-vbox.patch \ No newline at end of file diff --git a/recipes/core/base/P2-xhcid-remaining.patch b/recipes/core/base/P2-xhcid-remaining.patch deleted file mode 120000 index a697629b..00000000 --- a/recipes/core/base/P2-xhcid-remaining.patch +++ /dev/null @@ -1 +0,0 @@ -../../../local/patches/base/P2-xhcid-remaining.patch \ No newline at end of file diff --git a/recipes/core/base/P3-acpi-power-dmi.patch b/recipes/core/base/P3-acpi-power-dmi.patch deleted file mode 120000 index 4f27d6ff..00000000 --- a/recipes/core/base/P3-acpi-power-dmi.patch +++ /dev/null @@ -1 +0,0 @@ -../../../local/patches/base/P3-acpi-power-dmi.patch \ No newline at end of file diff --git a/recipes/core/base/P3-acpi-wave12-hardening.patch b/recipes/core/base/P3-acpi-wave12-hardening.patch deleted file mode 120000 index 511886fc..00000000 --- a/recipes/core/base/P3-acpi-wave12-hardening.patch +++ /dev/null @@ -1 +0,0 @@ -../../../local/patches/base/P3-acpi-wave12-hardening.patch \ No newline at end of file diff --git a/recipes/core/base/P3-pcid-aer-scheme.patch b/recipes/core/base/P3-pcid-aer-scheme.patch deleted file mode 120000 index 2de4aca4..00000000 --- a/recipes/core/base/P3-pcid-aer-scheme.patch +++ /dev/null @@ -1 +0,0 @@ -../../../local/patches/base/P3-pcid-aer-scheme.patch \ No newline at end of file diff --git a/recipes/core/base/P3-pcid-bind-scheme.patch b/recipes/core/base/P3-pcid-bind-scheme.patch deleted file mode 120000 index 67889a22..00000000 --- a/recipes/core/base/P3-pcid-bind-scheme.patch +++ /dev/null @@ -1 +0,0 @@ -../../../local/patches/base/P3-pcid-bind-scheme.patch \ No newline at end of file diff --git a/recipes/core/base/P3-pcid-uevent-format-fix.patch b/recipes/core/base/P3-pcid-uevent-format-fix.patch deleted file mode 120000 index dc7095da..00000000 --- a/recipes/core/base/P3-pcid-uevent-format-fix.patch +++ /dev/null @@ -1 +0,0 @@ -../../../local/patches/base/P3-pcid-uevent-format-fix.patch \ No newline at end of file diff --git a/recipes/core/base/P3-xhci-device-hardening.patch b/recipes/core/base/P3-xhci-device-hardening.patch deleted file mode 120000 index a35ac64c..00000000 --- a/recipes/core/base/P3-xhci-device-hardening.patch +++ /dev/null @@ -1 +0,0 @@ -../../../local/patches/base/P3-xhci-device-hardening.patch \ No newline at end of file diff --git a/recipes/core/base/P5-init-daemon-panic-hardening.patch b/recipes/core/base/P5-init-daemon-panic-hardening.patch deleted file mode 120000 index 9f80f92c..00000000 --- a/recipes/core/base/P5-init-daemon-panic-hardening.patch +++ /dev/null @@ -1 +0,0 @@ -../../../local/patches/base/P5-init-daemon-panic-hardening.patch \ No newline at end of file diff --git a/recipes/core/base/P5-init-supervisor-restart.patch b/recipes/core/base/P5-init-supervisor-restart.patch deleted file mode 120000 index 8d95c8b9..00000000 --- a/recipes/core/base/P5-init-supervisor-restart.patch +++ /dev/null @@ -1 +0,0 @@ -../../../local/patches/base/P5-init-supervisor-restart.patch \ No newline at end of file diff --git a/recipes/core/base/init/src/main.rs b/recipes/core/base/init/src/main.rs index 72c97f53..e78a6699 100644 --- a/recipes/core/base/init/src/main.rs +++ b/recipes/core/base/init/src/main.rs @@ -177,7 +177,9 @@ fn main() { } }; + eprintln!("init: DEBUG step start — {} jobs pending", scheduler.pending_len()); scheduler.step(&mut unit_store, &mut init_config); + eprintln!("init: DEBUG step done — entering waitpid loop"); if let Err(err) = libredox::call::setrens(0, 0) { eprintln!("init: failed to enter null namespace: {}", err); diff --git a/recipes/core/base/init/src/scheduler.rs b/recipes/core/base/init/src/scheduler.rs index 333e0e20..44215bbb 100644 --- a/recipes/core/base/init/src/scheduler.rs +++ b/recipes/core/base/init/src/scheduler.rs @@ -66,6 +66,10 @@ impl Scheduler { } } + pub fn pending_len(&self) -> usize { + self.pending.len() + } + pub fn step(&mut self, unit_store: &mut UnitStore, init_config: &mut InitConfig) { 'a: loop { let Some(mut job) = self.pending.pop_front() else { @@ -75,6 +79,16 @@ impl Scheduler { match job.kind { JobKind::Start => { let unit = unit_store.unit(&job.unit); + eprintln!( + "init: DEBUG processing {} ({}) — deps: {:?}", + job.unit.0, + match &unit.kind { + crate::unit::UnitKind::LegacyScript { .. } => "script", + crate::unit::UnitKind::Service { service } => &service.cmd, + crate::unit::UnitKind::Target { .. } => "target", + }, + unit.info.requires_weak.iter().map(|u| &u.0).collect::>() + ); let timeout_secs = unit.info.dependency_timeout_secs; let mut deps_pending = false; @@ -91,6 +105,10 @@ impl Scheduler { } if deps_pending { + eprintln!( + "init: DEBUG {} waiting for deps (retry {})", + job.unit.0, job.dep_retries + ); if timeout_secs > 0 { job.dep_retries += 1; let max_retries = timeout_secs * 100; // ~10ms per retry diff --git a/recipes/core/base/ipcd/src/uds/stream.rs b/recipes/core/base/ipcd/src/uds/stream.rs index 81c846fd..46de854e 100644 --- a/recipes/core/base/ipcd/src/uds/stream.rs +++ b/recipes/core/base/ipcd/src/uds/stream.rs @@ -180,7 +180,7 @@ pub struct Socket { options: HashSet, flags: usize, state: State, - awaiting: VecDeque, + awaiting: VecDeque<(usize, ucred)>, connection: Option, issued_token: Option, ucred: ucred, @@ -241,6 +241,7 @@ impl Socket { &mut self, primary_id: usize, awaiting_client_id: usize, + client_ucred: ucred, ctx: &CallerCtx, ) -> Result { if !self.is_listening() { @@ -250,15 +251,17 @@ impl Socket { ); return Err(Error::new(EINVAL)); } - Ok(Self::new( + Ok(Self { primary_id, - self.path.clone(), - State::Established, - self.options.clone(), - self.flags, - Some(Connection::new(awaiting_client_id)), - ctx, - )) + path: self.path.clone(), + state: State::Established, + options: self.options.clone(), + flags: self.flags, + awaiting: VecDeque::new(), + connection: Some(Connection::new(awaiting_client_id)), + issued_token: None, + ucred: client_ucred, + }) } fn establish(&mut self, new_socket: &mut Self, peer: usize) -> Result<()> { @@ -286,7 +289,7 @@ impl Socket { Ok(()) } - fn connect(&mut self, other: &mut Socket) -> Result<()> { + fn connect(&mut self, other: &mut Socket, client_ucred: ucred) -> Result<()> { match self.state { State::Unbound | State::Bound => { // If the socket is unbound or bound, wait for the listener to start listening. @@ -302,12 +305,12 @@ impl Socket { } _ => return Err(Error::new(ECONNREFUSED)), } - self.connect_unchecked(other); + self.connect_unchecked(other, client_ucred); Ok(()) } - fn connect_unchecked(&mut self, other: &mut Socket) { - self.awaiting.push_back(other.primary_id); + fn connect_unchecked(&mut self, other: &mut Socket, client_ucred: ucred) { + self.awaiting.push_back((other.primary_id, client_ucred)); other.state = State::Connecting; other.connection = Some(Connection::new(self.primary_id)); } @@ -495,7 +498,7 @@ impl<'sock> UdsStreamScheme<'sock> { }; match verb { SocketCall::Bind => self.handle_bind(id, &payload), - SocketCall::Connect => self.handle_connect(id, &payload), + SocketCall::Connect => self.handle_connect(id, &payload, ctx), SocketCall::SetSockOpt => self.handle_setsockopt( id, *metadata.get(1).ok_or(Error::new(EINVAL))? as i32, @@ -588,7 +591,7 @@ impl<'sock> UdsStreamScheme<'sock> { // and changes its own state to `Established`. // // After these three phases, the socket connection is considered established. - fn handle_connect(&mut self, id: usize, token_buf: &[u8]) -> Result { + fn handle_connect(&mut self, id: usize, token_buf: &[u8], ctx: &CallerCtx) -> Result { let token = read_num::(token_buf)?; let (listener_id, connecting_res) = { let listener_rc = self @@ -632,8 +635,14 @@ impl<'sock> UdsStreamScheme<'sock> { _ => {} } + let client_ucred = ucred { + pid: ctx.pid as _, + uid: ctx.uid as _, + gid: ctx.gid as _, + }; + // Phase 2: listener is now listening - listener.connect(&mut client)?; + listener.connect(&mut client, client_ucred)?; (listener_id, connecting_res) }; @@ -873,6 +882,7 @@ impl<'sock> UdsStreamScheme<'sock> { &mut self, listener_socket: &mut Socket, client_id: usize, + client_ucred: ucred, ctx: &CallerCtx, ) -> Result> { let (new_id, new) = { @@ -880,7 +890,7 @@ impl<'sock> UdsStreamScheme<'sock> { return Ok(None); // Client socket has been closed, nothing to accept }; let new_id = self.next_id; - let mut new = listener_socket.accept(new_id, client_id, ctx)?; + let mut new = listener_socket.accept(new_id, client_id, client_ucred, ctx)?; let mut client_socket = client_rc.borrow_mut(); client_socket.establish(&mut new, listener_socket.primary_id)?; @@ -912,14 +922,14 @@ impl<'sock> UdsStreamScheme<'sock> { } loop { // Try to accept a waiting connection - let Some(client_id) = socket.awaiting.pop_front() else { + let Some((client_id, client_ucred)) = socket.awaiting.pop_front() else { if flags & O_NONBLOCK == O_NONBLOCK { return Err(Error::new(EAGAIN)); } else { return Err(Error::new(EWOULDBLOCK)); } }; - return match self.accept_connection(socket, client_id, ctx) { + return match self.accept_connection(socket, client_id, client_ucred, ctx) { Ok(conn) => Ok(conn), Err(Error { errno: EAGAIN }) => continue, Err(e) => Err(e), @@ -991,7 +1001,12 @@ impl<'sock> UdsStreamScheme<'sock> { ); return Err(Error::new(EPIPE)); } - socket.connect_unchecked(&mut new); + let pair_ucred = ucred { + pid: ctx.pid as _, + uid: ctx.uid as _, + gid: ctx.gid as _, + }; + socket.connect_unchecked(&mut new, pair_ucred); } // smoltcp sends writeable whenever a listener gets a @@ -1186,7 +1201,7 @@ impl<'sock> UdsStreamScheme<'sock> { } // Notify all waiting clients about listener closure - for client_id in &socket.awaiting { + for (client_id, _) in &socket.awaiting { if let Ok(client_rc) = self.get_socket(*client_id) { { let mut client = client_rc.borrow_mut(); diff --git a/recipes/core/base/redox.patch b/recipes/core/base/redox.patch deleted file mode 120000 index d3f75a80..00000000 --- a/recipes/core/base/redox.patch +++ /dev/null @@ -1 +0,0 @@ -../../../local/patches/base/redox.patch \ No newline at end of file