From 2b8dd96a5c6df3efd90697885022da11dc33220f Mon Sep 17 00:00:00 2001 From: Vasilito Date: Mon, 11 May 2026 10:09:25 +0100 Subject: [PATCH] fix: absorb redundant base daemon and driver patches Consolidate ~30 absorbed base patches into surviving carriers. Add new init service files, driver sources, and network/storage modules for the base recipe. Move absorbed patches to local/patches/base/absorbed/. --- .../patches/base/P0-acpid-fadt-shutdown.patch | 364 - .../base/P0-bootstrap-workspace-fix.patch | 10 +- .../P0-cumulative-daemon-driver-fixes.patch | 655 -- .../base/P0-inputd-named-producers.patch | 94 +- .../base/P0-inputd-per-device-consumers.patch | 274 +- local/patches/base/P1-acpid-acpi-core.patch | 1682 ----- .../base/P1-acpid-power-enumeration.patch | 1956 ------ .../base/P1-acpid-scheme-surface.patch | 719 -- local/patches/base/P1-pci-irq-wave1-3.patch | 1184 ---- local/patches/base/P1-pci-irq-wave1-5.patch | 2962 -------- .../patches/base/P1-pcid-uevent-surface.patch | 65 +- .../base/P1-xhcid-device-lifecycle.patch | 573 +- .../base/P1-xhcid-port-pm-read-fix.patch | 942 --- .../base/P1-xhcid-uevent-logging.patch | 35 +- local/patches/base/P2-ac97d-ihdad-main.patch | 287 - local/patches/base/P2-acpi-defer-aml.patch | 31 - .../patches/base/P2-acpi-i2c-resources.patch | 6251 ----------------- .../patches/base/P2-acpid-core-refactor.patch | 3150 --------- .../patches/base/P2-boot-runtime-fixes.patch | 176 +- .../P2-boot-runtime-noise-and-net-race.patch | 144 - .../base/P2-daemon-ready-graceful.patch | 23 - local/patches/base/P2-hwd-misc.patch | 14 +- .../base/P2-hwd-remove-acpid-spawn.patch | 21 + .../base/P2-ihdad-device-refactor.patch | 1022 --- local/patches/base/P2-ihdad-hda-stream.patch | 1150 --- local/patches/base/P2-init-subsystems.patch | 0 local/patches/base/P2-inputd.patch | 0 local/patches/base/P2-logd.patch | 0 .../base/P2-network-driver-mains.patch | 607 -- .../base/P2-network-error-handling.patch | 118 - .../base/P2-pcid-acpid-graceful-fd.patch | 35 + local/patches/base/P2-pcid-cfg-access.patch | 142 +- .../base/P2-pcid-driver-interface.patch | 1463 ---- local/patches/base/P2-ps2d-improvements.patch | 384 - .../base/P2-storage-driver-mains.patch | 625 -- .../base/P2-storage-error-handling.patch | 601 -- .../patches/base/P2-usb-pm-and-drivers.patch | 101 +- local/patches/base/P2-xhcid-remaining.patch | 1700 +---- local/patches/base/P3-acpi-power-dmi.patch | 1294 ---- .../base/P3-acpi-wave12-hardening.patch | 844 --- local/patches/base/P3-pcid-aer-scheme.patch | 398 -- local/patches/base/P3-pcid-bind-scheme.patch | 182 - .../base/P3-pcid-uevent-format-fix.patch | 479 -- local/patches/base/P4-login-rate-limit.patch | 43 - .../base/P5-init-daemon-panic-hardening.patch | 685 -- .../base/P5-init-supervisor-restart.patch | 633 -- .../patches/base/P6-cpufreqd-real-impl.patch | 347 +- .../base/P6-driver-phase0-phase2.patch | 89 - .../base/P6-e1000d-msi-migration.patch | 63 - .../patches/base/P9-init-debug-logging.patch | 32 - .../base/P9-init-debug-scheduler.patch | 11 - .../base/P9-init-scheduler-debug.patch | 36 - .../P0-daemon-init-notify-graceful.patch | 0 .../P0-daemon-silence-init-notify.patch | 0 .../P0-driver-api-migration-fixes.patch | 0 .../{ => absorbed}/P2-daemon-hardening.patch | 0 .../base/drivers/acpi-resource/Cargo.toml | 13 + .../base/drivers/acpi-resource/src/lib.rs | 688 ++ .../core/base/drivers/gpio/gpiod/Cargo.toml | 21 + .../core/base/drivers/gpio/gpiod/src/main.rs | 496 ++ .../gpio/i2c-gpio-expanderd/Cargo.toml | 21 + .../gpio/i2c-gpio-expanderd/src/main.rs | 454 ++ .../base/drivers/gpio/intel-gpiod/Cargo.toml | 20 + .../base/drivers/gpio/intel-gpiod/src/main.rs | 401 ++ .../base/drivers/i2c/amd-mp2-i2cd/Cargo.toml | 22 + .../base/drivers/i2c/amd-mp2-i2cd/src/main.rs | 106 + .../base/drivers/i2c/dw-acpi-i2cd/Cargo.toml | 21 + .../base/drivers/i2c/dw-acpi-i2cd/src/main.rs | 361 + .../base/drivers/i2c/i2c-interface/Cargo.toml | 12 + .../base/drivers/i2c/i2c-interface/src/lib.rs | 92 + recipes/core/base/drivers/i2c/i2cd/Cargo.toml | 22 + .../core/base/drivers/i2c/i2cd/src/main.rs | 377 + .../drivers/i2c/intel-lpss-i2cd/Cargo.toml | 21 + .../drivers/i2c/intel-lpss-i2cd/src/main.rs | 361 + .../base/drivers/input/i2c-hidd/Cargo.toml | 26 + .../base/drivers/input/i2c-hidd/src/acpi.rs | 307 + .../base/drivers/input/i2c-hidd/src/hid.rs | 195 + .../base/drivers/input/i2c-hidd/src/input.rs | 175 + .../base/drivers/input/i2c-hidd/src/main.rs | 114 + .../base/drivers/input/i2c-hidd/src/quirks.rs | 88 + .../drivers/input/intel-thc-hidd/Cargo.toml | 26 + .../drivers/input/intel-thc-hidd/src/main.rs | 260 + .../input/intel-thc-hidd/src/quicki2c.rs | 86 + .../drivers/input/intel-thc-hidd/src/thc.rs | 78 + recipes/core/base/drivers/thermald/Cargo.toml | 12 + .../core/base/drivers/thermald/src/main.rs | 30 + .../core/base/drivers/usb/ucsid/Cargo.toml | 23 + .../core/base/drivers/usb/ucsid/src/main.rs | 839 +++ .../core/base/init.d/05_boot_essential.target | 3 + recipes/core/base/init.d/12_boot_late.target | 3 + recipes/core/base/init.d/12_dbus.service | 8 + recipes/core/base/init.d/13_seatd.service | 8 + .../base/init.d/29_activate_console.service | 8 + recipes/core/base/init.d/30_console.service | 8 + recipes/core/base/init.d/30_thermald.service | 7 + .../core/base/init.d/31_debug_console.service | 8 + .../base/init.initfs.d/30_redox-drm.service | 8 + .../base/init.initfs.d/45_usbscsid.service | 8 + .../base/init.initfs.d/60_smolnetd.service | 7 + .../core/base/init.initfs.d/61_dhcpd.service | 7 + recipes/core/base/init/src/color.rs | 27 + recipes/core/base/net/e1000d/src/itr.rs | 9 + recipes/core/base/net/rtl8168d/src/phy.rs | 5 + recipes/core/base/recipe.toml | 33 +- .../core/base/storage/ahcid/src/ahci/ncq.rs | 12 + 105 files changed, 6633 insertions(+), 34000 deletions(-) delete mode 100644 local/patches/base/P0-acpid-fadt-shutdown.patch delete mode 100644 local/patches/base/P0-cumulative-daemon-driver-fixes.patch delete mode 100644 local/patches/base/P1-acpid-acpi-core.patch delete mode 100644 local/patches/base/P1-acpid-power-enumeration.patch delete mode 100644 local/patches/base/P1-acpid-scheme-surface.patch delete mode 100644 local/patches/base/P1-pci-irq-wave1-3.patch delete mode 100644 local/patches/base/P1-pci-irq-wave1-5.patch delete mode 100644 local/patches/base/P1-xhcid-port-pm-read-fix.patch delete mode 100644 local/patches/base/P2-ac97d-ihdad-main.patch delete mode 100644 local/patches/base/P2-acpi-defer-aml.patch delete mode 100644 local/patches/base/P2-acpi-i2c-resources.patch delete mode 100644 local/patches/base/P2-acpid-core-refactor.patch delete mode 100644 local/patches/base/P2-boot-runtime-noise-and-net-race.patch delete mode 100644 local/patches/base/P2-daemon-ready-graceful.patch create mode 100644 local/patches/base/P2-hwd-remove-acpid-spawn.patch delete mode 100644 local/patches/base/P2-ihdad-device-refactor.patch delete mode 100644 local/patches/base/P2-ihdad-hda-stream.patch delete mode 100644 local/patches/base/P2-init-subsystems.patch delete mode 100644 local/patches/base/P2-inputd.patch delete mode 100644 local/patches/base/P2-logd.patch delete mode 100644 local/patches/base/P2-network-driver-mains.patch delete mode 100644 local/patches/base/P2-network-error-handling.patch create mode 100644 local/patches/base/P2-pcid-acpid-graceful-fd.patch delete mode 100644 local/patches/base/P2-pcid-driver-interface.patch delete mode 100644 local/patches/base/P2-ps2d-improvements.patch delete mode 100644 local/patches/base/P2-storage-driver-mains.patch delete mode 100644 local/patches/base/P2-storage-error-handling.patch delete mode 100644 local/patches/base/P3-acpi-power-dmi.patch delete mode 100644 local/patches/base/P3-acpi-wave12-hardening.patch delete mode 100644 local/patches/base/P3-pcid-aer-scheme.patch delete mode 100644 local/patches/base/P3-pcid-bind-scheme.patch delete mode 100644 local/patches/base/P3-pcid-uevent-format-fix.patch delete mode 100644 local/patches/base/P4-login-rate-limit.patch delete mode 100644 local/patches/base/P5-init-daemon-panic-hardening.patch delete mode 100644 local/patches/base/P5-init-supervisor-restart.patch delete mode 100644 local/patches/base/P6-driver-phase0-phase2.patch delete mode 100644 local/patches/base/P6-e1000d-msi-migration.patch delete mode 100644 local/patches/base/P9-init-debug-logging.patch delete mode 100644 local/patches/base/P9-init-debug-scheduler.patch delete mode 100644 local/patches/base/P9-init-scheduler-debug.patch rename local/patches/base/{ => absorbed}/P0-daemon-init-notify-graceful.patch (100%) rename local/patches/base/{ => absorbed}/P0-daemon-silence-init-notify.patch (100%) rename local/patches/base/{ => absorbed}/P0-driver-api-migration-fixes.patch (100%) rename local/patches/base/{ => absorbed}/P2-daemon-hardening.patch (100%) create mode 100644 recipes/core/base/drivers/acpi-resource/Cargo.toml create mode 100644 recipes/core/base/drivers/acpi-resource/src/lib.rs create mode 100644 recipes/core/base/drivers/gpio/gpiod/Cargo.toml create mode 100644 recipes/core/base/drivers/gpio/gpiod/src/main.rs create mode 100644 recipes/core/base/drivers/gpio/i2c-gpio-expanderd/Cargo.toml create mode 100644 recipes/core/base/drivers/gpio/i2c-gpio-expanderd/src/main.rs create mode 100644 recipes/core/base/drivers/gpio/intel-gpiod/Cargo.toml create mode 100644 recipes/core/base/drivers/gpio/intel-gpiod/src/main.rs create mode 100644 recipes/core/base/drivers/i2c/amd-mp2-i2cd/Cargo.toml create mode 100644 recipes/core/base/drivers/i2c/amd-mp2-i2cd/src/main.rs create mode 100644 recipes/core/base/drivers/i2c/dw-acpi-i2cd/Cargo.toml create mode 100644 recipes/core/base/drivers/i2c/dw-acpi-i2cd/src/main.rs create mode 100644 recipes/core/base/drivers/i2c/i2c-interface/Cargo.toml create mode 100644 recipes/core/base/drivers/i2c/i2c-interface/src/lib.rs create mode 100644 recipes/core/base/drivers/i2c/i2cd/Cargo.toml create mode 100644 recipes/core/base/drivers/i2c/i2cd/src/main.rs create mode 100644 recipes/core/base/drivers/i2c/intel-lpss-i2cd/Cargo.toml create mode 100644 recipes/core/base/drivers/i2c/intel-lpss-i2cd/src/main.rs create mode 100644 recipes/core/base/drivers/input/i2c-hidd/Cargo.toml create mode 100644 recipes/core/base/drivers/input/i2c-hidd/src/acpi.rs create mode 100644 recipes/core/base/drivers/input/i2c-hidd/src/hid.rs create mode 100644 recipes/core/base/drivers/input/i2c-hidd/src/input.rs create mode 100644 recipes/core/base/drivers/input/i2c-hidd/src/main.rs create mode 100644 recipes/core/base/drivers/input/i2c-hidd/src/quirks.rs create mode 100644 recipes/core/base/drivers/input/intel-thc-hidd/Cargo.toml create mode 100644 recipes/core/base/drivers/input/intel-thc-hidd/src/main.rs create mode 100644 recipes/core/base/drivers/input/intel-thc-hidd/src/quicki2c.rs create mode 100644 recipes/core/base/drivers/input/intel-thc-hidd/src/thc.rs create mode 100644 recipes/core/base/drivers/thermald/Cargo.toml create mode 100644 recipes/core/base/drivers/thermald/src/main.rs create mode 100644 recipes/core/base/drivers/usb/ucsid/Cargo.toml create mode 100644 recipes/core/base/drivers/usb/ucsid/src/main.rs create mode 100644 recipes/core/base/init.d/05_boot_essential.target create mode 100644 recipes/core/base/init.d/12_boot_late.target create mode 100644 recipes/core/base/init.d/12_dbus.service create mode 100644 recipes/core/base/init.d/13_seatd.service create mode 100644 recipes/core/base/init.d/29_activate_console.service create mode 100644 recipes/core/base/init.d/30_console.service create mode 100644 recipes/core/base/init.d/30_thermald.service create mode 100644 recipes/core/base/init.d/31_debug_console.service create mode 100644 recipes/core/base/init.initfs.d/30_redox-drm.service create mode 100644 recipes/core/base/init.initfs.d/45_usbscsid.service create mode 100644 recipes/core/base/init.initfs.d/60_smolnetd.service create mode 100644 recipes/core/base/init.initfs.d/61_dhcpd.service create mode 100644 recipes/core/base/init/src/color.rs create mode 100644 recipes/core/base/net/e1000d/src/itr.rs create mode 100644 recipes/core/base/net/rtl8168d/src/phy.rs create mode 100644 recipes/core/base/storage/ahcid/src/ahci/ncq.rs diff --git a/local/patches/base/P0-acpid-fadt-shutdown.patch b/local/patches/base/P0-acpid-fadt-shutdown.patch deleted file mode 100644 index ce007379ea..0000000000 --- a/local/patches/base/P0-acpid-fadt-shutdown.patch +++ /dev/null @@ -1,364 +0,0 @@ -diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs ---- a/drivers/acpid/src/acpi.rs -+++ b/drivers/acpid/src/acpi.rs -@@ -387,6 +387,12 @@ - tables: Vec, - dsdt: Option, - fadt: Option, -+ pm1a_cnt_blk: u64, -+ pm1b_cnt_blk: u64, -+ slp_typa_s5: u8, -+ slp_typb_s5: u8, -+ reset_reg: Option, -+ reset_value: u8, - - aml_symbols: RwLock, - -@@ -452,6 +458,12 @@ - tables, - dsdt: None, - fadt: None, -+ pm1a_cnt_blk: 0, -+ pm1b_cnt_blk: 0, -+ slp_typa_s5: 0, -+ slp_typb_s5: 0, -+ reset_reg: None, -+ reset_value: 0, - - // Temporary values - aml_symbols: RwLock::new(AmlSymbols::new(ec)), -@@ -575,6 +587,67 @@ - aml_symbols.symbol_cache = FxHashMap::default(); - } - -+ pub fn acpi_shutdown(&self) { -+ let pm1a_value = (u16::from(self.slp_typa_s5) << 10) | 0x2000; -+ let pm1b_value = (u16::from(self.slp_typb_s5) << 10) | 0x2000; -+ -+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -+ { -+ let Ok(pm1a_port) = u16::try_from(self.pm1a_cnt_blk) else { -+ log::error!("PM1a_CNT_BLK address is invalid: {:#X}", self.pm1a_cnt_blk); -+ 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(_) => { -+ log::error!("PM1b_CNT_BLK address is invalid: {:#X}", self.pm1b_cnt_blk); -+ } -+ } -+ } -+ } -+ -+ #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] -+ { -+ log::error!( -+ "Cannot shutdown with ACPI PM1_CNT writes on this architecture (PM1a={:#X}, PM1b={:#X})", -+ self.pm1a_cnt_blk, -+ self.pm1b_cnt_blk -+ ); -+ } -+ } -+ -+ pub fn acpi_reboot(&self) { -+ 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); -+ } -+ None => { -+ log::error!("Cannot reboot with ACPI: no reset register present in FADT"); -+ } -+ } -+ } -+ - /// Set Power State - /// See https://uefi.org/sites/default/files/resources/ACPI_6_1.pdf - /// - search for PM1a -@@ -583,83 +656,13 @@ - if state != 5 { - return; - } -- let fadt = match self.fadt() { -- Some(fadt) => fadt, -- None => { -- log::error!("Cannot set global S-state due to missing FADT."); -- return; -- } -- }; -- -- let port = fadt.pm1a_control_block as u16; -- let mut val = 1 << 13; -- -- let aml_symbols = self.aml_symbols.read(); -- -- 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; -- } -- }; -- -- 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; -- } -- }, -- 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; -- } -- }; -- -- let slp_typa = match package[0].deref() { -- acpi::aml::object::Object::Integer(i) => i.to_owned(), -- _ => { -- log::error!("typa is not an Integer"); -- return; -- } -- }; -- let slp_typb = match package[1].deref() { -- acpi::aml::object::Object::Integer(i) => i.to_owned(), -- _ => { -- log::error!("typb is not an Integer"); -- return; -- } -- }; -- -- log::trace!("Shutdown SLP_TYPa {:X}, SLP_TYPb {:X}", slp_typa, slp_typb); -- val |= slp_typa as u16; -- -- #[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); -- } -- -- // TODO: Handle SLP_TYPb -- -- #[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 -- ); -- } -+ -+ if self.fadt().is_none() { -+ log::error!("Cannot set global S-state due to missing FADT."); -+ return; -+ } -+ -+ self.acpi_shutdown(); - - loop { - core::hint::spin_loop(); -@@ -720,7 +723,7 @@ - - #[repr(C, packed)] - #[derive(Clone, Copy, Debug, Default)] --pub struct GenericAddressStructure { -+pub struct GenericAddress { - address_space: u8, - bit_width: u8, - bit_offset: u8, -@@ -728,11 +731,67 @@ - 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) { -+ match self.address_space { -+ 0 => { -+ let Ok(address) = usize::try_from(self.address) else { -+ log::error!("Reset register physical address is invalid: {:#X}", self.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(self.address) { -+ Ok(port) => { -+ Pio::::new(port).write(value); -+ } -+ Err(_) => { -+ log::error!("Reset register I/O port is invalid: {:#X}", self.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], -@@ -741,14 +800,14 @@ - 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 {} - -@@ -806,9 +865,25 @@ - None => usize::try_from(fadt.dsdt).expect("expected any given u32 to fit within usize"), - }; - -+ 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), -+ }; -+ - log::debug!("FACP at {:X}", { dsdt_ptr }); -- -- let dsdt_sdt = match Sdt::load_from_physical(fadt.dsdt as usize) { -+ 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); -@@ -816,8 +891,46 @@ - } - }; - -+ let (slp_typa_s5, slp_typb_s5) = match AmlName::from_str("\\_S5") { -+ Ok(s5_name) => match context.aml_eval(s5_name, Vec::new()) { -+ Ok(AmlSerdeValue::Package { contents }) => match (contents.get(0), 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)) => (slp_typa_s5, slp_typb_s5), -+ _ => { -+ log::warn!("\\_S5 values do not fit in u8: {:?}", contents); -+ (0, 0) -+ } -+ } -+ } -+ _ => { -+ log::warn!("\\_S5 package did not contain two integers: {:?}", contents); -+ (0, 0) -+ } -+ }, -+ Ok(value) => { -+ log::warn!("\\_S5 returned unexpected AML value: {:?}", value); -+ (0, 0) -+ } -+ Err(error) => { -+ log::warn!("Failed to evaluate \\_S5: {:?}", error); -+ (0, 0) -+ } -+ }, -+ Err(error) => { -+ log::warn!("Could not build AmlName for \\_S5: {:?}", error); -+ (0, 0) -+ } -+ }; -+ - 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.slp_typa_s5 = slp_typa_s5; -+ context.slp_typb_s5 = slp_typb_s5; -+ context.reset_reg = reset_reg; -+ context.reset_value = reset_value; - - context.tables.push(dsdt_sdt); - } diff --git a/local/patches/base/P0-bootstrap-workspace-fix.patch b/local/patches/base/P0-bootstrap-workspace-fix.patch index 7f0772901e..383617473d 100644 --- a/local/patches/base/P0-bootstrap-workspace-fix.patch +++ b/local/patches/base/P0-bootstrap-workspace-fix.patch @@ -1,13 +1,9 @@ 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" +@@ -7,5 +7,3 @@ edition = "2024" license = "MIT" -+[workspace] -+ +-[workspace] +- [dependencies] - hashbrown = { version = "0.15", default-features = false, features = [ - "inline-more", diff --git a/local/patches/base/P0-cumulative-daemon-driver-fixes.patch b/local/patches/base/P0-cumulative-daemon-driver-fixes.patch deleted file mode 100644 index 3f82d3a016..0000000000 --- a/local/patches/base/P0-cumulative-daemon-driver-fixes.patch +++ /dev/null @@ -1,655 +0,0 @@ -diff --git a/daemon/src/lib.rs b/daemon/src/lib.rs -index 9f507221..a0ba9d88 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}"), -+ )), - } - } - } -diff --git a/drivers/audio/ac97d/src/main.rs b/drivers/audio/ac97d/src/main.rs -index ffa8a94b..4e381e48 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,35 @@ 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"); -+ std::process::exit(1); -+ } -+ }; -+ let bar1 = match pci_config.func.bars[1].try_port() { -+ Ok(port) => port, -+ Err(err) => { -+ error!("ac97d: invalid BAR1"); -+ std::process::exit(1); -+ } -+ }; -+ let bar1 = match pci_config.func.bars[1].try_port() { -+ Ok(port) => port, -+ Err(err) => { -+ error!("ac97d: invalid BAR1"); -+ 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 +63,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"); -+ 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 +101,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 +187,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 +208,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/main.rs b/drivers/audio/ihdad/src/main.rs -index 31a2add7..4e455066 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"); -+ 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"); -+ 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/graphics/ihdgd/src/main.rs b/drivers/graphics/ihdgd/src/main.rs -index a8b6cc60..d855d108 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"); -+ 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/pcid/src/driver_interface/bar.rs b/drivers/pcid/src/driver_interface/bar.rs -index b2c1d35b..d333cd53 100644 ---- a/drivers/pcid/src/driver_interface/bar.rs -+++ b/drivers/pcid/src/driver_interface/bar.rs -@@ -1,4 +1,5 @@ - use std::convert::TryInto; -+use std::fmt; - - use serde::{Deserialize, Serialize}; - -@@ -12,6 +13,21 @@ pub enum PciBar { - Port(u16), - } - -+#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] -+pub enum PciBarError { -+ WrongType, -+ Missing, -+} -+ -+impl fmt::Display for PciBarError { -+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -+ match self { -+ PciBarError::WrongType => write!(f, "wrong BAR type"), -+ PciBarError::Missing => write!(f, "BAR not present"), -+ } -+ } -+} -+ - impl PciBar { - pub fn display(&self) -> String { - match self { -@@ -29,27 +45,33 @@ impl PciBar { - } - } - -- pub fn expect_port(&self) -> u16 { -+ pub fn try_port(&self) -> Result { - match *self { -- PciBar::Port(port) => port, -- PciBar::Memory32 { .. } | PciBar::Memory64 { .. } => { -- panic!("expected port BAR, found memory BAR"); -- } -- PciBar::None => panic!("expected BAR to exist"), -+ PciBar::Port(port) => Ok(port), -+ PciBar::Memory32 { .. } | PciBar::Memory64 { .. } => Err(PciBarError::WrongType), -+ PciBar::None => Err(PciBarError::Missing), - } - } - -- pub fn expect_mem(&self) -> (usize, usize) { -+ 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 } => ( -+ PciBar::Memory32 { addr, size } => Ok((addr as usize, size as usize)), -+ PciBar::Memory64 { addr, size } => Ok(( - 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::Port(_) => Err(PciBarError::WrongType), -+ PciBar::None => Err(PciBarError::Missing), - } - } -+ -+ pub fn expect_port(&self) -> u16 { -+ self.try_port().expect("expected port BAR") -+ } -+ -+ pub fn expect_mem(&self) -> (usize, usize) { -+ self.try_mem().expect("expected memory BAR") -+ } - } -diff --git a/drivers/pcid/src/driver_interface/irq_helpers.rs b/drivers/pcid/src/driver_interface/irq_helpers.rs -index 28ca077a..d0c7042e 100644 ---- a/drivers/pcid/src/driver_interface/irq_helpers.rs -+++ b/drivers/pcid/src/driver_interface/irq_helpers.rs -@@ -61,6 +61,14 @@ pub fn cpu_ids() -> io::Result> + 'static - ) - } - -+/// Allocate a single interrupt vector. Returns the InterruptVector on success. -+pub fn try_pci_allocate_interrupt_vector( -+ pcid_handle: &mut crate::driver_interface::PciFunctionHandle, -+ driver: &str, -+) -> Result { -+ Ok(pci_allocate_interrupt_vector(pcid_handle, driver)) -+} -+ - /// Allocate multiple interrupt vectors, from the IDT of the specified processor, returning the - /// start vector and the IRQ handles. - /// -diff --git a/drivers/pcid/src/driver_interface/mod.rs b/drivers/pcid/src/driver_interface/mod.rs -index bbc7304e..a77d79ec 100644 ---- a/drivers/pcid/src/driver_interface/mod.rs -+++ b/drivers/pcid/src/driver_interface/mod.rs -@@ -29,6 +29,10 @@ pub struct LegacyInterruptLine { - } - - impl LegacyInterruptLine { -+ /// Get an IRQ handle for this interrupt line. -+ pub fn try_irq_handle(self, driver: &str) -> Result { -+ Ok(self.irq_handle(driver)) -+ } - /// Get an IRQ handle for this interrupt line. - pub fn irq_handle(self, driver: &str) -> File { - if let Some((phandle, addr, cells)) = self.phandled { -@@ -452,6 +456,9 @@ impl PciFunctionHandle { - } - } - } -+ pub unsafe fn try_map_bar(&mut self, bir: u8) -> Result<&MappedBar, ()> { -+ Ok(self.map_bar(bir)) -+ } - pub unsafe fn map_bar(&mut self, bir: u8) -> &MappedBar { - let mapped_bar = &mut self.mapped_bars[bir as usize]; - if let Some(mapped_bar) = mapped_bar { -diff --git a/drivers/virtio-core/src/transport.rs b/drivers/virtio-core/src/transport.rs -index d3445d2d..2a316557 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("probe: {0}")] -+ Probe(&'static str), - } - - /// Returns the queue part sizes in bytes. diff --git a/local/patches/base/P0-inputd-named-producers.patch b/local/patches/base/P0-inputd-named-producers.patch index b6ba7a1fac..9092c0601e 100644 --- a/local/patches/base/P0-inputd-named-producers.patch +++ b/local/patches/base/P0-inputd-named-producers.patch @@ -1,7 +1,15 @@ -diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs --- a/drivers/inputd/src/main.rs +++ b/drivers/inputd/src/main.rs -@@ -35,6 +35,9 @@ +@@ -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}; +@@ -37,6 +37,9 @@ enum Handle { Producer, @@ -11,19 +19,18 @@ diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs Consumer { events: EventFlags, pending: Vec, -@@ -58,9 +61,11 @@ +@@ -60,8 +63,10 @@ struct InputScheme { - handles: BTreeMap, + handles: HandleMap, + devices: BTreeMap, - next_id: AtomicUsize, next_vt_id: AtomicUsize, + next_device_id: AtomicUsize, display: Option, vts: BTreeSet, -@@ -73,13 +78,30 @@ +@@ -74,12 +79,29 @@ has_new_events: bool, } @@ -45,10 +52,9 @@ diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs impl InputScheme { fn new() -> Self { Self { - handles: BTreeMap::new(), + handles: HandleMap::new(), + devices: BTreeMap::new(), - next_id: AtomicUsize::new(0), next_vt_id: AtomicUsize::new(2), // VT 1 is reserved for the bootlog + next_device_id: AtomicUsize::new(0), @@ -101,7 +107,7 @@ diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs } fn switch_vt(&mut self, new_active: usize) { -@@ -175,7 +237,13 @@ +@@ -170,7 +232,13 @@ let command = path_parts.next().ok_or(SysError::new(EINVAL))?; let handle_ty = match command { @@ -116,7 +122,7 @@ diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs "consumer" => { let vt = self.next_vt_id.fetch_add(1, Ordering::Relaxed); self.vts.insert(vt); -@@ -338,7 +406,7 @@ +@@ -330,7 +398,7 @@ } } @@ -125,18 +131,18 @@ diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs log::error!("producer tried to read"); return Err(SysError::new(EINVAL)); } -@@ -360,9 +428,7 @@ +@@ -352,9 +420,7 @@ ) -> syscall::Result { self.has_new_events = true; -- let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?; +- let handle = self.handles.get_mut(id)?; - - match handle { -+ let producer_kind = match self.handles.get(&id).ok_or(SysError::new(EINVAL))? { ++ let producer_kind = match self.handles.get(id)? { Handle::Control => { if buf.len() != size_of::() { log::error!("control tried to write incorrectly sized command"); -@@ -391,9 +457,10 @@ +@@ -383,9 +449,10 @@ log::error!("display tried to write"); return Err(SysError::new(EINVAL)); } @@ -149,17 +155,17 @@ diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs if buf.len() == 1 && buf[0] > 0xf4 { return Ok(1); -@@ -445,9 +512,6 @@ +@@ -437,9 +504,6 @@ } } -- let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?; +- let handle = self.handles.get_mut(id)?; - assert!(matches!(handle, Handle::Producer)); - let buf = unsafe { core::slice::from_raw_parts( (events.as_ptr()) as *const u8, -@@ -455,26 +519,11 @@ +@@ -447,26 +511,11 @@ ) }; @@ -191,7 +197,7 @@ diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs Ok(buf.len()) } -@@ -506,7 +555,7 @@ +@@ -496,7 +545,7 @@ *notified = false; Ok(EventFlags::empty()) } @@ -200,62 +206,20 @@ diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs log::error!("producer or control tried to use an event queue"); Err(SysError::new(EINVAL)) } -@@ -515,9 +564,15 @@ +@@ -505,7 +554,15 @@ } fn on_close(&mut self, id: usize) { -- let handle = self.handles.remove(&id).unwrap(); -+ let Some(handle) = self.handles.remove(&id) else { +- match self.handles.remove(id).unwrap() { ++ let Some(handle) = self.handles.remove(id) else { + log::warn!("received close for unknown input handle {id}"); + return; + }; - - match handle { ++ ++ match handle { + Handle::NamedProducer { name } => { + self.devices.remove(&name); + } Handle::Consumer { vt, .. } => { self.vts.remove(&vt); if self.active_vt == Some(vt) { -diff --git a/drivers/inputd/src/lib.rs b/drivers/inputd/src/lib.rs ---- a/drivers/inputd/src/lib.rs -+++ b/drivers/inputd/src/lib.rs -@@ -197,6 +197,38 @@ - pub vt: usize, - } - -+/// Handle for opening a named producer on the input scheme. -+/// Opens /scheme/input/producer/{name} -+pub struct NamedProducerHandle { -+ fd: File, -+} -+ -+impl NamedProducerHandle { -+ pub fn new(name: &str) -> io::Result { -+ if name.is_empty() { -+ return Err(io::Error::new( -+ io::ErrorKind::InvalidInput, -+ "input producer name must not be empty", -+ )); -+ } -+ -+ if name.contains('/') { -+ return Err(io::Error::new( -+ io::ErrorKind::InvalidInput, -+ "input producer name must not contain '/'", -+ )); -+ } -+ -+ let path = format!("/scheme/input/producer/{name}"); -+ File::open(path).map(|fd| Self { fd }) -+ } -+ -+ pub fn write_event(&mut self, event: &orbclient::Event) -> io::Result<()> { -+ self.fd.write(unsafe { any_as_u8_slice(event) })?; -+ Ok(()) -+ } -+} -+ - pub struct ProducerHandle(File); - - impl ProducerHandle { diff --git a/local/patches/base/P0-inputd-per-device-consumers.patch b/local/patches/base/P0-inputd-per-device-consumers.patch index 9eb85808f8..208826ba57 100644 --- a/local/patches/base/P0-inputd-per-device-consumers.patch +++ b/local/patches/base/P0-inputd-per-device-consumers.patch @@ -1,16 +1,15 @@ -diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs --- a/drivers/inputd/src/main.rs +++ b/drivers/inputd/src/main.rs -@@ -17,7 +17,7 @@ - use std::mem::transmute; +@@ -18,7 +18,7 @@ + use std::ops::ControlFlow; use std::sync::atomic::{AtomicUsize, Ordering}; -use inputd::{ControlEvent, VtEvent, VtEventKind}; +use inputd::{ControlEvent, HotplugEventHeader, VtEvent, VtEventKind}; use libredox::errno::ESTALE; - use redox_scheme::scheme::{SchemeState, SchemeSync}; -@@ -47,6 +47,17 @@ + use redox_scheme::scheme::SchemeSync; +@@ -49,6 +49,17 @@ notified: bool, vt: usize, }, @@ -28,7 +27,7 @@ diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs Display { events: EventFlags, pending: Vec, -@@ -88,6 +99,9 @@ +@@ -89,6 +100,9 @@ "control", ]; @@ -47,7 +46,7 @@ diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs return Err(SysError::new(EINVAL)); } -@@ -126,11 +140,57 @@ +@@ -126,9 +140,55 @@ let device_id = self.next_device_id.fetch_add(1, Ordering::SeqCst) as u32; self.devices.insert(name.to_owned(), device_id); @@ -55,8 +54,8 @@ diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs Ok(Handle::NamedProducer { name: name.to_owned(), }) - } - ++ } ++ + fn drain_pending_bytes(pending: &mut Vec, buf: &mut [u8]) -> usize { + let copy = core::cmp::min(pending.len(), buf.len()); + @@ -100,11 +99,9 @@ diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs + + self.has_new_events = true; + Ok(()) -+ } -+ + } + fn route_legacy_consumer_events(&mut self, buf: &[u8]) { - if let Some(active_vt) = self.active_vt { - for handle in self.handles.values_mut() { @@ -150,9 +210,21 @@ } } @@ -130,7 +127,7 @@ diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs } fn switch_vt(&mut self, new_active: usize) { -@@ -324,7 +396,24 @@ +@@ -319,7 +391,24 @@ is_earlyfb: command == "handle_early", } } @@ -155,7 +152,7 @@ diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs _ => { log::error!("invalid path '{path}'"); -@@ -380,13 +469,11 @@ +@@ -372,13 +461,11 @@ return Err(SysError::new(ESTALE)); } @@ -164,27 +161,28 @@ diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs - for (i, byte) in pending.drain(..copy).enumerate() { - buf[i] = byte; - } +- +- Ok(copy) + Ok(Self::drain_pending_bytes(pending, buf)) + } - -- Ok(copy) ++ + Handle::DeviceConsumer { pending, .. } | Handle::HotplugEvents { pending, .. } => { + Ok(Self::drain_pending_bytes(pending, buf)) } Handle::Display { pending, .. } => { -@@ -453,6 +540,10 @@ +@@ -443,6 +530,10 @@ + + Handle::Consumer { .. } => { log::error!("consumer tried to write"); - return Err(SysError::new(EINVAL)); - } -+ Handle::DeviceConsumer { .. } | Handle::HotplugEvents { .. } => { -+ log::error!("consumer or hotplug handle tried to write"); + return Err(SysError::new(EINVAL)); + } - Handle::Display { .. } => { - log::error!("display tried to write"); ++ Handle::DeviceConsumer { .. } | Handle::HotplugEvents { .. } => { ++ log::error!("consumer or hotplug handle tried to write"); return Err(SysError::new(EINVAL)); -@@ -541,6 +632,16 @@ + } + Handle::Display { .. } => { +@@ -531,6 +622,16 @@ ref mut events, ref mut notified, .. @@ -201,7 +199,7 @@ diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs } => { *events = flags; *notified = false; -@@ -571,7 +672,12 @@ +@@ -561,7 +662,12 @@ match handle { Handle::NamedProducer { name } => { @@ -215,14 +213,10 @@ diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs } Handle::Consumer { vt, .. } => { self.vts.remove(&vt); -@@ -658,6 +764,28 @@ - socket_file.write_response( - Response::post_fevent(*id, EventFlags::EVENT_READ.bits()), - SignalBehavior::Restart, -+ )?; -+ -+ *notified = true; -+ } +@@ -639,6 +745,28 @@ + + *notified = true; + } + Handle::DeviceConsumer { + events, + pending, @@ -241,210 +235,10 @@ diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs + socket_file.write_response( + Response::post_fevent(*id, EventFlags::EVENT_READ.bits()), + SignalBehavior::Restart, - )?; - - *notified = true; -diff --git a/drivers/inputd/src/lib.rs b/drivers/inputd/src/lib.rs ---- a/drivers/inputd/src/lib.rs -+++ b/drivers/inputd/src/lib.rs -@@ -1,5 +1,5 @@ - use std::fs::{File, OpenOptions}; --use std::io::{self, Read, Write}; -+use std::io::{self, ErrorKind, Read, Write}; - use std::mem::size_of; - use std::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, RawFd}; - use std::os::unix::fs::OpenOptionsExt; -@@ -31,6 +31,24 @@ - slice::from_raw_parts_mut((p as *mut T) as *mut u8, size_of::()) - } - -+fn validate_input_name(kind: &str, name: &str) -> io::Result<()> { -+ if name.is_empty() { -+ return Err(io::Error::new( -+ io::ErrorKind::InvalidInput, -+ format!("input {kind} name must not be empty"), -+ )); -+ } ++ )?; + -+ if name.contains('/') { -+ return Err(io::Error::new( -+ io::ErrorKind::InvalidInput, -+ format!("input {kind} name must not contain '/'"), -+ )); -+ } -+ -+ Ok(()) -+} -+ - pub struct ConsumerHandle(File); - - pub enum ConsumerHandleEvent<'a> { -@@ -197,6 +215,22 @@ - pub vt: usize, - } - -+#[derive(Debug, Clone, Copy)] -+#[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, -+} -+ - /// Handle for opening a named producer on the input scheme. - /// Opens /scheme/input/producer/{name} - pub struct NamedProducerHandle { -@@ -205,19 +239,7 @@ - - impl NamedProducerHandle { - pub fn new(name: &str) -> io::Result { -- if name.is_empty() { -- return Err(io::Error::new( -- io::ErrorKind::InvalidInput, -- "input producer name must not be empty", -- )); -- } -- -- if name.contains('/') { -- return Err(io::Error::new( -- io::ErrorKind::InvalidInput, -- "input producer name must not contain '/'", -- )); -- } -+ validate_input_name("producer", name)?; - - let path = format!("/scheme/input/producer/{name}"); - File::open(path).map(|fd| Self { fd }) -@@ -229,6 +251,124 @@ - } - } - -+pub struct DeviceConsumerHandle { -+ fd: File, -+} -+ -+impl DeviceConsumerHandle { -+ pub fn new(device_name: &str) -> io::Result { -+ validate_input_name("device", device_name)?; -+ -+ let fd = OpenOptions::new() -+ .read(true) -+ .custom_flags(O_NONBLOCK as i32) -+ .open(format!("/scheme/input/{device_name}"))?; -+ -+ Ok(Self { fd }) -+ } -+ -+ pub fn event_handle(&self) -> BorrowedFd<'_> { -+ self.fd.as_fd() -+ } -+ -+ pub fn read_event(&mut self) -> io::Result> { -+ let mut raw = [0_u8; size_of::()]; -+ -+ match self.fd.read(&mut raw) { -+ Ok(0) => Ok(None), -+ Ok(read) => { -+ assert_eq!(read, raw.len()); -+ Ok(Some(unsafe { -+ core::ptr::read_unaligned(raw.as_ptr().cast::()) -+ })) -+ } -+ Err(err) if err.kind() == ErrorKind::WouldBlock => Ok(None), -+ Err(err) => Err(err), -+ } -+ } -+} -+ -+pub struct HotplugHandle { -+ fd: File, -+ partial: Vec, -+} -+ -+impl HotplugHandle { -+ pub fn new() -> io::Result { -+ let fd = OpenOptions::new() -+ .read(true) -+ .custom_flags(O_NONBLOCK as i32) -+ .open("/scheme/input/events")?; -+ -+ Ok(Self { -+ fd, -+ partial: Vec::new(), -+ }) -+ } -+ -+ pub fn event_handle(&self) -> BorrowedFd<'_> { -+ self.fd.as_fd() -+ } -+ -+ pub fn read_event(&mut self) -> io::Result> { -+ let mut buf = [0_u8; 1024]; -+ -+ loop { -+ if let Some(event) = Self::try_parse_event(&mut self.partial)? { -+ return Ok(Some(event)); -+ } -+ -+ match self.fd.read(&mut buf) { -+ Ok(0) => return Ok(None), -+ Ok(read) => self.partial.extend_from_slice(&buf[..read]), -+ Err(err) if err.kind() == ErrorKind::WouldBlock => return Ok(None), -+ Err(err) => return Err(err), -+ } -+ } -+ } -+ -+ fn try_parse_event(partial: &mut Vec) -> io::Result> { -+ if partial.len() < size_of::() { -+ return Ok(None); -+ } -+ -+ let header = -+ unsafe { core::ptr::read_unaligned(partial.as_ptr().cast::()) }; -+ let name_len = usize::try_from(header.name_len).map_err(|_| { -+ io::Error::new( -+ io::ErrorKind::InvalidData, -+ "invalid input hotplug name length", -+ ) -+ })?; -+ let total_len = size_of::() -+ .checked_add(name_len) -+ .ok_or_else(|| { -+ io::Error::new(io::ErrorKind::InvalidData, "input hotplug event too large") -+ })?; -+ -+ if partial.len() < total_len { -+ return Ok(None); -+ } -+ -+ let name = std::str::from_utf8(&partial[size_of::()..total_len]) -+ .map_err(|_| { -+ io::Error::new( -+ io::ErrorKind::InvalidData, -+ "input hotplug name is not UTF-8", -+ ) -+ })? -+ .to_owned(); -+ -+ partial.drain(..total_len); -+ -+ Ok(Some(HotplugEvent { -+ kind: header.kind, -+ device_id: header.device_id, -+ name, -+ })) -+ } -+} -+ - pub struct ProducerHandle(File); - - impl ProducerHandle { ++ *notified = true; ++ } + _ => {} + } + } diff --git a/local/patches/base/P1-acpid-acpi-core.patch b/local/patches/base/P1-acpid-acpi-core.patch deleted file mode 100644 index ae12c6bbc4..0000000000 --- a/local/patches/base/P1-acpid-acpi-core.patch +++ /dev/null @@ -1,1682 +0,0 @@ -diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs -index 94a1eb17..dad44d1d 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(()) - } -@@ -316,7 +928,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 +955,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 +991,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 +1002,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 +1183,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 +1197,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 +1322,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 +1390,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 +1540,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 +1552,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 +1825,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 +1833,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 +1903,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 +1949,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 +1969,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 +1997,16 @@ 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); -+ -+ if let Err(error) = context.refresh_s5_values() { -+ log::warn!("Failed to evaluate \\_S5 during FADT init: {error}"); -+ } - } - } - diff --git a/local/patches/base/P1-acpid-power-enumeration.patch b/local/patches/base/P1-acpid-power-enumeration.patch deleted file mode 100644 index 9fb27b3fbd..0000000000 --- a/local/patches/base/P1-acpid-power-enumeration.patch +++ /dev/null @@ -1,1956 +0,0 @@ -diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs -index 94a1eb17..1ceb27be 100644 ---- a/drivers/acpid/src/acpi.rs -+++ b/drivers/acpid/src/acpi.rs -@@ -8,6 +8,7 @@ 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}; -@@ -25,6 +26,8 @@ use amlserde::{AmlSerde, AmlSerdeValue}; - - #[cfg(target_arch = "x86_64")] - pub mod dmar; -+#[cfg(target_arch = "x86_64")] -+use self::dmar::Dmar; - use crate::aml_physmem::{AmlPageCache, AmlPhysMemHandler}; - - /// The raw SDT header struct, as defined by the ACPI specification. -@@ -206,6 +209,464 @@ 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::{ -+ apply_acpi_table_quirks, parse_acpi_table_quirks, smbios_string, DmiInfo, Sdt, SdtHeader, -+ }; -+ use std::sync::Arc; -+ -+ fn make_sdt(signature: [u8; 4]) -> Sdt { -+ let header = SdtHeader { -+ signature, -+ length: std::mem::size_of::() as u32, -+ revision: 1, -+ checksum: 0, -+ oem_id: *b"REDBRR", -+ oem_table_id: *b"QUIRKDEM", -+ oem_revision: 0, -+ creator_id: 0, -+ creator_revision: 0, -+ }; -+ let mut bytes = [0u8; std::mem::size_of::()]; -+ // SAFETY: SdtHeader is #[repr(C, packed)], [u8; N] is Plain, sizes match. -+ unsafe { -+ std::ptr::copy_nonoverlapping( -+ &header as *const SdtHeader as *const u8, -+ &mut bytes as *mut [u8] as *mut u8, -+ std::mem::size_of::(), -+ ); -+ } -+ let sum = bytes -+ .iter() -+ .copied() -+ .fold(0u8, |acc, byte| acc.wrapping_add(byte)); -+ bytes[9] = 0u8.wrapping_sub(sum); -+ Sdt::new(Arc::<[u8]>::from(bytes)).unwrap() -+ } -+ -+ #[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); -+ } -+ -+ // 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; - -@@ -356,6 +817,12 @@ impl AmlSymbols { - - self.symbol_cache = symbol_cache; - } -+ -+ pub fn reset(&mut self) { -+ self.aml_context = None; -+ self.symbol_cache = FxHashMap::default(); -+ *self.page_cache.lock().unwrap() = AmlPageCache::default(); -+ } - } - - #[derive(Debug, Error)] -@@ -375,10 +842,122 @@ impl From for AmlEvalError { - } - } - -+#[derive(Clone, Debug)] -+pub struct AcpiPowerAdapter { -+ pub id: String, -+ pub path: String, -+ pub online: bool, -+} -+ -+#[derive(Clone, Debug)] -+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 { -+ if self.adapters.iter().any(|adapter| adapter.online) { -+ return false; -+ } -+ -+ self.batteries -+ .iter() -+ .any(|battery| battery.state & 0x1 != 0) -+ } -+} -+ -+fn sanitize_power_id(path: &str) -> String { -+ let sanitized = path -+ .chars() -+ .map(|ch| if ch.is_ascii_alphanumeric() { ch } else { '_' }) -+ .collect::() -+ .trim_matches('_') -+ .to_string(); -+ -+ if sanitized.is_empty() { -+ String::from("device") -+ } else { -+ sanitized -+ } -+} -+ -+fn aml_value_as_integer(value: &AmlSerdeValue) -> Option { -+ match value { -+ AmlSerdeValue::Integer(value) => Some(*value), -+ _ => None, -+ } -+} -+ -+fn aml_value_as_string(value: &AmlSerdeValue) -> Option { -+ match value { -+ AmlSerdeValue::String(value) => { -+ let trimmed = value.trim(); -+ if trimmed.is_empty() { -+ None -+ } else { -+ Some(trimmed.to_string()) -+ } -+ } -+ _ => None, -+ } -+} -+ -+fn power_unit_name(value: u64) -> Option<&'static str> { -+ match value { -+ 0 => Some("mWh"), -+ 1 => Some("mAh"), -+ _ => None, -+ } -+} -+ -+fn battery_technology_name(value: u64) -> Option<&'static str> { -+ match value { -+ 0 => Some("non-rechargeable"), -+ 1 => Some("rechargeable"), -+ _ => None, -+ } -+} -+ -+fn battery_percentage(remaining_capacity: u64, full_capacity: u64) -> Option { -+ if full_capacity == 0 { -+ return None; -+ } -+ -+ Some((remaining_capacity as f64 * 100.0 / full_capacity as f64).clamp(0.0, 100.0)) -+} -+ - pub struct AcpiContext { - tables: Vec, - dsdt: Option, - fadt: Option, -+ pm1a_cnt_blk: u64, -+ pm1b_cnt_blk: u64, -+ s5_values: RwLock>, -+ reset_reg: Option, -+ reset_value: u8, -+ -+ pci_fd: RwLock>, - - aml_symbols: RwLock, - -@@ -388,16 +967,187 @@ pub struct AcpiContext { - sdt_order: RwLock>>, - - pub next_ctx: RwLock, -+ dmi_info: Option, - } - - impl AcpiContext { -+ fn evaluate_acpi_object( -+ &self, -+ path: &str, -+ object: &str, -+ args: &[u64], -+ ) -> Result { -+ let full_path = format!("{path}.{object}"); -+ let aml_name = -+ AmlName::from_str(&full_path).map_err(|_| AmlEvalError::DeserializationError)?; -+ let args = args -+ .iter() -+ .copied() -+ .map(AmlSerdeValue::Integer) -+ .collect::>(); -+ -+ self.aml_eval(aml_name, args) -+ } -+ -+ fn try_evaluate_acpi_object( -+ &self, -+ path: &str, -+ object: &str, -+ args: &[u64], -+ ) -> Option { -+ match self.evaluate_acpi_object(path, object, args) { -+ Ok(value) => Some(value), -+ Err(error) => { -+ log::debug!("Failed to evaluate {}.{}: {:?}", path, object, error); -+ None -+ } -+ } -+ } -+ -+ fn power_device_paths(&self) -> Result, AmlEvalError> { -+ let mut symbols = self.aml_symbols.write(); -+ let pci_fd = self.pci_fd.read(); -+ let interpreter = symbols.aml_context_mut(pci_fd.as_ref())?; -+ -+ let mut names = Vec::with_capacity(512); -+ { -+ let mut namespace = interpreter.namespace.lock(); -+ namespace -+ .traverse(|level_aml_name, level| { -+ for (child_seg, _) in level.values.iter() { -+ if let Ok(aml_name) = -+ AmlName::from_name_seg(child_seg.to_owned()).resolve(level_aml_name) -+ { -+ names.push(aml_name); -+ } -+ } -+ Ok(true) -+ }) -+ .map_err(AmlEvalError::from)?; -+ } -+ -+ let mut namespace = interpreter.namespace.lock(); -+ let mut devices = Vec::new(); -+ -+ for name in names { -+ let Ok(object) = namespace.get(name.clone()) else { -+ continue; -+ }; -+ -+ if matches!(object.deref(), Object::Device) { -+ devices.push(name.to_string()); -+ } -+ } -+ -+ Ok(devices) -+ } -+ -+ fn power_device_present(&self, path: &str) -> bool { -+ match self.try_evaluate_acpi_object(path, "_STA", &[]) { -+ Some(AmlSerdeValue::Integer(status)) => status & 0x1 != 0, -+ Some(_) => false, -+ None => true, -+ } -+ } -+ -+ fn power_adapter_from_path(&self, path: &str) -> Option { -+ let online = match self.try_evaluate_acpi_object(path, "_PSR", &[])? { -+ AmlSerdeValue::Integer(value) => value != 0, -+ _ => return None, -+ }; -+ -+ Some(AcpiPowerAdapter { -+ id: sanitize_power_id(path), -+ path: path.to_string(), -+ online, -+ }) -+ } -+ -+ fn power_battery_from_path(&self, path: &str) -> Option { -+ let bif_contents = match self.try_evaluate_acpi_object(path, "_BIF", &[])? { -+ AmlSerdeValue::Package { contents } => contents, -+ _ => return None, -+ }; -+ let bst_contents = match self.try_evaluate_acpi_object(path, "_BST", &[])? { -+ AmlSerdeValue::Package { contents } => contents, -+ _ => return None, -+ }; -+ -+ if bif_contents.len() < 13 || bst_contents.len() < 4 { -+ return None; -+ } -+ -+ let state = aml_value_as_integer(&bst_contents[0])?; -+ let present_rate = aml_value_as_integer(&bst_contents[1]); -+ let remaining_capacity = aml_value_as_integer(&bst_contents[2]); -+ let present_voltage = aml_value_as_integer(&bst_contents[3]); -+ -+ let design_capacity = aml_value_as_integer(&bif_contents[1]); -+ let last_full_capacity = aml_value_as_integer(&bif_contents[2]); -+ let design_voltage = aml_value_as_integer(&bif_contents[4]); -+ let percentage = remaining_capacity.and_then(|remaining| { -+ let full_capacity = last_full_capacity.or(design_capacity)?; -+ battery_percentage(remaining, full_capacity) -+ }); -+ -+ Some(AcpiBattery { -+ id: sanitize_power_id(path), -+ path: path.to_string(), -+ state, -+ present_rate, -+ remaining_capacity, -+ present_voltage, -+ power_unit: aml_value_as_integer(&bif_contents[0]) -+ .and_then(power_unit_name) -+ .map(str::to_string), -+ design_capacity, -+ last_full_capacity, -+ design_voltage, -+ technology: aml_value_as_integer(&bif_contents[3]) -+ .and_then(battery_technology_name) -+ .map(str::to_string), -+ model: aml_value_as_string(&bif_contents[9]), -+ serial: aml_value_as_string(&bif_contents[10]), -+ battery_type: aml_value_as_string(&bif_contents[11]), -+ oem_info: aml_value_as_string(&bif_contents[12]), -+ percentage, -+ }) -+ } -+ -+ pub fn power_snapshot(&self) -> Result { -+ let mut adapters = Vec::new(); -+ let mut batteries = Vec::new(); -+ -+ for device_path in self.power_device_paths()? { -+ if !self.power_device_present(&device_path) { -+ continue; -+ } -+ -+ if let Some(adapter) = self.power_adapter_from_path(&device_path) { -+ adapters.push(adapter); -+ } -+ if let Some(battery) = self.power_battery_from_path(&device_path) { -+ batteries.push(battery); -+ } -+ } -+ -+ adapters.sort_by(|left, right| left.id.cmp(&right.id)); -+ batteries.sort_by(|left, right| left.id.cmp(&right.id)); -+ -+ Ok(AcpiPowerSnapshot { -+ adapters, -+ batteries, -+ }) -+ } -+ - pub fn aml_eval( - &self, - symbol: AmlName, - 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 -@@ -424,6 +1174,54 @@ impl AcpiContext { - .flatten() - } - -+ pub fn evaluate_acpi_method( -+ &self, -+ path: &str, -+ method: &str, -+ args: &[u64], -+ ) -> Result, AmlEvalError> { -+ match self.evaluate_acpi_object(path, method, 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(&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(&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(&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, - ec: Vec<(RegionSpace, Box)>, -@@ -440,10 +1238,20 @@ impl AcpiContext { - }) - .collect::>(); - -+ let dmi_info = load_dmi_info(); -+ let tables = apply_acpi_table_quirks(tables, dmi_info.as_ref()); -+ - let mut this = Self { - tables, - dsdt: None, - fadt: None, -+ pm1a_cnt_blk: 0, -+ pm1b_cnt_blk: 0, -+ s5_values: RwLock::new(None), -+ reset_reg: None, -+ reset_value: 0, -+ -+ pci_fd: RwLock::new(None), - - // Temporary values - aml_symbols: RwLock::new(AmlSymbols::new(ec)), -@@ -451,6 +1259,7 @@ impl AcpiContext { - next_ctx: RwLock::new(0), - - sdt_order: RwLock::new(Vec::new()), -+ dmi_info, - }; - - for table in &this.tables { -@@ -458,7 +1267,10 @@ impl AcpiContext { - } - - Fadt::init(&mut this); -- //TODO (hangs on real hardware): Dmar::init(&this); -+ // DMAR (Intel VT-d) init — previously disabled due to iterator bug (type_bytes copied -+ // instead of len_bytes in DmarRawIter). Safe to call now: on AMD systems, no DMAR table -+ // exists and this returns early with a warning. -+ Dmar::init(&this); - - this - } -@@ -521,22 +1333,22 @@ impl AcpiContext { - pub fn tables(&self) -> &[Sdt] { - &self.tables - } -+ pub fn dmi_info(&self) -> Option<&DmiInfo> { -+ self.dmi_info.as_ref() -+ } - pub fn new_index(&self, signature: &SdtSignature) { - self.sdt_order.write().push(Some(*signature)); - } - - 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> { - // return the cached value if it exists - let symbols = self.aml_symbols.read(); - if !symbols.symbols_cache().is_empty() { -@@ -549,8 +1361,9 @@ impl AcpiContext { - log::trace!("Creating symbols list"); - - let mut aml_symbols = self.aml_symbols.write(); -+ let pci_fd = self.pci_fd.read(); - -- aml_symbols.build_cache(pci_fd); -+ aml_symbols.build_cache(pci_fd.as_ref()); - - // return the cached value - Ok(RwLockWriteGuard::downgrade(aml_symbols)) -@@ -559,99 +1372,163 @@ impl AcpiContext { - /// Discard any cached symbols list. To be called if the AML namespace changes. - pub fn aml_symbols_reset(&self) { - let mut aml_symbols = self.aml_symbols.write(); -- aml_symbols.symbol_cache = FxHashMap::default(); -+ aml_symbols.reset(); - } - -- /// 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; -- } -- let fadt = match self.fadt() { -- Some(fadt) => fadt, -- None => { -- log::error!("Cannot set global S-state due to missing FADT."); -- return; -- } -- }; -+ pub fn pci_ready(&self) -> bool { -+ self.pci_fd.read().is_some() -+ } - -- let port = fadt.pm1a_control_block as u16; -- let mut val = 1 << 13; -+ pub fn register_pci_fd(&self, pci_fd: libredox::Fd) -> Result<(), libredox::Fd> { -+ let mut aml_symbols = self.aml_symbols.write(); -+ let mut registered_pci_fd = self.pci_fd.write(); - -- let aml_symbols = self.aml_symbols.read(); -+ if registered_pci_fd.is_some() { -+ return Err(pci_fd); -+ } - -- 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; -- } -- }; -+ *registered_pci_fd = Some(pci_fd); - -- 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; -- } -- }, -- None => { -- log::error!("Cannot set S-state, AML context not initialized"); -- return; -- } -- }; -+ if aml_symbols.aml_context.is_some() || !aml_symbols.symbol_cache.is_empty() { -+ log::warn!("PCI registration arrived after AML init; rebuilding AML interpreter state"); -+ aml_symbols.reset(); -+ } - -- 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(()) -+ } - -- let slp_typa = match package[0].deref() { -- acpi::aml::object::Object::Integer(i) => i.to_owned(), -- _ => { -- log::error!("typa is not an Integer"); -- return; -- } -- }; -- let slp_typb = match package[1].deref() { -- acpi::aml::object::Object::Integer(i) => i.to_owned(), -- _ => { -- log::error!("typb is not an Integer"); -- return; -- } -+ pub fn acpi_shutdown(&self) { -+ let Some((slp_typa_s5, slp_typb_s5)) = self.ensure_s5_values() else { -+ log::error!("Cannot shut down with ACPI: failed to resolve \\_S5 sleep type values"); -+ return; - }; - -- log::trace!("Shutdown SLP_TYPa {:X}, SLP_TYPb {:X}", slp_typa, slp_typb); -- val |= slp_typa as u16; -+ let pm1a_value = (u16::from(slp_typa_s5) << 10) | 0x2000; -+ let pm1b_value = (u16::from(slp_typb_s5) << 10) | 0x2000; - - #[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); -- } -+ let Ok(pm1a_port) = u16::try_from(self.pm1a_cnt_blk) else { -+ log::error!("PM1a_CNT_BLK address is invalid: {:#X}", self.pm1a_cnt_blk); -+ return; -+ }; - -- // TODO: Handle SLP_TYPb -+ 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(_) => { -+ log::error!("PM1b_CNT_BLK address is invalid: {:#X}", self.pm1b_cnt_blk); -+ } -+ } -+ } -+ } - - #[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 -+ "Cannot shutdown with ACPI PM1_CNT writes on this architecture (PM1a={:#X}, PM1b={:#X})", -+ self.pm1a_cnt_blk, -+ self.pm1b_cnt_blk - ); - } -+ } -+ -+ pub fn acpi_reboot(&self) { -+ 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); -+ } -+ None => { -+ log::error!("Cannot reboot with ACPI: no reset register present in FADT"); -+ } -+ } -+ } -+ -+ /// 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; -+ } -+ -+ if self.fadt().is_none() { -+ log::error!("Cannot set global S-state due to missing FADT."); -+ return; -+ } -+ -+ self.acpi_shutdown(); - - loop { - core::hint::spin_loop(); - } - } -+ -+ fn evaluate_s5_values(&self) -> Option<(u8, u8)> { -+ match AmlName::from_str("\\_S5") { -+ Ok(s5_name) => match self.aml_eval(s5_name, Vec::new()) { -+ Ok(AmlSerdeValue::Package { contents }) => match (contents.get(0), 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)) => Some((slp_typa_s5, slp_typb_s5)), -+ _ => { -+ log::warn!("\\_S5 values do not fit in u8: {:?}", contents); -+ None -+ } -+ }, -+ _ => { -+ log::warn!("\\_S5 package did not contain two integers: {:?}", contents); -+ None -+ } -+ }, -+ Ok(value) => { -+ log::warn!("\\_S5 returned unexpected AML value: {:?}", value); -+ None -+ } -+ Err(error) => { -+ log::warn!("Failed to evaluate \\_S5: {:?}", error); -+ None -+ } -+ }, -+ Err(error) => { -+ log::warn!("Could not build AmlName for \\_S5: {:?}", error); -+ None -+ } -+ } -+ } -+ -+ fn ensure_s5_values(&self) -> Option<(u8, u8)> { -+ if let Some(values) = *self.s5_values.read() { -+ return Some(values); -+ } -+ -+ let values = self.evaluate_s5_values()?; -+ *self.s5_values.write() = Some(values); -+ Some(values) -+ } - } - - #[repr(C, packed)] -@@ -707,7 +1584,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 +1592,77 @@ 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) { -+ match self.address_space { -+ 0 => { -+ let raw_address = self.address; -+ let Ok(address) = usize::try_from(raw_address) else { -+ log::error!( -+ "Reset register physical address is invalid: {:#X}", -+ raw_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(self.address) { -+ Ok(port) => { -+ Pio::::new(port).write(value); -+ } -+ Err(_) => { -+ let raw_address = self.address; -+ log::error!("Reset register I/O port is invalid: {:#X}", raw_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 +1671,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 {} - -@@ -793,9 +1736,27 @@ 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,6 +1766,10 @@ 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/scheme.rs b/drivers/acpid/src/scheme.rs -index 5a5040c3..5f1232bd 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,19 @@ 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}; - 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, -+}; - - pub struct AcpiScheme<'acpi, 'sock> { - ctx: &'acpi AcpiContext, - handles: HandleMap>, -- pci_fd: Option, - socket: &'sock Socket, - } - -@@ -41,10 +42,156 @@ enum HandleKind<'a> { - Table(SdtSignature), - Symbols(RwLockReadGuard<'a, AmlSymbols>), - Symbol { name: String, description: String }, -+ 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 -+} -+ - impl HandleKind<'_> { - fn is_dir(&self) -> bool { - match self { -@@ -53,6 +200,14 @@ impl HandleKind<'_> { - Self::Table(_) => false, - Self::Symbols(_) => true, - Self::Symbol { .. } => 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 +220,18 @@ impl HandleKind<'_> { - .ok_or(Error::new(EBADFD))? - .length(), - Self::Symbol { description, .. } => description.len(), -+ Self::Dmi(contents) => contents.len(), -+ Self::PowerFile(contents) => contents.len(), - // Directories -- Self::TopLevel | Self::Symbols(_) | Self::Tables => 0, -+ Self::TopLevel -+ | Self::Symbols(_) -+ | 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 +242,99 @@ 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| { -+ log::warn!("Failed to build ACPI power snapshot: {:?}", error); -+ Error::new(EIO) -+ }) -+ } -+ -+ fn power_handle(&self, path: &str) -> Result> { -+ let normalized = path.trim_matches('/'); -+ -+ 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 { -@@ -184,9 +438,9 @@ impl SchemeSync for AcpiScheme<'_, '_> { - HandleKind::SchemeRoot => { - // TODO: arrayvec - let components = { -- let mut v = arrayvec::ArrayVec::<&str, 3>::new(); -+ let mut v = arrayvec::ArrayVec::<&str, 4>::new(); - let it = path.split('/'); -- for component in it.take(3) { -+ for component in it.take(4) { - v.push(component); - } - -@@ -195,6 +449,24 @@ impl SchemeSync for AcpiScheme<'_, '_> { - - match &*components { - [""] => HandleKind::TopLevel, -+ ["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, - -@@ -204,7 +476,11 @@ impl SchemeSync for AcpiScheme<'_, '_> { - } - - ["symbols"] => { -- if let Ok(aml_symbols) = self.ctx.aml_symbols(self.pci_fd.as_ref()) { -+ 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)); -@@ -212,6 +488,12 @@ impl SchemeSync for AcpiScheme<'_, '_> { - } - - ["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(), -@@ -225,6 +507,15 @@ impl SchemeSync for AcpiScheme<'_, '_> { - _ => 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::Symbols(ref aml_symbols) => { - if let Some(description) = aml_symbols.lookup(path) { - HandleKind::Symbol { -@@ -235,6 +526,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 +604,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 +617,8 @@ impl SchemeSync for AcpiScheme<'_, '_> { - .ok_or(Error::new(EBADFD))? - .as_slice(), - HandleKind::Symbol { description, .. } => description.as_bytes(), -+ HandleKind::Dmi(contents) => contents.as_bytes(), -+ HandleKind::PowerFile(contents) => contents.as_bytes(), - _ => return Err(Error::new(EINVAL)), - }; - -@@ -328,11 +638,11 @@ 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"]; -+ const TOPLEVEL_ENTRIES: &[&str] = &["tables", "symbols", "dmi", "power"]; - - for (idx, name) in TOPLEVEL_ENTRIES - .iter() -@@ -347,6 +657,111 @@ impl SchemeSync for AcpiScheme<'_, '_> { - })?; - } - } -+ 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::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 power_adapter_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::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() -@@ -470,10 +885,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 +896,38 @@ impl SchemeSync for AcpiScheme<'_, '_> { - self.handles.remove(id); - } - } -+ -+#[cfg(test)] -+mod tests { -+ use super::dmi_contents; -+ use crate::acpi::DmiInfo; -+ -+ #[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); -+ } -+} diff --git a/local/patches/base/P1-acpid-scheme-surface.patch b/local/patches/base/P1-acpid-scheme-surface.patch deleted file mode 100644 index 0e7b0cd52d..0000000000 --- a/local/patches/base/P1-acpid-scheme-surface.patch +++ /dev/null @@ -1,719 +0,0 @@ -diff --git a/drivers/acpid/src/scheme.rs b/drivers/acpid/src/scheme.rs -index 5a5040c3..7070e8b9 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,19 @@ 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, -+}; - - pub struct AcpiScheme<'acpi, 'sock> { - ctx: &'acpi AcpiContext, - handles: HandleMap>, -- pci_fd: Option, - socket: &'sock Socket, - } - -@@ -41,10 +42,170 @@ enum HandleKind<'a> { - Table(SdtSignature), - Symbols(RwLockReadGuard<'a, AmlSymbols>), - Symbol { name: String, description: 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), -+ ("dmi", DirentKind::Directory), -+ ("reboot", DirentKind::Regular), -+ ]; -+ if power_available { -+ entries.push(("power", DirentKind::Directory)); -+ } -+ entries -+} -+ - impl HandleKind<'_> { - fn is_dir(&self) -> bool { - match self { -@@ -53,6 +214,15 @@ impl HandleKind<'_> { - Self::Table(_) => false, - Self::Symbols(_) => true, - Self::Symbol { .. } => 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 +235,19 @@ impl HandleKind<'_> { - .ok_or(Error::new(EBADFD))? - .length(), - Self::Symbol { description, .. } => description.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::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 +258,111 @@ 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 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 { -@@ -184,9 +466,9 @@ impl SchemeSync for AcpiScheme<'_, '_> { - HandleKind::SchemeRoot => { - // TODO: arrayvec - let components = { -- let mut v = arrayvec::ArrayVec::<&str, 3>::new(); -+ let mut v = arrayvec::ArrayVec::<&str, 4>::new(); - let it = path.split('/'); -- for component in it.take(3) { -+ for component in it.take(4) { - v.push(component); - } - -@@ -195,6 +477,25 @@ impl SchemeSync for AcpiScheme<'_, '_> { - - 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, - -@@ -204,7 +505,11 @@ impl SchemeSync for AcpiScheme<'_, '_> { - } - - ["symbols"] => { -- if let Ok(aml_symbols) = self.ctx.aml_symbols(self.pci_fd.as_ref()) { -+ 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)); -@@ -212,6 +517,12 @@ impl SchemeSync for AcpiScheme<'_, '_> { - } - - ["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(), -@@ -225,6 +536,15 @@ impl SchemeSync for AcpiScheme<'_, '_> { - _ => 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::Symbols(ref aml_symbols) => { - if let Some(description) = aml_symbols.lookup(path) { - HandleKind::Symbol { -@@ -235,6 +555,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 +633,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 +646,8 @@ impl SchemeSync for AcpiScheme<'_, '_> { - .ok_or(Error::new(EBADFD))? - .as_slice(), - HandleKind::Symbol { description, .. } => description.as_bytes(), -+ HandleKind::Dmi(contents) => contents.as_bytes(), -+ HandleKind::PowerFile(contents) => contents.as_bytes(), - _ => return Err(Error::new(EINVAL)), - }; - -@@ -328,13 +667,82 @@ 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::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 +751,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 +886,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 +944,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 +955,58 @@ impl SchemeSync for AcpiScheme<'_, '_> { - self.handles.remove(id); - } - } -+ -+#[cfg(test)] -+mod tests { -+ use super::{dmi_contents, 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) -+ })); -+ } -+} diff --git a/local/patches/base/P1-pci-irq-wave1-3.patch b/local/patches/base/P1-pci-irq-wave1-3.patch deleted file mode 100644 index 770df8eb11..0000000000 --- a/local/patches/base/P1-pci-irq-wave1-3.patch +++ /dev/null @@ -1,1184 +0,0 @@ -diff --git a/daemon/src/lib.rs b/daemon/src/lib.rs -index 9f507221..f74fe715 100644 ---- a/daemon/src/lib.rs -+++ b/daemon/src/lib.rs -@@ -57,25 +57,28 @@ impl Daemon { - /// 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) { -+ pub fn spawn(mut cmd: Command) -> io::Result<()> { - let (mut read_pipe, write_pipe) = io::pipe().unwrap(); - - 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}"), -+ )), - } - } - } -diff --git a/drivers/audio/ac97d/src/main.rs b/drivers/audio/ac97d/src/main.rs -index ffa8a94b..deef5343 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,7 +56,10 @@ 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"); - -diff --git a/drivers/hwd/src/backend/acpi.rs b/drivers/hwd/src/backend/acpi.rs -index 3da41d63..c24dfc4b 100644 ---- a/drivers/hwd/src/backend/acpi.rs -+++ b/drivers/hwd/src/backend/acpi.rs -@@ -14,7 +14,7 @@ impl Backend for AcpiBackend { - // 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")); -+ let _ = daemon::Daemon::spawn(Command::new("acpid")); - - Ok(Self { rxsdt }) - } -diff --git a/drivers/hwd/src/main.rs b/drivers/hwd/src/main.rs -index 79360e34..4a2b9469 100644 ---- a/drivers/hwd/src/main.rs -+++ b/drivers/hwd/src/main.rs -@@ -38,7 +38,7 @@ 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")); -+ let _ = daemon::Daemon::spawn(process::Command::new("pcid")); - - daemon.ready(); - -diff --git a/drivers/pcid-spawner/src/main.rs b/drivers/pcid-spawner/src/main.rs -index a968f4d4..cc3d2947 100644 ---- a/drivers/pcid-spawner/src/main.rs -+++ b/drivers/pcid-spawner/src/main.rs -@@ -55,10 +55,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 +68,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 +86,42 @@ fn main() -> Result<()> { - let mut command = Command::new(program); - command.args(args); - -- log::info!("pcid-spawner: spawn {:?}", command); -- -- handle.enable_device(); -+ 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; -+ } - - 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 let Err(err) = daemon::Daemon::spawn(command) { -+ log::error!( -+ "pcid-spawner: spawn/readiness failed for {}: {}", -+ device_addr, -+ err -+ ); -+ } -+ 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/driver_handler.rs b/drivers/pcid/src/driver_handler.rs -index f70a7f6d..bd0db746 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, - }) -@@ -266,7 +276,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 +288,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..7eaade51 100644 ---- a/drivers/pcid/src/driver_interface/bar.rs -+++ b/drivers/pcid/src/driver_interface/bar.rs -@@ -1,7 +1,37 @@ - use std::convert::TryInto; -+use std::fmt; - - 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 +60,76 @@ impl PciBar { - } - - pub fn expect_port(&self) -> u16 { -+ self.try_port().unwrap_or_else(|err| panic!("{err}")) -+ } -+ -+ 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) { -+ self.try_mem().unwrap_or_else(|err| panic!("{err}")) -+ } -+ -+ 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..495aac61 100644 ---- a/drivers/pcid/src/driver_interface/cap.rs -+++ b/drivers/pcid/src/driver_interface/cap.rs -@@ -1,14 +1,37 @@ - use pci_types::capability::PciCapabilityAddress; - use pci_types::ConfigRegionAccess; - use serde::{Deserialize, Serialize}; -+use std::fmt; - - #[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 { -+ Self::try_parse(addr, access).unwrap_or_else(|err| panic!("{err}")) -+ } -+ -+ 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 +40,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 +54,69 @@ 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() -+ .unwrap() -+ .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.unwrap().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/irq_helpers.rs b/drivers/pcid/src/driver_interface/irq_helpers.rs -index 28ca077a..25609781 100644 ---- a/drivers/pcid/src/driver_interface/irq_helpers.rs -+++ b/drivers/pcid/src/driver_interface/irq_helpers.rs -@@ -180,27 +180,38 @@ pub fn allocate_single_interrupt_vector(cpu_id: usize) -> io::Result (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_single_interrupt_vector_for_msi(cpu_id: usize) -> (MsiAddrAndData, File) { -+ try_allocate_single_interrupt_vector_for_msi(cpu_id) -+ .unwrap_or_else(|err| panic!("failed to allocate MSI interrupt vector: {err}")) - } - - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -diff --git a/drivers/pcid/src/driver_interface/mod.rs b/drivers/pcid/src/driver_interface/mod.rs -index bbc7304e..4a64c9f4 100644 ---- a/drivers/pcid/src/driver_interface/mod.rs -+++ b/drivers/pcid/src/driver_interface/mod.rs -@@ -247,6 +247,7 @@ pub enum PcidClientRequest { - pub enum PcidServerResponseError { - NonexistentFeature(PciFeature), - InvalidBitPattern, -+ UnrecognizedRequest, - } - - #[derive(Debug, Serialize, Deserialize)] -@@ -307,6 +308,38 @@ fn recv(r: &mut File) -> T { - bincode::deserialize_from(&data[..]).expect("couldn't deserialize pcid message") - } - -+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 { -+ return Err(io::Error::new( -+ io::ErrorKind::InvalidData, -+ format!("pcid_interface: buffer too large ({length} bytes)"), -+ )); -+ } -+ let mut data = vec![0u8; length as usize]; -+ r.read_exact(&mut data)?; -+ bincode::deserialize_from(&data[..]) -+ .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err)) -+} -+ - impl PciFunctionHandle { - fn connect_default() -> Self { - let channel_fd = match env::var("PCID_CLIENT_CHANNEL") { -@@ -369,14 +402,21 @@ 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); - } - } - -@@ -457,7 +497,13 @@ impl PciFunctionHandle { - if let Some(mapped_bar) = mapped_bar { - mapped_bar - } else { -- let (bar, bar_size) = self.config.func.bars[bir as usize].expect_mem(); -+ let (bar, bar_size) = match self.config.func.bars[bir as usize].try_mem() { -+ Ok(memory_bar) => memory_bar, -+ Err(err) => { -+ log::error!("failed to use BAR {} as memory: {}", bir, err); -+ process::exit(1); -+ } -+ }; - - let ptr = match unsafe { - common::physmap( -diff --git a/drivers/pcid/src/driver_interface/msi.rs b/drivers/pcid/src/driver_interface/msi.rs -index 0ca68ec5..6934ad49 100644 ---- a/drivers/pcid/src/driver_interface/msi.rs -+++ b/drivers/pcid/src/driver_interface/msi.rs -@@ -1,6 +1,7 @@ - use std::fmt; - use std::ptr::NonNull; - -+use crate::driver_interface::bar::PciBarError; - use crate::driver_interface::PciBar; - use crate::PciFunctionHandle; - -@@ -33,9 +34,65 @@ 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, -+ }, -+} -+ -+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}" -+ ), -+ } -+ } -+} -+ - impl MsixInfo { - pub unsafe fn map_and_mask_all(self, pcid_handle: &mut PciFunctionHandle) -> MappedMsixRegs { -- self.validate(pcid_handle.config().func.bars); -+ self.try_map_and_mask_all(pcid_handle) -+ .unwrap_or_else(|err| panic!("{err}")) -+ } -+ -+ 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 +103,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::()) -+ .expect("MSI-X BAR mapping resulted in null pointer"), - info: self, - }; - -@@ -56,21 +114,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 +132,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 +182,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/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/ided/src/main.rs b/drivers/storage/ided/src/main.rs -index 4197217d..6983912c 100644 ---- a/drivers/storage/ided/src/main.rs -+++ b/drivers/storage/ided/src/main.rs -@@ -43,19 +43,42 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { - // Get controller DMA capable - let dma = pci_config.func.full_device_id.interface & 0x80 != 0; - -- let busmaster_base = pci_config.func.bars[4].expect_port(); -+ let busmaster_base = match pci_config.func.bars[4].try_port() { -+ Ok(port) => port, -+ Err(err) => { -+ error!("ided: missing/invalid busmaster BAR: {err}"); -+ std::process::exit(1); -+ } -+ }; - let (primary, primary_irq) = if pci_config.func.full_device_id.interface & 1 != 0 { -- panic!("TODO: IDE primary channel is PCI native"); -+ error!("ided: PCI native primary IDE channel is not supported yet"); -+ std::process::exit(1); - } else { -- (Channel::primary_compat(busmaster_base).unwrap(), 14) -+ match Channel::primary_compat(busmaster_base) { -+ Ok(channel) => (channel, 14), -+ Err(err) => { -+ error!("ided: failed to initialize primary IDE channel: {err}"); -+ std::process::exit(1); -+ } -+ } - }; - let (secondary, secondary_irq) = if pci_config.func.full_device_id.interface & 1 != 0 { -- panic!("TODO: IDE secondary channel is PCI native"); -+ error!("ided: PCI native secondary IDE channel is not supported yet"); -+ std::process::exit(1); - } else { -- (Channel::secondary_compat(busmaster_base + 8).unwrap(), 15) -+ match Channel::secondary_compat(busmaster_base + 8) { -+ Ok(channel) => (channel, 15), -+ Err(err) => { -+ error!("ided: failed to initialize secondary IDE channel: {err}"); -+ std::process::exit(1); -+ } -+ } - }; - -- common::acquire_port_io_rights().expect("ided: failed to get I/O privilege"); -+ if let Err(err) = common::acquire_port_io_rights() { -+ error!("ided: failed to get I/O privilege: {err}"); -+ std::process::exit(1); -+ } - - //TODO: move this to ide.rs? - let chans = vec![ -diff --git a/drivers/virtio-core/src/arch/x86.rs b/drivers/virtio-core/src/arch/x86.rs -index aea86c4a..d8595645 100644 ---- a/drivers/virtio-core/src/arch/x86.rs -+++ b/drivers/virtio-core/src/arch/x86.rs -@@ -1,6 +1,8 @@ - use crate::transport::Error; - --use pcid_interface::irq_helpers::{allocate_single_interrupt_vector_for_msi, read_bsp_apic_id}; -+use pcid_interface::irq_helpers::{ -+ read_bsp_apic_id, try_allocate_single_interrupt_vector_for_msi, -+}; - use std::fs::File; - - use crate::MSIX_PRIMARY_VECTOR; -@@ -11,9 +13,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!(), -+ _ => return Err(Error::MissingMsix), - }; -- 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(|err| Error::MsixSetup(format!("failed to map MSI-X registers: {err}")))?; - - // Allocate the primary MSI vector. - // FIXME allow the driver to register multiple MSI-X vectors -@@ -21,9 +24,12 @@ 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 (msg_addr_and_data, interrupt_handle) = -- allocate_single_interrupt_vector_for_msi(destination_id); -+ let destination_id = read_bsp_apic_id() -+ .map_err(|err| Error::MsixSetup(format!("failed to read BSP APIC ID: {err}")))?; -+ let (msg_addr_and_data, interrupt_handle) = try_allocate_single_interrupt_vector_for_msi( -+ destination_id, -+ ) -+ .map_err(|err| Error::MsixSetup(format!("failed to allocate MSI-X vector: {err}")))?; - table_entry_pointer.write_addr_and_data(msg_addr_and_data); - table_entry_pointer.unmask(); - -diff --git a/drivers/virtio-core/src/probe.rs b/drivers/virtio-core/src/probe.rs -index 5631ef67..3a231a2f 100644 ---- a/drivers/virtio-core/src/probe.rs -+++ b/drivers/virtio-core/src/probe.rs -@@ -32,15 +32,12 @@ pub const MSIX_PRIMARY_VECTOR: u16 = 0; - /// * 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 { -+ return Err(Error::NotVirtio); -+ } - - let mut common_addr = None; - let mut notify_addr = None; -@@ -100,19 +97,18 @@ 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"); -+ let common_addr = common_addr.ok_or(Error::MissingCapability("common"))?; -+ let device_addr = device_addr.ok_or(Error::MissingCapability("device"))?; -+ let (notify_addr, notify_multiplier) = notify_addr.ok_or(Error::MissingCapability("notify"))?; - - // 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" -- ); -+ if notify_multiplier == 0 { -+ return Err(Error::InvalidNotifyMultiplier); -+ } - - let common = unsafe { &mut *(common_addr as *mut CommonCfg) }; - let device_space = unsafe { &mut *(device_addr as *mut u8) }; -@@ -129,7 +125,9 @@ pub fn probe_device(pcid_handle: &mut PciFunctionHandle) -> Result io::Result<()> { - let (mut read_pipe, write_pipe) = io::pipe().unwrap(); - - 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}"), -+ )), - } - } - } -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/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/graphics/ihdgd/src/device/mod.rs b/drivers/graphics/ihdgd/src/device/mod.rs -index ced9dd56..0dc2e659 100644 ---- a/drivers/graphics/ihdgd/src/device/mod.rs -+++ b/drivers/graphics/ihdgd/src/device/mod.rs -@@ -246,7 +246,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 +257,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); -diff --git a/drivers/graphics/ihdgd/src/main.rs b/drivers/graphics/ihdgd/src/main.rs -index a8b6cc60..e468aca7 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,10 +29,21 @@ 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.*. -diff --git a/drivers/graphics/virtio-gpud/src/main.rs b/drivers/graphics/virtio-gpud/src/main.rs -index b27f4c56..5e9d810d 100644 ---- a/drivers/graphics/virtio-gpud/src/main.rs -+++ b/drivers/graphics/virtio-gpud/src/main.rs -@@ -482,7 +482,10 @@ fn main() { - } - - fn daemon_runner(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { -- deamon(daemon, pcid_handle).unwrap(); -+ if let Err(err) = deamon(daemon, pcid_handle) { -+ log::error!("virtio-gpud: startup failed: {err}"); -+ std::process::exit(1); -+ } - unreachable!(); - } - -@@ -500,7 +503,12 @@ 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 { -+ return Err(anyhow::anyhow!( -+ "unexpected virtio-gpu device id: {:04x}", -+ pci_config.func.full_device_id.device_id -+ )); -+ } - log::info!("virtio-gpu: initiating startup sequence :^)"); - - let device = DEVICE.try_call_once(|| virtio_core::probe_device(&mut pcid_handle))?; -@@ -530,8 +538,8 @@ fn deamon(deamon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> anyhow: - // Needs to be before GpuScheme::new to avoid a deadlock due to initnsmgr blocking on - // /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"); -+ let event_queue: EventQueue = EventQueue::new() -+ .map_err(|err| anyhow::anyhow!("failed to create event queue: {err}"))?; - - let mut scheme = scheme::GpuScheme::new( - config, -diff --git a/drivers/hwd/src/backend/acpi.rs b/drivers/hwd/src/backend/acpi.rs -index 3da41d63..c24dfc4b 100644 ---- a/drivers/hwd/src/backend/acpi.rs -+++ b/drivers/hwd/src/backend/acpi.rs -@@ -14,7 +14,7 @@ impl Backend for AcpiBackend { - // 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")); -+ let _ = daemon::Daemon::spawn(Command::new("acpid")); - - Ok(Self { rxsdt }) - } -diff --git a/drivers/hwd/src/main.rs b/drivers/hwd/src/main.rs -index 79360e34..4a2b9469 100644 ---- a/drivers/hwd/src/main.rs -+++ b/drivers/hwd/src/main.rs -@@ -38,7 +38,7 @@ 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")); -+ let _ = daemon::Daemon::spawn(process::Command::new("pcid")); - - daemon.ready(); - -diff --git a/drivers/net/e1000d/src/main.rs b/drivers/net/e1000d/src/main.rs -index 373ea9b3..d971c0a1 100644 ---- a/drivers/net/e1000d/src/main.rs -+++ b/drivers/net/e1000d/src/main.rs -@@ -28,17 +28,38 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - let irq = pci_config - .func - .legacy_interrupt_line -- .expect("e1000d: no legacy interrupts supported"); -+ .unwrap_or_else(|| { -+ log::error!("e1000d: no legacy interrupts supported"); -+ std::process::exit(1); -+ }); - - log::info!("E1000 {}", pci_config.func.display()); - -- let mut irq_file = irq.irq_handle("e1000d"); -+ let mut irq_file = match irq.try_irq_handle("e1000d") { -+ Ok(file) => file, -+ Err(err) => { -+ log::error!("e1000d: failed to open IRQ handle: {err}"); -+ std::process::exit(1); -+ } -+ }; - -- 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!("e1000d: failed to map BAR0: {err}"); -+ std::process::exit(1); -+ } -+ }; - - let mut scheme = NetworkScheme::new( - move || unsafe { -- device::Intel8254x::new(address).expect("e1000d: failed to allocate device") -+ match device::Intel8254x::new(address) { -+ Ok(device) => device, -+ Err(err) => { -+ log::error!("e1000d: failed to allocate device: {err}"); -+ std::process::exit(1); -+ } -+ } - }, - daemon, - format!("network.{name}"), -@@ -51,7 +72,13 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - } - } - -- let event_queue = EventQueue::::new().expect("e1000d: failed to create event queue"); -+ let event_queue = match EventQueue::::new() { -+ Ok(queue) => queue, -+ Err(err) => { -+ log::error!("e1000d: failed to create event queue: {err}"); -+ std::process::exit(1); -+ } -+ }; - - event_queue - .subscribe( -@@ -59,32 +86,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}"); -+ std::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"); -+ .unwrap_or_else(|err| { -+ log::error!("e1000d: failed to subscribe to scheme fd: {err}"); -+ std::process::exit(1); -+ }); -+ -+ if let Err(err) = libredox::call::setrens(0, 0) { -+ log::error!("e1000d: failed to enter null namespace: {err}"); -+ std::process::exit(1); -+ } - -- scheme.tick().unwrap(); -+ if let Err(err) = scheme.tick() { -+ log::error!("e1000d: failed initial scheme tick: {err}"); -+ std::process::exit(1); -+ } - -- for event in event_queue.map(|e| e.expect("e1000d: failed to get event")) { -+ for event in event_queue { -+ let event = match event { -+ Ok(event) => event, -+ Err(err) => { -+ log::error!("e1000d: failed to get event: {err}"); -+ 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 file: {err}"); -+ break; -+ } - 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 acknowledge IRQ: {err}"); -+ break; -+ } -+ -+ if let Err(err) = scheme.tick() { -+ log::error!("e1000d: failed to handle IRQ: {err}"); -+ break; -+ } -+ } -+ } -+ Source::Scheme => { -+ if let Err(err) = scheme.tick() { -+ log::error!("e1000d: failed to handle scheme op: {err}"); -+ break; - } - } -- Source::Scheme => scheme.tick().expect("e1000d: failed to handle scheme op"), - } - } -- unreachable!() -+ std::process::exit(1) - } -diff --git a/drivers/net/ixgbed/src/main.rs b/drivers/net/ixgbed/src/main.rs -index 4a6ce74d..a9b6dd82 100644 ---- a/drivers/net/ixgbed/src/main.rs -+++ b/drivers/net/ixgbed/src/main.rs -@@ -22,20 +22,44 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - let irq = pci_config - .func - .legacy_interrupt_line -- .expect("ixgbed: no legacy interrupts supported"); -+ .unwrap_or_else(|| { -+ eprintln!("ixgbed: no legacy interrupts supported"); -+ std::process::exit(1); -+ }); - - println!(" + IXGBE {}", pci_config.func.display()); - -- let mut irq_file = irq.irq_handle("ixgbed"); -+ let mut irq_file = match irq.try_irq_handle("ixgbed") { -+ Ok(file) => file, -+ Err(err) => { -+ eprintln!("ixgbed: failed to open IRQ handle: {err}"); -+ std::process::exit(1); -+ } -+ }; - -- let mapped_bar = unsafe { pcid_handle.map_bar(0) }; -+ if let Err(err) = pci_config.func.bars[0].try_mem() { -+ eprintln!("ixgbed: invalid BAR0: {err}"); -+ std::process::exit(1); -+ } -+ let mapped_bar = match unsafe { pcid_handle.try_map_bar(0) } { -+ Ok(bar) => bar, -+ Err(err) => { -+ eprintln!("ixgbed: failed to map BAR0: {err}"); -+ std::process::exit(1); -+ } -+ }; - let address = mapped_bar.ptr.as_ptr(); - let size = mapped_bar.bar_size; - - let mut scheme = NetworkScheme::new( - move || { -- device::Intel8259x::new(address as usize, size) -- .expect("ixgbed: failed to allocate device") -+ match device::Intel8259x::new(address as usize, size) { -+ Ok(device) => device, -+ Err(err) => { -+ eprintln!("ixgbed: failed to allocate device: {err}"); -+ std::process::exit(1); -+ } -+ } - }, - daemon, - format!("network.{name}"), -@@ -48,41 +72,78 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - } - } - -- let event_queue = EventQueue::::new().expect("ixgbed: Could not create event queue."); -+ let event_queue = match EventQueue::::new() { -+ Ok(queue) => queue, -+ Err(err) => { -+ eprintln!("ixgbed: 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!("ixgbed: failed to subscribe IRQ fd: {err}"); -+ std::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| { -+ eprintln!("ixgbed: failed to subscribe scheme fd: {err}"); -+ std::process::exit(1); -+ }); -+ -+ if let Err(err) = libredox::call::setrens(0, 0) { -+ eprintln!("ixgbed: failed to enter null namespace: {err}"); -+ std::process::exit(1); -+ } - -- scheme.tick().unwrap(); -+ if let Err(err) = scheme.tick() { -+ eprintln!("ixgbed: failed initial scheme tick: {err}"); -+ std::process::exit(1); -+ } - -- for event in event_queue.map(|e| e.expect("ixgbed: failed to get next event")) { -+ for event in event_queue { -+ let event = match event { -+ Ok(event) => event, -+ Err(err) => { -+ eprintln!("ixgbed: failed to get next event: {err}"); -+ 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) { -+ eprintln!("ixgbed: failed to read IRQ file: {err}"); -+ break; -+ } - if scheme.adapter().irq() { -- irq_file.write(&mut irq).unwrap(); -- -- scheme.tick().unwrap(); -+ if let Err(err) = irq_file.write(&mut irq) { -+ eprintln!("ixgbed: failed to acknowledge IRQ: {err}"); -+ break; -+ } -+ -+ if let Err(err) = scheme.tick() { -+ eprintln!("ixgbed: failed to handle IRQ: {err}"); -+ break; -+ } - } - } - Source::Scheme => { -- scheme.tick().unwrap(); -+ if let Err(err) = scheme.tick() { -+ eprintln!("ixgbed: failed to handle scheme op: {err}"); -+ break; -+ } - } - } - } -- unreachable!() -+ std::process::exit(0) - } -diff --git a/drivers/net/rtl8139d/src/main.rs b/drivers/net/rtl8139d/src/main.rs -index d470e814..aa377446 100644 ---- a/drivers/net/rtl8139d/src/main.rs -+++ b/drivers/net/rtl8139d/src/main.rs -@@ -3,7 +3,7 @@ use std::os::unix::io::AsRawFd; - - use driver_network::NetworkScheme; - 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 device; -@@ -20,19 +20,19 @@ where - } - } - --fn map_bar(pcid_handle: &mut PciFunctionHandle) -> *mut u8 { -+fn map_bar(pcid_handle: &mut PciFunctionHandle) -> Result<*mut u8, &'static str> { - let config = pcid_handle.config(); - - // RTL8139 uses BAR2, RTL8169 uses BAR1, search in that order - for &barnum in &[2, 1] { - match config.func.bars[usize::from(barnum)] { - pcid_interface::PciBar::Memory32 { .. } | pcid_interface::PciBar::Memory64 { .. } => unsafe { -- return pcid_handle.map_bar(barnum).ptr.as_ptr(); -+ return Ok(pcid_handle.map_bar(barnum).ptr.as_ptr()); - }, - other => log::warn!("BAR {} is {:?} instead of memory BAR", barnum, other), - } - } -- panic!("rtl8139d: failed to find BAR"); -+ Err("failed to find a usable MMIO BAR") - } - - fn main() { -@@ -55,13 +55,31 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - - log::info!(" + RTL8139 {}", pci_config.func.display()); - -- let bar = map_bar(&mut pcid_handle); -+ let bar = match map_bar(&mut pcid_handle) { -+ Ok(bar) => bar, -+ Err(err) => { -+ log::error!("rtl8139d: {err}"); -+ std::process::exit(1); -+ } -+ }; - -- let irq_file = pci_allocate_interrupt_vector(&mut pcid_handle, "rtl8139d"); -+ let irq_file = match try_pci_allocate_interrupt_vector(&mut pcid_handle, "rtl8139d") { -+ Ok(irq) => irq, -+ Err(err) => { -+ log::error!("rtl8139d: failed to allocate interrupt vector: {err}"); -+ std::process::exit(1); -+ } -+ }; - - let mut scheme = NetworkScheme::new( - move || unsafe { -- device::Rtl8139::new(bar as usize).expect("rtl8139d: failed to allocate device") -+ match device::Rtl8139::new(bar as usize) { -+ Ok(device) => device, -+ Err(err) => { -+ log::error!("rtl8139d: failed to allocate device: {err}"); -+ std::process::exit(1); -+ } -+ } - }, - daemon, - format!("network.{name}"), -diff --git a/drivers/net/rtl8168d/src/main.rs b/drivers/net/rtl8168d/src/main.rs -index 1d9963a3..c6e21bd9 100644 ---- a/drivers/net/rtl8168d/src/main.rs -+++ b/drivers/net/rtl8168d/src/main.rs -@@ -3,7 +3,7 @@ use std::os::unix::io::AsRawFd; - - use driver_network::NetworkScheme; - 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 device; -@@ -20,19 +20,19 @@ where - } - } - --fn map_bar(pcid_handle: &mut PciFunctionHandle) -> *mut u8 { -+fn map_bar(pcid_handle: &mut PciFunctionHandle) -> Result<*mut u8, &'static str> { - let config = pcid_handle.config(); - - // RTL8168 uses BAR2, RTL8169 uses BAR1, search in that order - for &barnum in &[2, 1] { - match config.func.bars[usize::from(barnum)] { - pcid_interface::PciBar::Memory32 { .. } | pcid_interface::PciBar::Memory64 { .. } => unsafe { -- return pcid_handle.map_bar(barnum).ptr.as_ptr(); -+ return Ok(pcid_handle.map_bar(barnum).ptr.as_ptr()); - }, - other => log::warn!("BAR {} is {:?} instead of memory BAR", barnum, other), - } - } -- panic!("rtl8168d: failed to find BAR"); -+ Err("failed to find a usable MMIO BAR") - } - - fn main() { -@@ -55,13 +55,31 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - - log::info!("RTL8168 {}", pci_config.func.display()); - -- let bar = map_bar(&mut pcid_handle); -+ let bar = match map_bar(&mut pcid_handle) { -+ Ok(bar) => bar, -+ Err(err) => { -+ log::error!("rtl8168d: {err}"); -+ std::process::exit(1); -+ } -+ }; - -- let irq_file = pci_allocate_interrupt_vector(&mut pcid_handle, "rtl8168d"); -+ let irq_file = match try_pci_allocate_interrupt_vector(&mut pcid_handle, "rtl8168d") { -+ Ok(irq) => irq, -+ Err(err) => { -+ log::error!("rtl8168d: failed to allocate interrupt vector: {err}"); -+ std::process::exit(1); -+ } -+ }; - - let mut scheme = NetworkScheme::new( - move || unsafe { -- device::Rtl8168::new(bar as usize).expect("rtl8168d: failed to allocate device") -+ match device::Rtl8168::new(bar as usize) { -+ Ok(device) => device, -+ Err(err) => { -+ log::error!("rtl8168d: failed to allocate device: {err}"); -+ std::process::exit(1); -+ } -+ } - }, - daemon, - format!("network.{name}"), -diff --git a/drivers/net/virtio-netd/src/main.rs b/drivers/net/virtio-netd/src/main.rs -index 17d168ef..56f2c045 100644 ---- a/drivers/net/virtio-netd/src/main.rs -+++ b/drivers/net/virtio-netd/src/main.rs -@@ -31,7 +31,10 @@ fn main() { - } - - fn daemon_runner(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { -- deamon(daemon, pcid_handle).unwrap(); -+ if let Err(err) = deamon(daemon, pcid_handle) { -+ log::error!("virtio-netd: startup failed: {err}"); -+ std::process::exit(1); -+ } - unreachable!(); - } - -@@ -52,7 +55,13 @@ 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 { -+ return Err(format!( -+ "unexpected virtio-net device id: {:04x}", -+ pci_config.func.full_device_id.device_id -+ ) -+ .into()); -+ } - log::info!("virtio-net: initiating startup sequence :^)"); - - let device = virtio_core::probe_device(&mut pcid_handle)?; -@@ -84,7 +93,7 @@ fn deamon( - device.transport.ack_driver_feature(VIRTIO_NET_F_MAC); - mac - } else { -- unimplemented!() -+ return Err("virtio-net: device does not expose VIRTIO_NET_F_MAC".into()); - }; - - device.transport.finalize_features(); -@@ -126,7 +135,7 @@ fn deamon( - data: 0, - })?; - -- libredox::call::setrens(0, 0).expect("virtio-netd: failed to enter null namespace"); -+ libredox::call::setrens(0, 0)?; - - scheme.tick()?; - -diff --git a/drivers/pcid-spawner/src/main.rs b/drivers/pcid-spawner/src/main.rs -index a968f4d4..e41caee0 100644 ---- a/drivers/pcid-spawner/src/main.rs -+++ b/drivers/pcid-spawner/src/main.rs -@@ -55,10 +55,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 +68,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 +86,46 @@ fn main() -> Result<()> { - let mut command = Command::new(program); - command.args(args); - -- log::info!("pcid-spawner: spawn {:?}", command); -- -- handle.enable_device(); -+ 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; -+ } - - 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 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 -+ ); -+ } - } - - Ok(()) -diff --git a/drivers/pcid/src/driver_handler.rs b/drivers/pcid/src/driver_handler.rs -index f70a7f6d..bd0db746 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, - }) -@@ -266,7 +276,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 +288,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..7eaade51 100644 ---- a/drivers/pcid/src/driver_interface/bar.rs -+++ b/drivers/pcid/src/driver_interface/bar.rs -@@ -1,7 +1,37 @@ - use std::convert::TryInto; -+use std::fmt; - - 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 +60,76 @@ impl PciBar { - } - - pub fn expect_port(&self) -> u16 { -+ self.try_port().unwrap_or_else(|err| panic!("{err}")) -+ } -+ -+ 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) { -+ self.try_mem().unwrap_or_else(|err| panic!("{err}")) -+ } -+ -+ 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..495aac61 100644 ---- a/drivers/pcid/src/driver_interface/cap.rs -+++ b/drivers/pcid/src/driver_interface/cap.rs -@@ -1,14 +1,37 @@ - use pci_types::capability::PciCapabilityAddress; - use pci_types::ConfigRegionAccess; - use serde::{Deserialize, Serialize}; -+use std::fmt; - - #[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 { -+ Self::try_parse(addr, access).unwrap_or_else(|err| panic!("{err}")) -+ } -+ -+ 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 +40,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 +54,69 @@ 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() -+ .unwrap() -+ .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.unwrap().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/irq_helpers.rs b/drivers/pcid/src/driver_interface/irq_helpers.rs -index 28ca077a..b595d703 100644 ---- a/drivers/pcid/src/driver_interface/irq_helpers.rs -+++ b/drivers/pcid/src/driver_interface/irq_helpers.rs -@@ -180,40 +180,51 @@ pub fn allocate_single_interrupt_vector(cpu_id: usize) -> io::Result (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) { -+ try_allocate_single_interrupt_vector_for_msi(cpu_id) -+ .unwrap_or_else(|err| panic!("failed to allocate MSI interrupt vector: {err}")) -+} -+ -+#[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 +233,20 @@ 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 { -+ try_allocate_first_msi_interrupt_on_bsp(pcid_handle) -+ .unwrap_or_else(|err| panic!("failed to allocate first MSI interrupt on BSP: {err}")) - } - - pub struct InterruptVector { -@@ -234,6 +255,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 +320,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 +332,79 @@ 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 { -+ try_pci_allocate_interrupt_vector(pcid_handle, driver) -+ .unwrap_or_else(|err| panic!("{driver}: {err}")) -+} -+ -+// 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 { -+ try_pci_allocate_interrupt_vector(pcid_handle, driver) -+ .unwrap_or_else(|err| panic!("{driver}: {err}")) -+} -diff --git a/drivers/pcid/src/driver_interface/mod.rs b/drivers/pcid/src/driver_interface/mod.rs -index bbc7304e..b0fb8aa8 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,17 +39,25 @@ 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 { -+ self.try_irq_handle(driver) -+ .unwrap_or_else(|err| panic!("{driver}: failed to open IRQ file: {err}")) -+ } - } - - impl fmt::Display for LegacyInterruptLine { -@@ -247,6 +255,7 @@ pub enum PcidClientRequest { - pub enum PcidServerResponseError { - NonexistentFeature(PciFeature), - InvalidBitPattern, -+ UnrecognizedRequest, - } - - #[derive(Debug, Serialize, Deserialize)] -@@ -307,6 +316,38 @@ fn recv(r: &mut File) -> T { - bincode::deserialize_from(&data[..]).expect("couldn't deserialize pcid message") - } - -+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 { -+ return Err(io::Error::new( -+ io::ErrorKind::InvalidData, -+ format!("pcid_interface: buffer too large ({length} bytes)"), -+ )); -+ } -+ let mut data = vec![0u8; length as usize]; -+ r.read_exact(&mut data)?; -+ bincode::deserialize_from(&data[..]) -+ .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err)) -+} -+ - impl PciFunctionHandle { - fn connect_default() -> Self { - let channel_fd = match env::var("PCID_CLIENT_CHANNEL") { -@@ -369,55 +410,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 +518,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 +569,23 @@ 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 { -+ Ok(mapped_bar.insert(MappedBar { - ptr: NonNull::new(ptr.cast::()).expect("Mapping a BAR resulted in a nullptr"), - 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..6934ad49 100644 ---- a/drivers/pcid/src/driver_interface/msi.rs -+++ b/drivers/pcid/src/driver_interface/msi.rs -@@ -1,6 +1,7 @@ - use std::fmt; - use std::ptr::NonNull; - -+use crate::driver_interface::bar::PciBarError; - use crate::driver_interface::PciBar; - use crate::PciFunctionHandle; - -@@ -33,9 +34,65 @@ 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, -+ }, -+} -+ -+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}" -+ ), -+ } -+ } -+} -+ - impl MsixInfo { - pub unsafe fn map_and_mask_all(self, pcid_handle: &mut PciFunctionHandle) -> MappedMsixRegs { -- self.validate(pcid_handle.config().func.bars); -+ self.try_map_and_mask_all(pcid_handle) -+ .unwrap_or_else(|err| panic!("{err}")) -+ } -+ -+ 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 +103,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::()) -+ .expect("MSI-X BAR mapping resulted in null pointer"), - info: self, - }; - -@@ -56,21 +114,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 +132,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 +182,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/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/main.rs b/drivers/storage/ahcid/src/main.rs -index 1f130a29..059cdd4e 100644 ---- a/drivers/storage/ahcid/src/main.rs -+++ b/drivers/storage/ahcid/src/main.rs -@@ -26,7 +26,10 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - let irq = pci_config - .func - .legacy_interrupt_line -- .expect("ahcid: no legacy interrupts supported"); -+ .unwrap_or_else(|| { -+ error!("ahcid: no legacy interrupts supported"); -+ std::process::exit(1); -+ }); - - common::setup_logging( - "disk", -@@ -38,6 +41,10 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - - info!("AHCI {}", pci_config.func.display()); - -+ if let Err(err) = pci_config.func.bars[5].try_mem() { -+ error!("ahcid: invalid BAR5: {err}"); -+ std::process::exit(1); -+ } - let address = unsafe { pcid_handle.map_bar(5) }.ptr.as_ptr() as usize; - { - let (hba_mem, disks) = ahci::disks(address as usize, &name); -@@ -54,31 +61,58 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - &FuturesExecutor, - ); - -- let mut irq_file = irq.irq_handle("ahcid"); -+ let mut irq_file = match irq.try_irq_handle("ahcid") { -+ Ok(file) => file, -+ Err(err) => { -+ error!("ahcid: failed to open IRQ handle: {err}"); -+ std::process::exit(1); -+ } -+ }; - 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 = match RawEventQueue::new() { -+ Ok(queue) => queue, -+ Err(err) => { -+ error!("ahcid: failed to create event queue: {err}"); -+ std::process::exit(1); -+ } -+ }; - -- libredox::call::setrens(0, 0).expect("ahcid: failed to enter null namespace"); -+ if let Err(err) = libredox::call::setrens(0, 0) { -+ error!("ahcid: failed to enter null namespace: {err}"); -+ std::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 subscribe scheme socket: {err}"); -+ std::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 subscribe IRQ fd: {err}"); -+ std::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 read event queue: {err}"); -+ break; -+ } -+ }; - 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 op: {err}"); -+ break; -+ } - } 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() -- { -+ match irq_file.read(&mut irq) { -+ Ok(read) if read >= irq.len() => { - let is = hba_mem.is.read(); - if is > 0 { - let pi = hba_mem.pi.read(); -@@ -92,11 +126,21 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - } - 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 acknowledge IRQ: {err}"); -+ break; -+ } - -- FuturesExecutor.block_on(scheme.tick()).unwrap(); -+ if let Err(err) = FuturesExecutor.block_on(scheme.tick()) { -+ error!("ahcid: failed to handle IRQ: {err}"); -+ break; -+ } -+ } -+ } -+ Ok(_) => {} -+ Err(err) => { -+ error!("ahcid: failed to read IRQ file: {err}"); -+ break; - } - } - } else { -@@ -105,5 +149,5 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - } - } - -- std::process::exit(0); -+ std::process::exit(1); - } -diff --git a/drivers/storage/ided/src/main.rs b/drivers/storage/ided/src/main.rs -index 4197217d..6983912c 100644 ---- a/drivers/storage/ided/src/main.rs -+++ b/drivers/storage/ided/src/main.rs -@@ -43,19 +43,42 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { - // Get controller DMA capable - let dma = pci_config.func.full_device_id.interface & 0x80 != 0; - -- let busmaster_base = pci_config.func.bars[4].expect_port(); -+ let busmaster_base = match pci_config.func.bars[4].try_port() { -+ Ok(port) => port, -+ Err(err) => { -+ error!("ided: missing/invalid busmaster BAR: {err}"); -+ std::process::exit(1); -+ } -+ }; - let (primary, primary_irq) = if pci_config.func.full_device_id.interface & 1 != 0 { -- panic!("TODO: IDE primary channel is PCI native"); -+ error!("ided: PCI native primary IDE channel is not supported yet"); -+ std::process::exit(1); - } else { -- (Channel::primary_compat(busmaster_base).unwrap(), 14) -+ match Channel::primary_compat(busmaster_base) { -+ Ok(channel) => (channel, 14), -+ Err(err) => { -+ error!("ided: failed to initialize primary IDE channel: {err}"); -+ std::process::exit(1); -+ } -+ } - }; - let (secondary, secondary_irq) = if pci_config.func.full_device_id.interface & 1 != 0 { -- panic!("TODO: IDE secondary channel is PCI native"); -+ error!("ided: PCI native secondary IDE channel is not supported yet"); -+ std::process::exit(1); - } else { -- (Channel::secondary_compat(busmaster_base + 8).unwrap(), 15) -+ match Channel::secondary_compat(busmaster_base + 8) { -+ Ok(channel) => (channel, 15), -+ Err(err) => { -+ error!("ided: failed to initialize secondary IDE channel: {err}"); -+ std::process::exit(1); -+ } -+ } - }; - -- common::acquire_port_io_rights().expect("ided: failed to get I/O privilege"); -+ if let Err(err) = common::acquire_port_io_rights() { -+ error!("ided: failed to get I/O privilege: {err}"); -+ std::process::exit(1); -+ } - - //TODO: move this to ide.rs? - let chans = vec![ -diff --git a/drivers/storage/nvmed/src/main.rs b/drivers/storage/nvmed/src/main.rs -index beb1b689..8c79ba5e 100644 ---- a/drivers/storage/nvmed/src/main.rs -+++ b/drivers/storage/nvmed/src/main.rs -@@ -75,30 +75,62 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - - log::debug!("NVME PCI CONFIG: {:?}", pci_config); - -+ if let Err(err) = pci_config.func.bars[0].try_mem() { -+ log::error!("nvmed: invalid BAR0: {err}"); -+ std::process::exit(1); -+ } - let address = unsafe { pcid_handle.map_bar(0).ptr }; - -- let interrupt_vector = irq_helpers::pci_allocate_interrupt_vector(&mut pcid_handle, "nvmed"); -+ let interrupt_vector = match irq_helpers::try_pci_allocate_interrupt_vector(&mut pcid_handle, "nvmed") { -+ Ok(vector) => vector, -+ Err(err) => { -+ log::error!("nvmed: failed to allocate interrupt vector: {err}"); -+ std::process::exit(1); -+ } -+ }; - let iv = interrupt_vector.vector(); -- let irq_handle = interrupt_vector.irq_handle().try_clone().unwrap(); -+ let irq_handle = match interrupt_vector.irq_handle().try_clone() { -+ Ok(handle) => handle, -+ Err(err) => { -+ log::error!("nvmed: failed to clone IRQ handle: {err}"); -+ std::process::exit(1); -+ } -+ }; - -- let mut nvme = Nvme::new(address.as_ptr() as usize, interrupt_vector, pcid_handle) -- .expect("nvmed: failed to allocate driver data"); -+ let mut nvme = match Nvme::new(address.as_ptr() as usize, interrupt_vector, pcid_handle) { -+ Ok(nvme) => nvme, -+ Err(err) => { -+ log::error!("nvmed: failed to allocate driver data: {err}"); -+ std::process::exit(1); -+ } -+ }; - -- unsafe { nvme.init().expect("nvmed: failed to init") } -+ if let Err(err) = unsafe { nvme.init() } { -+ log::error!("nvmed: failed to init: {err}"); -+ std::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"); -+ let mut time_handle = match File::open(&format!("/scheme/time/{}", libredox::flag::CLOCK_MONOTONIC)) { -+ Ok(handle) => handle, -+ Err(err) => { -+ log::error!("nvmed: failed to open time handle: {err}"); -+ std::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"); -+ if let Err(err) = time_arm(&mut time_handle, 5) { -+ log::error!("nvmed: failed to arm init timer: {err}"); -+ std::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 +138,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 waiting for queue initialization"); -+ std::process::exit(1); -+ } - } - }); - log::debug!("Initialized!"); -diff --git a/drivers/storage/virtio-blkd/src/main.rs b/drivers/storage/virtio-blkd/src/main.rs -index d21236b3..f66f725d 100644 ---- a/drivers/storage/virtio-blkd/src/main.rs -+++ b/drivers/storage/virtio-blkd/src/main.rs -@@ -103,7 +103,10 @@ fn main() { - } - - fn daemon_runner(redox_daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { -- daemon(redox_daemon, pcid_handle).unwrap(); -+ if let Err(err) = daemon(redox_daemon, pcid_handle) { -+ log::error!("virtio-blkd: startup failed: {err}"); -+ std::process::exit(1); -+ } - unreachable!(); - } - -@@ -121,7 +124,12 @@ 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 { -+ return Err(anyhow::anyhow!( -+ "unexpected virtio-blk device id: {:04x}", -+ pci_config.func.full_device_id.device_id -+ )); -+ } - log::info!("virtio-blk: initiating startup sequence :^)"); - - let device = virtio_core::probe_device(&mut pcid_handle)?; -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..d8595645 100644 ---- a/drivers/virtio-core/src/arch/x86.rs -+++ b/drivers/virtio-core/src/arch/x86.rs -@@ -1,6 +1,8 @@ - use crate::transport::Error; - --use pcid_interface::irq_helpers::{allocate_single_interrupt_vector_for_msi, read_bsp_apic_id}; -+use pcid_interface::irq_helpers::{ -+ read_bsp_apic_id, try_allocate_single_interrupt_vector_for_msi, -+}; - use std::fs::File; - - use crate::MSIX_PRIMARY_VECTOR; -@@ -11,9 +13,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!(), -+ _ => return Err(Error::MissingMsix), - }; -- 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(|err| Error::MsixSetup(format!("failed to map MSI-X registers: {err}")))?; - - // Allocate the primary MSI vector. - // FIXME allow the driver to register multiple MSI-X vectors -@@ -21,9 +24,12 @@ 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 (msg_addr_and_data, interrupt_handle) = -- allocate_single_interrupt_vector_for_msi(destination_id); -+ let destination_id = read_bsp_apic_id() -+ .map_err(|err| Error::MsixSetup(format!("failed to read BSP APIC ID: {err}")))?; -+ let (msg_addr_and_data, interrupt_handle) = try_allocate_single_interrupt_vector_for_msi( -+ destination_id, -+ ) -+ .map_err(|err| Error::MsixSetup(format!("failed to allocate MSI-X vector: {err}")))?; - table_entry_pointer.write_addr_and_data(msg_addr_and_data); - table_entry_pointer.unmask(); - -diff --git a/drivers/virtio-core/src/probe.rs b/drivers/virtio-core/src/probe.rs -index 5631ef67..3367586a 100644 ---- a/drivers/virtio-core/src/probe.rs -+++ b/drivers/virtio-core/src/probe.rs -@@ -32,21 +32,21 @@ pub const MSIX_PRIMARY_VECTOR: u16 = 0; - /// * 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 { -+ return Err(Error::NotVirtio); -+ } - - let mut common_addr = None; - let mut notify_addr = None; - let mut device_addr = None; - -- for raw_capability in pcid_handle.get_vendor_capabilities() { -+ for raw_capability in pcid_handle -+ .try_get_vendor_capabilities() -+ .map_err(|err| Error::MsixSetup(format!("failed to fetch vendor capabilities: {err}")))? -+ { - // SAFETY: We have verified that the length of the data is correct. - let capability = unsafe { &*(raw_capability.data.as_ptr() as *const PciCapability) }; - -@@ -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::MissingCapability("capability BAR"))?; - - let address = unsafe { - let addr = addr + capability.offset as usize; -@@ -100,19 +102,18 @@ 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"); -+ let common_addr = common_addr.ok_or(Error::MissingCapability("common"))?; -+ let device_addr = device_addr.ok_or(Error::MissingCapability("device"))?; -+ let (notify_addr, notify_multiplier) = notify_addr.ok_or(Error::MissingCapability("notify"))?; - - // 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" -- ); -+ if notify_multiplier == 0 { -+ return Err(Error::InvalidNotifyMultiplier); -+ } - - let common = unsafe { &mut *(common_addr as *mut CommonCfg) }; - let device_space = unsafe { &mut *(device_addr as *mut u8) }; -@@ -129,7 +130,9 @@ pub fn probe_device(pcid_handle: &mut PciFunctionHandle) -> Result Result<(), Error> { - .insert_status(DeviceStatusFlags::ACKNOWLEDGE); - - device.transport.insert_status(DeviceStatusFlags::DRIVER); -+ device.transport.finalize_features(); - Ok(()) - } -diff --git a/drivers/virtio-core/src/transport.rs b/drivers/virtio-core/src/transport.rs -index d3445d2d..4e116d2e 100644 ---- a/drivers/virtio-core/src/transport.rs -+++ b/drivers/virtio-core/src/transport.rs -@@ -19,6 +19,20 @@ pub enum Error { - SyscallError(#[from] libredox::error::Error), - #[error("the device is incapable of {0:?}")] - InCapable(CfgType), -+ #[error("device is not a virtio device")] -+ NotVirtio, -+ #[error("virtio capability `{0}` is missing")] -+ MissingCapability(&'static str), -+ #[error("virtio notify capability has an invalid zero multiplier")] -+ InvalidNotifyMultiplier, -+ #[error("device does not support MSI-X")] -+ MissingMsix, -+ #[error("MSI-X setup failed: {0}")] -+ MsixSetup(String), -+ #[error("virtio feature negotiation failed")] -+ FeaturesNotAccepted, -+ #[error("virtio queue operation failed: {0}")] -+ QueueSetup(&'static str), - } - - /// Returns the queue part sizes in bytes. -@@ -238,6 +252,26 @@ impl<'a> Queue<'a> { - } - } - -+fn finalize_features_checked(transport: &StandardTransport<'_>) -> Result<(), Error> { -+ if !transport.check_device_feature(VIRTIO_F_VERSION_1) { -+ return Err(Error::FeaturesNotAccepted); -+ } -+ transport.ack_driver_feature(VIRTIO_F_VERSION_1); -+ -+ let mut common = transport.common.lock().unwrap(); -+ -+ let status = common.device_status.get(); -+ common -+ .device_status -+ .set(status | DeviceStatusFlags::FEATURES_OK); -+ -+ let confirm = common.device_status.get(); -+ if (confirm & DeviceStatusFlags::FEATURES_OK) != DeviceStatusFlags::FEATURES_OK { -+ return Err(Error::FeaturesNotAccepted); -+ } -+ Ok(()) -+} -+ - unsafe impl Sync for Queue<'_> {} - unsafe impl Send for Queue<'_> {} - -@@ -590,21 +624,8 @@ impl Transport for StandardTransport<'_> { - } - - fn finalize_features(&self) { -- // Check VirtIO version 1 compliance. -- assert!(self.check_device_feature(VIRTIO_F_VERSION_1)); -- self.ack_driver_feature(VIRTIO_F_VERSION_1); -- -- let mut common = self.common.lock().unwrap(); -- -- let status = common.device_status.get(); -- common -- .device_status -- .set(status | DeviceStatusFlags::FEATURES_OK); -- -- // 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); -+ finalize_features_checked(self) -+ .unwrap_or_else(|err| panic!("{err}")) - } - - fn setup_config_notify(&self, vector: u16) { -@@ -640,7 +661,9 @@ 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 { -+ return Err(Error::QueueSetup("queue MSI-X vector was not accepted")); -+ } - - // Enable the queue. - common.queue_enable.set(1); -@@ -685,7 +708,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 { -+ panic!("virtio queue MSI-X vector was not accepted during reinit"); -+ } - - // Enable the queue. - common.queue_enable.set(1); diff --git a/local/patches/base/P1-pcid-uevent-surface.patch b/local/patches/base/P1-pcid-uevent-surface.patch index a019e72581..1511c50a96 100644 --- a/local/patches/base/P1-pcid-uevent-surface.patch +++ b/local/patches/base/P1-pcid-uevent-surface.patch @@ -1,61 +1,50 @@ -diff --git a/drivers/pcid/src/scheme.rs b/drivers/pcid/src/scheme.rs -index ce55b33f..c06bdec4 100644 --- a/drivers/pcid/src/scheme.rs +++ b/drivers/pcid/src/scheme.rs -@@ -21,6 +21,10 @@ enum Handle { - Access, - Device, +@@ -20,6 +20,7 @@ pub struct PciScheme { Channel { addr: PciAddress, st: ChannelState }, -+ // Uevent surface for hotplug consumers. Opening uevent returns an object -+ // from which device add/remove events can be read. Since pcid currently -+ // only scans at startup, this surface is ready for hotplug polling consumers. -+ Uevent, + // Uevent surface for hotplug consumers. pcid currently only scans at + // startup, so the surface exists for polling readers and will return no + // data until hotplug support grows real events. + Uevent, SchemeRoot, - /// Represents an open handle to a device's bind endpoint - Bind { addr: PciAddress }, -@@ -34,6 +38,6 @@ struct HandleWrapper { - } + } +@@ -33,7 +34,7 @@ impl Handle { fn is_file(&self) -> bool { -- matches!(self, Self::Access | Self::Channel { .. } | Self::Bind { .. }) -+ matches!(self, Self::Access | Self::Channel { .. } | Self::Bind { .. } | Self::Uevent) + matches!( + self, +- Self::Access | Self::Config { .. } | Self::Channel { .. } ++ Self::Access | Self::Config { .. } | Self::Channel { .. } | Self::Uevent + ) } fn is_dir(&self) -> bool { - !self.is_file() -@@ -96,6 +100,8 @@ impl SchemeSync for PciScheme { - } - } else if path == "access" { +@@ -106,6 +107,8 @@ impl SchemeSync for PciScheme { Handle::Access -+ } else if path == "uevent" { -+ Handle::Uevent + } else if path == "uevent" { + Handle::Uevent } else { let idx = path.find('/').unwrap_or(path.len()); - let (addr_str, after) = path.split_at(idx); -@@ -140,5 +146,6 @@ impl SchemeSync for PciScheme { - Handle::Device => (DEVICE_CONTENTS.len(), MODE_DIR | 0o755), - Handle::Access | Handle::Channel { .. } | Handle::Bind { .. } => (0, MODE_CHR | 0o600), +@@ -142,6 +145,7 @@ impl SchemeSync for PciScheme { + Handle::Access | Handle::Channel { .. } => (0, MODE_CHR | 0o600), + Handle::Uevent => (0, MODE_CHR | 0o644), Handle::SchemeRoot => return Err(Error::new(EBADF)), }; stat.st_size = len as u64; -@@ -164,7 +171,13 @@ impl SchemeSync for PciScheme { - Handle::Channel { - addr: _, +@@ -180,6 +184,10 @@ impl SchemeSync for PciScheme { ref mut st, } => Self::read_channel(st, buf), + Handle::Uevent => { -+ // Uevent surface is ready for hotplug polling consumers. -+ // pcid currently only scans at startup, so return empty (EAGAIN would indicate no data available). -+ // Consumers can poll and re-read to check for new events. ++ // The surface is intentionally present before pcid gains real ++ // hotplug event delivery so consumers can open/poll it ++ // consistently across boot/runtime environments. + Ok(0) + } - Handle::SchemeRoot | Handle::Bind { .. } => Err(Error::new(EBADF)), + Handle::SchemeRoot => Err(Error::new(EBADF)), _ => Err(Error::new(EBADF)), } -@@ -199,6 +212,6 @@ impl SchemeSync for PciScheme { - } +@@ -222,7 +230,7 @@ impl SchemeSync for PciScheme { Handle::Device => DEVICE_CONTENTS, -- Handle::Access | Handle::Channel { .. } | Handle::Bind { .. } => return Err(Error::new(ENOTDIR)), -+ Handle::Access | Handle::Channel { .. } | Handle::Bind { .. } | Handle::Uevent => return Err(Error::new(ENOTDIR)), +- Handle::Access | Handle::Config { .. } | Handle::Channel { .. } => { ++ Handle::Access | Handle::Config { .. } | Handle::Channel { .. } | Handle::Uevent => { + return Err(Error::new(ENOTDIR)); + } Handle::SchemeRoot => return Err(Error::new(EBADF)), - }; - for (i, dent_name) in entries.iter().enumerate().skip(offset) { diff --git a/local/patches/base/P1-xhcid-device-lifecycle.patch b/local/patches/base/P1-xhcid-device-lifecycle.patch index aba95b9190..4521060304 100644 --- a/local/patches/base/P1-xhcid-device-lifecycle.patch +++ b/local/patches/base/P1-xhcid-device-lifecycle.patch @@ -1,5 +1,3 @@ -diff --git a/drivers/storage/usbscsid/src/main.rs b/drivers/storage/usbscsid/src/main.rs -index 5382d118..4130a5df 100644 --- a/drivers/storage/usbscsid/src/main.rs +++ b/drivers/storage/usbscsid/src/main.rs @@ -1,20 +1,32 @@ @@ -32,14 +30,15 @@ index 5382d118..4130a5df 100644 + fn main() { - daemon::Daemon::new(daemon); -+ daemon(); - } +-} -fn daemon(daemon: daemon::Daemon) -> ! { ++ daemon(); ++} +fn daemon() -> ! { let mut args = env::args().skip(1); const USAGE: &'static str = "usbscsid "; -@@ -67,17 +79,56 @@ fn daemon(daemon: daemon::Daemon) -> ! { +@@ -67,17 +79,56 @@ }) .expect("Failed to find suitable configuration"); @@ -51,6 +50,8 @@ index 5382d118..4130a5df 100644 - hub_ports: None, - }) - .expect("Failed to configure endpoints"); +- +- let mut protocol = protocol::setup(&handle, protocol, &desc, &conf_desc, &if_desc) + println!( + "usbscsid: selected config {} iface {} alt {} endpoints {:?}", + configuration_value, @@ -78,8 +79,7 @@ index 5382d118..4130a5df 100644 + ); + std::process::exit(0); + } - -- let mut protocol = protocol::setup(&handle, protocol, &desc, &conf_desc, &if_desc) ++ + eprintln!( + "usbscsid: failed to configure endpoints on port {}: {}", + port, err @@ -105,7 +105,7 @@ index 5382d118..4130a5df 100644 // TODO: Let all of the USB drivers fork or be managed externally, and xhcid won't have to keep // track of all the drivers. -@@ -108,9 +159,6 @@ fn daemon(daemon: daemon::Daemon) -> ! { +@@ -108,9 +159,6 @@ &driver_block::FuturesExecutor, ); @@ -115,11 +115,10 @@ index 5382d118..4130a5df 100644 //libredox::call::setrens(0, 0).expect("nvmed: failed to enter null namespace"); event_queue -diff --git a/drivers/usb/xhcid/src/xhci/mod.rs b/drivers/usb/xhcid/src/xhci/mod.rs -index f2143676..4d3bea40 100644 + --- a/drivers/usb/xhcid/src/xhci/mod.rs +++ b/drivers/usb/xhcid/src/xhci/mod.rs -@@ -13,10 +13,10 @@ use std::collections::BTreeMap; +@@ -13,10 +13,10 @@ use std::convert::TryFrom; use std::fs::File; use std::sync::atomic::AtomicUsize; @@ -132,7 +131,7 @@ index f2143676..4d3bea40 100644 use syscall::{EAGAIN, PAGE_SIZE}; use chashmap::CHashMap; -@@ -150,7 +150,7 @@ impl Xhci { +@@ -150,7 +150,7 @@ trace!("Handling the transfer event TRB!"); self::scheme::handle_transfer_event_trb("GET_DESC", &event_trb, &status_trb)?; @@ -141,7 +140,7 @@ index f2143676..4d3bea40 100644 Ok(()) } -@@ -311,6 +311,144 @@ struct PortState { +@@ -311,6 +311,144 @@ input_context: Mutex>>, dev_desc: Option, endpoint_states: BTreeMap, @@ -286,7 +285,7 @@ index f2143676..4d3bea40 100644 } impl PortState { -@@ -615,29 +753,24 @@ impl Xhci { +@@ -615,29 +753,24 @@ route_string: 0, }; @@ -300,8 +299,9 @@ index f2143676..4d3bea40 100644 let flags = port.flags(); let ccs = flags.contains(PortFlags::CCS); - let csc = flags.contains(PortFlags::CSC); - +- - (ccs, csc, flags) ++ + (ccs, flags) }; @@ -325,7 +325,7 @@ index f2143676..4d3bea40 100644 } } } -@@ -757,7 +890,7 @@ impl Xhci { +@@ -757,7 +890,7 @@ trace!("Slot is enabled!"); self::scheme::handle_event_trb("ENABLE_SLOT", &event_trb, &command_trb)?; @@ -334,7 +334,7 @@ index f2143676..4d3bea40 100644 Ok(event_trb.event_slot()) } -@@ -768,7 +901,7 @@ impl Xhci { +@@ -768,7 +901,7 @@ .await; self::scheme::handle_event_trb("DISABLE_SLOT", &event_trb, &command_trb)?; @@ -343,7 +343,7 @@ index f2143676..4d3bea40 100644 Ok(()) } -@@ -798,6 +931,8 @@ impl Xhci { +@@ -798,6 +931,8 @@ return Err(syscall::Error::new(EAGAIN)); } @@ -352,7 +352,7 @@ index f2143676..4d3bea40 100644 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 +943,111 @@ impl Xhci { +@@ -808,74 +943,111 @@ port_id, data, state, speed, flags ); @@ -362,24 +362,11 @@ index f2143676..4d3bea40 100644 - 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 early_quirks = crate::usb_quirks::lookup_usb_quirks_early(port_id); + let slot_ty = match self.supported_protocol(port_id) { + Some(protocol) => protocol.proto_slot_ty(), @@ -388,11 +375,7 @@ index f2143676..4d3bea40 100644 + 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 { @@ -402,20 +385,10 @@ index f2143676..4d3bea40 100644 + return Err(err); + } + }; - -- let mut input = unsafe { self.alloc_dma_zeroed::>()? }; ++ + debug!("Enabled port {}, which the xHC mapped to {}", port_id, slot); + info!("xhcid: enabled slot {} for port {}", slot, port_id); - -- 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 => { @@ -429,15 +402,48 @@ index f2143676..4d3bea40 100644 } - }; - +- 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); +- } +- }; +- +- debug!("Enabled port {}, which the xHC mapped to {}", port_id, slot); +- +- //TODO: get correct speed for child devices +- let protocol_speed = self +- .lookup_psiv(port_id, speed) +- .expect("Failed to retrieve speed ID"); +- +- let mut input = unsafe { self.alloc_dma_zeroed::>()? }; +- +- 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); +- } +- }; +- - debug!("Addressed device"); +- +- // TODO: Should the descriptors be cached in PortState, or refetched? +- +- let mut port_state = PortState { + return Err(err); + } + }; - -- // TODO: Should the descriptors be cached in PortState, or refetched? ++ + let mut input = unsafe { self.alloc_dma_zeroed::>()? }; - -- let mut port_state = PortState { ++ + debug!("Attempting to address the device"); + let ring = match self + .address_device( @@ -460,6 +466,8 @@ index f2143676..4d3bea40 100644 - }; - self.port_states.insert(port_id, port_state); - debug!("Got port states!"); +- +- // Ensure correct packet size is used + speed, + early_quirks, + ) @@ -502,16 +510,16 @@ index f2143676..4d3bea40 100644 + }; + self.port_states.insert(port_id, port_state); + debug!("Got port states!"); - -- // Ensure correct packet size is used ++ + let attach_result = async { let dev_desc_8_byte = self.fetch_dev_desc_8_byte(port_id, slot).await?; + info!("xhcid: fetched 8-byte device descriptor for port {}", port_id); { - let mut port_state = self.port_states.get_mut(&port_id).unwrap(); -+ let mut port_state = self.port_states.get_mut(&port_id).ok_or(Error::new(ENOENT))?; - +- - let mut input = port_state.input_context.lock().unwrap(); ++ let mut port_state = self.port_states.get_mut(&port_id).ok_or(Error::new(ENOENT))?; ++ + let mut input = port_state + .input_context + .lock() @@ -519,7 +527,7 @@ index f2143676..4d3bea40 100644 self.update_max_packet_size(&mut *input, slot, dev_desc_8_byte) .await?; -@@ -884,37 +1056,87 @@ impl Xhci { +@@ -884,37 +1056,87 @@ debug!("Got the 8 byte dev descriptor: {:X?}", dev_desc_8_byte); let dev_desc = self.get_desc(port_id, slot).await?; @@ -542,9 +550,10 @@ index f2143676..4d3bea40 100644 debug!("Got the port states again!"); { - let mut port_state = self.port_states.get_mut(&port_id).unwrap(); -+ let mut port_state = self.port_states.get_mut(&port_id).ok_or(Error::new(ENOENT))?; - +- - let mut input = port_state.input_context.lock().unwrap(); ++ let mut port_state = self.port_states.get_mut(&port_id).ok_or(Error::new(ENOENT))?; ++ + let mut input = port_state + .input_context + .lock() @@ -558,6 +567,11 @@ index f2143676..4d3bea40 100644 } debug!("Updated the default control pipe"); +- +- match self.spawn_drivers(port_id) { +- Ok(()) => (), +- Err(err) => { +- error!("Failed to spawn driver for port {}: `{}`", port_id, err) + Ok(()) + } + .await; @@ -570,18 +584,20 @@ index f2143676..4d3bea40 100644 + port_id + ); + return Err(Error::new(EBUSY)); -+ } - -- match self.spawn_drivers(port_id) { -- Ok(()) => (), -- Err(err) => { -- error!("Failed to spawn driver for port {}: `{}`", port_id, err) + } +- } +- } else { +- warn!("Attempted to attach a device that didnt have CCS=1"); +- } +- +- Ok(()) ++ + 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(()) + } @@ -594,12 +610,8 @@ index f2143676..4d3bea40 100644 + ); + } + 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 { @@ -619,7 +631,7 @@ index f2143676..4d3bea40 100644 if let Some(children) = self.drivers.remove(&port_id) { for mut child in children { info!("killing driver process {} for port {}", child.id(), port_id); -@@ -962,20 +1184,20 @@ impl Xhci { +@@ -962,20 +1184,20 @@ } } @@ -654,7 +666,7 @@ index f2143676..4d3bea40 100644 } } -@@ -1004,7 +1226,7 @@ impl Xhci { +@@ -1004,7 +1226,7 @@ .await; self::scheme::handle_event_trb("EVALUATE_CONTEXT", &event_trb, &command_trb)?; @@ -663,7 +675,7 @@ index f2143676..4d3bea40 100644 Ok(()) } -@@ -1039,7 +1261,7 @@ impl Xhci { +@@ -1039,7 +1261,7 @@ debug!("Completed the command to update the default control pipe"); self::scheme::handle_event_trb("EVALUATE_CONTEXT", &event_trb, &command_trb)?; @@ -672,7 +684,7 @@ index f2143676..4d3bea40 100644 Ok(()) } -@@ -1052,6 +1274,7 @@ impl Xhci { +@@ -1052,6 +1274,7 @@ slot: u8, protocol_speed: &ProtocolSpeed, speed: u8, @@ -680,7 +692,7 @@ index f2143676..4d3bea40 100644 ) -> Result { // Collect MTT, parent port number, parent slot ID let mut mtt = false; -@@ -1162,11 +1385,16 @@ impl Xhci { +@@ -1162,11 +1385,16 @@ let input_context_physical = input_context.physical(); @@ -702,7 +714,7 @@ index f2143676..4d3bea40 100644 if event_trb.completion_code() != TrbCompletionCode::Success as u8 { error!( -@@ -1175,10 +1403,10 @@ impl Xhci { +@@ -1175,10 +1403,10 @@ port, event_trb.completion_code() ); @@ -715,7 +727,7 @@ index f2143676..4d3bea40 100644 Ok(ring) } -@@ -1281,6 +1509,12 @@ impl Xhci { +@@ -1281,6 +1509,12 @@ ifdesc.sub_class, ifdesc.protocol, ); @@ -728,8 +740,7 @@ index f2143676..4d3bea40 100644 let (command, args) = driver.command.split_first().ok_or(Error::new(EBADMSG))?; let command = if command.starts_with('/') { -diff --git a/drivers/usb/xhcid/src/xhci/scheme.rs b/drivers/usb/xhcid/src/xhci/scheme.rs -index f2d439a4..53770407 100644 + --- a/drivers/usb/xhcid/src/xhci/scheme.rs +++ b/drivers/usb/xhcid/src/xhci/scheme.rs @@ -18,12 +18,15 @@ @@ -747,8 +758,8 @@ index f2d439a4..53770407 100644 +use futures::FutureExt; use log::{debug, error, info, trace, warn}; use redox_scheme::scheme::SchemeSync; - use smallvec::SmallVec; -@@ -32,16 +35,16 @@ use common::io::Io; + use scheme_utils::FpathWriter; +@@ -33,16 +36,16 @@ use redox_scheme::{CallerCtx, OpenResult}; use syscall::schemev2::NewFdFlags; use syscall::{ @@ -769,7 +780,7 @@ index f2d439a4..53770407 100644 SLOT_CONTEXT_STATE_MASK, SLOT_CONTEXT_STATE_SHIFT, }; use super::extended::ProtocolSpeed; -@@ -60,10 +63,16 @@ lazy_static! { +@@ -61,10 +64,16 @@ .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."); @@ -786,7 +797,7 @@ index f2d439a4..53770407 100644 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$") -@@ -137,12 +146,15 @@ pub enum Handle { +@@ -138,12 +147,15 @@ Port(PortId, Vec), // port, contents PortDesc(PortId, Vec), // port, contents PortState(PortId), // port @@ -802,7 +813,7 @@ index f2d439a4..53770407 100644 SchemeRoot, } -@@ -172,6 +184,8 @@ enum SchemeParameters { +@@ -173,6 +185,8 @@ PortDesc(PortId), // port number /// /port/state PortState(PortId), // port number @@ -811,7 +822,7 @@ index f2d439a4..53770407 100644 /// /port/request PortReq(PortId), // port number /// /port/endpoints -@@ -187,6 +201,10 @@ enum SchemeParameters { +@@ -188,6 +202,10 @@ AttachDevice(PortId), // port number /// /port/detach DetachDevice(PortId), // port number @@ -822,17 +833,17 @@ index f2d439a4..53770407 100644 } impl Handle { -@@ -209,6 +227,9 @@ impl Handle { +@@ -209,6 +227,9 @@ + } 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) - } -@@ -235,6 +256,12 @@ impl Handle { +@@ -236,6 +257,12 @@ Handle::DetachDevice(port_num) => { format!("port{}/detach", port_num) } @@ -845,7 +856,7 @@ index f2d439a4..53770407 100644 Handle::SchemeRoot => String::from(""), } } -@@ -258,10 +285,13 @@ impl Handle { +@@ -259,10 +286,13 @@ &Handle::PortReq(_, PortReqState::Tmp) => unreachable!(), &Handle::PortReq(_, PortReqState::TmpSetup(_)) => unreachable!(), &Handle::PortState(_) => HandleType::Character, @@ -859,7 +870,7 @@ index f2d439a4..53770407 100644 &Handle::Endpoint(_, _, ref st) => match st { EndpointHandleTy::Data => HandleType::Character, EndpointHandleTy::Ctl => HandleType::Character, -@@ -289,10 +319,13 @@ impl Handle { +@@ -290,10 +320,13 @@ &Handle::PortReq(_, PortReqState::Tmp) => None, &Handle::PortReq(_, PortReqState::TmpSetup(_)) => None, &Handle::PortState(_) => None, @@ -873,7 +884,7 @@ index f2d439a4..53770407 100644 &Handle::Endpoint(_, _, ref st) => match st { EndpointHandleTy::Data => None, EndpointHandleTy::Ctl => None, -@@ -383,6 +416,14 @@ impl SchemeParameters { +@@ -384,6 +417,14 @@ let port_num = get_port_id_from_regex(®EX_PORT_DETACH, scheme, 0)?; Ok(Self::DetachDevice(port_num)) @@ -888,7 +899,7 @@ index f2d439a4..53770407 100644 } else if REGEX_PORT_DESCRIPTORS.is_match(scheme) { let port_num = get_port_id_from_regex(®EX_PORT_DESCRIPTORS, scheme, 0)?; -@@ -391,6 +432,10 @@ impl SchemeParameters { +@@ -392,6 +433,10 @@ let port_num = get_port_id_from_regex(®EX_PORT_STATE, scheme, 0)?; Ok(Self::PortState(port_num)) @@ -899,7 +910,7 @@ index f2d439a4..53770407 100644 } else if REGEX_PORT_REQUEST.is_match(scheme) { let port_num = get_port_id_from_regex(®EX_PORT_REQUEST, scheme, 0)?; -@@ -556,6 +601,47 @@ impl AnyDescriptor { +@@ -557,6 +602,47 @@ } impl Xhci { @@ -947,7 +958,7 @@ index f2d439a4..53770407 100644 async fn new_if_desc( &self, port_id: PortId, -@@ -564,15 +650,22 @@ impl Xhci { +@@ -565,15 +651,22 @@ endps: impl IntoIterator, hid_descs: impl IntoIterator, lang_id: u16, @@ -973,7 +984,7 @@ index f2d439a4..53770407 100644 } else { None }, -@@ -590,10 +683,9 @@ impl Xhci { +@@ -591,10 +684,9 @@ /// # Locking /// This function will lock `Xhci::cmd` and `Xhci::dbs`. pub async fn execute_command(&self, f: F) -> (Trb, Trb) { @@ -985,10 +996,11 @@ index f2d439a4..53770407 100644 } let next_event = { -@@ -628,6 +720,54 @@ impl Xhci { +@@ -628,6 +720,54 @@ + ); (event_trb, command_trb) - } ++ } + pub fn execute_command_with_timeout( + &self, + timeout: common::timeout::Timeout, @@ -1036,11 +1048,10 @@ index f2d439a4..53770407 100644 + + timeout.run().map_err(|()| Error::new(EIO))?; + } -+ } + } pub async fn execute_control_transfer( &self, - port_num: PortId, -@@ -639,6 +779,9 @@ impl Xhci { +@@ -640,6 +780,9 @@ where D: FnMut(&mut Trb, bool) -> ControlFlow, { @@ -1050,7 +1061,7 @@ index f2d439a4..53770407 100644 let future = { let mut port_state = self.port_state_mut(port_num)?; let slot = port_state.slot; -@@ -690,7 +833,21 @@ impl Xhci { +@@ -691,7 +834,21 @@ handle_transfer_event_trb("CONTROL_TRANSFER", &event_trb, &status_trb)?; @@ -1073,7 +1084,7 @@ index f2d439a4..53770407 100644 Ok(event_trb) } -@@ -709,6 +866,9 @@ impl Xhci { +@@ -710,6 +867,9 @@ where D: FnMut(&mut Trb, bool) -> ControlFlow, { @@ -1083,7 +1094,7 @@ index f2d439a4..53770407 100644 let endp_idx = endp_num.checked_sub(1).ok_or(Error::new(EIO))?; let mut port_state = self.port_state_mut(port_num)?; -@@ -785,7 +945,31 @@ impl Xhci { +@@ -786,7 +946,31 @@ let event_trb = trbs.event_trb; let transfer_trb = trbs.src_trb.ok_or(Error::new(EIO))?; @@ -1116,16 +1127,16 @@ index f2d439a4..53770407 100644 // FIXME: EDTLA if event data was set if event_trb.completion_code() != TrbCompletionCode::ShortPacket as u8 -@@ -798,6 +982,8 @@ impl Xhci { +@@ -798,6 +982,8 @@ + // TODO: Handle event data trace!("EVENT DATA: {:?}", event_trb.event_data()); - -+ self.event_handler_finished(); + ++ self.event_handler_finished(); + Ok(event_trb) } - async fn device_req_no_data(&self, port: PortId, req: usb::Setup) -> Result<()> { -@@ -857,10 +1043,27 @@ impl Xhci { +@@ -858,9 +1044,26 @@ trb.reset_endpoint(slot, endp_num_xhc, tsp, cycle); }) .await; @@ -1133,7 +1144,7 @@ index f2d439a4..53770407 100644 + self.event_handler_finished(); handle_event_trb("RESET_ENDPOINT", &event_trb, &command_trb) - } ++ } + async fn reset_device_slot(&self, port_num: PortId) -> Result<()> { + let slot = self + .port_states @@ -1150,11 +1161,10 @@ index f2d439a4..53770407 100644 + self.event_handler_finished(); + + handle_event_trb("RESET_DEVICE", &event_trb, &command_trb) -+ } + } fn endp_ctx_interval(speed_id: &ProtocolSpeed, endp_desc: &EndpDesc) -> u8 { - /// Logarithmic (base 2) 125 µs periods per millisecond. -@@ -949,35 +1152,65 @@ impl Xhci { +@@ -950,35 +1153,65 @@ self.port_states.get_mut(&port).ok_or(Error::new(EBADF)) } @@ -1197,21 +1207,21 @@ index f2d439a4..53770407 100644 - 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) = { -+ 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 config_desc = port_state - .dev_desc - .as_ref() - .unwrap() ++ let (dev_desc, endpoint_descs, new_context_entries, configuration_value) = { ++ 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 config_desc = dev_desc .config_descs .iter() .find(|desc| desc.configuration_value == req.config_desc) .ok_or(Error::new(EBADFD))?; -+ let configuration_value = config_desc.configuration_value; - +- - //TODO: USE ENDPOINTS FROM ALL INTERFACES - let mut endp_desc_count = 0; - let mut new_context_entries = 1; @@ -1222,6 +1232,8 @@ index f2d439a4..53770407 100644 - if entry > new_context_entries { - new_context_entries = entry; - } ++ let configuration_value = config_desc.configuration_value; ++ + let endpoint_descs = config_desc + .interface_descs + .iter() @@ -1238,7 +1250,7 @@ index f2d439a4..53770407 100644 } } new_context_entries += 1; -@@ -988,11 +1221,13 @@ impl Xhci { +@@ -989,11 +1222,13 @@ } ( @@ -1254,7 +1266,7 @@ index f2d439a4..53770407 100644 let lec = self.cap.lec(); let log_max_psa_size = self.cap.max_psa_size(); -@@ -1002,9 +1237,160 @@ impl Xhci { +@@ -1003,9 +1238,160 @@ Error::new(EIO) })?; @@ -1416,7 +1428,7 @@ index f2d439a4..53770407 100644 // Configure the slot context as well, which holds the last index of the endp descs. input_context.add_context.write(1); -@@ -1015,25 +1401,26 @@ impl Xhci { +@@ -1016,25 +1402,26 @@ const HUB_PORTS_MASK: u32 = 0xFF00_0000; const HUB_PORTS_SHIFT: u8 = 24; @@ -1445,7 +1457,7 @@ index f2d439a4..53770407 100644 let control = if self.op.lock().unwrap().cie() { (u32::from(req.alternate_setting.unwrap_or(0)) << 16) -@@ -1043,174 +1430,132 @@ impl Xhci { +@@ -1044,174 +1431,132 @@ 0 }; input_context.control.write(control); @@ -1460,7 +1472,7 @@ index f2d439a4..53770407 100644 - warn!("failed to find endpoint {}", endp_idx); - Error::new(EIO) - })?; - +- - let endp_num_xhc = Self::endp_num_to_dci(endp_num, endp_desc); - - let usb_log_max_streams = endp_desc.log_max_streams(); @@ -1472,11 +1484,7 @@ index f2d439a4..53770407 100644 - cmp::min(u8::from(log_max_streams), log_max_psa_size + 1) - 1 - } else { - 0 -- } -- } else { -- 0 -- }; -- let linear_stream_array = if primary_streams != 0 { true } else { false }; ++ + 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); @@ -1486,22 +1494,46 @@ index f2d439a4..53770407 100644 + input_context.device.endpoints[endp_i].trh.write(program.trh); + input_context.device.endpoints[endp_i].c.write(program.c); + } - -- // TODO: Interval related fields -- // TODO: Max ESIT payload size. ++ + (configure_snapshot, endpoint_snapshots, input_context.physical()) + }; - -- let mult = endp_desc.isoch_mult(lec); ++ + let port_state = self.port_states.get(&port).ok_or(Error::new(EBADFD))?; + let slot = port_state.slot; - -- 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 (event_trb, command_trb) = self + .execute_command(|trb, cycle| trb.configure_endpoint(slot, input_context_physical, cycle)) + .await; - ++ ++ self.event_handler_finished(); ++ ++ if let Err(err) = handle_event_trb("CONFIGURE_ENDPOINT", &event_trb, &command_trb) { ++ 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 CONFIGURE_ENDPOINT failure: {:?}", ++ restore_err ++ ); ++ return Err(err); + } +- } else { +- 0 + }; +- let linear_stream_array = if primary_streams != 0 { true } else { false }; +- +- // TODO: Interval related fields +- // TODO: Max ESIT payload size. +- +- 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_esit_payload = Self::endp_ctx_max_esit_payload( - speed_id, - dev_desc, @@ -1525,26 +1557,11 @@ index f2d439a4..53770407 100644 - EndpointTy::Ctrl => { - warn!("trying to use control endpoint"); - return Err(Error::new(EIO)); // only endpoint zero is of type control, and is configured separately with the address device command. -+ self.event_handler_finished(); -+ -+ if let Err(err) = handle_event_trb("CONFIGURE_ENDPOINT", &event_trb, &command_trb) { -+ 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 CONFIGURE_ENDPOINT failure: {:?}", -+ restore_err -+ ); -+ return Err(err); - } +- } - EndpointTy::Bulk | EndpointTy::Isoch => 3072, // 3 KiB - EndpointTy::Interrupt => 1024, // 1 KiB - }; - +- }; +- - assert_eq!(ep_ty & 0x7, ep_ty); - assert_eq!(mult & 0x3, mult); - assert_eq!(max_error_count & 0x3, max_error_count); @@ -1553,17 +1570,11 @@ index f2d439a4..53770407 100644 - let ring_ptr = if usb_log_max_streams.is_some() { - let mut array = - StreamContextArray::new::(self.cap.ac64(), 1 << (primary_streams + 1))?; -+ let (rollback_event_trb, rollback_command_trb) = self -+ .execute_command(|trb, cycle| { -+ trb.configure_endpoint(slot, rollback_input_context_physical, cycle) -+ }) -+ .await; - +- - // TODO: Use as many stream rings as needed. - array.add_ring::(self.cap.ac64(), 1, true)?; - let array_ptr = array.register(); -+ self.event_handler_finished(); - +- - assert_eq!( - array_ptr & 0xFFFF_FFFF_FFFF_FF81, - array_ptr, @@ -1575,16 +1586,8 @@ index f2d439a4..53770407 100644 - transfer: super::RingOrStreams::Streams(array), - driver_if_state: EndpIfState::Init, - }, -+ 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 failure {:?}: {:?}", -+ err, -+ rollback_err - ); -+ } - +- ); +- - array_ptr - } else { - let ring = Ring::new::(self.cap.ac64(), 16, true)?; @@ -1624,16 +1627,55 @@ index f2d439a4..53770407 100644 - | u32::from(max_burst_size) << 8 - | u32::from(max_packet_size) << 16, - ); -+ return Err(err); -+ } - +- - 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); -+ // Tell the device about this configuration. +- +- input_context.device.endpoints[endp_i] +- .c +- .write(u32::from(avg_trb_len) | (u32::from(max_esit_payload_lo) << 16)); +- +- log::debug!("initialized endpoint {}", endp_num); +- } +- +- { +- 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 (event_trb, command_trb) = self ++ ++ let (rollback_event_trb, rollback_command_trb) = self + .execute_command(|trb, cycle| { +- trb.configure_endpoint(slot, input_context_physical, cycle) ++ trb.configure_endpoint(slot, rollback_input_context_physical, cycle) + }) + .await; + +- //self.event_handler_finished(); +- +- handle_event_trb("CONFIGURE_ENDPOINT", &event_trb, &command_trb)?; ++ self.event_handler_finished(); ++ ++ 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 failure {:?}: {:?}", ++ err, ++ rollback_err ++ ); ++ } ++ ++ return Err(err); + } + + // Tell the device about this configuration. +- self.set_configuration(port, configuration_value).await?; + let skip_set_configuration = self + .port_states + .get(&port) @@ -1660,10 +1702,7 @@ index f2d439a4..53770407 100644 + return Err(err); + } + }; - -- input_context.device.endpoints[endp_i] -- .c -- .write(u32::from(avg_trb_len) | (u32::from(max_esit_payload_lo) << 16)); ++ + let (rollback_event_trb, rollback_command_trb) = self + .execute_command(|trb, cycle| { + trb.configure_endpoint(slot, rollback_input_context_physical, cycle) @@ -1683,37 +1722,20 @@ index f2d439a4..53770407 100644 + rollback_err + ); + } - -- log::debug!("initialized endpoint {}", endp_num); ++ + return Err(err); + } - } - - { -- 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 (event_trb, command_trb) = self -- .execute_command(|trb, cycle| { -- trb.configure_endpoint(slot, input_context_physical, cycle) -- }) -- .await; -- -- //self.event_handler_finished(); -- -- handle_event_trb("CONFIGURE_ENDPOINT", &event_trb, &command_trb)?; ++ } ++ ++ { + 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); + } - } ++ } -- // Tell the device about this configuration. -- self.set_configuration(port, configuration_value).await?; -- Ok(()) } @@ -1722,7 +1744,7 @@ index f2d439a4..53770407 100644 let mut req: ConfigureEndpointsReq = serde_json::from_slice(json_buf).or(Err(Error::new(EBADMSG)))?; -@@ -1234,8 +1579,20 @@ impl Xhci { +@@ -1235,8 +1580,20 @@ if let Some(interface_num) = req.interface_desc { if let Some(alternate_setting) = req.alternate_setting { @@ -1745,7 +1767,7 @@ index f2d439a4..53770407 100644 } } -@@ -1432,7 +1789,7 @@ impl Xhci { +@@ -1433,7 +1790,7 @@ }, ) .await?; @@ -1754,7 +1776,7 @@ index f2d439a4..53770407 100644 let bytes_transferred = dma_buf .as_ref() -@@ -1453,52 +1810,109 @@ impl Xhci { +@@ -1454,52 +1811,109 @@ let raw_dd = self.fetch_dev_desc(port_id, slot).await?; log::debug!("port {} slot {} desc {:X?}", port_id, slot, raw_dd); @@ -1800,7 +1822,7 @@ index f2d439a4..53770407 100644 + None => 0, + } + } -+ } + } + Err(err) if bad_descriptor => { + log::warn!( + "port {} slot {}: failed to fetch language IDs with BAD_DESCRIPTOR set: {}", @@ -1809,7 +1831,7 @@ index f2d439a4..53770407 100644 + err + ); + 0 - } ++ } + Err(err) => return Err(err), } } else { @@ -1822,25 +1844,10 @@ index f2d439a4..53770407 100644 - Some( - self.fetch_string_desc(port_id, slot, raw_dd.manufacturer_str, lang_id) - .await?, -- ) -- } else { -- None -- }, -- if raw_dd.product_str > 0 { -- Some( -- self.fetch_string_desc(port_id, slot, raw_dd.product_str, lang_id) -- .await?, -- ) + let (manufacturer_str, product_str, serial_str) = + if quirks.contains(crate::usb_quirks::UsbQuirkFlags::NO_STRING_FETCH) { + (None, None, None) - } else { -- None -- }, -- if raw_dd.serial_str > 0 { -- Some( -- self.fetch_string_desc(port_id, slot, raw_dd.serial_str, lang_id) -- .await?, ++ } else { + ( + if raw_dd.manufacturer_str > 0 { + if bad_descriptor { @@ -1893,12 +1900,28 @@ index f2d439a4..53770407 100644 - } else { - None - }, +- if raw_dd.product_str > 0 { +- Some( +- self.fetch_string_desc(port_id, slot, raw_dd.product_str, lang_id) +- .await?, +- ) +- } else { +- None +- }, +- if raw_dd.serial_str > 0 { +- Some( +- self.fetch_string_desc(port_id, slot, raw_dd.serial_str, lang_id) +- .await?, +- ) +- } else { +- None +- }, - ); + }; log::debug!( "manufacturer {:?} product {:?} serial {:?}", manufacturer_str, -@@ -1508,14 +1922,39 @@ impl Xhci { +@@ -1509,14 +1923,39 @@ //TODO let (bos_desc, bos_data) = self.fetch_bos_desc(port_id, slot).await?; @@ -1943,7 +1966,7 @@ index f2d439a4..53770407 100644 debug!("Fetching the config descriptor at index {}", index); let (desc, data) = self.fetch_config_desc(port_id, slot, index).await?; log::debug!( -@@ -1541,6 +1980,12 @@ impl Xhci { +@@ -1542,6 +1981,12 @@ let mut iter = descriptors.into_iter().peekable(); while let Some(item) = iter.next() { @@ -1956,7 +1979,7 @@ index f2d439a4..53770407 100644 if let AnyDescriptor::Interface(idesc) = item { let mut endpoints = SmallVec::<[EndpDesc; 4]>::new(); let mut hid_descs = SmallVec::<[HidDesc; 1]>::new(); -@@ -1554,6 +1999,9 @@ impl Xhci { +@@ -1555,6 +2000,9 @@ } Some(unexpected) => { log::warn!("expected endpoint, got {:X?}", unexpected); @@ -1966,7 +1989,7 @@ index f2d439a4..53770407 100644 break; } None => break, -@@ -1578,8 +2026,16 @@ impl Xhci { +@@ -1579,8 +2027,16 @@ } interface_descs.push( @@ -1985,7 +2008,7 @@ index f2d439a4..53770407 100644 ); } else { log::warn!("expected interface, got {:?}", item); -@@ -1590,11 +2046,20 @@ impl Xhci { +@@ -1591,11 +2047,20 @@ config_descs.push(ConfDesc { kind: desc.kind, @@ -2010,7 +2033,7 @@ index f2d439a4..53770407 100644 } else { None }, -@@ -1856,7 +2321,7 @@ impl Xhci { +@@ -1857,7 +2322,7 @@ if (flags & O_DIRECTORY != 0) || (flags & O_STAT != 0) { let mut contents = Vec::new(); @@ -2019,25 +2042,27 @@ index f2d439a4..53770407 100644 if self.slot_state( self.port_states -@@ -1893,6 +2358,14 @@ impl Xhci { - Ok(Handle::PortState(port_num)) - } +@@ -1892,6 +2357,14 @@ + } + 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 -@@ -2087,6 +2560,30 @@ impl Xhci { - Ok(Handle::DetachDevice(port_num)) } + /// implements open() for /port/endpoints +@@ -2086,6 +2559,30 @@ + } + + 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)); @@ -2060,12 +2085,10 @@ index f2d439a4..53770407 100644 + } + + Ok(Handle::ResumeDevice(port_num)) -+ } -+ + } + /// implements open() for /port/request - /// - /// # Arguments -@@ -2155,6 +2652,9 @@ impl SchemeSync for &Xhci { +@@ -2156,6 +2653,9 @@ SchemeParameters::PortState(port_number) => { self.open_handle_port_state(port_number, flags)? } @@ -2075,20 +2098,20 @@ index f2d439a4..53770407 100644 SchemeParameters::PortReq(port_number) => { self.open_handle_port_request(port_number, flags)? } -@@ -2173,6 +2673,12 @@ impl SchemeSync for &Xhci { +@@ -2173,6 +2673,12 @@ + } 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); -@@ -2203,7 +2709,11 @@ impl SchemeSync for &Xhci { +@@ -2204,7 +2710,11 @@ //If we have a handle to the configure scheme, we need to mark it as write only. match &*guard { @@ -2101,7 +2124,7 @@ index f2d439a4..53770407 100644 stat.st_mode = stat.st_mode | 0o200; } _ => {} -@@ -2263,6 +2773,8 @@ impl SchemeSync for &Xhci { +@@ -2254,6 +2764,8 @@ Handle::ConfigureEndpoints(_) => Err(Error::new(EBADF)), Handle::AttachDevice(_) => Err(Error::new(EBADF)), Handle::DetachDevice(_) => Err(Error::new(EBADF)), @@ -2110,18 +2133,18 @@ index f2d439a4..53770407 100644 Handle::SchemeRoot => Err(Error::new(EBADF)), &mut Handle::Endpoint(port_num, endp_num, ref mut st) => match st { -@@ -2294,6 +2806,10 @@ impl SchemeSync for &Xhci { +@@ -2284,6 +2796,10 @@ + .as_bytes(); 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 -@@ -2333,6 +2849,14 @@ impl SchemeSync for &Xhci { +@@ -2324,6 +2840,14 @@ block_on(self.detach_device(port_num))?; Ok(buf.len()) } @@ -2136,10 +2159,11 @@ index f2d439a4..53770407 100644 &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 => { -@@ -2356,6 +2880,59 @@ impl Xhci { +@@ -2346,6 +2870,59 @@ self.handles.remove(&fd); } - + } ++ + fn ensure_port_active(&self, port_num: PortId) -> Result<()> { + let port_state = self.port_states.get(&port_num).ok_or(Error::new(EBADFD))?; + if port_state.lifecycle.state() == super::PortLifecycleState::Detaching { @@ -2192,11 +2216,10 @@ index f2d439a4..53770407 100644 + port_state.pm_state = super::PortPmState::Active; + 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))?; -@@ -2406,6 +2983,8 @@ impl Xhci { + impl Xhci { + pub fn get_endp_status(&self, port_num: PortId, endp_num: u8) -> Result { +@@ -2398,6 +2975,8 @@ endp_num: u8, clear_feature: bool, ) -> Result<()> { @@ -2205,7 +2228,7 @@ index f2d439a4..53770407 100644 if self.get_endp_status(port_num, endp_num)? != EndpointStatus::Halted { return Err(Error::new(EPROTO)); } -@@ -2531,7 +3110,7 @@ impl Xhci { +@@ -2523,7 +3102,7 @@ ) }) .await; @@ -2214,7 +2237,7 @@ index f2d439a4..53770407 100644 handle_event_trb("SET_TR_DEQUEUE_PTR", &event_trb, &command_trb) } -@@ -2541,10 +3120,14 @@ impl Xhci { +@@ -2533,10 +3112,14 @@ endp_num: u8, buf: &[u8], ) -> Result { @@ -2229,7 +2252,7 @@ index f2d439a4..53770407 100644 let ep_if_state = &mut port_state .endpoint_states -@@ -2562,6 +3145,7 @@ impl Xhci { +@@ -2554,6 +3137,7 @@ }, XhciEndpCtlReq::Reset { no_clear_feature } => match ep_if_state { EndpIfState::Init => { @@ -2237,7 +2260,7 @@ index f2d439a4..53770407 100644 self.on_req_reset_device(port_num, endp_num, !no_clear_feature) .await? } -@@ -2631,6 +3215,9 @@ impl Xhci { +@@ -2623,6 +3207,9 @@ endp_num: u8, buf: &[u8], ) -> Result { @@ -2247,7 +2270,7 @@ index f2d439a4..53770407 100644 let mut port_state = self .port_states .get_mut(&port_num) -@@ -2732,6 +3319,9 @@ impl Xhci { +@@ -2724,6 +3311,9 @@ endp_num: u8, buf: &mut [u8], ) -> Result { @@ -2257,7 +2280,7 @@ index f2d439a4..53770407 100644 let mut port_state = self .port_states .get_mut(&port_num) -@@ -2832,6 +3422,64 @@ pub fn handle_transfer_event_trb(name: &str, event_trb: &Trb, transfer_trb: &Trb +@@ -2824,6 +3414,64 @@ Err(Error::new(EIO)) } } @@ -2322,7 +2345,7 @@ index f2d439a4..53770407 100644 use lazy_static::lazy_static; use std::ops::{Add, Div, Rem}; -@@ -2845,3 +3493,26 @@ where +@@ -2837,3 +3485,26 @@ a / b } } diff --git a/local/patches/base/P1-xhcid-port-pm-read-fix.patch b/local/patches/base/P1-xhcid-port-pm-read-fix.patch deleted file mode 100644 index feb847b5f3..0000000000 --- a/local/patches/base/P1-xhcid-port-pm-read-fix.patch +++ /dev/null @@ -1,942 +0,0 @@ -diff --git a/drivers/usb/xhcid/src/xhci/mod.rs b/drivers/usb/xhcid/src/xhci/mod.rs -index f2143676..74126f67 100644 ---- a/drivers/usb/xhcid/src/xhci/mod.rs -+++ b/drivers/usb/xhcid/src/xhci/mod.rs -@@ -311,6 +311,22 @@ struct PortState { - input_context: Mutex>>, - dev_desc: Option, - endpoint_states: BTreeMap, -+ quirks: crate::usb_quirks::UsbQuirkFlags, -+ pm_state: PortPmState, -+} -+ -+#[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 { -@@ -809,6 +825,7 @@ impl Xhci { - ); - - if flags.contains(port::PortFlags::CCS) { -+ let early_quirks = crate::usb_quirks::lookup_usb_quirks_early(port_id); - let slot_ty = match self.supported_protocol(port_id) { - Some(protocol) => protocol.proto_slot_ty(), - None => { -@@ -838,7 +855,15 @@ impl Xhci { - - debug!("Attempting to address the device"); - let mut ring = match self -- .address_device(&mut input, port_id, slot_ty, slot, protocol_speed, speed) -+ .address_device( -+ &mut input, -+ port_id, -+ slot_ty, -+ slot, -+ protocol_speed, -+ speed, -+ early_quirks, -+ ) - .await - { - Ok(device_ring) => device_ring, -@@ -866,6 +891,8 @@ impl Xhci { - }, - )) - .collect::>(), -+ quirks: early_quirks, -+ pm_state: PortPmState::Active, - }; - self.port_states.insert(port_id, port_state); - debug!("Got port states!"); -@@ -884,8 +911,14 @@ impl Xhci { - debug!("Got the 8 byte dev descriptor: {:X?}", dev_desc_8_byte); - - let dev_desc = self.get_desc(port_id, slot).await?; -+ let quirks = early_quirks -+ | crate::usb_quirks::lookup_usb_quirks(dev_desc.vendor, dev_desc.product); - debug!("Got the full device descriptor!"); -- self.port_states.get_mut(&port_id).unwrap().dev_desc = Some(dev_desc); -+ { -+ let mut port_state = self.port_states.get_mut(&port_id).unwrap(); -+ port_state.quirks = quirks; -+ port_state.dev_desc = Some(dev_desc); -+ } - - debug!("Got the port states again!"); - { -@@ -1052,6 +1085,7 @@ impl Xhci { - slot: u8, - protocol_speed: &ProtocolSpeed, - speed: u8, -+ quirks: crate::usb_quirks::UsbQuirkFlags, - ) -> Result { - // Collect MTT, parent port number, parent slot ID - let mut mtt = false; -@@ -1162,11 +1196,16 @@ impl Xhci { - - let input_context_physical = input_context.physical(); - -- let (event_trb, _) = self -- .execute_command(|trb, cycle| { -- trb.address_device(slot, input_context_physical, false, cycle) -- }) -- .await; -+ let address_timeout = if quirks.contains(crate::usb_quirks::UsbQuirkFlags::SHORT_SET_ADDR_TIMEOUT) -+ { -+ Timeout::from_millis(100) -+ } else { -+ Timeout::from_secs(1) -+ }; -+ -+ let (event_trb, _) = self.execute_command_with_timeout(address_timeout, |trb, cycle| { -+ trb.address_device(slot, input_context_physical, false, cycle) -+ })?; - - if event_trb.completion_code() != TrbCompletionCode::Success as u8 { - error!( -diff --git a/drivers/usb/xhcid/src/xhci/scheme.rs b/drivers/usb/xhcid/src/xhci/scheme.rs -index f2d439a4..dfe9fdec 100644 ---- a/drivers/usb/xhcid/src/xhci/scheme.rs -+++ b/drivers/usb/xhcid/src/xhci/scheme.rs -@@ -24,6 +24,7 @@ use std::{cmp, fmt, io, mem, str}; - - use common::dma::Dma; - use futures::executor::block_on; -+use futures::FutureExt; - use log::{debug, error, info, trace, warn}; - use redox_scheme::scheme::SchemeSync; - use smallvec::SmallVec; -@@ -32,9 +33,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}; -@@ -60,10 +61,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$") -@@ -137,12 +144,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, - } - -@@ -172,6 +182,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 -@@ -187,6 +199,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 { -@@ -209,6 +225,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) - } -@@ -235,6 +254,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(""), - } - } -@@ -258,10 +283,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, -@@ -289,10 +317,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, -@@ -383,6 +414,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)?; - -@@ -391,6 +430,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)?; - -@@ -564,15 +607,22 @@ impl Xhci { - endps: impl IntoIterator, - hid_descs: impl IntoIterator, - lang_id: u16, -+ quirks: crate::usb_quirks::UsbQuirkFlags, - ) -> Result { - Ok(IfDesc { - alternate_setting: desc.alternate_setting, - class: desc.class, - interface_str: if desc.interface_str > 0 { -- Some( -+ if quirks.contains(crate::usb_quirks::UsbQuirkFlags::BAD_DESCRIPTOR) { - self.fetch_string_desc(port_id, slot, desc.interface_str, lang_id) -- .await?, -- ) -+ .await -+ .ok() -+ } else { -+ Some( -+ self.fetch_string_desc(port_id, slot, desc.interface_str, lang_id) -+ .await?, -+ ) -+ } - } else { - None - }, -@@ -628,6 +678,53 @@ impl Xhci { - - (event_trb, command_trb) - } -+ pub fn execute_command_with_timeout( -+ &self, -+ timeout: common::timeout::Timeout, -+ f: F, -+ ) -> Result<(Trb, Trb)> { -+ if self.interrupt_is_pending(0) { -+ debug!("The EHB bit is already set!"); -+ } -+ -+ let next_event = { -+ let mut command_ring = self.cmd.lock().unwrap(); -+ let (cmd_index, cycle) = (command_ring.next_index(), command_ring.cycle); -+ -+ debug!("Sending command with cycle bit {}", cycle as u8); -+ -+ { -+ let command_trb = &mut command_ring.trbs[cmd_index]; -+ f(command_trb, cycle); -+ } -+ -+ let command_trb = &command_ring.trbs[cmd_index]; -+ self.next_command_completion_event_trb( -+ &*command_ring, -+ command_trb, -+ EventDoorbell::new(self, 0, 0), -+ ) -+ }; -+ -+ let mut next_event = Box::pin(next_event); -+ -+ loop { -+ if let Some(trbs) = next_event.as_mut().now_or_never() { -+ let event_trb = trbs.event_trb; -+ let command_trb = trbs.src_trb.ok_or(Error::new(EIO))?; -+ -+ assert_eq!( -+ event_trb.trb_type(), -+ TrbType::CommandCompletion as u8, -+ "The IRQ reactor (or the xHC) gave an invalid event TRB" -+ ); -+ -+ return Ok((event_trb, command_trb)); -+ } -+ -+ timeout.run().map_err(|()| Error::new(EIO))?; -+ } -+ } - pub async fn execute_control_transfer( - &self, - port_num: PortId, -@@ -639,6 +736,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; -@@ -690,6 +789,20 @@ impl Xhci { - - handle_transfer_event_trb("CONTROL_TRANSFER", &event_trb, &status_trb)?; - -+ let delay_ctrl_msg = self -+ .port_states -+ .get(&port_num) -+ .map(|port_state| { -+ port_state -+ .quirks -+ .contains(crate::usb_quirks::UsbQuirkFlags::DELAY_CTRL_MSG) -+ }) -+ .unwrap_or(false); -+ -+ if delay_ctrl_msg { -+ std::thread::sleep(std::time::Duration::from_millis(20)); -+ } -+ - //self.event_handler_finished(); - - Ok(event_trb) -@@ -709,6 +822,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)?; - -@@ -785,7 +900,29 @@ impl Xhci { - let event_trb = trbs.event_trb; - let transfer_trb = trbs.src_trb.ok_or(Error::new(EIO))?; - -- handle_transfer_event_trb("EXECUTE_TRANSFER", &event_trb, &transfer_trb)?; -+ if let Err(err) = handle_transfer_event_trb("EXECUTE_TRANSFER", &event_trb, &transfer_trb) -+ { -+ let need_reset = self -+ .port_states -+ .get(&port_num) -+ .map(|port_state| { -+ port_state -+ .quirks -+ .contains(crate::usb_quirks::UsbQuirkFlags::NEED_RESET) -+ }) -+ .unwrap_or(false); -+ -+ if need_reset { -+ if let Err(reset_err) = self.reset_device_slot(port_num).await { -+ error!( -+ "EXECUTE_TRANSFER reset recovery failed for port {}: {}", -+ port_num, reset_err -+ ); -+ } -+ } -+ -+ return Err(err); -+ } - - // FIXME: EDTLA if event data was set - if event_trb.completion_code() != TrbCompletionCode::ShortPacket as u8 -@@ -861,6 +998,21 @@ impl Xhci { - - handle_event_trb("RESET_ENDPOINT", &event_trb, &command_trb) - } -+ async fn reset_device_slot(&self, port_num: PortId) -> Result<()> { -+ let slot = self -+ .port_states -+ .get(&port_num) -+ .ok_or(Error::new(EBADF))? -+ .slot; -+ -+ let (event_trb, command_trb) = self -+ .execute_command(|trb, cycle| { -+ trb.reset_device(slot, cycle); -+ }) -+ .await; -+ -+ handle_event_trb("RESET_DEVICE", &event_trb, &command_trb) -+ } - - fn endp_ctx_interval(speed_id: &ProtocolSpeed, endp_desc: &EndpDesc) -> u8 { - /// Logarithmic (base 2) 125 µs periods per millisecond. -@@ -1205,7 +1357,19 @@ impl Xhci { - } - - // Tell the device about this configuration. -- self.set_configuration(port, configuration_value).await?; -+ let skip_set_configuration = self -+ .port_states -+ .get(&port) -+ .map(|port_state| { -+ port_state -+ .quirks -+ .contains(crate::usb_quirks::UsbQuirkFlags::NO_SET_CONFIG) -+ }) -+ .unwrap_or(false); -+ -+ if !skip_set_configuration { -+ self.set_configuration(port, configuration_value).await?; -+ } - - Ok(()) - } -@@ -1234,8 +1398,20 @@ impl Xhci { - - if let Some(interface_num) = req.interface_desc { - if let Some(alternate_setting) = req.alternate_setting { -- self.set_interface(port, interface_num, alternate_setting) -- .await?; -+ let skip_set_interface = self -+ .port_states -+ .get(&port) -+ .map(|port_state| { -+ port_state -+ .quirks -+ .contains(crate::usb_quirks::UsbQuirkFlags::NO_SET_INTF) -+ }) -+ .unwrap_or(false); -+ -+ if !skip_set_interface { -+ self.set_interface(port, interface_num, alternate_setting) -+ .await?; -+ } - } - } - -@@ -1453,52 +1629,109 @@ impl Xhci { - let raw_dd = self.fetch_dev_desc(port_id, slot).await?; - log::debug!("port {} slot {} desc {:X?}", port_id, slot, raw_dd); - -+ let vendor = raw_dd.vendor; -+ let product = raw_dd.product; -+ let quirks = crate::usb_quirks::lookup_usb_quirks(vendor, product); -+ if !quirks.is_empty() { -+ log::info!( -+ "port {}: USB quirks for {:04x}:{:04x}: {:?}", -+ port_id, vendor, product, quirks -+ ); -+ } -+ - // Only fetch language IDs if we need to. Some devices will fail to return this descriptor - //TODO: also check configurations and interfaces for defined strings? -+ let bad_descriptor = quirks.contains(crate::usb_quirks::UsbQuirkFlags::BAD_DESCRIPTOR); -+ - let lang_id = -- if raw_dd.manufacturer_str > 0 || raw_dd.product_str > 0 || raw_dd.serial_str > 0 { -- let lang_ids = self.fetch_lang_ids_desc(port_id, slot).await?; -- // Prefer US English, but fall back to first language ID, or zero -- let en_us_id = 0x409; -- if lang_ids.contains(&en_us_id) { -- en_us_id -- } else { -- match lang_ids.first() { -- Some(some) => *some, -- None => 0, -+ if !quirks.contains(crate::usb_quirks::UsbQuirkFlags::NO_STRING_FETCH) -+ && (raw_dd.manufacturer_str > 0 -+ || raw_dd.product_str > 0 -+ || raw_dd.serial_str > 0) -+ { -+ match self.fetch_lang_ids_desc(port_id, slot).await { -+ Ok(lang_ids) => { -+ // Prefer US English, but fall back to first language ID, or zero -+ let en_us_id = 0x409; -+ if lang_ids.contains(&en_us_id) { -+ en_us_id -+ } else { -+ match lang_ids.first() { -+ Some(some) => *some, -+ None => 0, -+ } -+ } -+ } -+ Err(err) if bad_descriptor => { -+ log::warn!( -+ "port {} slot {}: failed to fetch language IDs with BAD_DESCRIPTOR set: {}", -+ port_id, -+ slot, -+ err -+ ); -+ 0 - } -+ Err(err) => return Err(err), - } - } else { - 0 - }; - log::debug!("port {} using language ID 0x{:04x}", port_id, lang_id); - -- let (manufacturer_str, product_str, serial_str) = ( -- if raw_dd.manufacturer_str > 0 { -- Some( -- self.fetch_string_desc(port_id, slot, raw_dd.manufacturer_str, lang_id) -- .await?, -- ) -- } else { -- None -- }, -- if raw_dd.product_str > 0 { -- Some( -- self.fetch_string_desc(port_id, slot, raw_dd.product_str, lang_id) -- .await?, -- ) -+ let (manufacturer_str, product_str, serial_str) = -+ if quirks.contains(crate::usb_quirks::UsbQuirkFlags::NO_STRING_FETCH) { -+ (None, None, None) - } else { -- None -- }, -- if raw_dd.serial_str > 0 { -- Some( -- self.fetch_string_desc(port_id, slot, raw_dd.serial_str, lang_id) -- .await?, -+ ( -+ if raw_dd.manufacturer_str > 0 { -+ if bad_descriptor { -+ self.fetch_string_desc(port_id, slot, raw_dd.manufacturer_str, lang_id) -+ .await -+ .ok() -+ } else { -+ Some( -+ self.fetch_string_desc( -+ port_id, -+ slot, -+ raw_dd.manufacturer_str, -+ lang_id, -+ ) -+ .await?, -+ ) -+ } -+ } else { -+ None -+ }, -+ if raw_dd.product_str > 0 { -+ if bad_descriptor { -+ self.fetch_string_desc(port_id, slot, raw_dd.product_str, lang_id) -+ .await -+ .ok() -+ } else { -+ Some( -+ self.fetch_string_desc(port_id, slot, raw_dd.product_str, lang_id) -+ .await?, -+ ) -+ } -+ } else { -+ None -+ }, -+ if raw_dd.serial_str > 0 { -+ if bad_descriptor { -+ self.fetch_string_desc(port_id, slot, raw_dd.serial_str, lang_id) -+ .await -+ .ok() -+ } else { -+ Some( -+ self.fetch_string_desc(port_id, slot, raw_dd.serial_str, lang_id) -+ .await?, -+ ) -+ } -+ } else { -+ None -+ }, - ) -- } else { -- None -- }, -- ); -+ }; - log::debug!( - "manufacturer {:?} product {:?} serial {:?}", - manufacturer_str, -@@ -1508,14 +1741,39 @@ impl Xhci { - - //TODO let (bos_desc, bos_data) = self.fetch_bos_desc(port_id, slot).await?; - -- let supports_superspeed = false; -- //TODO usb::bos_capability_descs(bos_desc, &bos_data).any(|desc| desc.is_superspeed()); -- let supports_superspeedplus = false; -- //TODO usb::bos_capability_descs(bos_desc, &bos_data).any(|desc| desc.is_superspeedplus()); -+ let (supports_superspeed, supports_superspeedplus) = -+ if quirks.contains(crate::usb_quirks::UsbQuirkFlags::NO_BOS) { -+ (false, false) -+ } else { -+ match self.fetch_bos_desc(port_id, slot).await { -+ Ok((bos_desc, bos_data)) => ( -+ usb::bos_capability_descs(bos_desc, &bos_data) -+ .any(|desc| desc.is_superspeed()), -+ usb::bos_capability_descs(bos_desc, &bos_data) -+ .any(|desc| desc.is_superspeedplus()), -+ ), -+ Err(err) => { -+ log::debug!( -+ "port {} slot {}: failed to fetch BOS descriptor: {}", -+ port_id, -+ slot, -+ err -+ ); -+ (false, false) -+ } -+ } -+ }; - - let mut config_descs = SmallVec::new(); - -- for index in 0..raw_dd.configurations { -+ let configuration_indices: Vec = -+ if quirks.contains(crate::usb_quirks::UsbQuirkFlags::FORCE_ONE_CONFIG) { -+ vec![0] -+ } else { -+ (0..raw_dd.configurations).collect() -+ }; -+ -+ for index in configuration_indices { - debug!("Fetching the config descriptor at index {}", index); - let (desc, data) = self.fetch_config_desc(port_id, slot, index).await?; - log::debug!( -@@ -1541,6 +1799,12 @@ impl Xhci { - let mut iter = descriptors.into_iter().peekable(); - - while let Some(item) = iter.next() { -+ if quirks.contains(crate::usb_quirks::UsbQuirkFlags::HONOR_BNUMINTERFACES) -+ && interface_descs.len() >= desc.interfaces as usize -+ { -+ break; -+ } -+ - if let AnyDescriptor::Interface(idesc) = item { - let mut endpoints = SmallVec::<[EndpDesc; 4]>::new(); - let mut hid_descs = SmallVec::<[HidDesc; 1]>::new(); -@@ -1554,6 +1818,9 @@ impl Xhci { - } - Some(unexpected) => { - log::warn!("expected endpoint, got {:X?}", unexpected); -+ if bad_descriptor { -+ continue; -+ } - break; - } - None => break, -@@ -1578,8 +1845,16 @@ impl Xhci { - } - - interface_descs.push( -- self.new_if_desc(port_id, slot, idesc, endpoints, hid_descs, lang_id) -- .await?, -+ self.new_if_desc( -+ port_id, -+ slot, -+ idesc, -+ endpoints, -+ hid_descs, -+ lang_id, -+ quirks, -+ ) -+ .await?, - ); - } else { - log::warn!("expected interface, got {:?}", item); -@@ -1590,11 +1865,20 @@ impl Xhci { - - config_descs.push(ConfDesc { - kind: desc.kind, -- configuration: if desc.configuration_str > 0 { -- Some( -+ configuration: if quirks.contains(crate::usb_quirks::UsbQuirkFlags::NO_STRING_FETCH) -+ { -+ None -+ } else if desc.configuration_str > 0 { -+ if bad_descriptor { - self.fetch_string_desc(port_id, slot, desc.configuration_str, lang_id) -- .await?, -- ) -+ .await -+ .ok() -+ } else { -+ Some( -+ self.fetch_string_desc(port_id, slot, desc.configuration_str, lang_id) -+ .await?, -+ ) -+ } - } else { - None - }, -@@ -1856,7 +2140,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 -@@ -1893,6 +2177,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 -@@ -2087,6 +2379,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 -@@ -2155,6 +2471,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)? - } -@@ -2173,6 +2492,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); -@@ -2203,7 +2528,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; - } - _ => {} -@@ -2263,6 +2592,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 { -@@ -2294,6 +2625,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 -@@ -2333,6 +2668,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 => { -@@ -2356,6 +2699,38 @@ impl Xhci { - self.handles.remove(&fd); - } - -+ fn ensure_port_active(&self, port_num: PortId) -> Result<()> { -+ let pm_state = self -+ .port_states -+ .get(&port_num) -+ .ok_or(Error::new(EBADFD))? -+ .pm_state; -+ match pm_state { -+ super::PortPmState::Active => Ok(()), -+ super::PortPmState::Suspended => 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 -+ .quirks -+ .contains(crate::usb_quirks::UsbQuirkFlags::NO_SUSPEND) -+ { -+ return Err(Error::new(EOPNOTSUPP)); -+ } -+ -+ port_state.pm_state = super::PortPmState::Suspended; -+ 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))?; -+ port_state.pm_state = super::PortPmState::Active; -+ 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))?; - -@@ -2406,6 +2781,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)); - } -@@ -2562,6 +2939,7 @@ impl Xhci { - }, - XhciEndpCtlReq::Reset { no_clear_feature } => match ep_if_state { - EndpIfState::Init => { -+ self.ensure_port_active(port_num)?; - self.on_req_reset_device(port_num, endp_num, !no_clear_feature) - .await? - } -@@ -2571,6 +2949,7 @@ impl Xhci { - }, - XhciEndpCtlReq::Transfer { direction, count } => match ep_if_state { - state @ EndpIfState::Init => { -+ self.ensure_port_active(port_num)?; - if direction == XhciEndpCtlDirection::NoData { - // Yield the result directly because no bytes have to be sent or received - // beforehand. -@@ -2631,6 +3010,8 @@ impl Xhci { - endp_num: u8, - buf: &[u8], - ) -> Result { -+ self.ensure_port_active(port_num)?; -+ - let mut port_state = self - .port_states - .get_mut(&port_num) -@@ -2732,6 +3113,8 @@ impl Xhci { - endp_num: u8, - buf: &mut [u8], - ) -> Result { -+ self.ensure_port_active(port_num)?; -+ - let mut port_state = self - .port_states - .get_mut(&port_num) diff --git a/local/patches/base/P1-xhcid-uevent-logging.patch b/local/patches/base/P1-xhcid-uevent-logging.patch index 0e568ef262..d8462c6aed 100644 --- a/local/patches/base/P1-xhcid-uevent-logging.patch +++ b/local/patches/base/P1-xhcid-uevent-logging.patch @@ -1,20 +1,21 @@ -diff --git a/drivers/usb/xhcid/src/xhci/mod.rs b/drivers/usb/xhcid/src/xhci/mod.rs -index f1c6d08e..a3f2e15c 100644 --- a/drivers/usb/xhcid/src/xhci/mod.rs +++ b/drivers/usb/xhcid/src/xhci/mod.rs -@@ -904,6 +904,7 @@ impl Xhci { - match self.spawn_drivers(port_id) { - Ok(()) => { - info!("xhcid: uevent add device usb/{}", port_id.root_hub_port_num()); -+ // NOTE: driver-manager hotplug loop detects new USB devices via this log +@@ -1102,7 +1102,9 @@ } - Err(err) => { - error!("Failed to spawn driver for port {}: `{}`", port_id, err) -@@ -974,6 +975,7 @@ impl Xhci { - info!("xhcid: uevent remove device usb/{}", port_id.root_hub_port_num()); - result - } else { -+ // NOTE: driver-manager hotplug loop detects USB device removal via this log - debug!( - "Attempted to detach from port {}, which wasn't previously attached.", - port_id + + match self.spawn_drivers(port_id) { +- Ok(()) => (), ++ Ok(()) => { ++ info!("xhcid: uevent add device usb/{}", port_id.root_hub_port_num()); ++ } + Err(err) => { + error!("Failed to spawn driver for port {}: `{}`", port_id, err) + } +@@ -1189,6 +1191,7 @@ + Ok(()) => { + let _ = self.port_states.remove(&port_id); + debug!("disabled port slot {} for port {}", slot, port_id); ++ info!("xhcid: uevent remove device usb/{}", port_id.root_hub_port_num()); + Ok(true) + } + Err(err) => { diff --git a/local/patches/base/P2-ac97d-ihdad-main.patch b/local/patches/base/P2-ac97d-ihdad-main.patch deleted file mode 100644 index 4a23a31b09..0000000000 --- a/local/patches/base/P2-ac97d-ihdad-main.patch +++ /dev/null @@ -1,287 +0,0 @@ -# P2-ac97d-ihdad-main.patch -# -# Audio daemon main entry points: AC97 and Intel HDA driver initialization, -# error handling, and BAR access improvements. -# -# Covers: -# - ac97d/src/main.rs: BAR access, error handling, codec initialization -# - ihdad/src/main.rs: error handling, device initialization -# -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,8 +201,8 @@ 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/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/local/patches/base/P2-acpi-defer-aml.patch b/local/patches/base/P2-acpi-defer-aml.patch deleted file mode 100644 index cca9c4eaeb..0000000000 --- a/local/patches/base/P2-acpi-defer-aml.patch +++ /dev/null @@ -1,31 +0,0 @@ -Defer AML initialization until PCI registration completes. - -When acpid starts before pcid has registered the PCI fd, AML -initialization fails with a misleading ERROR-level message. This is -expected on every boot because the service ordering requires acpid to -start before pcid-spawner. The AML interpreter initializes successfully -after pcid registers via /scheme/acpi/register_pci. - -Changes: -- aml_context_mut(): log at DEBUG instead of ERROR when PCI fd is None - (expected pre-registration state, not a fault) -- Fadt::init(): skip \\_S5 evaluation when PCI is not yet registered, - since refresh_s5_values() is retried in register_pci_fd() after PCI - registration completes - -diff -urN a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs ---- a/drivers/acpid/src/acpi.rs -+++ b/drivers/acpid/src/acpi.rs -@@ -896,7 +896,11 @@ - 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); -+ } - } - } - } diff --git a/local/patches/base/P2-acpi-i2c-resources.patch b/local/patches/base/P2-acpi-i2c-resources.patch deleted file mode 100644 index c3624dca5e..0000000000 --- a/local/patches/base/P2-acpi-i2c-resources.patch +++ /dev/null @@ -1,6251 +0,0 @@ -diff --git a/Cargo.lock b/Cargo.lock -index a123cb91..71eeea7d 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", -@@ -81,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" -@@ -643,6 +669,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" -@@ -910,6 +952,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" -@@ -1005,6 +1063,68 @@ dependencies = [ - "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" -@@ -1129,6 +1249,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" -@@ -2391,6 +2563,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..8cecf423 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", -@@ -82,6 +93,7 @@ fdt = "0.1.5" - 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" - parking_lot = "0.12" -diff --git a/drivers/acpi-resource/Cargo.toml b/drivers/acpi-resource/Cargo.toml -new file mode 100644 -index 00000000..f30c6d02 ---- /dev/null -+++ b/drivers/acpi-resource/Cargo.toml -@@ -0,0 +1,13 @@ -+[package] -+name = "acpi-resource" -+description = "Shared ACPI resource template decoder" -+version = "0.0.1" -+authors = ["Red Bear OS"] -+repository = "https://gitlab.redox-os.org/redox-os/drivers" -+categories = ["hardware-support"] -+license = "MIT/Apache-2.0" -+edition = "2021" -+ -+[dependencies] -+serde.workspace = true -+thiserror.workspace = true -diff --git a/drivers/acpi-resource/src/lib.rs b/drivers/acpi-resource/src/lib.rs -new file mode 100644 -index 00000000..57ae4b4b ---- /dev/null -+++ b/drivers/acpi-resource/src/lib.rs -@@ -0,0 +1,688 @@ -+use serde::{Deserialize, Serialize}; -+use thiserror::Error; -+ -+const SMALL_IRQ: u8 = 0x20; -+const SMALL_END_TAG: u8 = 0x78; -+ -+const LARGE_MEMORY32: u8 = 0x85; -+const LARGE_FIXED_MEMORY32: u8 = 0x86; -+const LARGE_ADDRESS32: u8 = 0x87; -+const LARGE_EXTENDED_IRQ: u8 = 0x89; -+const LARGE_ADDRESS64: u8 = 0x8A; -+const LARGE_GPIO: u8 = 0x8C; -+const LARGE_SERIAL_BUS: u8 = 0x8E; -+ -+const SERIAL_BUS_I2C: u8 = 1; -+const I2C_TYPE_DATA_LEN: usize = 6; -+ -+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -+pub enum InterruptTrigger { -+ Edge, -+ Level, -+} -+ -+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -+pub enum InterruptPolarity { -+ ActiveHigh, -+ ActiveLow, -+} -+ -+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -+pub enum AddressResourceType { -+ MemoryRange, -+ IoRange, -+ BusNumberRange, -+ Unknown(u8), -+} -+ -+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -+pub struct ResourceSource { -+ pub index: u8, -+ pub source: String, -+} -+ -+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -+pub struct IrqDescriptor { -+ pub interrupts: Vec, -+ pub triggering: InterruptTrigger, -+ pub polarity: InterruptPolarity, -+ pub shareable: bool, -+ pub wake_capable: bool, -+} -+ -+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -+pub struct ExtendedIrqDescriptor { -+ pub producer_consumer: bool, -+ pub interrupts: Vec, -+ pub triggering: InterruptTrigger, -+ pub polarity: InterruptPolarity, -+ pub shareable: bool, -+ pub wake_capable: bool, -+ pub resource_source: Option, -+} -+ -+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -+pub struct GpioDescriptor { -+ pub revision_id: u8, -+ pub producer_consumer: bool, -+ pub pin_config: u8, -+ pub shareable: bool, -+ pub wake_capable: bool, -+ pub io_restriction: u8, -+ pub triggering: InterruptTrigger, -+ pub polarity: InterruptPolarity, -+ pub drive_strength: u16, -+ pub debounce_timeout: u16, -+ pub pins: Vec, -+ pub resource_source: Option, -+ pub vendor_data: Vec, -+} -+ -+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -+pub struct I2cSerialBusDescriptor { -+ pub revision_id: u8, -+ pub producer_consumer: bool, -+ pub slave_mode: bool, -+ pub connection_sharing: bool, -+ pub type_revision_id: u8, -+ pub access_mode_10bit: bool, -+ pub slave_address: u16, -+ pub connection_speed: u32, -+ pub resource_source: Option, -+ pub vendor_data: Vec, -+} -+ -+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -+pub struct Memory32RangeDescriptor { -+ pub write_protect: bool, -+ pub minimum: u32, -+ pub maximum: u32, -+ pub alignment: u32, -+ pub address_length: u32, -+} -+ -+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -+pub struct FixedMemory32Descriptor { -+ pub write_protect: bool, -+ pub address: u32, -+ pub address_length: u32, -+} -+ -+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -+pub struct Address32Descriptor { -+ pub resource_type: AddressResourceType, -+ pub producer_consumer: bool, -+ pub decode: bool, -+ pub min_address_fixed: bool, -+ pub max_address_fixed: bool, -+ pub specific_flags: u8, -+ pub granularity: u32, -+ pub minimum: u32, -+ pub maximum: u32, -+ pub translation_offset: u32, -+ pub address_length: u32, -+ pub resource_source: Option, -+} -+ -+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -+pub struct Address64Descriptor { -+ pub resource_type: AddressResourceType, -+ pub producer_consumer: bool, -+ pub decode: bool, -+ pub min_address_fixed: bool, -+ pub max_address_fixed: bool, -+ pub specific_flags: u8, -+ pub granularity: u64, -+ pub minimum: u64, -+ pub maximum: u64, -+ pub translation_offset: u64, -+ pub address_length: u64, -+ pub resource_source: Option, -+} -+ -+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -+pub enum ResourceDescriptor { -+ Irq(IrqDescriptor), -+ ExtendedIrq(ExtendedIrqDescriptor), -+ GpioInt(GpioDescriptor), -+ GpioIo(GpioDescriptor), -+ I2cSerialBus(I2cSerialBusDescriptor), -+ Memory32Range(Memory32RangeDescriptor), -+ FixedMemory32(FixedMemory32Descriptor), -+ Address32(Address32Descriptor), -+ Address64(Address64Descriptor), -+} -+ -+#[derive(Debug, Error, PartialEq, Eq)] -+pub enum ResourceDecodeError { -+ #[error("descriptor at offset {offset} overruns the resource template")] -+ TruncatedDescriptor { offset: usize }, -+ -+ #[error("unsupported small descriptor length {length} for tag {tag:#04x} at offset {offset}")] -+ InvalidSmallLength { -+ offset: usize, -+ tag: u8, -+ length: usize, -+ }, -+ -+ #[error("descriptor {descriptor} at offset {offset} is shorter than {minimum} bytes")] -+ InvalidLargeLength { -+ offset: usize, -+ descriptor: &'static str, -+ minimum: usize, -+ }, -+ -+ #[error("descriptor {descriptor} at offset {offset} has an invalid internal offset")] -+ InvalidInternalOffset { -+ offset: usize, -+ descriptor: &'static str, -+ }, -+} -+ -+pub fn decode_resource_template( -+ bytes: &[u8], -+) -> Result, ResourceDecodeError> { -+ let mut resources = Vec::new(); -+ let mut offset = 0usize; -+ -+ while offset < bytes.len() { -+ let descriptor = *bytes -+ .get(offset) -+ .ok_or(ResourceDecodeError::TruncatedDescriptor { offset })?; -+ -+ if descriptor & 0x80 == 0 { -+ let length = usize::from(descriptor & 0x07); -+ let end = offset + 1 + length; -+ let desc = bytes -+ .get(offset..end) -+ .ok_or(ResourceDecodeError::TruncatedDescriptor { offset })?; -+ let body = &desc[1..]; -+ -+ match descriptor & 0x78 { -+ SMALL_IRQ => resources.push(ResourceDescriptor::Irq(parse_irq(body, offset)?)), -+ SMALL_END_TAG => break, -+ _ => {} -+ } -+ -+ offset = end; -+ continue; -+ } -+ -+ let length = usize::from(read_u16(bytes, offset + 1)?); -+ let end = offset + 3 + length; -+ let desc = bytes -+ .get(offset..end) -+ .ok_or(ResourceDecodeError::TruncatedDescriptor { offset })?; -+ let body = &desc[3..]; -+ -+ match descriptor { -+ LARGE_MEMORY32 => resources.push(ResourceDescriptor::Memory32Range(parse_memory32( -+ body, offset, -+ )?)), -+ LARGE_FIXED_MEMORY32 => resources.push(ResourceDescriptor::FixedMemory32( -+ parse_fixed_memory32(body, offset)?, -+ )), -+ LARGE_ADDRESS32 => { -+ resources.push(ResourceDescriptor::Address32(parse_address32( -+ desc, body, offset, -+ )?)); -+ } -+ LARGE_ADDRESS64 => { -+ resources.push(ResourceDescriptor::Address64(parse_address64( -+ desc, body, offset, -+ )?)); -+ } -+ LARGE_EXTENDED_IRQ => resources.push(ResourceDescriptor::ExtendedIrq( -+ parse_extended_irq(desc, body, offset)?, -+ )), -+ LARGE_GPIO => { -+ let (is_interrupt, descriptor) = parse_gpio(desc, body, offset)?; -+ resources.push(if is_interrupt { -+ ResourceDescriptor::GpioInt(descriptor) -+ } else { -+ ResourceDescriptor::GpioIo(descriptor) -+ }); -+ } -+ LARGE_SERIAL_BUS => { -+ if let Some(descriptor) = parse_i2c_serial_bus(desc, body, offset)? { -+ resources.push(ResourceDescriptor::I2cSerialBus(descriptor)); -+ } -+ } -+ _ => {} -+ } -+ -+ offset = end; -+ } -+ -+ Ok(resources) -+} -+ -+fn parse_irq(body: &[u8], offset: usize) -> Result { -+ if body.len() != 2 && body.len() != 3 { -+ return Err(ResourceDecodeError::InvalidSmallLength { -+ offset, -+ tag: SMALL_IRQ, -+ length: body.len(), -+ }); -+ } -+ -+ let mask = u16::from_le_bytes([body[0], body[1]]); -+ let flags = body.get(2).copied().unwrap_or(0); -+ let interrupts = (0..16) -+ .filter(|irq| mask & (1 << irq) != 0) -+ .map(|irq| irq as u8) -+ .collect(); -+ -+ Ok(IrqDescriptor { -+ interrupts, -+ triggering: if flags & 0x01 != 0 { -+ InterruptTrigger::Level -+ } else { -+ InterruptTrigger::Edge -+ }, -+ polarity: if flags & 0x08 != 0 { -+ InterruptPolarity::ActiveLow -+ } else { -+ InterruptPolarity::ActiveHigh -+ }, -+ shareable: flags & 0x10 != 0, -+ wake_capable: flags & 0x20 != 0, -+ }) -+} -+ -+fn parse_extended_irq( -+ desc: &[u8], -+ body: &[u8], -+ offset: usize, -+) -> Result { -+ ensure_length(body, 2, offset, "ExtendedIrq")?; -+ -+ let flags = body[0]; -+ let count = usize::from(body[1]); -+ let ints_len = count * 4; -+ ensure_length(body, 2 + ints_len, offset, "ExtendedIrq")?; -+ -+ let interrupts = (0..count) -+ .map(|index| read_u32(body, 2 + index * 4)) -+ .collect::, _>>()?; -+ let resource_source = if body.len() > 2 + ints_len { -+ Some(parse_source_inline(&body[2 + ints_len..])) -+ } else { -+ None -+ }; -+ -+ let _ = desc; -+ -+ Ok(ExtendedIrqDescriptor { -+ producer_consumer: flags & 0x01 != 0, -+ triggering: if flags & 0x02 != 0 { -+ InterruptTrigger::Level -+ } else { -+ InterruptTrigger::Edge -+ }, -+ polarity: if flags & 0x04 != 0 { -+ InterruptPolarity::ActiveLow -+ } else { -+ InterruptPolarity::ActiveHigh -+ }, -+ shareable: flags & 0x08 != 0, -+ wake_capable: flags & 0x10 != 0, -+ interrupts, -+ resource_source, -+ }) -+} -+ -+fn parse_gpio( -+ desc: &[u8], -+ body: &[u8], -+ offset: usize, -+) -> Result<(bool, GpioDescriptor), ResourceDecodeError> { -+ ensure_length(body, 20, offset, "Gpio")?; -+ -+ let connection_type = body[1]; -+ let flags = read_u16(body, 2)?; -+ let int_flags = read_u16(body, 4)?; -+ let pin_table_offset = usize::from(read_u16(body, 11)?); -+ let resource_source_index = body[13]; -+ let resource_source_offset = usize::from(read_u16(body, 14)?); -+ let vendor_offset = usize::from(read_u16(body, 16)?); -+ let vendor_length = usize::from(read_u16(body, 18)?); -+ -+ let pins_end = min_nonzero([resource_source_offset, vendor_offset, desc.len()]); -+ let pins = parse_u16_list(desc, pin_table_offset, pins_end, offset, "Gpio")?; -+ let resource_source = parse_source_absolute( -+ desc, -+ resource_source_offset, -+ min_nonzero([vendor_offset, desc.len()]), -+ resource_source_index, -+ offset, -+ "Gpio", -+ )?; -+ let vendor_data = parse_blob_absolute(desc, vendor_offset, vendor_length, offset, "Gpio")?; -+ -+ Ok(( -+ connection_type == 0, -+ GpioDescriptor { -+ revision_id: body[0], -+ producer_consumer: flags & 0x0001 != 0, -+ pin_config: body[6], -+ shareable: int_flags & 0x0008 != 0, -+ wake_capable: int_flags & 0x0010 != 0, -+ io_restriction: (int_flags & 0x0003) as u8, -+ triggering: if int_flags & 0x0001 != 0 { -+ InterruptTrigger::Level -+ } else { -+ InterruptTrigger::Edge -+ }, -+ polarity: if int_flags & 0x0002 != 0 { -+ InterruptPolarity::ActiveLow -+ } else { -+ InterruptPolarity::ActiveHigh -+ }, -+ drive_strength: read_u16(body, 7)?, -+ debounce_timeout: read_u16(body, 9)?, -+ pins, -+ resource_source, -+ vendor_data, -+ }, -+ )) -+} -+ -+fn parse_i2c_serial_bus( -+ desc: &[u8], -+ body: &[u8], -+ offset: usize, -+) -> Result, ResourceDecodeError> { -+ ensure_length(body, 15, offset, "SerialBus")?; -+ if body[2] != SERIAL_BUS_I2C { -+ return Ok(None); -+ } -+ -+ let type_data_length = usize::from(read_u16(body, 7)?); -+ if type_data_length < I2C_TYPE_DATA_LEN { -+ return Err(ResourceDecodeError::InvalidLargeLength { -+ offset, -+ descriptor: "I2cSerialBus", -+ minimum: 15, -+ }); -+ } -+ -+ let vendor_length = type_data_length - I2C_TYPE_DATA_LEN; -+ let vendor_data = parse_blob_absolute(desc, 18, vendor_length, offset, "I2cSerialBus")?; -+ let resource_source = parse_source_absolute( -+ desc, -+ 12 + type_data_length, -+ desc.len(), -+ body[1], -+ offset, -+ "I2cSerialBus", -+ )?; -+ -+ Ok(Some(I2cSerialBusDescriptor { -+ revision_id: body[0], -+ producer_consumer: body[3] & 0x02 != 0, -+ slave_mode: body[3] & 0x01 != 0, -+ connection_sharing: body[3] & 0x04 != 0, -+ type_revision_id: body[6], -+ access_mode_10bit: read_u16(body, 4)? & 0x0001 != 0, -+ connection_speed: read_u32(body, 9)?, -+ slave_address: read_u16(body, 13)?, -+ resource_source, -+ vendor_data, -+ })) -+} -+ -+fn parse_memory32( -+ body: &[u8], -+ offset: usize, -+) -> Result { -+ ensure_length(body, 17, offset, "Memory32Range")?; -+ Ok(Memory32RangeDescriptor { -+ write_protect: body[0] & 0x01 != 0, -+ minimum: read_u32(body, 1)?, -+ maximum: read_u32(body, 5)?, -+ alignment: read_u32(body, 9)?, -+ address_length: read_u32(body, 13)?, -+ }) -+} -+ -+fn parse_fixed_memory32( -+ body: &[u8], -+ offset: usize, -+) -> Result { -+ ensure_length(body, 9, offset, "FixedMemory32")?; -+ Ok(FixedMemory32Descriptor { -+ write_protect: body[0] & 0x01 != 0, -+ address: read_u32(body, 1)?, -+ address_length: read_u32(body, 5)?, -+ }) -+} -+ -+fn parse_address32( -+ desc: &[u8], -+ body: &[u8], -+ offset: usize, -+) -> Result { -+ ensure_length(body, 23, offset, "Address32")?; -+ Ok(Address32Descriptor { -+ resource_type: parse_address_type(body[0]), -+ producer_consumer: body[1] & 0x01 != 0, -+ decode: body[1] & 0x02 != 0, -+ min_address_fixed: body[1] & 0x04 != 0, -+ max_address_fixed: body[1] & 0x08 != 0, -+ specific_flags: body[2], -+ granularity: read_u32(body, 3)?, -+ minimum: read_u32(body, 7)?, -+ maximum: read_u32(body, 11)?, -+ translation_offset: read_u32(body, 15)?, -+ address_length: read_u32(body, 19)?, -+ resource_source: if desc.len() > 26 { -+ parse_source_absolute(desc, 26, desc.len(), desc[26], offset, "Address32")? -+ } else { -+ None -+ }, -+ }) -+} -+ -+fn parse_address64( -+ desc: &[u8], -+ body: &[u8], -+ offset: usize, -+) -> Result { -+ ensure_length(body, 43, offset, "Address64")?; -+ Ok(Address64Descriptor { -+ resource_type: parse_address_type(body[0]), -+ producer_consumer: body[1] & 0x01 != 0, -+ decode: body[1] & 0x02 != 0, -+ min_address_fixed: body[1] & 0x04 != 0, -+ max_address_fixed: body[1] & 0x08 != 0, -+ specific_flags: body[2], -+ granularity: read_u64(body, 3)?, -+ minimum: read_u64(body, 11)?, -+ maximum: read_u64(body, 19)?, -+ translation_offset: read_u64(body, 27)?, -+ address_length: read_u64(body, 35)?, -+ resource_source: if desc.len() > 46 { -+ parse_source_absolute(desc, 46, desc.len(), desc[46], offset, "Address64")? -+ } else { -+ None -+ }, -+ }) -+} -+ -+fn ensure_length( -+ body: &[u8], -+ minimum: usize, -+ offset: usize, -+ descriptor: &'static str, -+) -> Result<(), ResourceDecodeError> { -+ if body.len() < minimum { -+ return Err(ResourceDecodeError::InvalidLargeLength { -+ offset, -+ descriptor, -+ minimum, -+ }); -+ } -+ Ok(()) -+} -+ -+fn parse_source_inline(bytes: &[u8]) -> ResourceSource { -+ let index = bytes.first().copied().unwrap_or(0); -+ let source = bytes.get(1..).map(parse_nul_string).unwrap_or_default(); -+ ResourceSource { index, source } -+} -+ -+fn parse_source_absolute( -+ desc: &[u8], -+ start: usize, -+ end: usize, -+ index: u8, -+ offset: usize, -+ descriptor: &'static str, -+) -> Result, ResourceDecodeError> { -+ if start == 0 || start >= end || start > desc.len() { -+ return Ok(None); -+ } -+ let slice = desc -+ .get(start..end) -+ .ok_or(ResourceDecodeError::InvalidInternalOffset { offset, descriptor })?; -+ Ok(Some(ResourceSource { -+ index, -+ source: parse_nul_string(slice), -+ })) -+} -+ -+fn parse_blob_absolute( -+ desc: &[u8], -+ start: usize, -+ length: usize, -+ offset: usize, -+ descriptor: &'static str, -+) -> Result, ResourceDecodeError> { -+ if start == 0 || length == 0 { -+ return Ok(Vec::new()); -+ } -+ let end = start + length; -+ Ok(desc -+ .get(start..end) -+ .ok_or(ResourceDecodeError::InvalidInternalOffset { offset, descriptor })? -+ .to_vec()) -+} -+ -+fn parse_u16_list( -+ desc: &[u8], -+ start: usize, -+ end: usize, -+ offset: usize, -+ descriptor: &'static str, -+) -> Result, ResourceDecodeError> { -+ if start == 0 || start >= end || start > desc.len() { -+ return Ok(Vec::new()); -+ } -+ let slice = desc -+ .get(start..end) -+ .ok_or(ResourceDecodeError::InvalidInternalOffset { offset, descriptor })?; -+ if slice.len() % 2 != 0 { -+ return Err(ResourceDecodeError::InvalidInternalOffset { offset, descriptor }); -+ } -+ slice -+ .chunks_exact(2) -+ .map(|chunk| Ok(u16::from_le_bytes([chunk[0], chunk[1]]))) -+ .collect() -+} -+ -+fn parse_nul_string(bytes: &[u8]) -> String { -+ let end = bytes -+ .iter() -+ .position(|byte| *byte == 0) -+ .unwrap_or(bytes.len()); -+ String::from_utf8_lossy(&bytes[..end]).to_string() -+} -+ -+fn parse_address_type(value: u8) -> AddressResourceType { -+ match value { -+ 0 => AddressResourceType::MemoryRange, -+ 1 => AddressResourceType::IoRange, -+ 2 => AddressResourceType::BusNumberRange, -+ other => AddressResourceType::Unknown(other), -+ } -+} -+ -+fn read_u16(bytes: &[u8], offset: usize) -> Result { -+ let slice = bytes -+ .get(offset..offset + 2) -+ .ok_or(ResourceDecodeError::TruncatedDescriptor { offset })?; -+ Ok(u16::from_le_bytes([slice[0], slice[1]])) -+} -+ -+fn read_u32(bytes: &[u8], offset: usize) -> Result { -+ let slice = bytes -+ .get(offset..offset + 4) -+ .ok_or(ResourceDecodeError::TruncatedDescriptor { offset })?; -+ Ok(u32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]])) -+} -+ -+fn read_u64(bytes: &[u8], offset: usize) -> Result { -+ let slice = bytes -+ .get(offset..offset + 8) -+ .ok_or(ResourceDecodeError::TruncatedDescriptor { offset })?; -+ Ok(u64::from_le_bytes([ -+ slice[0], slice[1], slice[2], slice[3], slice[4], slice[5], slice[6], slice[7], -+ ])) -+} -+ -+fn min_nonzero(values: [usize; N]) -> usize { -+ values -+ .into_iter() -+ .filter(|value| *value != 0) -+ .min() -+ .unwrap_or(0) -+} -+ -+#[cfg(test)] -+mod tests { -+ use super::{decode_resource_template, ResourceDescriptor}; -+ -+ #[test] -+ fn decodes_small_irq_descriptor() { -+ let resources = decode_resource_template(&[0x23, 0x0A, 0x00, 0x19, 0x79, 0x00]).unwrap(); -+ -+ assert!(matches!( -+ &resources[0], -+ ResourceDescriptor::Irq(descriptor) -+ if descriptor.interrupts == vec![1, 3] -+ && descriptor.shareable -+ && descriptor.wake_capable == false -+ )); -+ } -+ -+ #[test] -+ fn decodes_i2c_serial_bus_descriptor() { -+ let template = [ -+ 0x8E, 0x14, 0x00, 0x01, 0x02, 0x01, 0x02, 0x00, 0x00, 0x01, 0x06, 0x00, 0x80, 0x1A, -+ 0x06, 0x00, 0x15, 0x00, b'I', b'2', b'C', b'0', 0x00, 0x79, 0x00, -+ ]; -+ let resources = decode_resource_template(&template).unwrap(); -+ -+ assert!(matches!( -+ &resources[0], -+ ResourceDescriptor::I2cSerialBus(descriptor) -+ if descriptor.connection_speed == 400_000 -+ && descriptor.slave_address == 0x15 -+ && descriptor.resource_source.as_ref().map(|source| source.source.as_str()) -+ == Some("I2C0") -+ )); -+ } -+ -+ #[test] -+ fn decodes_gpio_interrupt_descriptor() { -+ let template = [ -+ 0x8C, 0x1B, 0x00, 0x01, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, -+ 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, b'\\', b'_', b'S', b'B', -+ 0x00, 0x79, 0x00, -+ ]; -+ let resources = decode_resource_template(&template).unwrap(); -+ -+ assert!(matches!(&resources[0], ResourceDescriptor::GpioInt(_))); -+ } -+} -diff --git a/drivers/acpid/src/resources.rs b/drivers/acpid/src/resources.rs -new file mode 100644 -index 00000000..86bcfd1e ---- /dev/null -+++ b/drivers/acpid/src/resources.rs -@@ -0,0 +1 @@ -+pub use acpi_resource::{decode_resource_template, ResourceDescriptor}; -diff --git a/drivers/acpid/src/sleep.rs b/drivers/acpid/src/sleep.rs -new file mode 100644 -index 00000000..e5d6ab22 ---- /dev/null -+++ b/drivers/acpid/src/sleep.rs -@@ -0,0 +1,44 @@ -+use std::convert::TryFrom; -+ -+#[derive(Clone, Copy, Debug, Eq, PartialEq)] -+pub enum SleepTarget { -+ S0, -+ S1, -+ S2, -+ S3, -+ S4, -+ S5, -+} -+ -+impl SleepTarget { -+ pub fn aml_method_name(self) -> &'static str { -+ match self { -+ Self::S0 => "_S0", -+ Self::S1 => "_S1", -+ Self::S2 => "_S2", -+ Self::S3 => "_S3", -+ Self::S4 => "_S4", -+ Self::S5 => "_S5", -+ } -+ } -+ -+ pub fn is_soft_off(self) -> bool { -+ matches!(self, Self::S5) -+ } -+} -+ -+impl TryFrom for SleepTarget { -+ type Error = (); -+ -+ fn try_from(value: u8) -> Result { -+ match value { -+ 0 => Ok(Self::S0), -+ 1 => Ok(Self::S1), -+ 2 => Ok(Self::S2), -+ 3 => Ok(Self::S3), -+ 4 => Ok(Self::S4), -+ 5 => Ok(Self::S5), -+ _ => Err(()), -+ } -+ } -+} -diff --git a/drivers/gpio/gpiod/Cargo.toml b/drivers/gpio/gpiod/Cargo.toml -new file mode 100644 -index 00000000..7e087bd7 ---- /dev/null -+++ b/drivers/gpio/gpiod/Cargo.toml -@@ -0,0 +1,21 @@ -+[package] -+name = "gpiod" -+description = "GPIO controller registry daemon" -+version = "0.1.0" -+edition = "2021" -+ -+[dependencies] -+anyhow.workspace = true -+log.workspace = true -+redox_syscall = { workspace = true, features = ["std"] } -+libredox.workspace = true -+redox-scheme.workspace = true -+ron.workspace = true -+serde.workspace = true -+ -+common = { path = "../../common" } -+daemon = { path = "../../../daemon" } -+scheme-utils = { path = "../../../scheme-utils" } -+ -+[lints] -+workspace = true -diff --git a/drivers/gpio/gpiod/src/main.rs b/drivers/gpio/gpiod/src/main.rs -new file mode 100644 -index 00000000..41618ba1 ---- /dev/null -+++ b/drivers/gpio/gpiod/src/main.rs -@@ -0,0 +1,496 @@ -+use std::collections::BTreeMap; -+use std::process; -+ -+use anyhow::{Context, Result}; -+use redox_scheme::scheme::SchemeSync; -+use redox_scheme::{CallerCtx, OpenResult, Socket}; -+use scheme_utils::{Blocking, HandleMap}; -+use serde::{Deserialize, Serialize}; -+use syscall::schemev2::NewFdFlags; -+use syscall::{Error as SysError, EACCES, EBADF, EINVAL, ENOENT}; -+ -+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -+pub struct GpioControllerInfo { -+ pub id: u32, -+ pub name: String, -+ pub pin_count: usize, -+ pub supports_interrupt: bool, -+} -+ -+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -+pub enum GpioControlRequest { -+ RegisterController { info: GpioControllerInfo }, -+ ReadPin { controller_id: u32, pin: u32 }, -+ WritePin { controller_id: u32, pin: u32, value: bool }, -+ ConfigurePin { controller_id: u32, pin: u32, config: PinConfig }, -+ ListControllers, -+} -+ -+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -+pub struct PinConfig { -+ pub direction: PinDirection, -+ pub pull: PullMode, -+ pub interrupt_mode: Option, -+} -+ -+#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] -+pub enum PinDirection { -+ Input, -+ Output, -+} -+ -+#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] -+pub enum PullMode { -+ None, -+ Up, -+ Down, -+} -+ -+#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] -+pub enum InterruptMode { -+ EdgeRising, -+ EdgeFalling, -+ EdgeBoth, -+ LevelHigh, -+ LevelLow, -+} -+ -+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -+enum GpioControlResponse { -+ ControllerRegistered { id: u32 }, -+ Controllers(Vec), -+ Controller(GpioControllerInfo), -+ PinValue(bool), -+ Ack, -+ Error(String), -+} -+ -+#[derive(Clone, Copy, Debug, PartialEq, Eq)] -+enum PinOpKind { -+ Read, -+ Write, -+ Configure, -+} -+ -+enum Handle { -+ SchemeRoot, -+ Register { pending: Vec }, -+ Provider { controller_id: u32, pending: Vec }, -+ ControllersDir { pending: Vec }, -+ ControllerDetail { id: u32, pending: Vec }, -+ PinOp { kind: PinOpKind, pending: Vec }, -+} -+ -+struct ControllerEntry { -+ info: GpioControllerInfo, -+ provider_handle: usize, -+} -+ -+struct GpioDaemon { -+ handles: HandleMap, -+ controllers: BTreeMap, -+ next_id: u32, -+} -+ -+impl GpioDaemon { -+ fn new() -> Self { -+ Self { -+ handles: HandleMap::new(), -+ controllers: BTreeMap::new(), -+ next_id: 0, -+ } -+ } -+ -+ fn controller_list(&self) -> Vec { -+ self.controllers -+ .values() -+ .map(|entry| entry.info.clone()) -+ .collect() -+ } -+ -+ fn serialize_response(response: &GpioControlResponse) -> syscall::Result> { -+ ron::ser::to_string(response) -+ .map(|text| text.into_bytes()) -+ .map_err(|err| { -+ log::error!("gpiod: failed to serialize control response: {err}"); -+ SysError::new(EINVAL) -+ }) -+ } -+ -+ fn deserialize_request(buf: &[u8]) -> syscall::Result { -+ let text = std::str::from_utf8(buf).map_err(|err| { -+ log::warn!("gpiod: invalid UTF-8 request payload: {err}"); -+ SysError::new(EINVAL) -+ })?; -+ -+ ron::from_str(text).map_err(|err| { -+ log::warn!("gpiod: failed to decode control request: {err}"); -+ SysError::new(EINVAL) -+ }) -+ } -+ -+ fn set_pending_response(handle: &mut Handle, response: GpioControlResponse) -> syscall::Result<()> { -+ let pending = Self::serialize_response(&response)?; -+ Self::set_pending_bytes(handle, pending) -+ } -+ -+ fn set_pending_bytes(handle: &mut Handle, pending: Vec) -> syscall::Result<()> { -+ match handle { -+ Handle::Register { pending: slot } -+ | Handle::Provider { pending: slot, .. } -+ | Handle::ControllersDir { pending: slot } -+ | Handle::ControllerDetail { pending: slot, .. } -+ | Handle::PinOp { pending: slot, .. } => { -+ *slot = pending; -+ Ok(()) -+ } -+ Handle::SchemeRoot => Err(SysError::new(EBADF)), -+ } -+ } -+ -+ fn copy_pending(handle: &mut Handle, buf: &mut [u8], offset: u64) -> syscall::Result { -+ let pending = match handle { -+ Handle::Register { pending } -+ | Handle::Provider { pending, .. } -+ | Handle::ControllersDir { pending } -+ | Handle::ControllerDetail { pending, .. } -+ | Handle::PinOp { pending, .. } => pending, -+ Handle::SchemeRoot => return Err(SysError::new(EBADF)), -+ }; -+ -+ let offset = usize::try_from(offset).map_err(|_| SysError::new(EINVAL))?; -+ if offset >= pending.len() { -+ return Ok(0); -+ } -+ -+ let copy_len = buf.len().min(pending.len() - offset); -+ buf[..copy_len].copy_from_slice(&pending[offset..offset + copy_len]); -+ Ok(copy_len) -+ } -+ -+ fn validate_pin_target( -+ &self, -+ controller_id: u32, -+ pin: u32, -+ ) -> std::result::Result { -+ let entry = self -+ .controllers -+ .get(&controller_id) -+ .ok_or_else(|| format!("unknown controller {controller_id}"))?; -+ if usize::try_from(pin) -+ .ok() -+ .filter(|pin| *pin < entry.info.pin_count) -+ .is_none() -+ { -+ return Err(format!( -+ "pin {pin} is out of range for controller {} (pin_count={})", -+ entry.info.name, entry.info.pin_count -+ )); -+ } -+ Ok(entry.info.clone()) -+ } -+} -+ -+impl SchemeSync for GpioDaemon { -+ fn scheme_root(&mut self) -> syscall::Result { -+ Ok(self.handles.insert(Handle::SchemeRoot)) -+ } -+ -+ fn openat( -+ &mut self, -+ dirfd: usize, -+ path: &str, -+ _flags: usize, -+ _fcntl_flags: u32, -+ _ctx: &CallerCtx, -+ ) -> syscall::Result { -+ let handle = self.handles.get(dirfd)?; -+ let segments = path.trim_matches('/'); -+ -+ let new_handle = match handle { -+ Handle::SchemeRoot => { -+ if segments.is_empty() { -+ return Err(SysError::new(EINVAL)); -+ } -+ -+ let mut parts = segments.split('/'); -+ match parts.next() { -+ Some("register") if parts.next().is_none() => Handle::Register { -+ pending: Vec::new(), -+ }, -+ Some("controllers") => match parts.next() { -+ None => Handle::ControllersDir { -+ pending: Vec::new(), -+ }, -+ Some(id) if parts.next().is_none() => Handle::ControllerDetail { -+ id: id.parse::().map_err(|_| SysError::new(EINVAL))?, -+ pending: Vec::new(), -+ }, -+ _ => return Err(SysError::new(EINVAL)), -+ }, -+ Some("read_pin") if parts.next().is_none() => Handle::PinOp { -+ kind: PinOpKind::Read, -+ pending: Vec::new(), -+ }, -+ Some("write_pin") if parts.next().is_none() => Handle::PinOp { -+ kind: PinOpKind::Write, -+ pending: Vec::new(), -+ }, -+ Some("configure_pin") if parts.next().is_none() => Handle::PinOp { -+ kind: PinOpKind::Configure, -+ pending: Vec::new(), -+ }, -+ _ => return Err(SysError::new(ENOENT)), -+ } -+ } -+ Handle::ControllersDir { .. } => { -+ if segments.is_empty() { -+ return Err(SysError::new(EINVAL)); -+ } -+ -+ Handle::ControllerDetail { -+ id: segments.parse::().map_err(|_| SysError::new(EINVAL))?, -+ pending: Vec::new(), -+ } -+ } -+ _ => return Err(SysError::new(EACCES)), -+ }; -+ -+ let fd = self.handles.insert(new_handle); -+ Ok(OpenResult::ThisScheme { -+ number: fd, -+ flags: NewFdFlags::empty(), -+ }) -+ } -+ -+ fn read( -+ &mut self, -+ id: usize, -+ buf: &mut [u8], -+ offset: u64, -+ _fcntl_flags: u32, -+ _ctx: &CallerCtx, -+ ) -> syscall::Result { -+ let controllers = self.controller_list(); -+ let detail = match self.handles.get(id)? { -+ Handle::ControllerDetail { id, .. } => self.controllers.get(id).map(|entry| entry.info.clone()), -+ _ => None, -+ }; -+ -+ let handle = self.handles.get_mut(id)?; -+ match handle { -+ Handle::ControllersDir { pending } if pending.is_empty() => { -+ *pending = Self::serialize_response(&GpioControlResponse::Controllers(controllers))?; -+ } -+ Handle::ControllerDetail { id, pending } if pending.is_empty() => { -+ let info = detail.ok_or(SysError::new(ENOENT))?; -+ *pending = Self::serialize_response(&GpioControlResponse::Controller(info))?; -+ log::debug!("gpiod: served controller detail for id={id}"); -+ } -+ _ => {} -+ } -+ -+ Self::copy_pending(handle, buf, offset) -+ } -+ -+ fn write( -+ &mut self, -+ id: usize, -+ buf: &[u8], -+ _offset: u64, -+ _fcntl_flags: u32, -+ _ctx: &CallerCtx, -+ ) -> syscall::Result { -+ let request = Self::deserialize_request(buf)?; -+ -+ match request { -+ GpioControlRequest::RegisterController { mut info } => { -+ if !matches!(self.handles.get(id)?, Handle::Register { .. }) { -+ return Err(SysError::new(EINVAL)); -+ } -+ -+ let controller_id = self.next_id; -+ self.next_id = self.next_id.checked_add(1).ok_or(SysError::new(EINVAL))?; -+ info.id = controller_id; -+ self.controllers.insert( -+ controller_id, -+ ControllerEntry { -+ info: info.clone(), -+ provider_handle: id, -+ }, -+ ); -+ -+ let handle = self.handles.get_mut(id)?; -+ *handle = Handle::Provider { -+ controller_id, -+ pending: Self::serialize_response(&GpioControlResponse::ControllerRegistered { -+ id: controller_id, -+ })?, -+ }; -+ -+ log::info!( -+ "RB_GPIOD_CONTROLLER_REGISTERED id={} name={} pin_count={} supports_interrupt={}", -+ info.id, -+ info.name, -+ info.pin_count, -+ info.supports_interrupt, -+ ); -+ Ok(buf.len()) -+ } -+ GpioControlRequest::ListControllers => { -+ let controllers = self.controller_list(); -+ let handle = self.handles.get_mut(id)?; -+ Self::set_pending_response(handle, GpioControlResponse::Controllers(controllers))?; -+ Ok(buf.len()) -+ } -+ GpioControlRequest::ReadPin { controller_id, pin } => { -+ let validation = self.validate_pin_target(controller_id, pin); -+ let handle = self.handles.get_mut(id)?; -+ match handle { -+ Handle::PinOp { -+ kind: PinOpKind::Read, -+ .. -+ } => { -+ match validation { -+ Ok(info) => { -+ log::info!( -+ "RB_GPIOD_PIN_READ controller_id={} name={} pin={} routed=stub", -+ controller_id, -+ info.name, -+ pin, -+ ); -+ Self::set_pending_response(handle, GpioControlResponse::PinValue(false))?; -+ } -+ Err(message) => { -+ Self::set_pending_response(handle, GpioControlResponse::Error(message))?; -+ } -+ } -+ Ok(buf.len()) -+ } -+ _ => Err(SysError::new(EINVAL)), -+ } -+ } -+ GpioControlRequest::WritePin { -+ controller_id, -+ pin, -+ value, -+ } => { -+ let validation = self.validate_pin_target(controller_id, pin); -+ let handle = self.handles.get_mut(id)?; -+ match handle { -+ Handle::PinOp { -+ kind: PinOpKind::Write, -+ .. -+ } => { -+ match validation { -+ Ok(info) => { -+ log::info!( -+ "RB_GPIOD_PIN_WRITE controller_id={} name={} pin={} value={} routed=stub", -+ controller_id, -+ info.name, -+ pin, -+ value, -+ ); -+ Self::set_pending_response(handle, GpioControlResponse::Ack)?; -+ } -+ Err(message) => { -+ Self::set_pending_response(handle, GpioControlResponse::Error(message))?; -+ } -+ } -+ Ok(buf.len()) -+ } -+ _ => Err(SysError::new(EINVAL)), -+ } -+ } -+ GpioControlRequest::ConfigurePin { -+ controller_id, -+ pin, -+ config, -+ } => { -+ let validation = self.validate_pin_target(controller_id, pin); -+ let handle = self.handles.get_mut(id)?; -+ match handle { -+ Handle::PinOp { -+ kind: PinOpKind::Configure, -+ .. -+ } => { -+ match validation { -+ Ok(info) => { -+ log::info!( -+ "RB_GPIOD_PIN_CONFIG controller_id={} name={} pin={} direction={:?} pull={:?} interrupt={:?} routed=stub", -+ controller_id, -+ info.name, -+ pin, -+ config.direction, -+ config.pull, -+ config.interrupt_mode, -+ ); -+ Self::set_pending_response(handle, GpioControlResponse::Ack)?; -+ } -+ Err(message) => { -+ Self::set_pending_response(handle, GpioControlResponse::Error(message))?; -+ } -+ } -+ Ok(buf.len()) -+ } -+ _ => Err(SysError::new(EINVAL)), -+ } -+ } -+ } -+ } -+ -+ fn on_close(&mut self, id: usize) { -+ let Some(handle) = self.handles.remove(id) else { -+ return; -+ }; -+ if let Handle::Provider { controller_id, .. } = handle { -+ if let Some(entry) = self.controllers.remove(&controller_id) { -+ log::info!( -+ "RB_GPIOD_CONTROLLER_REMOVED id={} name={} provider_handle={}", -+ controller_id, -+ entry.info.name, -+ entry.provider_handle, -+ ); -+ } -+ } -+ } -+} -+ -+fn run_daemon(daemon: daemon::SchemeDaemon) -> Result<()> { -+ let socket = Socket::create().context("failed to create gpio scheme socket")?; -+ let mut scheme = GpioDaemon::new(); -+ let handler = Blocking::new(&socket, 16); -+ -+ daemon -+ .ready_sync_scheme(&socket, &mut scheme) -+ .context("failed to publish gpio scheme root")?; -+ -+ log::info!("RB_GPIOD_SCHEMA version=1"); -+ -+ libredox::call::setrens(0, 0).context("failed to enter null namespace")?; -+ -+ handler -+ .process_requests_blocking(scheme) -+ .context("failed to process gpiod requests")?; -+} -+ -+fn daemon_runner(daemon: daemon::SchemeDaemon) -> ! { -+ if let Err(err) = run_daemon(daemon) { -+ log::error!("gpiod: {err:#}"); -+ process::exit(1); -+ } -+ -+ process::exit(0); -+} -+ -+fn main() { -+ common::setup_logging( -+ "gpio", -+ "gpio", -+ "gpiod", -+ common::output_level(), -+ common::file_level(), -+ ); -+ -+ daemon::SchemeDaemon::new(daemon_runner); -+} -diff --git a/drivers/gpio/i2c-gpio-expanderd/Cargo.toml b/drivers/gpio/i2c-gpio-expanderd/Cargo.toml -new file mode 100644 -index 00000000..3e168e96 ---- /dev/null -+++ b/drivers/gpio/i2c-gpio-expanderd/Cargo.toml -@@ -0,0 +1,21 @@ -+[package] -+name = "i2c-gpio-expanderd" -+description = "I2C GPIO expander bridge daemon" -+version = "0.1.0" -+edition = "2021" -+ -+[dependencies] -+anyhow.workspace = true -+log.workspace = true -+redox_syscall = { workspace = true, features = ["std"] } -+libredox.workspace = true -+serde.workspace = true -+ron.workspace = true -+ -+acpi-resource = { path = "../../acpi-resource" } -+common = { path = "../../common" } -+daemon = { path = "../../../daemon" } -+i2c-interface = { path = "../../i2c/i2c-interface" } -+ -+[lints] -+workspace = true -diff --git a/drivers/gpio/i2c-gpio-expanderd/src/main.rs b/drivers/gpio/i2c-gpio-expanderd/src/main.rs -new file mode 100644 -index 00000000..223ebc01 ---- /dev/null -+++ b/drivers/gpio/i2c-gpio-expanderd/src/main.rs -@@ -0,0 +1,450 @@ -+use std::collections::BTreeMap; -+use std::fs::{self, File, OpenOptions}; -+use std::io::{Read, Write}; -+use std::path::Path; -+use std::process; -+ -+use acpi_resource::{GpioDescriptor, I2cSerialBusDescriptor, ResourceDescriptor}; -+use anyhow::{Context, Result}; -+use i2c_interface::{ -+ I2cAdapterInfo, I2cControlRequest, I2cControlResponse, I2cTransferRequest, -+ I2cTransferResponse, I2cTransferSegment, -+}; -+use serde::{Deserialize, Serialize}; -+ -+#[derive(Debug, Deserialize)] -+struct AmlSymbol { -+ name: String, -+ value: AmlValue, -+} -+ -+#[derive(Debug, Deserialize)] -+enum AmlValue { -+ Integer(u64), -+ String(String), -+} -+ -+#[derive(Clone, Debug)] -+struct ExpanderResources { -+ i2c: I2cSerialBusDescriptor, -+ pin_count: usize, -+ gpio_int_count: usize, -+ gpio_io_count: usize, -+} -+ -+#[derive(Debug)] -+struct ExpanderDescriptor { -+ device: String, -+ hid: String, -+ resources: ExpanderResources, -+} -+ -+struct RegisteredExpander { -+ _registration: File, -+} -+ -+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -+struct GpioControllerInfo { -+ id: u32, -+ name: String, -+ pin_count: usize, -+ supports_interrupt: bool, -+} -+ -+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -+enum GpioControlRequest { -+ RegisterController { info: GpioControllerInfo }, -+} -+ -+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -+enum GpioControlResponse { -+ ControllerRegistered { id: u32 }, -+ Error(String), -+} -+ -+fn main() { -+ common::setup_logging( -+ "gpio", -+ "i2c-gpio-expander", -+ "i2c-gpio-expanderd", -+ common::output_level(), -+ common::file_level(), -+ ); -+ -+ daemon::Daemon::new(daemon_runner); -+} -+ -+fn daemon_runner(daemon: daemon::Daemon) -> ! { -+ if let Err(err) = daemon_main(daemon) { -+ log::error!("i2c-gpio-expanderd: {err:#}"); -+ process::exit(1); -+ } -+ -+ process::exit(0); -+} -+ -+fn daemon_main(daemon: daemon::Daemon) -> Result<()> { -+ let expanders = discover_expanders().context("failed to discover ACPI I2C GPIO expanders")?; -+ if expanders.is_empty() { -+ log::info!("i2c-gpio-expanderd: no probable ACPI I2C GPIO expanders found"); -+ } -+ -+ let adapters = list_i2c_adapters().unwrap_or_else(|err| { -+ log::warn!("i2c-gpio-expanderd: unable to query i2cd adapters: {err:#}"); -+ Vec::new() -+ }); -+ -+ let mut registered = Vec::new(); -+ for expander in expanders { -+ match register_expander(expander, &adapters) { -+ Ok(expander) => registered.push(expander), -+ Err(err) => log::warn!("i2c-gpio-expanderd: expander registration skipped: {err:#}"), -+ } -+ } -+ -+ daemon.ready(); -+ libredox::call::setrens(0, 0).context("failed to enter null namespace")?; -+ -+ log::info!("i2c-gpio-expanderd: registered {} expander(s)", registered.len()); -+ -+ loop { -+ std::thread::park(); -+ } -+} -+ -+fn discover_expanders() -> Result> { -+ let mut matched = BTreeMap::new(); -+ -+ 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!("i2c-gpio-expanderd: ACPI symbols are not ready yet"); -+ return Ok(Vec::new()); -+ } -+ Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"), -+ }; -+ -+ for entry in entries { -+ let entry = entry.context("failed to read ACPI symbol directory entry")?; -+ let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else { -+ continue; -+ }; -+ if !file_name.ends_with("_HID") && !file_name.ends_with("_CID") { -+ continue; -+ } -+ -+ let Some(id) = read_symbol_id(&entry.path())? else { -+ continue; -+ }; -+ if is_excluded_device_id(&id) { -+ continue; -+ } -+ -+ let Some(device) = file_name -+ .strip_suffix("_HID") -+ .or_else(|| file_name.strip_suffix("_CID")) -+ .map(str::to_owned) -+ else { -+ continue; -+ }; -+ -+ let resources = match read_expander_resources(&device) { -+ Ok(resources) => resources, -+ Err(err) => { -+ log::debug!("i2c-gpio-expanderd: skipping {device}: {err:#}"); -+ continue; -+ } -+ }; -+ if resources.gpio_int_count == 0 && resources.gpio_io_count == 0 { -+ continue; -+ } -+ -+ matched.entry(device).or_insert((id, resources)); -+ } -+ -+ let mut expanders = Vec::new(); -+ for (device, (hid, resources)) in matched { -+ expanders.push(ExpanderDescriptor { -+ device, -+ hid, -+ resources, -+ }); -+ } -+ Ok(expanders) -+} -+ -+fn read_symbol_id(path: &Path) -> Result> { -+ let contents = fs::read_to_string(path) -+ .with_context(|| format!("failed to read ACPI symbol {}", path.display()))?; -+ let symbol = match ron::from_str::(&contents) { -+ Ok(symbol) => symbol, -+ Err(err) => { -+ log::debug!( -+ "i2c-gpio-expanderd: skipping {} because the symbol payload was not a scalar ID: {err}", -+ path.display(), -+ ); -+ return Ok(None); -+ } -+ }; -+ -+ let id = match symbol.value { -+ AmlValue::Integer(integer) => eisa_id_from_integer(integer), -+ AmlValue::String(string) => string, -+ }; -+ -+ log::debug!("i2c-gpio-expanderd: {} -> {id}", symbol.name); -+ Ok(Some(id)) -+} -+ -+fn read_expander_resources(device: &str) -> Result { -+ let contents = fs::read_to_string(format!("/scheme/acpi/resources/{device}")) -+ .with_context(|| format!("failed to read /scheme/acpi/resources/{device}"))?; -+ let resources = ron::from_str::>(&contents) -+ .with_context(|| format!("failed to decode RON resources for {device}"))?; -+ -+ let mut i2c = None; -+ let mut pin_count = 0usize; -+ let mut gpio_int_count = 0usize; -+ let mut gpio_io_count = 0usize; -+ -+ for resource in resources { -+ match resource { -+ ResourceDescriptor::I2cSerialBus(bus) if i2c.is_none() => i2c = Some(bus), -+ ResourceDescriptor::GpioInt(descriptor) => { -+ gpio_int_count += 1; -+ pin_count = pin_count.max(pin_count_from_descriptor(&descriptor)); -+ } -+ ResourceDescriptor::GpioIo(descriptor) => { -+ gpio_io_count += 1; -+ pin_count = pin_count.max(pin_count_from_descriptor(&descriptor)); -+ } -+ _ => {} -+ } -+ } -+ -+ Ok(ExpanderResources { -+ i2c: i2c.context("no I2cSerialBus resource was found")?, -+ pin_count, -+ gpio_int_count, -+ gpio_io_count, -+ }) -+} -+ -+fn pin_count_from_descriptor(descriptor: &GpioDescriptor) -> usize { -+ descriptor -+ .pins -+ .iter() -+ .copied() -+ .max() -+ .map(|pin| usize::from(pin).saturating_add(1)) -+ .unwrap_or(0) -+} -+ -+fn is_excluded_device_id(id: &str) -> bool { -+ matches!( -+ id, -+ "PNP0C50" -+ | "ACPI0C50" -+ | "INT34C5" -+ | "INTC1055" -+ | "INT33C2" -+ | "INT33C3" -+ | "INT3432" -+ | "INT3433" -+ | "INTC10EF" -+ | "AMDI0010" -+ | "AMDI0019" -+ | "AMDI0510" -+ | "PNP0CA0" -+ | "AMDI0042" -+ ) || id.starts_with("ELAN") -+ || id.starts_with("CYAP") -+ || id.starts_with("SYNA") -+} -+ -+fn register_expander(expander: ExpanderDescriptor, adapters: &[I2cAdapterInfo]) -> Result { -+ let ExpanderDescriptor { -+ device, -+ hid, -+ resources, -+ } = expander; -+ -+ let adapter_name = resources -+ .i2c -+ .resource_source -+ .as_ref() -+ .map(|source| source.source.clone()) -+ .filter(|source| !source.is_empty()) -+ .unwrap_or_else(|| String::from("ACPI-I2C")); -+ let adapter = match match_i2c_adapter(adapters, &adapter_name) { -+ Some(adapter) => Some(adapter.clone()), -+ None => { -+ log::warn!( -+ "i2c-gpio-expanderd: unable to resolve I2C adapter {} for {}", -+ adapter_name, -+ device, -+ ); -+ None -+ } -+ }; -+ -+ if let Some(adapter) = adapter.as_ref() { -+ if let Err(err) = probe_expander(adapter, &adapter_name, resources.i2c.slave_address) { -+ log::warn!( -+ "i2c-gpio-expanderd: expander {} probe on {}@{:04x} failed: {err:#}", -+ device, -+ adapter_name, -+ resources.i2c.slave_address, -+ ); -+ } -+ } -+ -+ let info = GpioControllerInfo { -+ id: 0, -+ name: format!("i2c-gpio-expander:{device}"), -+ pin_count: resources.pin_count, -+ supports_interrupt: resources.gpio_int_count > 0, -+ }; -+ let mut registration = register_with_gpiod(&info) -+ .with_context(|| format!("failed to register {device} with gpiod"))?; -+ let response = read_gpio_registration_response(&mut registration) -+ .with_context(|| format!("failed to read gpiod registration response for {device}"))?; -+ -+ match response { -+ GpioControlResponse::ControllerRegistered { id } => { -+ log::info!( -+ "RB_I2C_GPIO_EXPANDERD_DEVICE device={} hid={} controller_id={} adapter={} addr={:04x} pin_count={} gpio_int={} gpio_io={}", -+ device, -+ hid, -+ id, -+ adapter_name, -+ resources.i2c.slave_address, -+ info.pin_count, -+ resources.gpio_int_count, -+ resources.gpio_io_count, -+ ); -+ } -+ GpioControlResponse::Error(message) => { -+ anyhow::bail!("gpiod rejected expander {device}: {message}"); -+ } -+ } -+ -+ Ok(RegisteredExpander { -+ _registration: registration, -+ }) -+} -+ -+fn list_i2c_adapters() -> Result> { -+ let mut file = OpenOptions::new() -+ .read(true) -+ .write(true) -+ .open("/scheme/i2c/adapters") -+ .context("failed to open /scheme/i2c/adapters")?; -+ -+ let payload = ron::ser::to_string(&I2cControlRequest::ListAdapters) -+ .context("failed to encode I2C list-adapters request")?; -+ file.write_all(payload.as_bytes()) -+ .context("failed to request I2C adapter list")?; -+ -+ let response = read_i2c_control_response(&mut file)?; -+ match response { -+ I2cControlResponse::AdapterList(adapters) => Ok(adapters), -+ I2cControlResponse::Error(message) => anyhow::bail!("i2cd returned an error: {message}"), -+ other => anyhow::bail!("unexpected i2cd list-adapters response: {other:?}"), -+ } -+} -+ -+fn match_i2c_adapter<'a>(adapters: &'a [I2cAdapterInfo], wanted: &str) -> Option<&'a I2cAdapterInfo> { -+ adapters -+ .iter() -+ .find(|adapter| adapter.name == wanted) -+ .or_else(|| adapters.iter().find(|adapter| adapter.name.ends_with(wanted))) -+ .or_else(|| adapters.iter().find(|adapter| wanted.ends_with(&adapter.name))) -+} -+ -+fn probe_expander(adapter: &I2cAdapterInfo, adapter_name: &str, address: u16) -> Result { -+ let request = I2cTransferRequest { -+ adapter: adapter_name.to_string(), -+ segments: vec![I2cTransferSegment::read(address, 1)], -+ stop: true, -+ }; -+ -+ let mut file = OpenOptions::new() -+ .read(true) -+ .write(true) -+ .open("/scheme/i2c/transfer") -+ .context("failed to open /scheme/i2c/transfer")?; -+ let payload = ron::ser::to_string(&I2cControlRequest::Transfer { -+ adapter_id: adapter.id, -+ request, -+ }) -+ .context("failed to encode I2C expander probe request")?; -+ file.write_all(payload.as_bytes()) -+ .context("failed to send I2C expander probe request")?; -+ -+ let response = read_i2c_control_response(&mut file)?; -+ match response { -+ I2cControlResponse::TransferResult(result) => { -+ if !result.ok { -+ let detail = result -+ .error -+ .clone() -+ .unwrap_or_else(|| String::from("unknown I2C transfer failure")); -+ anyhow::bail!("I2C probe failed: {detail}"); -+ } -+ Ok(result) -+ } -+ I2cControlResponse::Error(message) => anyhow::bail!("i2cd returned an error: {message}"), -+ other => anyhow::bail!("unexpected I2C transfer response: {other:?}"), -+ } -+} -+ -+fn register_with_gpiod(info: &GpioControllerInfo) -> Result { -+ let mut file = OpenOptions::new() -+ .read(true) -+ .write(true) -+ .open("/scheme/gpio/register") -+ .context("failed to open /scheme/gpio/register")?; -+ let payload = ron::ser::to_string(&GpioControlRequest::RegisterController { info: info.clone() }) -+ .context("failed to encode GPIO controller registration")?; -+ file.write_all(payload.as_bytes()) -+ .context("failed to send GPIO controller registration")?; -+ Ok(file) -+} -+ -+fn read_gpio_registration_response(file: &mut File) -> Result { -+ let mut buffer = vec![0_u8; 4096]; -+ let count = file -+ .read(&mut buffer) -+ .context("failed to read GPIO registration response")?; -+ buffer.truncate(count); -+ let text = std::str::from_utf8(&buffer).context("GPIO registration response was not UTF-8")?; -+ ron::from_str(text).context("failed to decode GPIO registration response") -+} -+ -+fn read_i2c_control_response(file: &mut File) -> Result { -+ let mut buffer = vec![0_u8; 4096]; -+ let count = file -+ .read(&mut buffer) -+ .context("failed to read I2C control response")?; -+ buffer.truncate(count); -+ let text = std::str::from_utf8(&buffer).context("I2C control response was not UTF-8")?; -+ ron::from_str(text).context("failed to decode I2C control response") -+} -+ -+fn eisa_id_from_integer(integer: u64) -> String { -+ let vendor = integer & 0xFFFF; -+ let device = (integer >> 16) & 0xFFFF; -+ let vendor_rev = ((vendor & 0xFF) << 8) | (vendor >> 8); -+ let vendor_1 = (((vendor_rev >> 10) & 0x1F) as u8 + 64) as char; -+ let vendor_2 = (((vendor_rev >> 5) & 0x1F) as u8 + 64) as char; -+ let vendor_3 = (((vendor_rev >> 0) & 0x1F) as u8 + 64) as char; -+ let device_1 = (device >> 4) & 0xF; -+ let device_2 = (device >> 0) & 0xF; -+ let device_3 = (device >> 12) & 0xF; -+ let device_4 = (device >> 8) & 0xF; -+ -+ format!( -+ "{vendor_1}{vendor_2}{vendor_3}{device_1:01X}{device_2:01X}{device_3:01X}{device_4:01X}" -+ ) -+} -diff --git a/drivers/gpio/intel-gpiod/Cargo.toml b/drivers/gpio/intel-gpiod/Cargo.toml -new file mode 100644 -index 00000000..f5ac9355 ---- /dev/null -+++ b/drivers/gpio/intel-gpiod/Cargo.toml -@@ -0,0 +1,20 @@ -+[package] -+name = "intel-gpiod" -+description = "Intel ACPI GPIO registrar daemon" -+version = "0.1.0" -+edition = "2021" -+ -+[dependencies] -+anyhow.workspace = true -+log.workspace = true -+redox_syscall = { workspace = true, features = ["std"] } -+libredox.workspace = true -+serde.workspace = true -+ron.workspace = true -+ -+acpi-resource = { path = "../../acpi-resource" } -+common = { path = "../../common" } -+daemon = { path = "../../../daemon" } -+ -+[lints] -+workspace = true -diff --git a/drivers/gpio/intel-gpiod/src/main.rs b/drivers/gpio/intel-gpiod/src/main.rs -new file mode 100644 -index 00000000..78e60990 ---- /dev/null -+++ b/drivers/gpio/intel-gpiod/src/main.rs -@@ -0,0 +1,401 @@ -+use std::collections::BTreeMap; -+use std::fs::{self, File, OpenOptions}; -+use std::io::{Read, Write}; -+use std::path::Path; -+use std::process; -+ -+use acpi_resource::{ -+ AddressResourceType, ExtendedIrqDescriptor, FixedMemory32Descriptor, GpioDescriptor, -+ IrqDescriptor, Memory32RangeDescriptor, ResourceDescriptor, -+}; -+use anyhow::{Context, Result}; -+use common::{MemoryType, PhysBorrowed, Prot}; -+use serde::{Deserialize, Serialize}; -+ -+const SUPPORTED_IDS: &[&str] = &["INT34C5", "INTC1055"]; -+ -+const PADNFGPIO_OWN_BASE: usize = 0x20; -+const PADNFGPIO_PADCFG_BASE: usize = 0x700; -+const GPI_INT_STATUS: usize = 0x100; -+const GPI_INT_EN: usize = 0x120; -+const INTEL_GPIO_MMIO_WINDOW: usize = PADNFGPIO_PADCFG_BASE + core::mem::size_of::(); -+ -+#[derive(Debug, Deserialize)] -+struct AmlSymbol { -+ name: String, -+ value: AmlValue, -+} -+ -+#[derive(Debug, Deserialize)] -+enum AmlValue { -+ Integer(u64), -+ String(String), -+} -+ -+#[derive(Clone, Debug)] -+struct ControllerResources { -+ mmio_base: usize, -+ mmio_len: usize, -+ pin_count: usize, -+ supports_interrupt: bool, -+ gpio_int_count: usize, -+ gpio_io_count: usize, -+} -+ -+#[derive(Debug)] -+struct ControllerDescriptor { -+ device: String, -+ hid: String, -+ resources: ControllerResources, -+} -+ -+struct RegisteredController { -+ _mmio: Option, -+ _registration: File, -+} -+ -+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -+struct GpioControllerInfo { -+ id: u32, -+ name: String, -+ pin_count: usize, -+ supports_interrupt: bool, -+} -+ -+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -+enum GpioControlRequest { -+ RegisterController { info: GpioControllerInfo }, -+} -+ -+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -+enum GpioControlResponse { -+ ControllerRegistered { id: u32 }, -+ Error(String), -+} -+ -+fn main() { -+ common::setup_logging( -+ "gpio", -+ "intel-gpio", -+ "intel-gpiod", -+ common::output_level(), -+ common::file_level(), -+ ); -+ -+ daemon::Daemon::new(daemon_runner); -+} -+ -+fn daemon_runner(daemon: daemon::Daemon) -> ! { -+ if let Err(err) = daemon_main(daemon) { -+ log::error!("intel-gpiod: {err:#}"); -+ process::exit(1); -+ } -+ -+ process::exit(0); -+} -+ -+fn daemon_main(daemon: daemon::Daemon) -> Result<()> { -+ common::init(); -+ -+ let controllers = -+ discover_controllers(SUPPORTED_IDS).context("failed to discover Intel GPIO controllers")?; -+ if controllers.is_empty() { -+ log::info!("intel-gpiod: no supported Intel GPIO ACPI controllers found"); -+ } -+ -+ let mut registered = Vec::new(); -+ for controller in controllers { -+ match register_controller(controller) { -+ Ok(controller) => registered.push(controller), -+ Err(err) => log::warn!("intel-gpiod: controller registration skipped: {err:#}"), -+ } -+ } -+ -+ daemon.ready(); -+ libredox::call::setrens(0, 0).context("failed to enter null namespace")?; -+ -+ log::info!("intel-gpiod: registered {} controller(s)", registered.len()); -+ -+ loop { -+ std::thread::park(); -+ } -+} -+ -+fn discover_controllers(supported_ids: &[&str]) -> Result> { -+ let mut matched = BTreeMap::new(); -+ -+ 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!("intel-gpiod: ACPI symbols are not ready yet"); -+ return Ok(Vec::new()); -+ } -+ Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"), -+ }; -+ -+ for entry in entries { -+ let entry = entry.context("failed to read ACPI symbol directory entry")?; -+ let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else { -+ continue; -+ }; -+ if !file_name.ends_with("_HID") && !file_name.ends_with("_CID") { -+ continue; -+ } -+ -+ let Some(id) = read_symbol_id(&entry.path())? else { -+ continue; -+ }; -+ if !supported_ids.iter().any(|candidate| *candidate == id) { -+ continue; -+ } -+ -+ let device = file_name -+ .strip_suffix("_HID") -+ .or_else(|| file_name.strip_suffix("_CID")) -+ .map(str::to_owned); -+ if let Some(device) = device { -+ matched.entry(device).or_insert(id); -+ } -+ } -+ -+ let mut controllers = Vec::new(); -+ for (device, hid) in matched { -+ let resources = read_controller_resources(&device) -+ .with_context(|| format!("failed to read resources for {device}"))?; -+ controllers.push(ControllerDescriptor { -+ device, -+ hid, -+ resources, -+ }); -+ } -+ -+ Ok(controllers) -+} -+ -+fn read_symbol_id(path: &Path) -> Result> { -+ let contents = fs::read_to_string(path) -+ .with_context(|| format!("failed to read ACPI symbol {}", path.display()))?; -+ let symbol = match ron::from_str::(&contents) { -+ Ok(symbol) => symbol, -+ Err(err) => { -+ log::debug!( -+ "intel-gpiod: skipping {} because the symbol payload was not a scalar ID: {err}", -+ path.display(), -+ ); -+ return Ok(None); -+ } -+ }; -+ -+ let id = match symbol.value { -+ AmlValue::Integer(integer) => eisa_id_from_integer(integer), -+ AmlValue::String(string) => string, -+ }; -+ -+ log::debug!("intel-gpiod: {} -> {id}", symbol.name); -+ Ok(Some(id)) -+} -+ -+fn read_controller_resources(device: &str) -> Result { -+ let contents = fs::read_to_string(format!("/scheme/acpi/resources/{device}")) -+ .with_context(|| format!("failed to read /scheme/acpi/resources/{device}"))?; -+ let resources = ron::from_str::>(&contents) -+ .with_context(|| format!("failed to decode RON resources for {device}"))?; -+ -+ let mut mmio = None; -+ let mut supports_interrupt = false; -+ let mut gpio_int_count = 0usize; -+ let mut gpio_io_count = 0usize; -+ let mut pin_count = 0usize; -+ -+ for resource in &resources { -+ match resource { -+ ResourceDescriptor::FixedMemory32(FixedMemory32Descriptor { -+ address, -+ address_length, -+ .. -+ }) if mmio.is_none() => { -+ mmio = Some(( -+ *address as usize, -+ (*address_length as usize).max(INTEL_GPIO_MMIO_WINDOW), -+ )); -+ } -+ ResourceDescriptor::Memory32Range(Memory32RangeDescriptor { -+ minimum, -+ maximum, -+ address_length, -+ .. -+ }) if mmio.is_none() && maximum >= minimum => { -+ let span = maximum.saturating_sub(*minimum).saturating_add(1) as usize; -+ mmio = Some(( -+ *minimum as usize, -+ span.max((*address_length as usize).max(INTEL_GPIO_MMIO_WINDOW)), -+ )); -+ } -+ ResourceDescriptor::Address32(descriptor) -+ if mmio.is_none() -+ && matches!(descriptor.resource_type, AddressResourceType::MemoryRange) => -+ { -+ mmio = Some(( -+ descriptor.minimum as usize, -+ (descriptor.address_length as usize).max(INTEL_GPIO_MMIO_WINDOW), -+ )); -+ } -+ ResourceDescriptor::Address64(descriptor) -+ if mmio.is_none() -+ && matches!(descriptor.resource_type, AddressResourceType::MemoryRange) => -+ { -+ let base = usize::try_from(descriptor.minimum) -+ .context("64-bit MMIO base does not fit in usize")?; -+ let len = usize::try_from(descriptor.address_length) -+ .context("64-bit MMIO length does not fit in usize")?; -+ mmio = Some((base, len.max(INTEL_GPIO_MMIO_WINDOW))); -+ } -+ ResourceDescriptor::Irq(IrqDescriptor { interrupts, .. }) => { -+ supports_interrupt |= !interrupts.is_empty(); -+ } -+ ResourceDescriptor::ExtendedIrq(ExtendedIrqDescriptor { interrupts, .. }) => { -+ supports_interrupt |= !interrupts.is_empty(); -+ } -+ ResourceDescriptor::GpioInt(descriptor) => { -+ gpio_int_count += 1; -+ supports_interrupt = true; -+ pin_count = pin_count.max(pin_count_from_descriptor(descriptor)); -+ } -+ ResourceDescriptor::GpioIo(descriptor) => { -+ gpio_io_count += 1; -+ pin_count = pin_count.max(pin_count_from_descriptor(descriptor)); -+ } -+ _ => {} -+ } -+ } -+ -+ let (mmio_base, mmio_len) = mmio.context("no MMIO resource was found")?; -+ Ok(ControllerResources { -+ mmio_base, -+ mmio_len, -+ pin_count, -+ supports_interrupt, -+ gpio_int_count, -+ gpio_io_count, -+ }) -+} -+ -+fn pin_count_from_descriptor(descriptor: &GpioDescriptor) -> usize { -+ descriptor -+ .pins -+ .iter() -+ .copied() -+ .max() -+ .map(|pin| usize::from(pin).saturating_add(1)) -+ .unwrap_or(0) -+} -+ -+fn register_controller(controller: ControllerDescriptor) -> Result { -+ let ControllerDescriptor { -+ device, -+ hid, -+ resources, -+ } = controller; -+ -+ let mmio = match PhysBorrowed::map( -+ resources.mmio_base, -+ resources.mmio_len, -+ Prot::RW, -+ MemoryType::Uncacheable, -+ ) { -+ Ok(mapping) => Some(mapping), -+ Err(err) => { -+ log::warn!( -+ "intel-gpiod: failed to map MMIO for {device} ({:#x}, len {:#x}): {err}", -+ resources.mmio_base, -+ resources.mmio_len, -+ ); -+ None -+ } -+ }; -+ -+ log::info!( -+ "intel-gpiod: discovered {device} hid={hid} mmio={:#x}+{:#x} pin_count={} gpio_int={} gpio_io={} supports_interrupt={}", -+ resources.mmio_base, -+ resources.mmio_len, -+ resources.pin_count, -+ resources.gpio_int_count, -+ resources.gpio_io_count, -+ resources.supports_interrupt, -+ ); -+ log::debug!( -+ "intel-gpiod: register model own={PADNFGPIO_OWN_BASE:#x} padcfg={PADNFGPIO_PADCFG_BASE:#x} gpi_int_status={GPI_INT_STATUS:#x} gpi_int_en={GPI_INT_EN:#x}", -+ ); -+ -+ let info = GpioControllerInfo { -+ id: 0, -+ name: format!("intel-gpio:{device}"), -+ pin_count: resources.pin_count, -+ supports_interrupt: resources.supports_interrupt, -+ }; -+ let mut registration = register_with_gpiod(&info) -+ .with_context(|| format!("failed to register {device} with gpiod"))?; -+ let response = read_registration_response(&mut registration) -+ .with_context(|| format!("failed to read gpiod registration response for {device}"))?; -+ -+ match response { -+ GpioControlResponse::ControllerRegistered { id } => { -+ log::info!( -+ "RB_INTEL_GPIOD_DEVICE device={} hid={} controller_id={} pin_count={} supports_interrupt={}", -+ device, -+ hid, -+ id, -+ info.pin_count, -+ info.supports_interrupt, -+ ); -+ } -+ GpioControlResponse::Error(message) => { -+ anyhow::bail!("gpiod rejected Intel GPIO controller {device}: {message}"); -+ } -+ } -+ -+ Ok(RegisteredController { -+ _mmio: mmio, -+ _registration: registration, -+ }) -+} -+ -+fn register_with_gpiod(info: &GpioControllerInfo) -> Result { -+ let mut file = OpenOptions::new() -+ .read(true) -+ .write(true) -+ .open("/scheme/gpio/register") -+ .context("failed to open /scheme/gpio/register")?; -+ let payload = ron::ser::to_string(&GpioControlRequest::RegisterController { info: info.clone() }) -+ .context("failed to encode GPIO controller registration")?; -+ file.write_all(payload.as_bytes()) -+ .context("failed to send GPIO controller registration")?; -+ Ok(file) -+} -+ -+fn read_registration_response(file: &mut File) -> Result { -+ let mut buffer = vec![0_u8; 4096]; -+ let count = file -+ .read(&mut buffer) -+ .context("failed to read GPIO registration response")?; -+ buffer.truncate(count); -+ let text = std::str::from_utf8(&buffer).context("GPIO registration response was not UTF-8")?; -+ ron::from_str(text).context("failed to decode GPIO registration response") -+} -+ -+fn eisa_id_from_integer(integer: u64) -> String { -+ let vendor = integer & 0xFFFF; -+ let device = (integer >> 16) & 0xFFFF; -+ let vendor_rev = ((vendor & 0xFF) << 8) | (vendor >> 8); -+ let vendor_1 = (((vendor_rev >> 10) & 0x1F) as u8 + 64) as char; -+ let vendor_2 = (((vendor_rev >> 5) & 0x1F) as u8 + 64) as char; -+ let vendor_3 = (((vendor_rev >> 0) & 0x1F) as u8 + 64) as char; -+ let device_1 = (device >> 4) & 0xF; -+ let device_2 = (device >> 0) & 0xF; -+ let device_3 = (device >> 12) & 0xF; -+ let device_4 = (device >> 8) & 0xF; -+ -+ format!( -+ "{vendor_1}{vendor_2}{vendor_3}{device_1:01X}{device_2:01X}{device_3:01X}{device_4:01X}" -+ ) -+} -diff --git a/drivers/i2c/amd-mp2-i2cd/Cargo.toml b/drivers/i2c/amd-mp2-i2cd/Cargo.toml -new file mode 100644 -index 00000000..357ca948 ---- /dev/null -+++ b/drivers/i2c/amd-mp2-i2cd/Cargo.toml -@@ -0,0 +1,22 @@ -+[package] -+name = "amd-mp2-i2cd" -+description = "AMD MP2 PCI I2C controller driver" -+version = "0.1.0" -+edition = "2021" -+ -+[dependencies] -+anyhow.workspace = true -+log.workspace = true -+redox_syscall = { workspace = true, features = ["std"] } -+libredox.workspace = true -+serde.workspace = true -+ron.workspace = true -+ -+acpi-resource = { path = "../../acpi-resource" } -+common = { path = "../../common" } -+daemon = { path = "../../../daemon" } -+i2c-interface = { path = "../i2c-interface" } -+pcid = { path = "../../pcid" } -+ -+[lints] -+workspace = true -diff --git a/drivers/i2c/amd-mp2-i2cd/src/main.rs b/drivers/i2c/amd-mp2-i2cd/src/main.rs -new file mode 100644 -index 00000000..ab06ad2a ---- /dev/null -+++ b/drivers/i2c/amd-mp2-i2cd/src/main.rs -@@ -0,0 +1,107 @@ -+use std::fs::{File, OpenOptions}; -+use std::io::{Read, Write}; -+use std::process; -+ -+use anyhow::{Context, Result}; -+use i2c_interface::{I2cAdapterInfo, I2cControlRequest, I2cControlResponse}; -+use pcid_interface::PciFunctionHandle; -+ -+const MP2_MAILBOX_STATUS: usize = 0x00; -+const MP2_MAILBOX_COMMAND: usize = 0x04; -+const MP2_MAILBOX_ARGUMENT0: usize = 0x08; -+const MP2_MAILBOX_ARGUMENT1: usize = 0x0C; -+ -+fn main() { -+ pcid_interface::pci_daemon(daemon_runner); -+} -+ -+fn daemon_runner(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { -+ if let Err(err) = daemon_main(daemon, &mut pcid_handle) { -+ log::error!("amd-mp2-i2cd: {err:#}"); -+ process::exit(1); -+ } -+ -+ process::exit(0); -+} -+ -+fn daemon_main(daemon: daemon::Daemon, pcid_handle: &mut PciFunctionHandle) -> Result<()> { -+ let pci_config = pcid_handle.config(); -+ let log_name = format!("{}_amd-mp2-i2c", pci_config.func.name()); -+ -+ common::setup_logging( -+ "bus", -+ "i2c", -+ &log_name, -+ common::output_level(), -+ common::file_level(), -+ ); -+ -+ let (bar_addr, bar_size) = pci_config.func.bars[0] -+ .try_mem() -+ .map_err(|err| anyhow::anyhow!("BAR0 is not a memory BAR: {err}"))?; -+ let mapped_bar = unsafe { pcid_handle.map_bar(0) }; -+ -+ log::info!( -+ "amd-mp2-i2cd: {} BAR0={:#x}+{:#x} mapped={:p}+{:#x}", -+ pci_config.func.display(), -+ bar_addr, -+ bar_size, -+ mapped_bar.ptr.as_ptr(), -+ mapped_bar.bar_size, -+ ); -+ log::debug!( -+ "amd-mp2-i2cd: MP2 mailbox regs status={MP2_MAILBOX_STATUS:#x} cmd={MP2_MAILBOX_COMMAND:#x} arg0={MP2_MAILBOX_ARGUMENT0:#x} arg1={MP2_MAILBOX_ARGUMENT1:#x}", -+ ); -+ -+ let info = I2cAdapterInfo { -+ id: 0, -+ name: format!("amd-mp2:{}", pci_config.func.name()), -+ max_transaction_size: 0, -+ supports_10bit_addr: false, -+ }; -+ let mut registration = register_adapter(&info) -+ .context("failed to register AMD MP2 controller with i2cd")?; -+ let response = read_registration_response(&mut registration) -+ .context("failed to read AMD MP2 i2cd registration response")?; -+ -+ match response { -+ I2cControlResponse::AdapterRegistered { id } => { -+ log::info!("amd-mp2-i2cd: controller registered with i2cd as adapter {id}"); -+ } -+ other => anyhow::bail!("unexpected i2cd registration response: {other:?}"), -+ } -+ -+ daemon.ready(); -+ libredox::call::setrens(0, 0).context("failed to enter null namespace")?; -+ -+ let _keep_registration = registration; -+ let _keep_pcid = pcid_handle; -+ -+ loop { -+ std::thread::park(); -+ } -+} -+ -+fn register_adapter(info: &I2cAdapterInfo) -> Result { -+ let mut file = OpenOptions::new() -+ .read(true) -+ .write(true) -+ .open("/scheme/i2c/register") -+ .context("failed to open /scheme/i2c/register")?; -+ let payload = ron::ser::to_string(&I2cControlRequest::RegisterAdapter { info: info.clone() }) -+ .context("failed to encode AMD MP2 I2C registration payload")?; -+ file.write_all(payload.as_bytes()) -+ .context("failed to send AMD MP2 I2C registration payload")?; -+ Ok(file) -+} -+ -+fn read_registration_response(file: &mut File) -> Result { -+ let mut buffer = vec![0_u8; 4096]; -+ let count = file -+ .read(&mut buffer) -+ .context("failed to read AMD MP2 I2C registration response")?; -+ buffer.truncate(count); -+ let text = std::str::from_utf8(&buffer) -+ .context("AMD MP2 I2C registration response was not UTF-8")?; -+ ron::from_str(text).context("failed to decode AMD MP2 I2C registration response") -+} -diff --git a/drivers/i2c/dw-acpi-i2cd/Cargo.toml b/drivers/i2c/dw-acpi-i2cd/Cargo.toml -new file mode 100644 -index 00000000..a90b48cc ---- /dev/null -+++ b/drivers/i2c/dw-acpi-i2cd/Cargo.toml -@@ -0,0 +1,21 @@ -+[package] -+name = "dw-acpi-i2cd" -+description = "Generic DesignWare ACPI I2C controller driver" -+version = "0.1.0" -+edition = "2021" -+ -+[dependencies] -+anyhow.workspace = true -+log.workspace = true -+redox_syscall = { workspace = true, features = ["std"] } -+libredox.workspace = true -+serde.workspace = true -+ron.workspace = true -+ -+acpi-resource = { path = "../../acpi-resource" } -+common = { path = "../../common" } -+daemon = { path = "../../../daemon" } -+i2c-interface = { path = "../i2c-interface" } -+ -+[lints] -+workspace = true -diff --git a/drivers/i2c/dw-acpi-i2cd/src/main.rs b/drivers/i2c/dw-acpi-i2cd/src/main.rs -new file mode 100644 -index 00000000..b22a2773 ---- /dev/null -+++ b/drivers/i2c/dw-acpi-i2cd/src/main.rs -@@ -0,0 +1,361 @@ -+use std::collections::BTreeMap; -+use std::fs::{self, File, OpenOptions}; -+use std::io::{Read, Write}; -+use std::path::Path; -+use std::process; -+ -+use acpi_resource::{ -+ AddressResourceType, ExtendedIrqDescriptor, FixedMemory32Descriptor, I2cSerialBusDescriptor, -+ IrqDescriptor, Memory32RangeDescriptor, ResourceDescriptor, -+}; -+use anyhow::{Context, Result}; -+use common::{MemoryType, PhysBorrowed, Prot}; -+use i2c_interface::{I2cAdapterInfo, I2cControlRequest, I2cControlResponse}; -+use serde::Deserialize; -+ -+const SUPPORTED_IDS: &[&str] = &["80860F41", "808622C1", "AMDI0010", "AMDI0019", "AMDI0510"]; -+ -+const DW_IC_CON: usize = 0x00; -+const DW_IC_TAR: usize = 0x04; -+const DW_IC_SS_SCL_HCNT: usize = 0x14; -+const DW_IC_SS_SCL_LCNT: usize = 0x18; -+const DW_IC_DATA_CMD: usize = 0x10; -+const DW_IC_INTR_MASK: usize = 0x30; -+const DW_IC_CLR_INTR: usize = 0x40; -+const DW_IC_ENABLE: usize = 0x6C; -+const DW_IC_STATUS: usize = 0x70; -+const DW_MMIO_WINDOW: usize = DW_IC_STATUS + core::mem::size_of::(); -+ -+#[derive(Debug, Deserialize)] -+struct AmlSymbol { -+ name: String, -+ value: AmlValue, -+} -+ -+#[derive(Debug, Deserialize)] -+enum AmlValue { -+ Integer(u64), -+ String(String), -+} -+ -+#[derive(Clone, Debug)] -+struct ControllerResources { -+ mmio_base: usize, -+ mmio_len: usize, -+ irq: Option, -+ serial_bus: Option, -+} -+ -+#[derive(Debug)] -+struct ControllerDescriptor { -+ device: String, -+ hid: String, -+ resources: ControllerResources, -+} -+ -+struct RegisteredController { -+ _mmio: Option, -+ _registration: File, -+} -+ -+fn main() { -+ common::setup_logging( -+ "bus", -+ "i2c", -+ "dw-acpi-i2cd", -+ common::output_level(), -+ common::file_level(), -+ ); -+ -+ daemon::Daemon::new(daemon_runner); -+} -+ -+fn daemon_runner(daemon: daemon::Daemon) -> ! { -+ if let Err(err) = daemon_main(daemon) { -+ log::error!("dw-acpi-i2cd: {err:#}"); -+ process::exit(1); -+ } -+ -+ process::exit(0); -+} -+ -+fn daemon_main(daemon: daemon::Daemon) -> Result<()> { -+ common::init(); -+ -+ let controllers = discover_controllers(SUPPORTED_IDS) -+ .context("failed to discover DesignWare ACPI I2C controllers")?; -+ if controllers.is_empty() { -+ log::info!("dw-acpi-i2cd: no supported ACPI controllers found"); -+ } -+ -+ let mut registered = Vec::new(); -+ for controller in controllers { -+ match register_controller("dw-acpi", controller) { -+ Ok(controller) => registered.push(controller), -+ Err(err) => log::warn!("dw-acpi-i2cd: controller registration skipped: {err:#}"), -+ } -+ } -+ -+ daemon.ready(); -+ libredox::call::setrens(0, 0).context("failed to enter null namespace")?; -+ -+ log::info!("dw-acpi-i2cd: registered {} controller(s)", registered.len()); -+ -+ loop { -+ std::thread::park(); -+ } -+} -+ -+fn discover_controllers(supported_ids: &[&str]) -> Result> { -+ let mut matched = BTreeMap::new(); -+ -+ 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!("dw-acpi-i2cd: ACPI symbols are not ready yet"); -+ return Ok(Vec::new()); -+ } -+ Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"), -+ }; -+ -+ for entry in entries { -+ let entry = entry.context("failed to read ACPI symbol directory entry")?; -+ let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else { -+ continue; -+ }; -+ if !file_name.ends_with("_HID") && !file_name.ends_with("_CID") { -+ continue; -+ } -+ -+ let Some(id) = read_symbol_id(&entry.path())? else { -+ continue; -+ }; -+ if !supported_ids.iter().any(|candidate| *candidate == id) { -+ continue; -+ } -+ -+ let device = file_name -+ .strip_suffix("_HID") -+ .or_else(|| file_name.strip_suffix("_CID")) -+ .map(str::to_owned); -+ if let Some(device) = device { -+ matched.entry(device).or_insert(id); -+ } -+ } -+ -+ let mut controllers = Vec::new(); -+ for (device, hid) in matched { -+ let resources = read_controller_resources(&device) -+ .with_context(|| format!("failed to read resources for {device}"))?; -+ controllers.push(ControllerDescriptor { -+ device, -+ hid, -+ resources, -+ }); -+ } -+ -+ Ok(controllers) -+} -+ -+fn read_symbol_id(path: &Path) -> Result> { -+ let contents = fs::read_to_string(path) -+ .with_context(|| format!("failed to read ACPI symbol {}", path.display()))?; -+ let symbol = match ron::from_str::(&contents) { -+ Ok(symbol) => symbol, -+ Err(err) => { -+ log::debug!( -+ "dw-acpi-i2cd: skipping {} because the symbol payload was not a scalar ID: {err}", -+ path.display(), -+ ); -+ return Ok(None); -+ } -+ }; -+ -+ let id = match symbol.value { -+ AmlValue::Integer(integer) => eisa_id_from_integer(integer), -+ AmlValue::String(string) => string, -+ }; -+ -+ log::debug!("dw-acpi-i2cd: {} -> {id}", symbol.name); -+ Ok(Some(id)) -+} -+ -+fn read_controller_resources(device: &str) -> Result { -+ let contents = fs::read_to_string(format!("/scheme/acpi/resources/{device}")) -+ .with_context(|| format!("failed to read /scheme/acpi/resources/{device}"))?; -+ let resources = ron::from_str::>(&contents) -+ .with_context(|| format!("failed to decode RON resources for {device}"))?; -+ -+ let mut mmio = None; -+ let mut irq = None; -+ let mut serial_bus = None; -+ -+ for resource in &resources { -+ match resource { -+ ResourceDescriptor::FixedMemory32(FixedMemory32Descriptor { -+ address, -+ address_length, -+ .. -+ }) if mmio.is_none() => { -+ mmio = Some((*address as usize, (*address_length as usize).max(DW_MMIO_WINDOW))); -+ } -+ ResourceDescriptor::Memory32Range(Memory32RangeDescriptor { -+ minimum, -+ maximum, -+ address_length, -+ .. -+ }) if mmio.is_none() && maximum >= minimum => { -+ let span = maximum.saturating_sub(*minimum).saturating_add(1) as usize; -+ mmio = Some(( -+ *minimum as usize, -+ span.max((*address_length as usize).max(DW_MMIO_WINDOW)), -+ )); -+ } -+ ResourceDescriptor::Address32(descriptor) -+ if mmio.is_none() -+ && matches!(descriptor.resource_type, AddressResourceType::MemoryRange) => -+ { -+ mmio = Some(( -+ descriptor.minimum as usize, -+ (descriptor.address_length as usize).max(DW_MMIO_WINDOW), -+ )); -+ } -+ ResourceDescriptor::Address64(descriptor) -+ if mmio.is_none() -+ && matches!(descriptor.resource_type, AddressResourceType::MemoryRange) => -+ { -+ let base = usize::try_from(descriptor.minimum) -+ .context("64-bit MMIO base does not fit in usize")?; -+ let len = usize::try_from(descriptor.address_length) -+ .context("64-bit MMIO length does not fit in usize")?; -+ mmio = Some((base, len.max(DW_MMIO_WINDOW))); -+ } -+ ResourceDescriptor::Irq(IrqDescriptor { interrupts, .. }) if irq.is_none() => { -+ irq = interrupts.first().copied().map(u32::from); -+ } -+ ResourceDescriptor::ExtendedIrq(ExtendedIrqDescriptor { interrupts, .. }) -+ if irq.is_none() => -+ { -+ irq = interrupts.first().copied(); -+ } -+ ResourceDescriptor::I2cSerialBus(descriptor) if serial_bus.is_none() => { -+ serial_bus = Some(descriptor.clone()); -+ } -+ _ => {} -+ } -+ } -+ -+ let (mmio_base, mmio_len) = mmio.context("no MMIO resource was found")?; -+ Ok(ControllerResources { -+ mmio_base, -+ mmio_len, -+ irq, -+ serial_bus, -+ }) -+} -+ -+fn register_controller(prefix: &str, controller: ControllerDescriptor) -> Result { -+ let ControllerDescriptor { -+ device, -+ hid, -+ resources, -+ } = controller; -+ -+ let mmio = match PhysBorrowed::map( -+ resources.mmio_base, -+ resources.mmio_len, -+ Prot::RW, -+ MemoryType::Uncacheable, -+ ) { -+ Ok(mapping) => Some(mapping), -+ Err(err) => { -+ log::warn!( -+ "dw-acpi-i2cd: failed to map MMIO for {device} ({:#x}, len {:#x}): {err}", -+ resources.mmio_base, -+ resources.mmio_len, -+ ); -+ None -+ } -+ }; -+ -+ log::info!( -+ "dw-acpi-i2cd: discovered {device} hid={hid} mmio={:#x}+{:#x} irq={:?}", -+ resources.mmio_base, -+ resources.mmio_len, -+ resources.irq, -+ ); -+ log::debug!( -+ "dw-acpi-i2cd: DesignWare regs con={DW_IC_CON:#x} tar={DW_IC_TAR:#x} data_cmd={DW_IC_DATA_CMD:#x} intr_mask={DW_IC_INTR_MASK:#x} clr_intr={DW_IC_CLR_INTR:#x} enable={DW_IC_ENABLE:#x} ss_hcnt={DW_IC_SS_SCL_HCNT:#x} ss_lcnt={DW_IC_SS_SCL_LCNT:#x}", -+ ); -+ -+ let info = I2cAdapterInfo { -+ id: 0, -+ name: format!("{prefix}:{device}"), -+ max_transaction_size: 0, -+ supports_10bit_addr: resources -+ .serial_bus -+ .as_ref() -+ .map(|bus| bus.access_mode_10bit) -+ .unwrap_or(false), -+ }; -+ let mut registration = register_adapter(&info) -+ .with_context(|| format!("failed to register {device} with i2cd"))?; -+ let response = read_registration_response(&mut registration) -+ .with_context(|| format!("failed to read i2cd registration response for {device}"))?; -+ -+ match response { -+ I2cControlResponse::AdapterRegistered { id } => { -+ log::info!("dw-acpi-i2cd: adapter {device} registered with i2cd as {id}"); -+ } -+ other => { -+ anyhow::bail!("unexpected i2cd registration response for {device}: {other:?}"); -+ } -+ } -+ -+ Ok(RegisteredController { -+ _mmio: mmio, -+ _registration: registration, -+ }) -+} -+ -+fn register_adapter(info: &I2cAdapterInfo) -> Result { -+ let mut file = OpenOptions::new() -+ .read(true) -+ .write(true) -+ .open("/scheme/i2c/register") -+ .context("failed to open /scheme/i2c/register")?; -+ let payload = ron::ser::to_string(&I2cControlRequest::RegisterAdapter { info: info.clone() }) -+ .context("failed to encode I2C adapter registration")?; -+ file.write_all(payload.as_bytes()) -+ .context("failed to send I2C adapter registration")?; -+ Ok(file) -+} -+ -+fn read_registration_response(file: &mut File) -> Result { -+ let mut buffer = vec![0_u8; 4096]; -+ let count = file -+ .read(&mut buffer) -+ .context("failed to read I2C registration response")?; -+ buffer.truncate(count); -+ let text = std::str::from_utf8(&buffer).context("I2C registration response was not UTF-8")?; -+ ron::from_str(text).context("failed to decode I2C registration response") -+} -+ -+fn eisa_id_from_integer(integer: u64) -> String { -+ let vendor = integer & 0xFFFF; -+ let device = (integer >> 16) & 0xFFFF; -+ let vendor_rev = ((vendor & 0xFF) << 8) | (vendor >> 8); -+ let vendor_1 = (((vendor_rev >> 10) & 0x1F) as u8 + 64) as char; -+ let vendor_2 = (((vendor_rev >> 5) & 0x1F) as u8 + 64) as char; -+ let vendor_3 = (((vendor_rev >> 0) & 0x1F) as u8 + 64) as char; -+ let device_1 = (device >> 4) & 0xF; -+ let device_2 = (device >> 0) & 0xF; -+ let device_3 = (device >> 12) & 0xF; -+ let device_4 = (device >> 8) & 0xF; -+ -+ format!( -+ "{vendor_1}{vendor_2}{vendor_3}{device_1:01X}{device_2:01X}{device_3:01X}{device_4:01X}" -+ ) -+} -diff --git a/drivers/i2c/i2c-interface/Cargo.toml b/drivers/i2c/i2c-interface/Cargo.toml -new file mode 100644 -index 00000000..fa9bbe4f ---- /dev/null -+++ b/drivers/i2c/i2c-interface/Cargo.toml -@@ -0,0 +1,12 @@ -+[package] -+name = "i2c-interface" -+description = "Shared I2C transfer and registry types" -+version = "0.1.0" -+edition = "2021" -+ -+[dependencies] -+serde.workspace = true -+redox_syscall = { workspace = true, features = ["std"] } -+ -+[lints] -+workspace = true -diff --git a/drivers/i2c/i2c-interface/src/lib.rs b/drivers/i2c/i2c-interface/src/lib.rs -new file mode 100644 -index 00000000..9e5ab444 ---- /dev/null -+++ b/drivers/i2c/i2c-interface/src/lib.rs -@@ -0,0 +1,92 @@ -+use serde::{Deserialize, Serialize}; -+ -+pub use syscall; -+ -+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -+pub enum I2cTransferOp { -+ Write(Vec), -+ Read(usize), -+} -+ -+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -+pub struct I2cTransferSegment { -+ pub address: u16, -+ pub ten_bit_address: bool, -+ pub op: I2cTransferOp, -+} -+ -+impl I2cTransferSegment { -+ pub fn write(address: u16, payload: impl Into>) -> Self { -+ Self { -+ address, -+ ten_bit_address: false, -+ op: I2cTransferOp::Write(payload.into()), -+ } -+ } -+ -+ pub fn read(address: u16, len: usize) -> Self { -+ Self { -+ address, -+ ten_bit_address: false, -+ op: I2cTransferOp::Read(len), -+ } -+ } -+} -+ -+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -+pub struct I2cTransferRequest { -+ pub adapter: String, -+ pub segments: Vec, -+ pub stop: bool, -+} -+ -+#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] -+pub struct I2cTransferResponse { -+ pub ok: bool, -+ pub read_data: Vec>, -+ pub error: Option, -+} -+ -+#[derive(Clone, Debug, Default, Serialize, Deserialize)] -+pub struct I2cAdapterRegistration { -+ pub name: String, -+ pub description: String, -+ pub acpi_companion: Option, -+ pub slave_address_override: Option, -+} -+ -+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -+pub struct I2cAdapterInfo { -+ pub id: u32, -+ pub name: String, -+ pub max_transaction_size: usize, -+ pub supports_10bit_addr: bool, -+} -+ -+#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] -+pub enum I2cTransferStatus { -+ Ok, -+ Nack, -+ Timeout, -+ Error, -+} -+ -+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -+pub enum I2cControlRequest { -+ RegisterAdapter { info: I2cAdapterInfo }, -+ OpenAdapter { id: u32 }, -+ Transfer { -+ adapter_id: u32, -+ request: I2cTransferRequest, -+ }, -+ ListAdapters, -+} -+ -+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -+pub enum I2cControlResponse { -+ AdapterRegistered { id: u32 }, -+ AdapterOpened, -+ TransferResult(I2cTransferResponse), -+ AdapterList(Vec), -+ Error(String), -+} -diff --git a/drivers/i2c/i2cd/Cargo.toml b/drivers/i2c/i2cd/Cargo.toml -new file mode 100644 -index 00000000..0fdd6911 ---- /dev/null -+++ b/drivers/i2c/i2cd/Cargo.toml -@@ -0,0 +1,22 @@ -+[package] -+name = "i2cd" -+description = "I2C adapter registry scheme daemon" -+version = "0.1.0" -+edition = "2021" -+ -+[dependencies] -+anyhow.workspace = true -+log.workspace = true -+redox_syscall = { workspace = true, features = ["std"] } -+libredox.workspace = true -+redox-scheme.workspace = true -+serde.workspace = true -+ron.workspace = true -+ -+common = { path = "../../common" } -+daemon = { path = "../../../daemon" } -+scheme-utils = { path = "../../../scheme-utils" } -+i2c-interface = { path = "../i2c-interface" } -+ -+[lints] -+workspace = true -diff --git a/drivers/i2c/i2cd/src/main.rs b/drivers/i2c/i2cd/src/main.rs -new file mode 100644 -index 00000000..b7b7d89a ---- /dev/null -+++ b/drivers/i2c/i2cd/src/main.rs -@@ -0,0 +1,377 @@ -+use std::collections::BTreeMap; -+use std::process; -+ -+use anyhow::{Context, Result}; -+use i2c_interface::{ -+ I2cAdapterInfo, I2cControlRequest, I2cControlResponse, I2cTransferRequest, -+ I2cTransferResponse, -+}; -+use redox_scheme::scheme::SchemeSync; -+use redox_scheme::{CallerCtx, OpenResult, Socket}; -+use scheme_utils::{Blocking, HandleMap}; -+use syscall::schemev2::NewFdFlags; -+use syscall::{Error as SysError, EACCES, EBADF, EINVAL, ENOENT}; -+ -+enum Handle { -+ SchemeRoot, -+ Register { pending: Vec }, -+ Provider { adapter_id: u32, pending: Vec }, -+ Adapters { pending: Vec }, -+ AdapterDetail { id: u32, pending: Vec }, -+ Transfer { pending: Vec }, -+} -+ -+struct AdapterEntry { -+ info: I2cAdapterInfo, -+ provider_handle: usize, -+} -+ -+struct I2cDaemon { -+ handles: HandleMap, -+ adapters: BTreeMap, -+ next_id: u32, -+} -+ -+impl I2cDaemon { -+ fn new() -> Self { -+ Self { -+ handles: HandleMap::new(), -+ adapters: BTreeMap::new(), -+ next_id: 0, -+ } -+ } -+ -+ fn adapter_list(&self) -> Vec { -+ self.adapters.values().map(|entry| entry.info.clone()).collect() -+ } -+ -+ fn serialize_response(response: &I2cControlResponse) -> syscall::Result> { -+ ron::ser::to_string(response) -+ .map(|text| text.into_bytes()) -+ .map_err(|err| { -+ log::error!("i2cd: failed to serialize control response: {err}"); -+ SysError::new(EINVAL) -+ }) -+ } -+ -+ fn deserialize_request(buf: &[u8]) -> syscall::Result { -+ let text = std::str::from_utf8(buf).map_err(|err| { -+ log::warn!("i2cd: invalid UTF-8 control payload: {err}"); -+ SysError::new(EINVAL) -+ })?; -+ -+ ron::from_str(text).map_err(|err| { -+ log::warn!("i2cd: failed to decode control request: {err}"); -+ SysError::new(EINVAL) -+ }) -+ } -+ -+ fn set_pending_response(handle: &mut Handle, response: I2cControlResponse) -> syscall::Result<()> { -+ let pending = Self::serialize_response(&response)?; -+ match handle { -+ Handle::Register { pending: slot } -+ | Handle::Provider { pending: slot, .. } -+ | Handle::Adapters { pending: slot } -+ | Handle::AdapterDetail { pending: slot, .. } -+ | Handle::Transfer { pending: slot } => { -+ *slot = pending; -+ Ok(()) -+ } -+ Handle::SchemeRoot => Err(SysError::new(EBADF)), -+ } -+ } -+ -+ fn queue_adapter_list(handle: &mut Handle, adapters: Vec) -> syscall::Result<()> { -+ Self::set_pending_response(handle, I2cControlResponse::AdapterList(adapters)) -+ } -+ -+ fn queue_transfer_stub( -+ handle: &mut Handle, -+ adapter: &I2cAdapterInfo, -+ request: &I2cTransferRequest, -+ ) -> syscall::Result<()> { -+ let write_bytes = request -+ .segments -+ .iter() -+ .filter_map(|segment| match &segment.op { -+ i2c_interface::I2cTransferOp::Write(bytes) => Some(bytes.len()), -+ i2c_interface::I2cTransferOp::Read(_) => None, -+ }) -+ .sum::(); -+ let read_segments = request -+ .segments -+ .iter() -+ .filter(|segment| matches!(segment.op, i2c_interface::I2cTransferOp::Read(_))) -+ .count(); -+ -+ log::info!( -+ "i2cd: routing transfer to adapter {} ({}) name={} segments={} write_bytes={} read_segments={} stop={} (stubbed)", -+ adapter.id, -+ adapter.name, -+ request.adapter, -+ request.segments.len(), -+ write_bytes, -+ read_segments, -+ request.stop, -+ ); -+ -+ Self::set_pending_response( -+ handle, -+ I2cControlResponse::TransferResult(I2cTransferResponse { -+ ok: false, -+ read_data: Vec::new(), -+ error: Some(String::from("I2C controller transfer path is not implemented yet")), -+ }), -+ ) -+ } -+ -+ fn copy_pending(handle: &mut Handle, buf: &mut [u8], offset: u64) -> syscall::Result { -+ let pending = match handle { -+ Handle::Register { pending } -+ | Handle::Provider { pending, .. } -+ | Handle::Adapters { pending } -+ | Handle::AdapterDetail { pending, .. } -+ | Handle::Transfer { pending } => pending, -+ Handle::SchemeRoot => return Err(SysError::new(EBADF)), -+ }; -+ -+ let offset = usize::try_from(offset).map_err(|_| SysError::new(EINVAL))?; -+ if offset >= pending.len() { -+ return Ok(0); -+ } -+ -+ let copy_len = buf.len().min(pending.len() - offset); -+ buf[..copy_len].copy_from_slice(&pending[offset..offset + copy_len]); -+ Ok(copy_len) -+ } -+} -+ -+impl SchemeSync for I2cDaemon { -+ fn scheme_root(&mut self) -> syscall::Result { -+ Ok(self.handles.insert(Handle::SchemeRoot)) -+ } -+ -+ fn openat( -+ &mut self, -+ dirfd: usize, -+ path: &str, -+ _flags: usize, -+ _fcntl_flags: u32, -+ _ctx: &CallerCtx, -+ ) -> syscall::Result { -+ let handle = self.handles.get(dirfd)?; -+ let segments = path.trim_matches('/'); -+ -+ let new_handle = match handle { -+ Handle::SchemeRoot => { -+ if segments.is_empty() { -+ return Err(SysError::new(EINVAL)); -+ } -+ -+ let mut parts = segments.split('/'); -+ match parts.next() { -+ Some("register") if parts.next().is_none() => Handle::Register { -+ pending: Vec::new(), -+ }, -+ Some("adapters") => match parts.next() { -+ None => Handle::Adapters { -+ pending: Vec::new(), -+ }, -+ Some(id) if parts.next().is_none() => { -+ let id = id.parse::().map_err(|_| SysError::new(EINVAL))?; -+ Handle::AdapterDetail { -+ id, -+ pending: Vec::new(), -+ } -+ } -+ _ => return Err(SysError::new(EINVAL)), -+ }, -+ Some("transfer") if parts.next().is_none() => Handle::Transfer { -+ pending: Vec::new(), -+ }, -+ _ => return Err(SysError::new(ENOENT)), -+ } -+ } -+ Handle::Adapters { .. } => { -+ if segments.is_empty() { -+ return Err(SysError::new(EINVAL)); -+ } -+ -+ let id = segments.parse::().map_err(|_| SysError::new(EINVAL))?; -+ Handle::AdapterDetail { -+ id, -+ pending: Vec::new(), -+ } -+ } -+ _ => return Err(SysError::new(EACCES)), -+ }; -+ -+ let fd = self.handles.insert(new_handle); -+ Ok(OpenResult::ThisScheme { -+ number: fd, -+ flags: NewFdFlags::empty(), -+ }) -+ } -+ -+ fn read( -+ &mut self, -+ id: usize, -+ buf: &mut [u8], -+ offset: u64, -+ _fcntl_flags: u32, -+ _ctx: &CallerCtx, -+ ) -> syscall::Result { -+ let adapters = self.adapter_list(); -+ let handle = self.handles.get_mut(id)?; -+ -+ match handle { -+ Handle::Adapters { pending } if pending.is_empty() => { -+ *pending = Self::serialize_response(&I2cControlResponse::AdapterList(adapters))?; -+ } -+ Handle::AdapterDetail { id, pending } if pending.is_empty() => { -+ let info = self -+ .adapters -+ .get(id) -+ .map(|entry| entry.info.clone()) -+ .ok_or(SysError::new(ENOENT))?; -+ *pending = Self::serialize_response(&I2cControlResponse::AdapterList(vec![info]))?; -+ } -+ _ => {} -+ } -+ -+ Self::copy_pending(handle, buf, offset) -+ } -+ -+ fn write( -+ &mut self, -+ id: usize, -+ buf: &[u8], -+ _offset: u64, -+ _fcntl_flags: u32, -+ _ctx: &CallerCtx, -+ ) -> syscall::Result { -+ let request = Self::deserialize_request(buf)?; -+ -+ match request { -+ I2cControlRequest::RegisterAdapter { mut info } => { -+ let adapter_id = self.next_id; -+ self.next_id = self.next_id.checked_add(1).ok_or(SysError::new(EINVAL))?; -+ info.id = adapter_id; -+ -+ self.adapters.insert( -+ adapter_id, -+ AdapterEntry { -+ info: info.clone(), -+ provider_handle: id, -+ }, -+ ); -+ -+ let handle = self.handles.get_mut(id)?; -+ *handle = Handle::Provider { -+ adapter_id, -+ pending: Self::serialize_response(&I2cControlResponse::AdapterRegistered { -+ id: adapter_id, -+ })?, -+ }; -+ -+ log::info!( -+ "RB_I2CD_ADAPTER_REGISTERED id={} name={} max_transaction_size={} supports_10bit_addr={}", -+ info.id, -+ info.name, -+ info.max_transaction_size, -+ info.supports_10bit_addr, -+ ); -+ Ok(buf.len()) -+ } -+ I2cControlRequest::ListAdapters => { -+ let adapters = self.adapter_list(); -+ let handle = self.handles.get_mut(id)?; -+ Self::queue_adapter_list(handle, adapters)?; -+ Ok(buf.len()) -+ } -+ I2cControlRequest::OpenAdapter { id: adapter_id } => { -+ if !self.adapters.contains_key(&adapter_id) { -+ return Err(SysError::new(ENOENT)); -+ } -+ -+ let handle = self.handles.get_mut(id)?; -+ match handle { -+ Handle::Adapters { .. } | Handle::AdapterDetail { .. } => { -+ Self::set_pending_response(handle, I2cControlResponse::AdapterOpened)?; -+ Ok(buf.len()) -+ } -+ _ => Err(SysError::new(EINVAL)), -+ } -+ } -+ I2cControlRequest::Transfer { -+ adapter_id, -+ request, -+ } => { -+ let entry = self.adapters.get(&adapter_id).ok_or(SysError::new(ENOENT))?; -+ log::debug!( -+ "i2cd: transfer requested for adapter {} via provider fd {}", -+ adapter_id, -+ entry.provider_handle, -+ ); -+ -+ let adapter_info = entry.info.clone(); -+ let handle = self.handles.get_mut(id)?; -+ match handle { -+ Handle::Transfer { .. } => { -+ Self::queue_transfer_stub(handle, &adapter_info, &request)?; -+ Ok(buf.len()) -+ } -+ _ => Err(SysError::new(EINVAL)), -+ } -+ } -+ } -+ } -+ -+ fn on_close(&mut self, id: usize) { -+ let Some(handle) = self.handles.remove(id) else { -+ return; -+ }; -+ if let Handle::Provider { adapter_id, .. } = handle { -+ self.adapters.remove(&adapter_id); -+ } -+ } -+} -+ -+fn run_daemon(daemon: daemon::SchemeDaemon) -> Result<()> { -+ let socket = Socket::create().context("failed to create i2c scheme socket")?; -+ let mut scheme = I2cDaemon::new(); -+ let handler = Blocking::new(&socket, 16); -+ -+ daemon -+ .ready_sync_scheme(&socket, &mut scheme) -+ .context("failed to publish i2c scheme root")?; -+ -+ log::info!("RB_I2CD_SCHEMA"); -+ -+ libredox::call::setrens(0, 0).context("failed to enter null namespace")?; -+ -+ handler -+ .process_requests_blocking(scheme) -+ .context("failed to process i2cd requests")?; -+} -+ -+fn daemon_runner(daemon: daemon::SchemeDaemon) -> ! { -+ if let Err(err) = run_daemon(daemon) { -+ log::error!("i2cd: {err:#}"); -+ process::exit(1); -+ } -+ -+ process::exit(0); -+} -+ -+fn main() { -+ common::setup_logging( -+ "bus", -+ "i2c", -+ "i2cd", -+ common::output_level(), -+ common::file_level(), -+ ); -+ -+ daemon::SchemeDaemon::new(daemon_runner); -+} -diff --git a/drivers/i2c/intel-lpss-i2cd/Cargo.toml b/drivers/i2c/intel-lpss-i2cd/Cargo.toml -new file mode 100644 -index 00000000..0e74cf94 ---- /dev/null -+++ b/drivers/i2c/intel-lpss-i2cd/Cargo.toml -@@ -0,0 +1,21 @@ -+[package] -+name = "intel-lpss-i2cd" -+description = "Intel LPSS ACPI I2C controller driver" -+version = "0.1.0" -+edition = "2021" -+ -+[dependencies] -+anyhow.workspace = true -+log.workspace = true -+redox_syscall = { workspace = true, features = ["std"] } -+libredox.workspace = true -+serde.workspace = true -+ron.workspace = true -+ -+acpi-resource = { path = "../../acpi-resource" } -+common = { path = "../../common" } -+daemon = { path = "../../../daemon" } -+i2c-interface = { path = "../i2c-interface" } -+ -+[lints] -+workspace = true -diff --git a/drivers/i2c/intel-lpss-i2cd/src/main.rs b/drivers/i2c/intel-lpss-i2cd/src/main.rs -new file mode 100644 -index 00000000..ca3ead43 ---- /dev/null -+++ b/drivers/i2c/intel-lpss-i2cd/src/main.rs -@@ -0,0 +1,361 @@ -+use std::collections::BTreeMap; -+use std::fs::{self, File, OpenOptions}; -+use std::io::{Read, Write}; -+use std::path::Path; -+use std::process; -+ -+use acpi_resource::{ -+ AddressResourceType, ExtendedIrqDescriptor, FixedMemory32Descriptor, I2cSerialBusDescriptor, -+ IrqDescriptor, Memory32RangeDescriptor, ResourceDescriptor, -+}; -+use anyhow::{Context, Result}; -+use common::{MemoryType, PhysBorrowed, Prot}; -+use i2c_interface::{I2cAdapterInfo, I2cControlRequest, I2cControlResponse}; -+use serde::Deserialize; -+ -+const SUPPORTED_IDS: &[&str] = &["INT33C2", "INT33C3", "INT3432", "INT3433", "INTC10EF"]; -+ -+const DW_IC_CON: usize = 0x00; -+const DW_IC_TAR: usize = 0x04; -+const DW_IC_SS_SCL_HCNT: usize = 0x14; -+const DW_IC_SS_SCL_LCNT: usize = 0x18; -+const DW_IC_DATA_CMD: usize = 0x10; -+const DW_IC_INTR_MASK: usize = 0x30; -+const DW_IC_CLR_INTR: usize = 0x40; -+const DW_IC_ENABLE: usize = 0x6C; -+const DW_IC_STATUS: usize = 0x70; -+const DW_MMIO_WINDOW: usize = DW_IC_STATUS + core::mem::size_of::(); -+ -+#[derive(Debug, Deserialize)] -+struct AmlSymbol { -+ name: String, -+ value: AmlValue, -+} -+ -+#[derive(Debug, Deserialize)] -+enum AmlValue { -+ Integer(u64), -+ String(String), -+} -+ -+#[derive(Clone, Debug)] -+struct ControllerResources { -+ mmio_base: usize, -+ mmio_len: usize, -+ irq: Option, -+ serial_bus: Option, -+} -+ -+#[derive(Debug)] -+struct ControllerDescriptor { -+ device: String, -+ hid: String, -+ resources: ControllerResources, -+} -+ -+struct RegisteredController { -+ _mmio: Option, -+ _registration: File, -+} -+ -+fn main() { -+ common::setup_logging( -+ "bus", -+ "i2c", -+ "intel-lpss-i2cd", -+ common::output_level(), -+ common::file_level(), -+ ); -+ -+ daemon::Daemon::new(daemon_runner); -+} -+ -+fn daemon_runner(daemon: daemon::Daemon) -> ! { -+ if let Err(err) = daemon_main(daemon) { -+ log::error!("intel-lpss-i2cd: {err:#}"); -+ process::exit(1); -+ } -+ -+ process::exit(0); -+} -+ -+fn daemon_main(daemon: daemon::Daemon) -> Result<()> { -+ common::init(); -+ -+ let controllers = discover_controllers(SUPPORTED_IDS) -+ .context("failed to discover Intel LPSS ACPI I2C controllers")?; -+ if controllers.is_empty() { -+ log::info!("intel-lpss-i2cd: no supported ACPI controllers found"); -+ } -+ -+ let mut registered = Vec::new(); -+ for controller in controllers { -+ match register_controller("intel-lpss", controller) { -+ Ok(controller) => registered.push(controller), -+ Err(err) => log::warn!("intel-lpss-i2cd: controller registration skipped: {err:#}"), -+ } -+ } -+ -+ daemon.ready(); -+ libredox::call::setrens(0, 0).context("failed to enter null namespace")?; -+ -+ log::info!("intel-lpss-i2cd: registered {} controller(s)", registered.len()); -+ -+ loop { -+ std::thread::park(); -+ } -+} -+ -+fn discover_controllers(supported_ids: &[&str]) -> Result> { -+ let mut matched = BTreeMap::new(); -+ -+ 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!("intel-lpss-i2cd: ACPI symbols are not ready yet"); -+ return Ok(Vec::new()); -+ } -+ Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"), -+ }; -+ -+ for entry in entries { -+ let entry = entry.context("failed to read ACPI symbol directory entry")?; -+ let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else { -+ continue; -+ }; -+ if !file_name.ends_with("_HID") && !file_name.ends_with("_CID") { -+ continue; -+ } -+ -+ let Some(id) = read_symbol_id(&entry.path())? else { -+ continue; -+ }; -+ if !supported_ids.iter().any(|candidate| *candidate == id) { -+ continue; -+ } -+ -+ let device = file_name -+ .strip_suffix("_HID") -+ .or_else(|| file_name.strip_suffix("_CID")) -+ .map(str::to_owned); -+ if let Some(device) = device { -+ matched.entry(device).or_insert(id); -+ } -+ } -+ -+ let mut controllers = Vec::new(); -+ for (device, hid) in matched { -+ let resources = read_controller_resources(&device) -+ .with_context(|| format!("failed to read resources for {device}"))?; -+ controllers.push(ControllerDescriptor { -+ device, -+ hid, -+ resources, -+ }); -+ } -+ -+ Ok(controllers) -+} -+ -+fn read_symbol_id(path: &Path) -> Result> { -+ let contents = fs::read_to_string(path) -+ .with_context(|| format!("failed to read ACPI symbol {}", path.display()))?; -+ let symbol = match ron::from_str::(&contents) { -+ Ok(symbol) => symbol, -+ Err(err) => { -+ log::debug!( -+ "intel-lpss-i2cd: skipping {} because the symbol payload was not a scalar ID: {err}", -+ path.display(), -+ ); -+ return Ok(None); -+ } -+ }; -+ -+ let id = match symbol.value { -+ AmlValue::Integer(integer) => eisa_id_from_integer(integer), -+ AmlValue::String(string) => string, -+ }; -+ -+ log::debug!("intel-lpss-i2cd: {} -> {id}", symbol.name); -+ Ok(Some(id)) -+} -+ -+fn read_controller_resources(device: &str) -> Result { -+ let contents = fs::read_to_string(format!("/scheme/acpi/resources/{device}")) -+ .with_context(|| format!("failed to read /scheme/acpi/resources/{device}"))?; -+ let resources = ron::from_str::>(&contents) -+ .with_context(|| format!("failed to decode RON resources for {device}"))?; -+ -+ let mut mmio = None; -+ let mut irq = None; -+ let mut serial_bus = None; -+ -+ for resource in &resources { -+ match resource { -+ ResourceDescriptor::FixedMemory32(FixedMemory32Descriptor { -+ address, -+ address_length, -+ .. -+ }) if mmio.is_none() => { -+ mmio = Some((*address as usize, (*address_length as usize).max(DW_MMIO_WINDOW))); -+ } -+ ResourceDescriptor::Memory32Range(Memory32RangeDescriptor { -+ minimum, -+ maximum, -+ address_length, -+ .. -+ }) if mmio.is_none() && maximum >= minimum => { -+ let span = maximum.saturating_sub(*minimum).saturating_add(1) as usize; -+ mmio = Some(( -+ *minimum as usize, -+ span.max((*address_length as usize).max(DW_MMIO_WINDOW)), -+ )); -+ } -+ ResourceDescriptor::Address32(descriptor) -+ if mmio.is_none() -+ && matches!(descriptor.resource_type, AddressResourceType::MemoryRange) => -+ { -+ mmio = Some(( -+ descriptor.minimum as usize, -+ (descriptor.address_length as usize).max(DW_MMIO_WINDOW), -+ )); -+ } -+ ResourceDescriptor::Address64(descriptor) -+ if mmio.is_none() -+ && matches!(descriptor.resource_type, AddressResourceType::MemoryRange) => -+ { -+ let base = usize::try_from(descriptor.minimum) -+ .context("64-bit MMIO base does not fit in usize")?; -+ let len = usize::try_from(descriptor.address_length) -+ .context("64-bit MMIO length does not fit in usize")?; -+ mmio = Some((base, len.max(DW_MMIO_WINDOW))); -+ } -+ ResourceDescriptor::Irq(IrqDescriptor { interrupts, .. }) if irq.is_none() => { -+ irq = interrupts.first().copied().map(u32::from); -+ } -+ ResourceDescriptor::ExtendedIrq(ExtendedIrqDescriptor { interrupts, .. }) -+ if irq.is_none() => -+ { -+ irq = interrupts.first().copied(); -+ } -+ ResourceDescriptor::I2cSerialBus(descriptor) if serial_bus.is_none() => { -+ serial_bus = Some(descriptor.clone()); -+ } -+ _ => {} -+ } -+ } -+ -+ let (mmio_base, mmio_len) = mmio.context("no MMIO resource was found")?; -+ Ok(ControllerResources { -+ mmio_base, -+ mmio_len, -+ irq, -+ serial_bus, -+ }) -+} -+ -+fn register_controller(prefix: &str, controller: ControllerDescriptor) -> Result { -+ let ControllerDescriptor { -+ device, -+ hid, -+ resources, -+ } = controller; -+ -+ let mmio = match PhysBorrowed::map( -+ resources.mmio_base, -+ resources.mmio_len, -+ Prot::RW, -+ MemoryType::Uncacheable, -+ ) { -+ Ok(mapping) => Some(mapping), -+ Err(err) => { -+ log::warn!( -+ "intel-lpss-i2cd: failed to map MMIO for {device} ({:#x}, len {:#x}): {err}", -+ resources.mmio_base, -+ resources.mmio_len, -+ ); -+ None -+ } -+ }; -+ -+ log::info!( -+ "intel-lpss-i2cd: discovered {device} hid={hid} mmio={:#x}+{:#x} irq={:?}", -+ resources.mmio_base, -+ resources.mmio_len, -+ resources.irq, -+ ); -+ log::debug!( -+ "intel-lpss-i2cd: DesignWare regs con={DW_IC_CON:#x} tar={DW_IC_TAR:#x} data_cmd={DW_IC_DATA_CMD:#x} intr_mask={DW_IC_INTR_MASK:#x} clr_intr={DW_IC_CLR_INTR:#x} enable={DW_IC_ENABLE:#x} ss_hcnt={DW_IC_SS_SCL_HCNT:#x} ss_lcnt={DW_IC_SS_SCL_LCNT:#x}", -+ ); -+ -+ let info = I2cAdapterInfo { -+ id: 0, -+ name: format!("{prefix}:{device}"), -+ max_transaction_size: 0, -+ supports_10bit_addr: resources -+ .serial_bus -+ .as_ref() -+ .map(|bus| bus.access_mode_10bit) -+ .unwrap_or(false), -+ }; -+ let mut registration = register_adapter(&info) -+ .with_context(|| format!("failed to register {device} with i2cd"))?; -+ let response = read_registration_response(&mut registration) -+ .with_context(|| format!("failed to read i2cd registration response for {device}"))?; -+ -+ match response { -+ I2cControlResponse::AdapterRegistered { id } => { -+ log::info!("intel-lpss-i2cd: adapter {device} registered with i2cd as {id}"); -+ } -+ other => { -+ anyhow::bail!("unexpected i2cd registration response for {device}: {other:?}"); -+ } -+ } -+ -+ Ok(RegisteredController { -+ _mmio: mmio, -+ _registration: registration, -+ }) -+} -+ -+fn register_adapter(info: &I2cAdapterInfo) -> Result { -+ let mut file = OpenOptions::new() -+ .read(true) -+ .write(true) -+ .open("/scheme/i2c/register") -+ .context("failed to open /scheme/i2c/register")?; -+ let payload = ron::ser::to_string(&I2cControlRequest::RegisterAdapter { info: info.clone() }) -+ .context("failed to encode I2C adapter registration")?; -+ file.write_all(payload.as_bytes()) -+ .context("failed to send I2C adapter registration")?; -+ Ok(file) -+} -+ -+fn read_registration_response(file: &mut File) -> Result { -+ let mut buffer = vec![0_u8; 4096]; -+ let count = file -+ .read(&mut buffer) -+ .context("failed to read I2C registration response")?; -+ buffer.truncate(count); -+ let text = std::str::from_utf8(&buffer).context("I2C registration response was not UTF-8")?; -+ ron::from_str(text).context("failed to decode I2C registration response") -+} -+ -+fn eisa_id_from_integer(integer: u64) -> String { -+ let vendor = integer & 0xFFFF; -+ let device = (integer >> 16) & 0xFFFF; -+ let vendor_rev = ((vendor & 0xFF) << 8) | (vendor >> 8); -+ let vendor_1 = (((vendor_rev >> 10) & 0x1F) as u8 + 64) as char; -+ let vendor_2 = (((vendor_rev >> 5) & 0x1F) as u8 + 64) as char; -+ let vendor_3 = (((vendor_rev >> 0) & 0x1F) as u8 + 64) as char; -+ let device_1 = (device >> 4) & 0xF; -+ let device_2 = (device >> 0) & 0xF; -+ let device_3 = (device >> 12) & 0xF; -+ let device_4 = (device >> 8) & 0xF; -+ -+ format!( -+ "{vendor_1}{vendor_2}{vendor_3}{device_1:01X}{device_2:01X}{device_3:01X}{device_4:01X}" -+ ) -+} -diff --git a/drivers/input/i2c-hidd/Cargo.toml b/drivers/input/i2c-hidd/Cargo.toml -new file mode 100644 -index 00000000..db7b3f03 ---- /dev/null -+++ b/drivers/input/i2c-hidd/Cargo.toml -@@ -0,0 +1,26 @@ -+[package] -+name = "i2c-hidd" -+description = "I2C HID client daemon" -+version = "0.1.0" -+edition = "2021" -+ -+[dependencies] -+anyhow.workspace = true -+log.workspace = true -+orbclient.workspace = true -+redox_syscall = { workspace = true, features = ["std"] } -+libredox.workspace = true -+redox-scheme.workspace = true -+ron.workspace = true -+serde.workspace = true -+ -+acpi-resource = { path = "../../acpi-resource" } -+amlserde = { path = "../../amlserde" } -+common = { path = "../../common" } -+daemon = { path = "../../../daemon" } -+i2c-interface = { path = "../../i2c/i2c-interface" } -+inputd = { path = "../../inputd" } -+scheme-utils = { path = "../../../scheme-utils" } -+ -+[lints] -+workspace = true -diff --git a/drivers/input/i2c-hidd/src/acpi.rs b/drivers/input/i2c-hidd/src/acpi.rs -new file mode 100644 -index 00000000..1132154c ---- /dev/null -+++ b/drivers/input/i2c-hidd/src/acpi.rs -@@ -0,0 +1,307 @@ -+use acpi_resource::{GpioDescriptor, ResourceDescriptor}; -+use amlserde::{AmlSerde, AmlSerdeValue}; -+use anyhow::{anyhow, bail, Context, Result}; -+use libredox::flag::{O_CLOEXEC, O_RDWR}; -+use std::collections::BTreeSet; -+use std::fs::{self, OpenOptions}; -+use std::io::{ErrorKind, Read}; -+ -+use crate::quirks::ProbeFailureQuirk; -+ -+const I2C_HID_DSM_GUID: [u8; 16] = [ -+ 0xF7, 0xF6, 0xDF, 0x3C, 0x67, 0x42, 0x55, 0x45, 0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x41, 0x76, -+]; -+ -+#[derive(Clone, Debug)] -+pub struct I2cBinding { -+ pub adapter: String, -+ pub address: u16, -+} -+ -+#[derive(Clone, Debug)] -+pub struct AcpiDeviceResources { -+ pub i2c: I2cBinding, -+ pub irq: Option, -+ pub gpio_int: Vec, -+ pub gpio_io: Vec, -+} -+ -+pub fn scan_acpi_i2c_hid_devices() -> Result> { -+ let entries = match fs::read_dir("/scheme/acpi/symbols") { -+ Ok(entries) => entries, -+ Err(err) if err.kind() == ErrorKind::WouldBlock || err.raw_os_error() == Some(11) => { -+ return Ok(Vec::new()); -+ } -+ Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"), -+ }; -+ -+ let mut devices = BTreeSet::new(); -+ for entry in entries { -+ let entry = entry.context("failed to enumerate ACPI symbol entry")?; -+ let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else { -+ continue; -+ }; -+ if !file_name.ends_with("._HID") && !file_name.ends_with("._CID") { -+ continue; -+ } -+ -+ let symbol = read_aml_symbol(&file_name) -+ .with_context(|| format!("failed to read ACPI symbol {file_name}"))?; -+ let Some(id) = decode_hardware_id(&symbol.value) else { -+ continue; -+ }; -+ -+ if matches!(id.as_str(), "PNP0C50" | "ACPI0C50") { -+ let device = symbol -+ .name -+ .strip_suffix("._HID") -+ .or_else(|| symbol.name.strip_suffix("._CID")) -+ .unwrap_or(&symbol.name) -+ .trim_start_matches('\\') -+ .trim_matches('/') -+ .replace('/', "."); -+ if !device.is_empty() { -+ devices.insert(device); -+ } -+ } -+ } -+ -+ Ok(devices.into_iter().collect()) -+} -+ -+pub fn read_decoded_resources(path: &str) -> Result { -+ let resource_path = format!("/scheme/acpi/resources/{}", normalize_device_path(path)); -+ let serialized = fs::read_to_string(&resource_path) -+ .with_context(|| format!("failed to read {resource_path}"))?; -+ let descriptors: Vec = ron::from_str(&serialized) -+ .with_context(|| format!("invalid ACPI resources in {resource_path}"))?; -+ -+ let mut i2c = None; -+ let mut irq = None; -+ let mut gpio_int = Vec::new(); -+ let mut gpio_io = Vec::new(); -+ -+ for descriptor in descriptors { -+ match descriptor { -+ ResourceDescriptor::I2cSerialBus(bus) => { -+ if i2c.is_none() { -+ let adapter = bus -+ .resource_source -+ .as_ref() -+ .map(|source| source.source.clone()) -+ .filter(|source| !source.is_empty()) -+ .unwrap_or_else(|| "ACPI-I2C".to_string()); -+ i2c = Some(I2cBinding { -+ adapter, -+ address: bus.slave_address, -+ }); -+ } -+ } -+ ResourceDescriptor::Irq(descriptor) => { -+ if irq.is_none() { -+ irq = descriptor.interrupts.first().copied().map(u32::from); -+ } -+ } -+ ResourceDescriptor::ExtendedIrq(descriptor) => { -+ if irq.is_none() { -+ irq = descriptor.interrupts.first().copied(); -+ } -+ } -+ ResourceDescriptor::GpioInt(descriptor) => gpio_int.push(descriptor), -+ ResourceDescriptor::GpioIo(descriptor) => gpio_io.push(descriptor), -+ _ => {} -+ } -+ } -+ -+ let mut resources = AcpiDeviceResources { -+ i2c: i2c.ok_or_else(|| anyhow!("no I2cSerialBus resource in _CRS"))?, -+ irq, -+ gpio_int, -+ gpio_io, -+ }; -+ -+ if let Some(override_address) = companion_icrs_override(path)? { -+ log::info!( -+ "{}: applying THC companion ICRS override {:04x} -> {:04x}", -+ path, -+ resources.i2c.address, -+ override_address -+ ); -+ resources.i2c.address = override_address; -+ } -+ -+ Ok(resources) -+} -+ -+pub fn prepare_acpi_device(path: &str) -> Result<()> { -+ let sta = evaluate_integer_method(path, "_STA").ok(); -+ if let Some(sta) = sta { -+ if sta & 0x01 == 0 { -+ bail!("ACPI device is not present according to _STA={sta:#x}"); -+ } -+ } -+ -+ let _ = evaluate_method(path, "_PS0", &[]); -+ let _ = evaluate_method(path, "_INI", &[]); -+ Ok(()) -+} -+ -+pub fn recover_acpi_device( -+ path: &str, -+ resources: &AcpiDeviceResources, -+ quirk: Option<&ProbeFailureQuirk>, -+) -> Result<()> { -+ let _ = evaluate_method(path, "_PS3", &[]); -+ -+ if let Some(quirk) = quirk { -+ if !resources.gpio_io.is_empty() { -+ log::warn!( -+ "{}: applying GPIO probe-failure recovery quirk {} vendor={:?} product={:?} board={:?} across {} GPIO IO resources", -+ path, -+ quirk.name, -+ quirk.system_vendor, -+ quirk.product_name, -+ quirk.board_name, -+ resources.gpio_io.len() -+ ); -+ } else { -+ log::warn!( -+ "{}: quirk {} vendor={:?} product={:?} board={:?} matched but no GPIO IO resource was exposed", -+ path, -+ quirk.name -+ , -+ quirk.system_vendor, -+ quirk.product_name, -+ quirk.board_name -+ ); -+ } -+ } -+ -+ let _ = evaluate_method(path, "_PS0", &[]); -+ let _ = evaluate_method(path, "_INI", &[]); -+ Ok(()) -+} -+ -+pub fn hid_descriptor_address(path: &str) -> Result { -+ let args = [ -+ AmlSerdeValue::Buffer(I2C_HID_DSM_GUID.to_vec()), -+ AmlSerdeValue::Integer(1), -+ AmlSerdeValue::Integer(1), -+ AmlSerdeValue::Package { -+ contents: Vec::new(), -+ }, -+ ]; -+ -+ match evaluate_method(path, "_DSM", &args) { -+ Ok(AmlSerdeValue::Integer(value)) => { -+ return u16::try_from(value).context("_DSM descriptor address out of range") -+ } -+ Ok(other) => log::warn!( -+ "{}._DSM returned unexpected value {:?}; retrying fallback index", -+ path, -+ other -+ ), -+ Err(err) => log::warn!( -+ "{}._DSM function 1 failed: {err}; retrying function 0", -+ path -+ ), -+ } -+ -+ let fallback = [ -+ AmlSerdeValue::Buffer(I2C_HID_DSM_GUID.to_vec()), -+ AmlSerdeValue::Integer(1), -+ AmlSerdeValue::Integer(0), -+ AmlSerdeValue::Package { -+ contents: Vec::new(), -+ }, -+ ]; -+ -+ match evaluate_method(path, "_DSM", &fallback)? { -+ AmlSerdeValue::Integer(value) => { -+ u16::try_from(value).context("fallback _DSM descriptor address out of range") -+ } -+ other => bail!("fallback _DSM returned unexpected value {other:?}"), -+ } -+} -+ -+fn companion_icrs_override(path: &str) -> Result> { -+ let value = match evaluate_integer_method(path, "ICRS") { -+ Ok(value) => value, -+ Err(_) => return Ok(None), -+ }; -+ Ok(Some( -+ u16::try_from(value).context("ICRS override out of range")?, -+ )) -+} -+ -+pub fn evaluate_integer_method(path: &str, method: &str) -> Result { -+ match evaluate_method(path, method, &[])? { -+ AmlSerdeValue::Integer(value) => Ok(value), -+ other => bail!( -+ "{}.{} returned non-integer AML value {other:?}", -+ path, -+ method -+ ), -+ } -+} -+ -+pub fn evaluate_method(path: &str, method: &str, args: &[AmlSerdeValue]) -> Result { -+ let symbol_name = format!("{}.{}", normalize_device_path(path), method); -+ let symbol_path = format!("/scheme/acpi/symbols/{symbol_name}"); -+ let fd = libredox::Fd::open(&symbol_path, O_RDWR | O_CLOEXEC, 0) -+ .with_context(|| format!("failed to open {symbol_path} for ACPI evaluation"))?; -+ -+ let serialized = ron::to_string(args) -+ .with_context(|| format!("failed to serialize ACPI arguments for {symbol_name}"))?; -+ let mut payload = serialized.into_bytes(); -+ payload.resize(payload.len() + 4096, 0); -+ -+ let used = libredox::call::call_ro(fd.raw(), &mut payload, syscall::CallFlags::empty(), &[]) -+ .with_context(|| format!("ACPI evaluation failed for {symbol_name}"))?; -+ let response = std::str::from_utf8(&payload[..used]) -+ .with_context(|| format!("invalid UTF-8 ACPI response for {symbol_name}"))?; -+ ron::from_str(response) -+ .with_context(|| format!("failed to decode ACPI response for {symbol_name}")) -+} -+ -+fn read_aml_symbol(file_name: &str) -> Result { -+ let path = format!("/scheme/acpi/symbols/{file_name}"); -+ let mut file = OpenOptions::new() -+ .read(true) -+ .open(&path) -+ .with_context(|| format!("failed to open {path}"))?; -+ let mut ron_text = String::new(); -+ file.read_to_string(&mut ron_text) -+ .with_context(|| format!("failed to read {path}"))?; -+ ron::from_str(&ron_text).with_context(|| format!("failed to decode {path}")) -+} -+ -+fn decode_hardware_id(value: &AmlSerdeValue) -> Option { -+ match value { -+ AmlSerdeValue::String(value) => Some(value.clone()), -+ AmlSerdeValue::Integer(integer) => { -+ let vendor = integer & 0xFFFF; -+ let device = (integer >> 16) & 0xFFFF; -+ let vendor_rev = ((vendor & 0xFF) << 8) | (vendor >> 8); -+ let vendor_1 = (((vendor_rev >> 10) & 0x1f) as u8 + 64) as char; -+ let vendor_2 = (((vendor_rev >> 5) & 0x1f) as u8 + 64) as char; -+ let vendor_3 = (((vendor_rev >> 0) & 0x1f) as u8 + 64) as char; -+ let device_1 = (device >> 4) & 0xF; -+ let device_2 = (device >> 0) & 0xF; -+ let device_3 = (device >> 12) & 0xF; -+ let device_4 = (device >> 8) & 0xF; -+ -+ Some(format!( -+ "{}{}{}{:01X}{:01X}{:01X}{:01X}", -+ vendor_1, vendor_2, vendor_3, device_1, device_2, device_3, device_4 -+ )) -+ } -+ _ => None, -+ } -+} -+ -+pub fn normalize_device_path(path: &str) -> String { -+ path.trim_start_matches('\\') -+ .trim_matches('/') -+ .replace('/', ".") -+} -diff --git a/drivers/input/i2c-hidd/src/hid.rs b/drivers/input/i2c-hidd/src/hid.rs -new file mode 100644 -index 00000000..dd18e36c ---- /dev/null -+++ b/drivers/input/i2c-hidd/src/hid.rs -@@ -0,0 +1,195 @@ -+use std::fs::OpenOptions; -+use std::io::{Read, Write}; -+ -+use anyhow::{bail, Context, Result}; -+use i2c_interface::{I2cTransferRequest, I2cTransferResponse, I2cTransferSegment}; -+use serde::{Deserialize, Serialize}; -+ -+use crate::acpi::I2cBinding; -+ -+#[derive(Clone, Debug, Serialize, Deserialize)] -+pub struct HidDescriptor { -+ pub hid_desc_length: u16, -+ pub bcd_version: u16, -+ pub report_desc_length: u16, -+ pub report_desc_register: u16, -+ pub input_register: u16, -+ pub max_input_length: u16, -+ pub output_register: u16, -+ pub max_output_length: u16, -+ pub command_register: u16, -+ pub data_register: u16, -+} -+ -+#[derive(Clone, Debug, Default)] -+pub struct ReportDescriptorSummary { -+ pub has_keyboard_page: bool, -+ pub has_pointer_page: bool, -+ pub report_ids: bool, -+} -+ -+#[derive(Clone, Debug)] -+pub struct I2cAdapterClient { -+ binding: I2cBinding, -+} -+ -+impl I2cAdapterClient { -+ pub fn new(binding: I2cBinding) -> Self { -+ Self { binding } -+ } -+ pub fn transfer(&self, segments: Vec) -> Result { -+ let request = I2cTransferRequest { -+ adapter: self.binding.adapter.clone(), -+ segments, -+ stop: true, -+ }; -+ -+ let serialized = ron::to_string(&request).context("failed to serialize I2C request")?; -+ let mut handle = OpenOptions::new() -+ .read(true) -+ .write(true) -+ .open("/scheme/i2c/transfer") -+ .context("failed to open /scheme/i2c/transfer")?; -+ handle -+ .write_all(serialized.as_bytes()) -+ .context("failed to write I2C transfer request")?; -+ -+ let mut response = String::new(); -+ handle -+ .read_to_string(&mut response) -+ .context("failed to read I2C transfer response")?; -+ -+ let transfer: I2cTransferResponse = -+ ron::from_str(&response).context("failed to decode I2C transfer response")?; -+ if !transfer.ok { -+ bail!( -+ "I2C transfer failed: {}", -+ transfer -+ .error -+ .unwrap_or_else(|| "unspecified transfer error".to_string()) -+ ); -+ } -+ Ok(transfer) -+ } -+ -+ pub fn write_read(&self, address: u16, write_data: &[u8], read_len: usize) -> Result> { -+ let response = self.transfer(vec![ -+ I2cTransferSegment::write(address, write_data.to_vec()), -+ I2cTransferSegment::read(address, read_len), -+ ])?; -+ -+ response -+ .read_data -+ .last() -+ .cloned() -+ .ok_or_else(|| anyhow::anyhow!("I2C transfer returned no readable segment payload")) -+ } -+} -+ -+pub fn fetch_hid_descriptor( -+ adapter: &I2cAdapterClient, -+ address: u16, -+ hid_desc_addr: u16, -+) -> Result { -+ let prefix = adapter -+ .write_read(address, &hid_desc_addr.to_le_bytes(), 2) -+ .context("failed to read HID descriptor length prefix")?; -+ if prefix.len() < 2 { -+ bail!("short HID descriptor prefix: {} bytes", prefix.len()); -+ } -+ -+ let hid_desc_length = u16::from_le_bytes([prefix[0], prefix[1]]); -+ if hid_desc_length < 18 { -+ bail!("invalid HID descriptor length {hid_desc_length}"); -+ } -+ -+ let raw = adapter -+ .write_read( -+ address, -+ &hid_desc_addr.to_le_bytes(), -+ usize::from(hid_desc_length), -+ ) -+ .context("failed to read full HID descriptor")?; -+ parse_hid_descriptor(&raw) -+} -+ -+pub fn fetch_report_descriptor( -+ adapter: &I2cAdapterClient, -+ address: u16, -+ desc: &HidDescriptor, -+) -> Result> { -+ adapter -+ .write_read( -+ address, -+ &desc.report_desc_register.to_le_bytes(), -+ usize::from(desc.report_desc_length), -+ ) -+ .context("failed to read HID report descriptor") -+} -+ -+pub fn stream_input_reports( -+ adapter: &I2cAdapterClient, -+ address: u16, -+ desc: &HidDescriptor, -+ report_desc: &[u8], -+ sink: &mut crate::input::InputForwarder, -+) -> Result<()> { -+ let summary = summarize_report_descriptor(report_desc); -+ let input_len = usize::from(desc.max_input_length.max(4)); -+ -+ loop { -+ let report = adapter -+ .write_read(address, &desc.input_register.to_le_bytes(), input_len) -+ .context("failed to fetch I2C HID input report")?; -+ sink.forward_report(&summary, &report)?; -+ } -+} -+ -+fn parse_hid_descriptor(bytes: &[u8]) -> Result { -+ if bytes.len() < 18 { -+ bail!("short HID descriptor: {} bytes", bytes.len()); -+ } -+ -+ Ok(HidDescriptor { -+ hid_desc_length: le16(bytes, 0)?, -+ bcd_version: le16(bytes, 2)?, -+ report_desc_length: le16(bytes, 4)?, -+ report_desc_register: le16(bytes, 6)?, -+ input_register: le16(bytes, 8)?, -+ max_input_length: le16(bytes, 10)?, -+ output_register: le16(bytes, 12)?, -+ max_output_length: le16(bytes, 14)?, -+ command_register: le16(bytes, 16)?, -+ data_register: if bytes.len() >= 20 { -+ le16(bytes, 18)? -+ } else { -+ 0 -+ }, -+ }) -+} -+ -+fn summarize_report_descriptor(report_desc: &[u8]) -> ReportDescriptorSummary { -+ let mut summary = ReportDescriptorSummary::default(); -+ -+ for window in report_desc.windows(2) { -+ match window { -+ [0x05, 0x01] => summary.has_pointer_page = true, -+ [0x05, 0x07] => summary.has_keyboard_page = true, -+ [0x85, _] => summary.report_ids = true, -+ _ => {} -+ } -+ } -+ -+ if !summary.has_keyboard_page && !summary.has_pointer_page { -+ summary.has_pointer_page = true; -+ } -+ -+ summary -+} -+ -+fn le16(bytes: &[u8], offset: usize) -> Result { -+ let slice = bytes -+ .get(offset..offset + 2) -+ .ok_or_else(|| anyhow::anyhow!("short LE16 field at offset {offset}"))?; -+ Ok(u16::from_le_bytes([slice[0], slice[1]])) -+} -diff --git a/drivers/input/i2c-hidd/src/input.rs b/drivers/input/i2c-hidd/src/input.rs -new file mode 100644 -index 00000000..432a0782 ---- /dev/null -+++ b/drivers/input/i2c-hidd/src/input.rs -@@ -0,0 +1,175 @@ -+use std::collections::BTreeSet; -+ -+use anyhow::Result; -+use inputd::ProducerHandle; -+use orbclient::{ -+ ButtonEvent, KeyEvent, MouseRelativeEvent, ScrollEvent, K_ALT, K_ALT_GR, K_BKSP, K_BRACE_CLOSE, -+ K_BRACE_OPEN, K_CAPS, K_COMMA, K_ENTER, K_EQUALS, K_ESC, K_LEFT_CTRL, K_LEFT_SHIFT, -+ K_LEFT_SUPER, K_MINUS, K_PERIOD, K_QUOTE, K_RIGHT_CTRL, K_RIGHT_SHIFT, K_RIGHT_SUPER, -+ K_SEMICOLON, K_SLASH, K_SPACE, K_TAB, K_TICK, -+}; -+ -+use crate::hid::ReportDescriptorSummary; -+ -+pub struct InputForwarder { -+ producer: ProducerHandle, -+ keyboard_state: BTreeSet, -+ last_buttons: u8, -+} -+ -+impl InputForwarder { -+ pub fn new() -> Result { -+ Ok(Self { -+ producer: ProducerHandle::new()?, -+ keyboard_state: BTreeSet::new(), -+ last_buttons: 0, -+ }) -+ } -+ -+ pub fn forward_report( -+ &mut self, -+ summary: &ReportDescriptorSummary, -+ report: &[u8], -+ ) -> Result<()> { -+ if report.is_empty() { -+ return Ok(()); -+ } -+ -+ if summary.has_keyboard_page && report.len() >= 8 { -+ self.forward_boot_keyboard(report)?; -+ return Ok(()); -+ } -+ -+ if summary.has_pointer_page && report.len() >= 3 { -+ self.forward_boot_pointer(report)?; -+ return Ok(()); -+ } -+ -+ Ok(()) -+ } -+ -+ fn forward_boot_keyboard(&mut self, report: &[u8]) -> Result<()> { -+ let modifiers = report[0]; -+ for (bit, scancode) in [ -+ (0_u8, K_LEFT_CTRL), -+ (1, K_LEFT_SHIFT), -+ (2, K_ALT), -+ (3, K_LEFT_SUPER), -+ (4, K_RIGHT_CTRL), -+ (5, K_RIGHT_SHIFT), -+ (6, K_ALT_GR), -+ (7, K_RIGHT_SUPER), -+ ] { -+ self.producer.write_event( -+ KeyEvent { -+ character: '\0', -+ scancode, -+ pressed: modifiers & (1 << bit) != 0, -+ } -+ .to_event(), -+ )?; -+ } -+ -+ let current = report[2..8] -+ .iter() -+ .copied() -+ .filter(|code| *code != 0) -+ .collect::>(); -+ -+ for code in current.difference(&self.keyboard_state) { -+ if let Some(scancode) = map_boot_keyboard_usage(*code) { -+ self.producer.write_event( -+ KeyEvent { -+ character: '\0', -+ scancode, -+ pressed: true, -+ } -+ .to_event(), -+ )?; -+ } -+ } -+ for code in self.keyboard_state.difference(¤t) { -+ if let Some(scancode) = map_boot_keyboard_usage(*code) { -+ self.producer.write_event( -+ KeyEvent { -+ character: '\0', -+ scancode, -+ pressed: false, -+ } -+ .to_event(), -+ )?; -+ } -+ } -+ -+ self.keyboard_state = current; -+ Ok(()) -+ } -+ -+ fn forward_boot_pointer(&mut self, report: &[u8]) -> Result<()> { -+ let dx = i8::from_ne_bytes([report[1]]) as i32; -+ let dy = i8::from_ne_bytes([report[2]]) as i32; -+ if dx != 0 || dy != 0 { -+ self.producer -+ .write_event(MouseRelativeEvent { dx, dy }.to_event())?; -+ } -+ -+ if let Some(scroll) = report.get(3).copied() { -+ let scroll = i8::from_ne_bytes([scroll]) as i32; -+ if scroll != 0 { -+ self.producer -+ .write_event(ScrollEvent { x: 0, y: scroll }.to_event())?; -+ } -+ } -+ -+ let buttons = report[0] & 0x07; -+ for index in 0..3 { -+ let mask = 1 << index; -+ if (buttons & mask) != (self.last_buttons & mask) { -+ self.producer.write_event( -+ ButtonEvent { -+ left: buttons & 0x01 != 0, -+ middle: buttons & 0x04 != 0, -+ right: buttons & 0x02 != 0, -+ } -+ .to_event(), -+ )?; -+ break; -+ } -+ } -+ self.last_buttons = buttons; -+ Ok(()) -+ } -+} -+ -+fn map_boot_keyboard_usage(usage: u8) -> Option { -+ Some(match usage { -+ 0x04..=0x1D => b'a' + (usage - 0x04), -+ 0x1E => b'1', -+ 0x1F => b'2', -+ 0x20 => b'3', -+ 0x21 => b'4', -+ 0x22 => b'5', -+ 0x23 => b'6', -+ 0x24 => b'7', -+ 0x25 => b'8', -+ 0x26 => b'9', -+ 0x27 => b'0', -+ 0x28 => K_ENTER, -+ 0x29 => K_ESC, -+ 0x2A => K_BKSP, -+ 0x2B => K_TAB, -+ 0x2C => K_SPACE, -+ 0x2D => K_MINUS, -+ 0x2E => K_EQUALS, -+ 0x2F => K_BRACE_OPEN, -+ 0x30 => K_BRACE_CLOSE, -+ 0x33 => K_SEMICOLON, -+ 0x34 => K_QUOTE, -+ 0x35 => K_TICK, -+ 0x36 => K_COMMA, -+ 0x37 => K_PERIOD, -+ 0x38 => K_SLASH, -+ 0x39 => K_CAPS, -+ _ => return None, -+ }) -+} -diff --git a/drivers/input/i2c-hidd/src/main.rs b/drivers/input/i2c-hidd/src/main.rs -new file mode 100644 -index 00000000..88270e37 ---- /dev/null -+++ b/drivers/input/i2c-hidd/src/main.rs -@@ -0,0 +1,114 @@ -+use std::process; -+use std::thread; -+use std::time::Duration; -+ -+use anyhow::{Context, Result}; -+ -+mod acpi; -+mod hid; -+mod input; -+mod quirks; -+ -+use acpi::{ -+ hid_descriptor_address, prepare_acpi_device, read_decoded_resources, recover_acpi_device, -+ scan_acpi_i2c_hid_devices, -+}; -+use hid::{fetch_hid_descriptor, fetch_report_descriptor, stream_input_reports, I2cAdapterClient}; -+use input::InputForwarder; -+use quirks::match_probe_failure_quirk; -+ -+fn main() { -+ daemon::Daemon::new(daemon); -+} -+ -+fn daemon(daemon: daemon::Daemon) -> ! { -+ common::setup_logging( -+ "input", -+ "i2c-hid", -+ "i2c-hidd", -+ common::output_level(), -+ common::file_level(), -+ ); -+ -+ if let Err(err) = run(daemon) { -+ log::error!("RB_I2C_HIDD_BLOCKER stage=startup error={err:#}"); -+ process::exit(1); -+ } -+ -+ process::exit(0); -+} -+ -+fn run(daemon: daemon::Daemon) -> Result<()> { -+ log::info!("RB_I2C_HIDD_SCHEMA version=1"); -+ -+ let devices = scan_acpi_i2c_hid_devices().context("failed to scan ACPI I2C HID devices")?; -+ if devices.is_empty() { -+ log::warn!("RB_I2C_HIDD_BLOCKER stage=scan error=no PNP0C50/ACPI0C50 devices found"); -+ } -+ -+ let mut workers = Vec::new(); -+ for device in devices { -+ log::info!("RB_I2C_HIDD_SNAPSHOT device={device}"); -+ workers.push(thread::spawn(move || { -+ if let Err(err) = bind_device(&device) { -+ log::error!("RB_I2C_HIDD_BLOCKER device={} error={:#}", device, err); -+ } -+ })); -+ } -+ -+ daemon.ready(); -+ -+ if workers.is_empty() { -+ loop { -+ thread::sleep(Duration::from_secs(5)); -+ } -+ } -+ -+ for worker in workers { -+ let _ = worker.join(); -+ } -+ Ok(()) -+} -+ -+pub fn bind_device(device_path: &str) -> Result<()> { -+ prepare_acpi_device(device_path) -+ .with_context(|| format!("failed to prepare ACPI device {device_path}"))?; -+ -+ let resources = read_decoded_resources(device_path) -+ .with_context(|| format!("failed to decode _CRS for {device_path}"))?; -+ log::info!( -+ "RB_I2C_HIDD_SNAPSHOT device={} adapter={} addr={:04x} irq={:?} gpio_int={} gpio_io={}", -+ device_path, -+ resources.i2c.adapter, -+ resources.i2c.address, -+ resources.irq, -+ resources.gpio_int.len(), -+ resources.gpio_io.len() -+ ); -+ -+ let hid_desc_addr = hid_descriptor_address(device_path) -+ .with_context(|| format!("failed to evaluate _DSM for {device_path}"))?; -+ let adapter = I2cAdapterClient::new(resources.i2c.clone()); -+ let hid_desc = fetch_hid_descriptor(&adapter, resources.i2c.address, hid_desc_addr) -+ .with_context(|| format!("failed to fetch HID descriptor for {device_path}"))?; -+ let report_desc = fetch_report_descriptor(&adapter, resources.i2c.address, &hid_desc) -+ .with_context(|| format!("failed to fetch report descriptor for {device_path}"))?; -+ let mut forwarder = InputForwarder::new().context("failed to connect to inputd producer")?; -+ -+ match stream_input_reports( -+ &adapter, -+ resources.i2c.address, -+ &hid_desc, -+ &report_desc, -+ &mut forwarder, -+ ) { -+ Ok(()) => Ok(()), -+ Err(err) => { -+ let quirk = -+ match_probe_failure_quirk().context("failed to evaluate DMI recovery quirks")?; -+ recover_acpi_device(device_path, &resources, quirk.as_ref()) -+ .with_context(|| format!("failed ACPI recovery for {device_path}"))?; -+ Err(err).with_context(|| format!("streaming input reports failed for {device_path}")) -+ } -+ } -+} -diff --git a/drivers/input/i2c-hidd/src/quirks.rs b/drivers/input/i2c-hidd/src/quirks.rs -new file mode 100644 -index 00000000..450cb19b ---- /dev/null -+++ b/drivers/input/i2c-hidd/src/quirks.rs -@@ -0,0 +1,88 @@ -+use std::fs; -+ -+use anyhow::{Context, Result}; -+use serde::Deserialize; -+ -+#[derive(Clone, Debug)] -+pub struct ProbeFailureQuirk { -+ pub name: String, -+ pub system_vendor: Option, -+ pub product_name: Option, -+ pub board_name: Option, -+} -+ -+#[derive(Clone, Debug, Default, Deserialize)] -+struct ProbeFailureQuirkFile { -+ quirks: Vec, -+} -+ -+#[derive(Clone, Debug, Deserialize)] -+struct ProbeFailureQuirkEntry { -+ name: String, -+ system_vendor: Option, -+ product_name: Option, -+ board_name: Option, -+} -+ -+#[derive(Default)] -+struct DmiSnapshot { -+ system_vendor: String, -+ product_name: String, -+ board_name: String, -+} -+ -+pub fn match_probe_failure_quirk() -> Result> { -+ let snapshot = read_dmi_snapshot()?; -+ for entry in load_quirks()? { -+ if field_matches(&entry.system_vendor, &snapshot.system_vendor) -+ && field_matches(&entry.product_name, &snapshot.product_name) -+ && field_matches(&entry.board_name, &snapshot.board_name) -+ { -+ return Ok(Some(ProbeFailureQuirk { -+ name: entry.name, -+ system_vendor: entry.system_vendor, -+ product_name: entry.product_name, -+ board_name: entry.board_name, -+ })); -+ } -+ } -+ -+ Ok(None) -+} -+ -+fn load_quirks() -> Result> { -+ let path = "/etc/i2c-hidd-quirks.ron"; -+ let text = match fs::read_to_string(path) { -+ Ok(text) => text, -+ Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(Vec::new()), -+ Err(err) => return Err(err).with_context(|| format!("failed to read {path}")), -+ }; -+ -+ let file: ProbeFailureQuirkFile = -+ ron::from_str(&text).with_context(|| format!("failed to decode {path}"))?; -+ Ok(file.quirks) -+} -+ -+fn read_dmi_snapshot() -> Result { -+ Ok(DmiSnapshot { -+ system_vendor: read_dmi_field("system_vendor")?, -+ product_name: read_dmi_field("product_name")?, -+ board_name: read_dmi_field("board_name")?, -+ }) -+} -+ -+fn read_dmi_field(field: &str) -> Result { -+ let path = format!("/scheme/acpi/dmi/{field}"); -+ match fs::read_to_string(&path) { -+ Ok(value) => Ok(value.trim().to_string()), -+ Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(String::new()), -+ Err(err) => Err(err).with_context(|| format!("failed to read {path}")), -+ } -+} -+ -+fn field_matches(expected: &Option, actual: &str) -> bool { -+ expected -+ .as_deref() -+ .map(|expected| actual.eq_ignore_ascii_case(expected)) -+ .unwrap_or(true) -+} -diff --git a/drivers/input/intel-thc-hidd/Cargo.toml b/drivers/input/intel-thc-hidd/Cargo.toml -new file mode 100644 -index 00000000..f6aa2248 ---- /dev/null -+++ b/drivers/input/intel-thc-hidd/Cargo.toml -@@ -0,0 +1,26 @@ -+[package] -+name = "intel-thc-hidd" -+description = "Intel THC QuickI2C HID transport daemon" -+version = "0.1.0" -+edition = "2021" -+ -+[dependencies] -+anyhow.workspace = true -+log.workspace = true -+pci_types = "0.10.1" -+redox_syscall = { workspace = true, features = ["std"] } -+libredox.workspace = true -+redox-scheme.workspace = true -+ron.workspace = true -+serde.workspace = true -+ -+acpi-resource = { path = "../../acpi-resource" } -+amlserde = { path = "../../amlserde" } -+common = { path = "../../common" } -+daemon = { path = "../../../daemon" } -+i2c-interface = { path = "../../i2c/i2c-interface" } -+pcid = { path = "../../pcid" } -+scheme-utils = { path = "../../../scheme-utils" } -+ -+[lints] -+workspace = true -diff --git a/drivers/input/intel-thc-hidd/src/main.rs b/drivers/input/intel-thc-hidd/src/main.rs -new file mode 100644 -index 00000000..55581462 ---- /dev/null -+++ b/drivers/input/intel-thc-hidd/src/main.rs -@@ -0,0 +1,260 @@ -+use std::collections::BTreeSet; -+use std::fs::{self, OpenOptions}; -+use std::io::Read; -+use std::process; -+use std::thread; -+use std::time::Duration; -+ -+use acpi_resource::ResourceDescriptor; -+use amlserde::{AmlSerde, AmlSerdeValue}; -+use anyhow::{bail, Context, Result}; -+use libredox::flag::{O_CLOEXEC, O_RDWR}; -+use pcid_interface::PciFunctionHandle; -+ -+mod quicki2c; -+mod thc; -+ -+use quicki2c::QuickI2cTransport; -+use thc::{ThcController, SUPPORTED_PCI_IDS}; -+ -+fn main() { -+ pcid_interface::pci_daemon(daemon); -+} -+ -+fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { -+ common::setup_logging( -+ "input", -+ "intel-thc", -+ "intel-thc-hidd", -+ common::output_level(), -+ common::file_level(), -+ ); -+ -+ if let Err(err) = run(daemon, &mut pcid_handle) { -+ log::error!("RB_THC_HIDD_FATAL error={err:#}"); -+ process::exit(1); -+ } -+ -+ process::exit(0); -+} -+ -+fn run(daemon: daemon::Daemon, pcid_handle: &mut PciFunctionHandle) -> Result<()> { -+ log::info!("RB_THC_HIDD_SCHEMA version=1"); -+ -+ let pci_config = pcid_handle.config(); -+ let id = ( -+ pci_config.func.full_device_id.vendor_id, -+ pci_config.func.full_device_id.device_id, -+ ); -+ if !SUPPORTED_PCI_IDS.contains(&id) { -+ bail!("unsupported Intel THC PCI device {:04x}:{:04x}", id.0, id.1); -+ } -+ -+ pcid_handle.enable_device(); -+ let bar = unsafe { pcid_handle.try_map_bar(0) }.context("failed to map THC BAR0")?; -+ let controller = ThcController::new(bar.ptr.as_ptr(), bar.bar_size) -+ .context("failed to create THC controller")?; -+ -+ let companion = resolve_acpi_companion(&pci_config.func.addr) -+ .context("failed to resolve ACPI companion for THC device")?; -+ let override_address = companion -+ .as_deref() -+ .map(companion_slave_address_override) -+ .transpose() -+ .context("failed to evaluate THC slave-address override")? -+ .flatten(); -+ let hid_devices = scan_bound_i2c_hid_devices(companion.as_deref()) -+ .context("failed to scan PNP0C50 devices for THC controller")?; -+ -+ let effective_address = override_address.unwrap_or(0x0015); -+ let transport = QuickI2cTransport::new(controller, effective_address); -+ transport.prime_controller(); -+ transport.emulate_transfer(&[]); -+ log::debug!("RB_THC_HIDD status={:#x}", transport.status()); -+ -+ match transport.register_with_i2cd(companion.as_deref(), override_address) { -+ Ok(()) => {} -+ Err(err) => { -+ log::warn!("RB_THC_HIDD registration error={err:#}"); -+ } -+ } -+ -+ log::info!( -+ "RB_THC_HIDD pci={} companion={:?} override={:?} hid_devices={}", -+ pci_config.func.name(), -+ companion, -+ override_address, -+ hid_devices.len() -+ ); -+ -+ daemon.ready(); -+ -+ loop { -+ thread::sleep(Duration::from_secs(5)); -+ } -+} -+ -+fn resolve_acpi_companion(addr: &pci_types::PciAddress) -> Result> { -+ let entries = -+ fs::read_dir("/scheme/acpi/symbols").context("failed to read /scheme/acpi/symbols")?; -+ let expected_adr = (u64::from(addr.device()) << 16) | u64::from(addr.function()); -+ -+ for entry in entries { -+ let entry = entry.context("failed to enumerate ACPI symbol entry")?; -+ let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else { -+ continue; -+ }; -+ if !file_name.ends_with("._ADR") { -+ continue; -+ } -+ -+ let symbol = read_aml_symbol(&file_name)?; -+ if !matches!(symbol.value, AmlSerdeValue::Integer(value) if value == expected_adr) { -+ continue; -+ } -+ -+ let device = symbol -+ .name -+ .strip_suffix("._ADR") -+ .unwrap_or(&symbol.name) -+ .trim_start_matches('\\') -+ .replace('/', "."); -+ return Ok(Some(device)); -+ } -+ -+ Ok(None) -+} -+ -+fn companion_slave_address_override(path: &str) -> Result> { -+ let icrs = evaluate_integer_method(path, "ICRS").ok(); -+ let isub = evaluate_integer_method(path, "ISUB").ok(); -+ Ok(icrs -+ .or(isub) -+ .map(|value| u16::try_from(value)) -+ .transpose() -+ .context("THC ACPI override out of range")?) -+} -+ -+fn scan_bound_i2c_hid_devices(companion: Option<&str>) -> Result> { -+ let entries = -+ fs::read_dir("/scheme/acpi/symbols").context("failed to read /scheme/acpi/symbols")?; -+ let mut devices = BTreeSet::new(); -+ -+ for entry in entries { -+ let entry = entry.context("failed to enumerate ACPI HID entry")?; -+ let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else { -+ continue; -+ }; -+ if !file_name.ends_with("._HID") && !file_name.ends_with("._CID") { -+ continue; -+ } -+ -+ let symbol = read_aml_symbol(&file_name)?; -+ let is_hid = matches!( -+ decode_hardware_id(&symbol.value).as_deref(), -+ Some("PNP0C50" | "ACPI0C50") -+ ); -+ if !is_hid { -+ continue; -+ } -+ -+ let device = symbol -+ .name -+ .strip_suffix("._HID") -+ .or_else(|| symbol.name.strip_suffix("._CID")) -+ .unwrap_or(&symbol.name) -+ .trim_start_matches('\\') -+ .replace('/', "."); -+ if let Some(companion) = companion { -+ if !is_bound_to_companion(&device, companion)? { -+ continue; -+ } -+ } -+ devices.insert(device); -+ } -+ -+ Ok(devices.into_iter().collect()) -+} -+ -+fn is_bound_to_companion(device: &str, companion: &str) -> Result { -+ let resource_path = format!("/scheme/acpi/resources/{device}"); -+ let serialized = match fs::read_to_string(&resource_path) { -+ Ok(serialized) => serialized, -+ Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(false), -+ Err(err) => return Err(err).with_context(|| format!("failed to read {resource_path}")), -+ }; -+ -+ let resources: Vec = -+ ron::from_str(&serialized).with_context(|| format!("failed to decode {resource_path}"))?; -+ Ok(resources.into_iter().any(|resource| match resource { -+ ResourceDescriptor::I2cSerialBus(bus) => bus -+ .resource_source -+ .as_ref() -+ .map(|source| source.source == companion) -+ .unwrap_or(false), -+ _ => false, -+ })) -+} -+ -+fn evaluate_integer_method(path: &str, method: &str) -> Result { -+ let symbol_name = format!("{}.{}", normalize_device_path(path), method); -+ let symbol_path = format!("/scheme/acpi/symbols/{symbol_name}"); -+ let fd = libredox::Fd::open(&symbol_path, O_RDWR | O_CLOEXEC, 0) -+ .with_context(|| format!("failed to open {symbol_path}"))?; -+ -+ let mut payload = ron::to_string(&Vec::::new()) -+ .context("failed to serialize ACPI call arguments")? -+ .into_bytes(); -+ payload.resize(payload.len() + 2048, 0); -+ let used = libredox::call::call_ro(fd.raw(), &mut payload, syscall::CallFlags::empty(), &[]) -+ .with_context(|| format!("ACPI evaluation failed for {symbol_name}"))?; -+ let response = std::str::from_utf8(&payload[..used]) -+ .with_context(|| format!("invalid UTF-8 ACPI response for {symbol_name}"))?; -+ match ron::from_str::(response) -+ .with_context(|| format!("failed to decode ACPI response for {symbol_name}"))? -+ { -+ AmlSerdeValue::Integer(value) => Ok(value), -+ other => bail!("{}.{} returned non-integer value {other:?}", path, method), -+ } -+} -+ -+fn read_aml_symbol(file_name: &str) -> Result { -+ let path = format!("/scheme/acpi/symbols/{file_name}"); -+ let mut file = OpenOptions::new() -+ .read(true) -+ .open(&path) -+ .with_context(|| format!("failed to open {path}"))?; -+ let mut ron_text = String::new(); -+ file.read_to_string(&mut ron_text) -+ .with_context(|| format!("failed to read {path}"))?; -+ ron::from_str(&ron_text).with_context(|| format!("failed to decode {path}")) -+} -+ -+fn decode_hardware_id(value: &AmlSerdeValue) -> Option { -+ match value { -+ AmlSerdeValue::String(value) => Some(value.clone()), -+ AmlSerdeValue::Integer(integer) => { -+ let vendor = integer & 0xFFFF; -+ let device = (integer >> 16) & 0xFFFF; -+ let vendor_rev = ((vendor & 0xFF) << 8) | (vendor >> 8); -+ let vendor_1 = (((vendor_rev >> 10) & 0x1f) as u8 + 64) as char; -+ let vendor_2 = (((vendor_rev >> 5) & 0x1f) as u8 + 64) as char; -+ let vendor_3 = (((vendor_rev >> 0) & 0x1f) as u8 + 64) as char; -+ let device_1 = (device >> 4) & 0xF; -+ let device_2 = (device >> 0) & 0xF; -+ let device_3 = (device >> 12) & 0xF; -+ let device_4 = (device >> 8) & 0xF; -+ Some(format!( -+ "{}{}{}{:01X}{:01X}{:01X}{:01X}", -+ vendor_1, vendor_2, vendor_3, device_1, device_2, device_3, device_4 -+ )) -+ } -+ _ => None, -+ } -+} -+ -+fn normalize_device_path(path: &str) -> String { -+ path.trim_start_matches('\\') -+ .trim_matches('/') -+ .replace('/', ".") -+} -diff --git a/drivers/input/intel-thc-hidd/src/quicki2c.rs b/drivers/input/intel-thc-hidd/src/quicki2c.rs -new file mode 100644 -index 00000000..721f0be3 ---- /dev/null -+++ b/drivers/input/intel-thc-hidd/src/quicki2c.rs -@@ -0,0 +1,86 @@ -+use std::fs::OpenOptions; -+use std::io::Write; -+ -+use anyhow::{Context, Result}; -+use i2c_interface::{I2cAdapterRegistration, I2cTransferSegment}; -+ -+use crate::thc::ThcController; -+ -+const QUICKI2C_OPCODE_WRITE: u32 = 0x1; -+const QUICKI2C_OPCODE_READ: u32 = 0x2; -+ -+pub struct QuickI2cTransport { -+ controller: ThcController, -+ slave_address: u16, -+} -+ -+impl QuickI2cTransport { -+ pub fn new(controller: ThcController, slave_address: u16) -> Self { -+ Self { -+ controller, -+ slave_address, -+ } -+ } -+ -+ pub fn prime_controller(&self) { -+ self.controller.initialize_quicki2c_mode(); -+ } -+ -+ pub fn emulate_transfer(&self, segments: &[I2cTransferSegment]) { -+ for segment in segments { -+ match &segment.op { -+ i2c_interface::I2cTransferOp::Write(data) => { -+ self.controller.program_subip_transaction( -+ QUICKI2C_OPCODE_WRITE, -+ segment.address, -+ data.len(), -+ ); -+ for (index, chunk) in data.chunks(4).enumerate() { -+ let mut word = [0_u8; 4]; -+ word[..chunk.len()].copy_from_slice(chunk); -+ self.controller -+ .write_subip_data(index * 4, u32::from_le_bytes(word)); -+ } -+ } -+ i2c_interface::I2cTransferOp::Read(len) => { -+ self.controller.program_subip_transaction( -+ QUICKI2C_OPCODE_READ, -+ segment.address, -+ *len, -+ ); -+ let _ = self.controller.read_subip_data(0); -+ } -+ } -+ } -+ } -+ -+ pub fn status(&self) -> u32 { -+ self.controller.status() -+ } -+ -+ pub fn register_with_i2cd( -+ &self, -+ acpi_companion: Option<&str>, -+ override_address: Option, -+ ) -> Result<()> { -+ let registration = I2cAdapterRegistration { -+ name: "intel-thc-quicki2c".to_string(), -+ description: format!( -+ "Intel THC QuickI2C adapter for slave {:04x}", -+ self.slave_address -+ ), -+ acpi_companion: acpi_companion.map(str::to_owned), -+ slave_address_override: override_address, -+ }; -+ let payload = -+ ron::to_string(®istration).context("failed to serialize i2cd registration")?; -+ -+ let mut file = OpenOptions::new() -+ .write(true) -+ .open("/scheme/i2c/register") -+ .context("failed to open /scheme/i2c/register")?; -+ file.write_all(payload.as_bytes()) -+ .context("failed to write i2cd adapter registration")?; -+ Ok(()) -+ } -+} -diff --git a/drivers/input/intel-thc-hidd/src/thc.rs b/drivers/input/intel-thc-hidd/src/thc.rs -new file mode 100644 -index 00000000..e06a6f8a ---- /dev/null -+++ b/drivers/input/intel-thc-hidd/src/thc.rs -@@ -0,0 +1,78 @@ -+use std::ptr::NonNull; -+ -+use anyhow::{bail, Result}; -+ -+pub const SUPPORTED_PCI_IDS: &[(u16, u16)] = &[ -+ (0x8086, 0x7eb8), -+ (0x8086, 0x7eb9), -+ (0x8086, 0x7ebd), -+ (0x8086, 0x7ebe), -+ (0x8086, 0xa8b8), -+ (0x8086, 0xa8b9), -+]; -+ -+pub const REG_CONTROL: usize = 0x0000; -+pub const REG_STATUS: usize = 0x0004; -+pub const REG_MODE: usize = 0x0010; -+pub const REG_SUBIP_OPCODE: usize = 0x0800; -+pub const REG_SUBIP_ADDRESS: usize = 0x0804; -+pub const REG_SUBIP_LENGTH: usize = 0x0808; -+pub const REG_SUBIP_DOORBELL: usize = 0x080C; -+pub const REG_SUBIP_DATA: usize = 0x0810; -+ -+#[derive(Clone, Copy)] -+pub struct ThcController { -+ base: NonNull, -+ len: usize, -+} -+ -+impl ThcController { -+ pub fn new(base: *mut u8, len: usize) -> Result { -+ let Some(base) = NonNull::new(base) else { -+ bail!("THC BAR mapping returned null base pointer"); -+ }; -+ Ok(Self { base, len }) -+ } -+ -+ pub fn initialize_quicki2c_mode(&self) { -+ self.write32(REG_MODE, 0x1); -+ self.write32(REG_CONTROL, 0x1); -+ } -+ -+ pub fn status(&self) -> u32 { -+ self.read32(REG_STATUS) -+ } -+ -+ pub fn program_subip_transaction(&self, opcode: u32, address: u16, len: usize) { -+ self.write32(REG_SUBIP_OPCODE, opcode); -+ self.write32(REG_SUBIP_ADDRESS, u32::from(address)); -+ self.write32(REG_SUBIP_LENGTH, len as u32); -+ self.write32(REG_SUBIP_DOORBELL, 1); -+ } -+ -+ pub fn write_subip_data(&self, offset: usize, value: u32) { -+ self.write32(REG_SUBIP_DATA + offset, value); -+ } -+ -+ pub fn read_subip_data(&self, offset: usize) -> u32 { -+ self.read32(REG_SUBIP_DATA + offset) -+ } -+ -+ fn read32(&self, offset: usize) -> u32 { -+ if offset + 4 > self.len { -+ return 0; -+ } -+ -+ let ptr = unsafe { self.base.as_ptr().add(offset).cast::() }; -+ unsafe { ptr.read_volatile() } -+ } -+ -+ fn write32(&self, offset: usize, value: u32) { -+ if offset + 4 > self.len { -+ return; -+ } -+ -+ let ptr = unsafe { self.base.as_ptr().add(offset).cast::() }; -+ unsafe { ptr.write_volatile(value) }; -+ } -+} -diff --git a/drivers/usb/ucsid/Cargo.toml b/drivers/usb/ucsid/Cargo.toml -new file mode 100644 -index 00000000..1a6833e5 ---- /dev/null -+++ b/drivers/usb/ucsid/Cargo.toml -@@ -0,0 +1,23 @@ -+[package] -+name = "ucsid" -+description = "USB-C UCSI topology daemon" -+version = "0.1.0" -+edition = "2021" -+ -+[dependencies] -+anyhow.workspace = true -+log.workspace = true -+redox_syscall = { workspace = true, features = ["std"] } -+libredox.workspace = true -+redox-scheme.workspace = true -+serde.workspace = true -+ron.workspace = true -+ -+acpi-resource = { path = "../../acpi-resource" } -+common = { path = "../../common" } -+daemon = { path = "../../../daemon" } -+i2c-interface = { path = "../../i2c/i2c-interface" } -+scheme-utils = { path = "../../../scheme-utils" } -+ -+[lints] -+workspace = true -diff --git a/drivers/usb/ucsid/src/main.rs b/drivers/usb/ucsid/src/main.rs -new file mode 100644 -index 00000000..612f5ce2 ---- /dev/null -+++ b/drivers/usb/ucsid/src/main.rs -@@ -0,0 +1,835 @@ -+use std::collections::BTreeMap; -+use std::fs::{self, File, OpenOptions}; -+use std::io::{Read, Write}; -+use std::path::Path; -+use std::process; -+ -+use acpi_resource::{ -+ AddressResourceType, FixedMemory32Descriptor, I2cSerialBusDescriptor, Memory32RangeDescriptor, -+ ResourceDescriptor, -+}; -+use anyhow::{bail, Context, Result}; -+use i2c_interface::{ -+ I2cAdapterInfo, I2cControlRequest, I2cControlResponse, I2cTransferRequest, -+ I2cTransferSegment, -+}; -+use libredox::flag::{O_CLOEXEC, O_RDWR}; -+use redox_scheme::scheme::SchemeSync; -+use redox_scheme::{CallerCtx, OpenResult, Socket}; -+use scheme_utils::{Blocking, HandleMap}; -+use serde::{Deserialize, Serialize}; -+use syscall::schemev2::NewFdFlags; -+use syscall::{Error as SysError, EACCES, EBADF, EINVAL, ENOENT}; -+ -+const SUPPORTED_IDS: &[&str] = &["PNP0CA0", "AMDI0042"]; -+const GET_CAPABILITY: u8 = 0x01; -+const GET_CONNECTOR_STATUS: u8 = 0x10; -+const UCSI_RESPONSE_HEADER_LEN: usize = 4; -+const UCSI_CAPABILITY_READ_LEN: usize = 20; -+const UCSI_CONNECTOR_STATUS_READ_LEN: usize = 20; -+const MAX_CONNECTOR_PROBE: u8 = 8; -+ -+#[derive(Debug, Deserialize)] -+struct AmlSymbol { -+ name: String, -+ value: AmlValue, -+} -+ -+#[derive(Debug, Deserialize)] -+enum AmlValue { -+ Integer(u64), -+ String(String), -+} -+ -+#[derive(Clone, Copy, Debug)] -+struct UcsiCommand { -+ command: u8, -+ data_length: u8, -+ specific_data: [u8; 6], -+} -+ -+impl UcsiCommand { -+ fn new(command: u8, data_length: u8, specific_data: [u8; 6]) -> Self { -+ Self { -+ command, -+ data_length, -+ specific_data, -+ } -+ } -+ -+ fn as_bytes(self) -> [u8; 8] { -+ let mut bytes = [0_u8; 8]; -+ bytes[0] = self.command; -+ bytes[1] = self.data_length; -+ bytes[2..].copy_from_slice(&self.specific_data); -+ bytes -+ } -+} -+ -+#[derive(Clone, Copy, Debug)] -+struct UcsiResponseHeader { -+ _status: u16, -+ data_length: u16, -+} -+ -+impl UcsiResponseHeader { -+ fn parse(bytes: &[u8]) -> Option { -+ let header = bytes.get(..UCSI_RESPONSE_HEADER_LEN)?; -+ Some(Self { -+ _status: u16::from_le_bytes([header[0], header[1]]), -+ data_length: u16::from_le_bytes([header[2], header[3]]), -+ }) -+ } -+} -+ -+#[derive(Clone, Debug)] -+struct DiscoveredUcsiDevice { -+ name: String, -+ hid: String, -+ transport: UcsiTransport, -+ dsm_probe: bool, -+} -+ -+#[derive(Clone, Debug, Serialize, Deserialize)] -+enum UcsiTransport { -+ I2c { -+ adapter: String, -+ address: u16, -+ ten_bit_address: bool, -+ }, -+ Mmio { -+ base: usize, -+ len: usize, -+ }, -+ Unknown, -+} -+ -+#[derive(Clone, Debug, Serialize, Deserialize)] -+struct UcsiCapability { -+ connector_count: u8, -+ supports_usb_pd: bool, -+ supports_alt_modes: bool, -+} -+ -+#[derive(Clone, Debug, Serialize, Deserialize)] -+struct UcsiConnectorSummary { -+ device: String, -+ connector_number: u8, -+ connected: bool, -+ data_role: String, -+ power_direction: String, -+ input_critical: bool, -+} -+ -+#[derive(Clone, Debug, Serialize, Deserialize)] -+struct UcsiDeviceSummary { -+ name: String, -+ hid: String, -+ transport: UcsiTransport, -+ capability: Option, -+ connectors: Vec, -+ dsm_probe: bool, -+ issues: Vec, -+} -+ -+#[derive(Clone, Debug, Serialize, Deserialize)] -+struct UcsiSummary { -+ schema_version: u32, -+ device_count: usize, -+ connector_count: usize, -+ input_critical_ports: usize, -+ devices: Vec, -+} -+ -+#[derive(Clone, Debug, Serialize, Deserialize)] -+struct UcsiHealth { -+ healthy: bool, -+ scanned_devices: usize, -+ responsive_devices: usize, -+ issues: Vec, -+} -+ -+struct UcsiState { -+ summary: UcsiSummary, -+ connectors: Vec, -+ health: UcsiHealth, -+} -+ -+enum Handle { -+ SchemeRoot, -+ Summary { pending: Vec }, -+ Connectors { pending: Vec }, -+ Health { pending: Vec }, -+} -+ -+struct UcsiScheme { -+ handles: HandleMap, -+ state: UcsiState, -+} -+ -+impl UcsiScheme { -+ fn new(state: UcsiState) -> Self { -+ Self { -+ handles: HandleMap::new(), -+ state, -+ } -+ } -+ -+ fn serialize_payload(value: &T) -> syscall::Result> { -+ ron::ser::to_string(value) -+ .map(|text| text.into_bytes()) -+ .map_err(|err| { -+ log::error!("ucsid: failed to serialize scheme payload: {err}"); -+ SysError::new(EINVAL) -+ }) -+ } -+ -+ fn set_pending(handle: &mut Handle, pending: Vec) -> syscall::Result<()> { -+ match handle { -+ Handle::Summary { pending: slot } -+ | Handle::Connectors { pending: slot } -+ | Handle::Health { pending: slot } => { -+ *slot = pending; -+ Ok(()) -+ } -+ Handle::SchemeRoot => Err(SysError::new(EBADF)), -+ } -+ } -+ -+ fn copy_pending(handle: &mut Handle, buf: &mut [u8], offset: u64) -> syscall::Result { -+ let pending = match handle { -+ Handle::Summary { pending } -+ | Handle::Connectors { pending } -+ | Handle::Health { pending } => pending, -+ Handle::SchemeRoot => return Err(SysError::new(EBADF)), -+ }; -+ -+ let offset = usize::try_from(offset).map_err(|_| SysError::new(EINVAL))?; -+ if offset >= pending.len() { -+ return Ok(0); -+ } -+ -+ let copy_len = buf.len().min(pending.len() - offset); -+ buf[..copy_len].copy_from_slice(&pending[offset..offset + copy_len]); -+ Ok(copy_len) -+ } -+} -+ -+impl SchemeSync for UcsiScheme { -+ fn scheme_root(&mut self) -> syscall::Result { -+ Ok(self.handles.insert(Handle::SchemeRoot)) -+ } -+ -+ fn openat( -+ &mut self, -+ dirfd: usize, -+ path: &str, -+ _flags: usize, -+ _fcntl_flags: u32, -+ _ctx: &CallerCtx, -+ ) -> syscall::Result { -+ if !matches!(self.handles.get(dirfd)?, Handle::SchemeRoot) { -+ return Err(SysError::new(EACCES)); -+ } -+ -+ let handle = match path.trim_matches('/') { -+ "summary" => Handle::Summary { -+ pending: Vec::new(), -+ }, -+ "connectors" => Handle::Connectors { -+ pending: Vec::new(), -+ }, -+ "health" => Handle::Health { -+ pending: Vec::new(), -+ }, -+ "" => return Err(SysError::new(EINVAL)), -+ _ => return Err(SysError::new(ENOENT)), -+ }; -+ -+ let fd = self.handles.insert(handle); -+ Ok(OpenResult::ThisScheme { -+ number: fd, -+ flags: NewFdFlags::empty(), -+ }) -+ } -+ -+ fn read( -+ &mut self, -+ id: usize, -+ buf: &mut [u8], -+ offset: u64, -+ _fcntl_flags: u32, -+ _ctx: &CallerCtx, -+ ) -> syscall::Result { -+ let payload = match self.handles.get(id)? { -+ Handle::Summary { pending } if pending.is_empty() => { -+ Some(Self::serialize_payload(&self.state.summary)?) -+ } -+ Handle::Connectors { pending } if pending.is_empty() => { -+ Some(Self::serialize_payload(&self.state.connectors)?) -+ } -+ Handle::Health { pending } if pending.is_empty() => { -+ log::info!( -+ "RB_UCSID_HEALTH healthy={} scanned_devices={} responsive_devices={} issues={}", -+ self.state.health.healthy, -+ self.state.health.scanned_devices, -+ self.state.health.responsive_devices, -+ self.state.health.issues.len(), -+ ); -+ Some(Self::serialize_payload(&self.state.health)?) -+ } -+ _ => None, -+ }; -+ -+ let handle = self.handles.get_mut(id)?; -+ if let Some(payload) = payload { -+ Self::set_pending(handle, payload)?; -+ } -+ Self::copy_pending(handle, buf, offset) -+ } -+} -+ -+fn main() { -+ common::setup_logging( -+ "usb", -+ "ucsi", -+ "ucsid", -+ common::output_level(), -+ common::file_level(), -+ ); -+ -+ daemon::SchemeDaemon::new(daemon_runner); -+} -+ -+fn daemon_runner(daemon: daemon::SchemeDaemon) -> ! { -+ if let Err(err) = run_daemon(daemon) { -+ log::error!("ucsid: {err:#}"); -+ process::exit(1); -+ } -+ -+ process::exit(0); -+} -+ -+fn run_daemon(daemon: daemon::SchemeDaemon) -> Result<()> { -+ log::info!("RB_UCSID_SCHEMA version=1"); -+ -+ let state = build_state().context("failed to build UCSI device snapshot")?; -+ let socket = Socket::create().context("failed to create ucsi scheme socket")?; -+ let mut scheme = UcsiScheme::new(state); -+ let handler = Blocking::new(&socket, 16); -+ -+ daemon -+ .ready_sync_scheme(&socket, &mut scheme) -+ .context("failed to publish ucsi scheme root")?; -+ -+ libredox::call::setrens(0, 0).context("failed to enter null namespace")?; -+ -+ handler -+ .process_requests_blocking(scheme) -+ .context("failed to process ucsid requests")?; -+} -+ -+fn build_state() -> Result { -+ let adapters = list_i2c_adapters().unwrap_or_else(|err| { -+ log::warn!("ucsid: failed to query i2cd adapters: {err:#}"); -+ Vec::new() -+ }); -+ let devices = discover_ucsi_devices().context("failed to discover ACPI UCSI devices")?; -+ -+ let mut summaries = Vec::new(); -+ let mut connectors = Vec::new(); -+ let mut issues = Vec::new(); -+ let mut responsive_devices = 0usize; -+ -+ for device in devices { -+ log::info!( -+ "RB_UCSID_DEVICE name={} hid={} transport={:?} dsm_probe={}", -+ device.name, -+ device.hid, -+ device.transport, -+ device.dsm_probe, -+ ); -+ let summary = summarize_device(device, &adapters) -+ .context("failed to summarize discovered UCSI device")?; -+ if summary.capability.is_some() { -+ responsive_devices += 1; -+ } -+ issues.extend(summary.issues.iter().cloned()); -+ connectors.extend(summary.connectors.iter().cloned()); -+ summaries.push(summary); -+ } -+ -+ let summary = UcsiSummary { -+ schema_version: 1, -+ device_count: summaries.len(), -+ connector_count: connectors.len(), -+ input_critical_ports: connectors.iter().filter(|connector| connector.input_critical).count(), -+ devices: summaries, -+ }; -+ let health = UcsiHealth { -+ healthy: issues.is_empty(), -+ scanned_devices: summary.device_count, -+ responsive_devices, -+ issues, -+ }; -+ -+ log::info!( -+ "RB_UCSID_SUMMARY devices={} connectors={} input_critical_ports={} healthy={}", -+ summary.device_count, -+ summary.connector_count, -+ summary.input_critical_ports, -+ health.healthy, -+ ); -+ -+ Ok(UcsiState { -+ summary, -+ connectors, -+ health, -+ }) -+} -+ -+fn discover_ucsi_devices() -> Result> { -+ let mut matched = BTreeMap::new(); -+ -+ 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!("ucsid: ACPI symbols are not ready yet"); -+ return Ok(Vec::new()); -+ } -+ Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"), -+ }; -+ -+ for entry in entries { -+ let entry = entry.context("failed to read ACPI symbol directory entry")?; -+ let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else { -+ continue; -+ }; -+ if !file_name.ends_with("_HID") && !file_name.ends_with("_CID") { -+ continue; -+ } -+ -+ let Some(id) = read_symbol_id(&entry.path())? else { -+ continue; -+ }; -+ if !SUPPORTED_IDS.iter().any(|candidate| *candidate == id) { -+ continue; -+ } -+ -+ let Some(device) = file_name -+ .strip_suffix("_HID") -+ .or_else(|| file_name.strip_suffix("_CID")) -+ .map(str::to_owned) -+ else { -+ continue; -+ }; -+ matched.entry(device).or_insert(id); -+ } -+ -+ let mut devices = Vec::new(); -+ for (device, hid) in matched { -+ let transport = read_ucsi_transport(&device) -+ .with_context(|| format!("failed to decode transport resources for {device}"))?; -+ let dsm_probe = bounded_dsm_probe(&device).unwrap_or_else(|err| { -+ log::debug!("ucsid: bounded _DSM probe failed for {device}: {err:#}"); -+ false -+ }); -+ devices.push(DiscoveredUcsiDevice { -+ name: device, -+ hid, -+ transport, -+ dsm_probe, -+ }); -+ } -+ -+ Ok(devices) -+} -+ -+fn summarize_device(device: DiscoveredUcsiDevice, adapters: &[I2cAdapterInfo]) -> Result { -+ let mut issues = Vec::new(); -+ let capability = match &device.transport { -+ UcsiTransport::I2c { -+ adapter, -+ address, -+ ten_bit_address, -+ } => match match_i2c_adapter(adapters, adapter) { -+ Some(adapter_info) => match execute_ucsi_i2c_command( -+ adapter_info, -+ adapter, -+ *address, -+ *ten_bit_address, -+ UcsiCommand::new(GET_CAPABILITY, 0, [0; 6]), -+ UCSI_CAPABILITY_READ_LEN, -+ ) { -+ Ok(bytes) => parse_ucsi_payload(&bytes) -+ .and_then(|(_header, payload)| parse_capability(payload)) -+ .or_else(|| { -+ issues.push(format!( -+ "{}: GET_CAPABILITY returned an unexpected payload", -+ device.name -+ )); -+ None -+ }), -+ Err(err) => { -+ issues.push(format!("{}: GET_CAPABILITY failed: {err:#}", device.name)); -+ None -+ } -+ }, -+ None => { -+ issues.push(format!( -+ "{}: no i2cd adapter matched ACPI source {}", -+ device.name, adapter -+ )); -+ None -+ } -+ }, -+ UcsiTransport::Mmio { base, len } => { -+ issues.push(format!( -+ "{}: MMIO UCSI transport discovered at {base:#x}+{len:#x} but command execution is not implemented yet", -+ device.name, -+ )); -+ None -+ } -+ UcsiTransport::Unknown => { -+ issues.push(format!( -+ "{}: no supported UCSI transport was decoded from ACPI resources", -+ device.name, -+ )); -+ None -+ } -+ }; -+ -+ let connector_count = capability -+ .as_ref() -+ .map(|capability| capability.connector_count.min(MAX_CONNECTOR_PROBE)) -+ .unwrap_or(0); -+ let mut connectors = Vec::new(); -+ for connector in 1..=connector_count { -+ match query_connector_status(&device, adapters, connector) { -+ Ok(connector_summary) => connectors.push(connector_summary), -+ Err(err) => issues.push(format!( -+ "{}: GET_CONNECTOR_STATUS({connector}) failed: {err:#}", -+ device.name, -+ )), -+ } -+ } -+ -+ Ok(UcsiDeviceSummary { -+ name: device.name, -+ hid: device.hid, -+ transport: device.transport, -+ capability, -+ connectors, -+ dsm_probe: device.dsm_probe, -+ issues, -+ }) -+} -+ -+fn read_ucsi_transport(device: &str) -> Result { -+ let contents = fs::read_to_string(format!("/scheme/acpi/resources/{device}")) -+ .with_context(|| format!("failed to read /scheme/acpi/resources/{device}"))?; -+ let resources = ron::from_str::>(&contents) -+ .with_context(|| format!("failed to decode RON resources for {device}"))?; -+ -+ let mut i2c = None::; -+ let mut mmio = None::<(usize, usize)>; -+ -+ for resource in resources { -+ match resource { -+ ResourceDescriptor::I2cSerialBus(bus) if i2c.is_none() => i2c = Some(bus), -+ ResourceDescriptor::FixedMemory32(FixedMemory32Descriptor { -+ address, -+ address_length, -+ .. -+ }) if mmio.is_none() => { -+ mmio = Some((address as usize, address_length as usize)); -+ } -+ ResourceDescriptor::Memory32Range(Memory32RangeDescriptor { -+ minimum, -+ maximum, -+ address_length, -+ .. -+ }) if mmio.is_none() && maximum >= minimum => { -+ let span = maximum.saturating_sub(minimum).saturating_add(1) as usize; -+ mmio = Some((minimum as usize, span.max(address_length as usize))); -+ } -+ ResourceDescriptor::Address32(descriptor) -+ if mmio.is_none() -+ && matches!(descriptor.resource_type, AddressResourceType::MemoryRange) => -+ { -+ mmio = Some((descriptor.minimum as usize, descriptor.address_length as usize)); -+ } -+ ResourceDescriptor::Address64(descriptor) -+ if mmio.is_none() -+ && matches!(descriptor.resource_type, AddressResourceType::MemoryRange) => -+ { -+ let base = usize::try_from(descriptor.minimum) -+ .context("64-bit MMIO base does not fit in usize")?; -+ let len = usize::try_from(descriptor.address_length) -+ .context("64-bit MMIO length does not fit in usize")?; -+ mmio = Some((base, len)); -+ } -+ _ => {} -+ } -+ } -+ -+ if let Some(bus) = i2c { -+ let adapter = bus -+ .resource_source -+ .as_ref() -+ .map(|source| source.source.clone()) -+ .filter(|source| !source.is_empty()) -+ .unwrap_or_else(|| String::from("ACPI-I2C")); -+ return Ok(UcsiTransport::I2c { -+ adapter, -+ address: bus.slave_address, -+ ten_bit_address: bus.access_mode_10bit, -+ }); -+ } -+ if let Some((base, len)) = mmio { -+ return Ok(UcsiTransport::Mmio { base, len }); -+ } -+ Ok(UcsiTransport::Unknown) -+} -+ -+fn bounded_dsm_probe(device: &str) -> Result { -+ let symbol_name = format!("{}.{}", normalize_device_path(device), "_DSM"); -+ let symbol_path = format!("/scheme/acpi/symbols/{symbol_name}"); -+ let fd = match libredox::Fd::open(&symbol_path, O_RDWR | O_CLOEXEC, 0) { -+ Ok(fd) => fd, -+ Err(err) => { -+ log::debug!("ucsid: {} has no callable _DSM: {err}", device); -+ return Ok(false); -+ } -+ }; -+ -+ let mut payload = ron::to_string(&Vec::::new()) -+ .context("failed to serialize bounded _DSM probe arguments")? -+ .into_bytes(); -+ payload.resize(payload.len() + 1024, 0); -+ match libredox::call::call_ro(fd.raw(), &mut payload, syscall::CallFlags::empty(), &[]) { -+ Ok(_) => Ok(true), -+ Err(err) => { -+ log::debug!("ucsid: bounded _DSM probe for {} failed: {err}", device); -+ Ok(false) -+ } -+ } -+} -+ -+fn parse_capability(payload: &[u8]) -> Option { -+ let connector_count = *payload.first()?; -+ let flags = payload.get(1).copied().unwrap_or(0); -+ Some(UcsiCapability { -+ connector_count, -+ supports_usb_pd: flags & 0x01 != 0, -+ supports_alt_modes: flags & 0x02 != 0, -+ }) -+} -+ -+fn query_connector_status( -+ device: &DiscoveredUcsiDevice, -+ adapters: &[I2cAdapterInfo], -+ connector: u8, -+) -> Result { -+ match &device.transport { -+ UcsiTransport::I2c { -+ adapter, -+ address, -+ ten_bit_address, -+ } => { -+ let adapter_info = match_i2c_adapter(adapters, adapter).with_context(|| { -+ format!("no i2cd adapter matched ACPI source {} for {}", adapter, device.name) -+ })?; -+ let bytes = execute_ucsi_i2c_command( -+ adapter_info, -+ adapter, -+ *address, -+ *ten_bit_address, -+ UcsiCommand::new(GET_CONNECTOR_STATUS, 1, [connector, 0, 0, 0, 0, 0]), -+ UCSI_CONNECTOR_STATUS_READ_LEN, -+ )?; -+ let (_header, payload) = parse_ucsi_payload(&bytes) -+ .with_context(|| format!("{}: malformed connector-status response", device.name))?; -+ Ok(parse_connector_summary(&device.name, connector, payload)) -+ } -+ UcsiTransport::Mmio { base, len } => bail!( -+ "MMIO connector-status transport is not implemented yet for {:#x}+{:#x}", -+ base, -+ len, -+ ), -+ UcsiTransport::Unknown => bail!("unknown UCSI transport"), -+ } -+} -+ -+fn parse_connector_summary(device_name: &str, connector: u8, payload: &[u8]) -> UcsiConnectorSummary { -+ let state = payload.first().copied().unwrap_or(0); -+ let connected = state & 0x01 != 0; -+ let power_direction = if state & 0x02 != 0 { "source" } else { "sink" }; -+ let data_role = if state & 0x04 != 0 { "dfp" } else { "ufp" }; -+ UcsiConnectorSummary { -+ device: device_name.to_string(), -+ connector_number: connector, -+ connected, -+ data_role: data_role.to_string(), -+ power_direction: power_direction.to_string(), -+ input_critical: classify_input_critical(device_name), -+ } -+} -+ -+fn classify_input_critical(device_name: &str) -> bool { -+ let normalized = device_name.to_ascii_lowercase(); -+ normalized.contains("kbd") -+ || normalized.contains("key") -+ || normalized.contains("touch") -+ || normalized.contains("thc") -+} -+ -+fn parse_ucsi_payload(bytes: &[u8]) -> Option<(UcsiResponseHeader, &[u8])> { -+ let header = UcsiResponseHeader::parse(bytes)?; -+ let body = bytes.get(UCSI_RESPONSE_HEADER_LEN..)?; -+ let body_len = usize::from(header.data_length).min(body.len()); -+ Some((header, &body[..body_len])) -+} -+ -+fn execute_ucsi_i2c_command( -+ adapter: &I2cAdapterInfo, -+ adapter_name: &str, -+ address: u16, -+ ten_bit_address: bool, -+ command: UcsiCommand, -+ read_len: usize, -+) -> Result> { -+ let request = I2cTransferRequest { -+ adapter: adapter_name.to_string(), -+ segments: vec![ -+ I2cTransferSegment { -+ address, -+ ten_bit_address, -+ op: i2c_interface::I2cTransferOp::Write(command.as_bytes().to_vec()), -+ }, -+ I2cTransferSegment { -+ address, -+ ten_bit_address, -+ op: i2c_interface::I2cTransferOp::Read(read_len), -+ }, -+ ], -+ stop: true, -+ }; -+ -+ let mut file = OpenOptions::new() -+ .read(true) -+ .write(true) -+ .open("/scheme/i2c/transfer") -+ .context("failed to open /scheme/i2c/transfer")?; -+ let payload = ron::ser::to_string(&I2cControlRequest::Transfer { -+ adapter_id: adapter.id, -+ request, -+ }) -+ .context("failed to encode UCSI I2C transfer request")?; -+ file.write_all(payload.as_bytes()) -+ .context("failed to send UCSI I2C transfer request")?; -+ -+ let response = read_i2c_control_response(&mut file)?; -+ match response { -+ I2cControlResponse::TransferResult(result) => { -+ if !result.ok { -+ let detail = result -+ .error -+ .clone() -+ .unwrap_or_else(|| String::from("unknown I2C transfer failure")); -+ bail!("UCSI I2C transfer failed: {detail}"); -+ } -+ result -+ .read_data -+ .into_iter() -+ .next() -+ .context("UCSI I2C transfer returned no response payload") -+ } -+ I2cControlResponse::Error(message) => bail!("i2cd returned an error: {message}"), -+ other => bail!("unexpected i2cd transfer response: {other:?}"), -+ } -+} -+ -+fn list_i2c_adapters() -> Result> { -+ let mut file = OpenOptions::new() -+ .read(true) -+ .write(true) -+ .open("/scheme/i2c/adapters") -+ .context("failed to open /scheme/i2c/adapters")?; -+ -+ let payload = ron::ser::to_string(&I2cControlRequest::ListAdapters) -+ .context("failed to encode I2C list-adapters request")?; -+ file.write_all(payload.as_bytes()) -+ .context("failed to request I2C adapter list")?; -+ -+ let response = read_i2c_control_response(&mut file)?; -+ match response { -+ I2cControlResponse::AdapterList(adapters) => Ok(adapters), -+ I2cControlResponse::Error(message) => bail!("i2cd returned an error: {message}"), -+ other => bail!("unexpected i2cd list-adapters response: {other:?}"), -+ } -+} -+ -+fn match_i2c_adapter<'a>(adapters: &'a [I2cAdapterInfo], wanted: &str) -> Option<&'a I2cAdapterInfo> { -+ adapters -+ .iter() -+ .find(|adapter| adapter.name == wanted) -+ .or_else(|| adapters.iter().find(|adapter| adapter.name.ends_with(wanted))) -+ .or_else(|| adapters.iter().find(|adapter| wanted.ends_with(&adapter.name))) -+} -+ -+fn read_i2c_control_response(file: &mut File) -> Result { -+ let mut buffer = vec![0_u8; 4096]; -+ let count = file -+ .read(&mut buffer) -+ .context("failed to read I2C control response")?; -+ buffer.truncate(count); -+ let text = std::str::from_utf8(&buffer).context("I2C control response was not UTF-8")?; -+ ron::from_str(text).context("failed to decode I2C control response") -+} -+ -+fn read_symbol_id(path: &Path) -> Result> { -+ let contents = fs::read_to_string(path) -+ .with_context(|| format!("failed to read ACPI symbol {}", path.display()))?; -+ let symbol = match ron::from_str::(&contents) { -+ Ok(symbol) => symbol, -+ Err(err) => { -+ log::debug!( -+ "ucsid: skipping {} because the symbol payload was not a scalar ID: {err}", -+ path.display(), -+ ); -+ return Ok(None); -+ } -+ }; -+ -+ let id = match symbol.value { -+ AmlValue::Integer(integer) => eisa_id_from_integer(integer), -+ AmlValue::String(string) => string, -+ }; -+ -+ log::debug!("ucsid: {} -> {id}", symbol.name); -+ Ok(Some(id)) -+} -+ -+fn normalize_device_path(path: &str) -> String { -+ path.trim_start_matches('\\') -+ .trim_matches('/') -+ .replace('/', ".") -+} -+ -+fn eisa_id_from_integer(integer: u64) -> String { -+ let vendor = integer & 0xFFFF; -+ let device = (integer >> 16) & 0xFFFF; -+ let vendor_rev = ((vendor & 0xFF) << 8) | (vendor >> 8); -+ let vendor_1 = (((vendor_rev >> 10) & 0x1F) as u8 + 64) as char; -+ let vendor_2 = (((vendor_rev >> 5) & 0x1F) as u8 + 64) as char; -+ let vendor_3 = (((vendor_rev >> 0) & 0x1F) as u8 + 64) as char; -+ let device_1 = (device >> 4) & 0xF; -+ let device_2 = (device >> 0) & 0xF; -+ let device_3 = (device >> 12) & 0xF; -+ let device_4 = (device >> 8) & 0xF; -+ -+ format!( -+ "{vendor_1}{vendor_2}{vendor_3}{device_1:01X}{device_2:01X}{device_3:01X}{device_4:01X}" -+ ) -+} diff --git a/local/patches/base/P2-acpid-core-refactor.patch b/local/patches/base/P2-acpid-core-refactor.patch deleted file mode 100644 index 2b2e733390..0000000000 --- a/local/patches/base/P2-acpid-core-refactor.patch +++ /dev/null @@ -1,3150 +0,0 @@ -# P2-acpid-core-refactor.patch -# -# Core acpid refactoring: DMI/SMBIOS discovery, ACPI power snapshot, sleep/S5 -# handling, FADT power blocks, GenericAddress I/O, AML mutex implementation, -# EC multi-byte region handler, DMAR validation, and scheme resources/power/DMI. -# -# Covers: -# - acpid/src/acpi.rs: DmiInfo, AcpiPowerSnapshot, sleep/S5, Fadt, GenericAddress, EC, quirks -# - acpid/src/acpi/dmar/mod.rs: DMAR structure length validation -# - acpid/src/aml_physmem.rs: AmlMutex implementation with Condvar -# - acpid/src/ec.rs: EC error type, multi-byte read/write, checked offsets -# - acpid/src/scheme.rs: resources, power, DMI directory entries (full section) -# -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,9 +2001,21 @@ 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); -+ -+ if context.pci_ready() { -+ if let Err(error) = context.refresh_s5_values() { -+ log::warn!("Failed to evaluate \\_S5 during FADT init: {error}"); -+ } -+ } else { -+ log::debug!("Deferring \\_S5 evaluation until PCI registration"); -+ } - } - } - - -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,11 +474,14 @@ 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,17 +432,103 @@ 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,59 +245,74 @@ 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/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/local/patches/base/P2-boot-runtime-fixes.patch b/local/patches/base/P2-boot-runtime-fixes.patch index 677151633b..2dc5723a35 100644 --- a/local/patches/base/P2-boot-runtime-fixes.patch +++ b/local/patches/base/P2-boot-runtime-fixes.patch @@ -1,7 +1,6 @@ -diff --git a/drivers/hwd/src/backend/acpi.rs b/drivers/hwd/src/backend/acpi.rs --- a/drivers/hwd/src/backend/acpi.rs +++ b/drivers/hwd/src/backend/acpi.rs -@@ -1,27 +1,36 @@ +@@ -1,26 +1,30 @@ use amlserde::{AmlSerde, AmlSerdeValue}; -use std::{error::Error, fs, process::Command}; +use std::{error::Error, fs}; @@ -35,10 +34,7 @@ diff --git a/drivers/hwd/src/backend/acpi.rs b/drivers/hwd/src/backend/acpi.rs - 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) => -+ { ++ 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(()); + } @@ -47,10 +43,10 @@ diff --git a/drivers/hwd/src/backend/acpi.rs b/drivers/hwd/src/backend/acpi.rs // TODO: Reimplement with getdents? let symbols_fd = libredox::Fd::open( "/scheme/acpi/symbols", -@@ -100,13 +109,104 @@ +@@ -83,6 +87,7 @@ impl Backend for AcpiBackend { "PNP0C0F" => "PCI interrupt link", "PNP0C50" => "I2C HID", - "PNP0F13" => "PS/2 port for PS/2-style mouse", + "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" => { @@ -121,9 +117,9 @@ diff --git a/drivers/hwd/src/backend/acpi.rs b/drivers/hwd/src/backend/acpi.rs + | "INTC1055" + | "INTC1050" + | "INTC1051" -+ | "INTC1080" ++ | "INT1080" + | "INTC1081" -+ | "INTC1082" ++ | "INT1082" + ) || is_elan_touchpad_id(id) + || is_cypress_touchpad_id(id) + || is_synaptics_rmi_id(id) @@ -151,163 +147,3 @@ diff --git a/drivers/hwd/src/backend/acpi.rs b/drivers/hwd/src/backend/acpi.rs +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/pcid-spawner/src/main.rs b/drivers/pcid-spawner/src/main.rs ---- a/drivers/pcid-spawner/src/main.rs -+++ b/drivers/pcid-spawner/src/main.rs -@@ -1,11 +1,40 @@ -+use std::env; - use std::fs; - use std::process::Command; -+use std::thread; - - 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,6 +59,7 @@ - } - - let config: Config = toml::from_str(&config_data)?; -+ let strict_usb_boot = strict_usb_boot(); - - for entry in fs::read_dir("/scheme/pci")? { - let entry = entry.context("failed to get entry")?; -@@ -87,15 +117,71 @@ - - log::info!("pcid-spawner: spawn {:?}", command); - -+ let device_addr = handle.config().func.addr; -+ - handle.enable_device(); - - let channel_fd = handle.into_inner_fd(); - command.env("PCID_CLIENT_CHANNEL", channel_fd.to_string()); - - #[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 { -+ #[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 -+ ); -+ } -+ } - } - - Ok(()) - -diff --git a/drivers/pcid/src/main.rs b/drivers/pcid/src/main.rs ---- a/drivers/pcid/src/main.rs -+++ b/drivers/pcid/src/main.rs -@@ -12,6 +12,7 @@ - }; - 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}; -@@ -262,14 +263,13 @@ - let access_fd = 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"); -+ sendfd( -+ register_pci.raw(), -+ access_fd as usize, -+ SendFdFlags::empty().bits(), -+ 0, -+ ) -+ .expect("failed to send pci_fd to acpid"); - } - Err(err) => { - if err.errno() == libredox::errno::ENODEV { diff --git a/local/patches/base/P2-boot-runtime-noise-and-net-race.patch b/local/patches/base/P2-boot-runtime-noise-and-net-race.patch deleted file mode 100644 index 2e957a8936..0000000000 --- a/local/patches/base/P2-boot-runtime-noise-and-net-race.patch +++ /dev/null @@ -1,144 +0,0 @@ -# P2-boot-runtime-noise-and-net-race.patch -# -# Reduce expected boot-time warning noise and harden netstack startup ordering: -# - procmgr: unknown cancellation is trace-level (benign race) -# - acpid: warn once for unsupported power surface -# - ahcid: SATAPI probe failures are informational on empty media -# - netstack: retry network adapter discovery during early boot races - -diff --git a/bootstrap/src/procmgr.rs b/bootstrap/src/procmgr.rs ---- a/bootstrap/src/procmgr.rs -+++ b/bootstrap/src/procmgr.rs -@@ -296,8 +296,8 @@ fn handle_scheme<'a>( - } - } - } else { -- log::warn!("Cancellation for unknown id {:?}", req.id); -+ log::trace!("Cancellation for unknown id {:?}", req.id); - Pending - } - } - -diff --git a/drivers/acpid/src/scheme.rs b/drivers/acpid/src/scheme.rs ---- a/drivers/acpid/src/scheme.rs -+++ b/drivers/acpid/src/scheme.rs -@@ -8,6 +8,7 @@ use ron::de::SpannedError; - use scheme_utils::HandleMap; - use std::convert::{TryFrom, TryInto}; - use std::str::FromStr; -+use std::sync::atomic::{AtomicBool, Ordering}; - use syscall::dirent::{DirEntry, DirentBuf, DirentKind}; - use syscall::schemev2::NewFdFlags; - use syscall::FobtainFdFlags; -@@ -29,6 +30,8 @@ use crate::acpi::{ - }; - use crate::resources::{decode_resource_template, ResourceDescriptor}; - -+static POWER_SURFACE_UNAVAILABLE_WARNED: AtomicBool = AtomicBool::new(false); -+ - pub struct AcpiScheme<'acpi, 'sock> { - ctx: &'acpi AcpiContext, - handles: HandleMap>, -@@ -307,8 +310,10 @@ impl<'acpi, 'sock> AcpiScheme<'acpi, 'sock> { - 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}"); -+ if !POWER_SURFACE_UNAVAILABLE_WARNED.swap(true, Ordering::Relaxed) { -+ log::warn!("ACPI power surface unavailable: {message}"); -+ } - Error::new(EOPNOTSUPP) - } - other => { - -diff --git a/drivers/storage/ahcid/src/ahci/mod.rs b/drivers/storage/ahcid/src/ahci/mod.rs ---- a/drivers/storage/ahcid/src/ahci/mod.rs -+++ b/drivers/storage/ahcid/src/ahci/mod.rs -@@ -64,8 +64,8 @@ pub fn disks(base: usize, name: &str) -> (&'static mut HbaMem, Vec) { - HbaPortType::SATAPI => match DiskATAPI::new(i, port) { - Ok(disk) => Some(AnyDisk::Atapi(disk)), - Err(err) => { -- error!("{}: {}", i, err); -+ info!("{}: {}", i, err); - None - } - }, - -diff --git a/netstack/src/main.rs b/netstack/src/main.rs ---- a/netstack/src/main.rs -+++ b/netstack/src/main.rs -@@ -6,6 +6,8 @@ use anyhow::{anyhow, bail, Context, Result}; - use event::{EventFlags, EventQueue}; - use libredox::flag::{O_NONBLOCK, O_RDWR}; - use libredox::Fd; -+use std::thread; -+use std::time::Duration; - - use redox_scheme::Socket; - use scheme::Smolnetd; -@@ -22,32 +24,45 @@ mod scheme; - fn get_network_adapter() -> Result { - use std::fs; - -- let mut adapters = vec![]; -+ const MAX_ATTEMPTS: u32 = 50; -+ const RETRY_DELAY: Duration = Duration::from_millis(100); - -- for entry_res in fs::read_dir("/scheme")? { -- let Ok(entry) = entry_res else { -- continue; -- }; -+ for attempt in 1..=MAX_ATTEMPTS { -+ let mut adapters = vec![]; - -- let Ok(scheme) = entry.file_name().into_string() else { -- continue; -- }; -+ for entry_res in fs::read_dir("/scheme")? { -+ let Ok(entry) = entry_res else { -+ continue; -+ }; - -- if !scheme.starts_with("network") { -- continue; -- } -+ let Ok(scheme) = entry.file_name().into_string() else { -+ continue; -+ }; - -- adapters.push(scheme); -- } -+ if !scheme.starts_with("network") { -+ continue; -+ } - -- if adapters.is_empty() { -- bail!("no network adapter found"); -- } else { -- let adapter = adapters.remove(0); -+ adapters.push(scheme); -+ } -+ - if !adapters.is_empty() { -- // FIXME allow using multiple network adapters at the same time -- warn!("Multiple network adapters found. Only {adapter} will be used"); -+ let adapter = adapters.remove(0); -+ if !adapters.is_empty() { -+ // FIXME allow using multiple network adapters at the same time -+ warn!("Multiple network adapters found. Only {adapter} will be used"); -+ } -+ return Ok(adapter); -+ } -+ -+ if attempt < MAX_ATTEMPTS { -+ warn!( -+ "no network adapter found yet (attempt {attempt}/{MAX_ATTEMPTS}), waiting {} ms", -+ RETRY_DELAY.as_millis() -+ ); -+ thread::sleep(RETRY_DELAY); - } -- Ok(adapter) - } -+ -+ bail!("no network adapter found") - } diff --git a/local/patches/base/P2-daemon-ready-graceful.patch b/local/patches/base/P2-daemon-ready-graceful.patch deleted file mode 100644 index 1f7b3ddf1f..0000000000 --- a/local/patches/base/P2-daemon-ready-graceful.patch +++ /dev/null @@ -1,23 +0,0 @@ -# P2-daemon-ready-graceful.patch -# -# Replace unwrap() in Daemon::ready() with graceful error handling. -# When hwd or pcid-spawner spawns a daemon fire-and-forget (dropping the -# pipe's read end before the child signals readiness), the unwrap() causes -# a BrokenPipe panic that kills the child and cascades into boot failures. -# -# The write may fail because init already closed the read end (service -# timeout, duplicate start, or oneshot completion). In all cases the -# daemon should continue running rather than panic. - -diff --git a/daemon/src/lib.rs b/daemon/src/lib.rs ---- a/daemon/src/lib.rs -+++ b/daemon/src/lib.rs -@@ -51,7 +51,7 @@ 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(); -+ let _ = self.write_pipe.write_all(&[0]); - } - - /// Executes `Command` as a child process. diff --git a/local/patches/base/P2-hwd-misc.patch b/local/patches/base/P2-hwd-misc.patch index b08985920f..831df10c1b 100644 --- a/local/patches/base/P2-hwd-misc.patch +++ b/local/patches/base/P2-hwd-misc.patch @@ -1,18 +1,14 @@ -# P2-hwd-misc.patch -# Keep hwd focused on hardware probing. Init owns boot-time pcid startup. - -diff --git a/drivers/hwd/src/main.rs b/drivers/hwd/src/main.rs -index 79360e34..4de3d9f3 100644 --- a/drivers/hwd/src/main.rs +++ b/drivers/hwd/src/main.rs -@@ -37,10 +37,7 @@ fn daemon(daemon: daemon::Daemon) -> ! { +@@ -35,12 +35,6 @@ fn daemon(daemon: daemon::Daemon) -> ! { + } + }; - //TODO: launch pcid based on backend information? - // Must launch after acpid but before probe calls /scheme/acpi/symbols +- //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")); - daemon.ready(); //TODO: HWD is meant to locate PCI/XHCI/etc devices in ACPI and DeviceTree definitions and start their drivers - diff --git a/local/patches/base/P2-hwd-remove-acpid-spawn.patch b/local/patches/base/P2-hwd-remove-acpid-spawn.patch new file mode 100644 index 0000000000..9ed08f709c --- /dev/null +++ b/local/patches/base/P2-hwd-remove-acpid-spawn.patch @@ -0,0 +1,21 @@ +--- drivers/hwd/src/backend/acpi.rs ++++ b/drivers/hwd/src/backend/acpi.rs +@@ -1,5 +1,5 @@ + use amlserde::{AmlSerde, AmlSerdeValue}; +-use std::{error::Error, fs, process::Command}; ++use std::{error::Error, fs}; + + use super::Backend; + +@@ -11,11 +11,6 @@ + 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 }) + } + diff --git a/local/patches/base/P2-ihdad-device-refactor.patch b/local/patches/base/P2-ihdad-device-refactor.patch deleted file mode 100644 index 006517e57e..0000000000 --- a/local/patches/base/P2-ihdad-device-refactor.patch +++ /dev/null @@ -1,1022 +0,0 @@ -# P2-ihdad-device-refactor.patch -# -# Intel HDA device refactoring: CodecTopology, ControllerPolicy, InputStream, -# configure_input, interrupt handling, and associated device-level improvements. -# -# Covers: -# - ihdad/src/hda/device.rs: CodecTopology, ControllerPolicy, InputStream, -# configure_input, interrupt handling -# -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/local/patches/base/P2-ihdad-hda-stream.patch b/local/patches/base/P2-ihdad-hda-stream.patch deleted file mode 100644 index 2bc2ff3dee..0000000000 --- a/local/patches/base/P2-ihdad-hda-stream.patch +++ /dev/null @@ -1,1150 +0,0 @@ -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/local/patches/base/P2-init-subsystems.patch b/local/patches/base/P2-init-subsystems.patch deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/local/patches/base/P2-inputd.patch b/local/patches/base/P2-inputd.patch deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/local/patches/base/P2-logd.patch b/local/patches/base/P2-logd.patch deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/local/patches/base/P2-network-driver-mains.patch b/local/patches/base/P2-network-driver-mains.patch deleted file mode 100644 index a2926d890b..0000000000 --- a/local/patches/base/P2-network-driver-mains.patch +++ /dev/null @@ -1,607 +0,0 @@ -# P2-network-driver-mains.patch -# Extract network driver main.rs hardening: replace panic/unwrap/expect with -# proper error handling and graceful exits. -# -# Files: drivers/net/e1000d/src/main.rs, drivers/net/ixgbed/src/main.rs, -# drivers/net/rtl8139d/src/main.rs, drivers/net/rtl8168d/src/main.rs, -# drivers/net/virtio-netd/src/main.rs - -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/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/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/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,11 +134,22 @@ 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/local/patches/base/P2-network-error-handling.patch b/local/patches/base/P2-network-error-handling.patch deleted file mode 100644 index 15026cbf4a..0000000000 --- a/local/patches/base/P2-network-error-handling.patch +++ /dev/null @@ -1,118 +0,0 @@ -# P2-network-error-handling.patch -# -# Network driver error handling: replace unwrap()/expect()/panic!() with proper -# error propagation and graceful exits across e1000, ixgbe, rtl8139, rtl8168d, -# and virtio-net drivers. -# -# Covers: -# - e1000d/device.rs: replace unreachable!() in DMA array conversion -# - ixgbed/Cargo.toml: add log dependency -# - rtl8139d/device.rs: replace unreachable!() with EIO error -# - rtl8168d/device.rs: replace unreachable!() with EIO error -# - virtio-netd/scheme.rs: DMA allocation error handling for rx buffers -# -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,12 +207,11 @@ 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/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,7 +7,8 @@ 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/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,8 +215,8 @@ 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/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,8 +185,8 @@ 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/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/local/patches/base/P2-pcid-acpid-graceful-fd.patch b/local/patches/base/P2-pcid-acpid-graceful-fd.patch new file mode 100644 index 0000000000..9664e9157d --- /dev/null +++ b/local/patches/base/P2-pcid-acpid-graceful-fd.patch @@ -0,0 +1,35 @@ +--- a/drivers/pcid/src/main.rs ++++ b/drivers/pcid/src/main.rs +@@ -263,13 +263,13 @@ + .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( ++ if let Err(err) = register_pci.call_wo( + &access_bytes, + syscall::CallFlags::WRITE | syscall::CallFlags::FD, + &[], +- ) +- .expect("failed to send pci_fd to acpid"); ++ ) { ++ warn!("pcid: failed to send pci_fd to acpid (error: {}). Running without ACPI integration.", err); ++ } + } + Err(err) => { + if err.errno() == libredox::errno::ENODEV { +@@ -304,8 +304,11 @@ + } + debug!("Enumeration complete, now starting pci scheme"); + +- 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) { ++ warn!("pcid: failed to register pci scheme (error: {}). pcid-spawner may already own it.", err); ++ let _ = daemon.ready(); ++ std::process::exit(0); ++ } + + let _ = daemon.ready(); + + diff --git a/local/patches/base/P2-pcid-cfg-access.patch b/local/patches/base/P2-pcid-cfg-access.patch index 53ea9766a0..6adde03b41 100644 --- a/local/patches/base/P2-pcid-cfg-access.patch +++ b/local/patches/base/P2-pcid-cfg-access.patch @@ -1,19 +1,9 @@ -# P2-pcid-cfg-access.patch -# -# PCI config access error handling: replace unwrap()/expect()/assert!() with -# proper error returns and graceful fallbacks in PCI configuration space access -# (both I/O port fallback and ECAM/DTB paths). -# -# Covers: -# - pcid/cfg_access/fallback.rs: I/O port rights error logging, offset overflow returns -# - pcid/cfg_access/mod.rs: DTB property access with proper error propagation, -# bus-range validation, interrupt-map phandle/property fallbacks -# -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 { +@@ -22,14 +22,17 @@ impl Pci { + // make sure that pcid is not granted io port permission unless pcie memory-mapped + // configuration space is not available. + info!( "PCI: couldn't find or access PCIe extended configuration, \ and thus falling back to PCI 3.0 io ports" ); @@ -27,143 +17,27 @@ index 671d17f7..ea8f69f8 100644 } }); } -@@ -61,8 +66,9 @@ impl ConfigRegionAccess for Pci { - +@@ -55,7 +58,10 @@ 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 { ++ // PCI config space is only 256 bytes (offset 0-255) + return 0xFFFFFFFF; + }; let address = Self::address(address, offset); Pio::::new(0xCF8).write(address); -@@ -74,8 +80,9 @@ impl ConfigRegionAccess for Pci { - +@@ -67,7 +73,10 @@ 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 { ++ // PCI config space is only 256 bytes (offset 0-255) + 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/local/patches/base/P2-pcid-driver-interface.patch b/local/patches/base/P2-pcid-driver-interface.patch deleted file mode 100644 index 3d93559847..0000000000 --- a/local/patches/base/P2-pcid-driver-interface.patch +++ /dev/null @@ -1,1463 +0,0 @@ -# P2-pcid-driver-interface.patch -# Extract pcid driver interface hardening: vendor capability try_parse, BAR -# try_port/try_mem, MSI/MSI-X error types, IRQ helper try_ variants, scheme -# config endpoint, UnrecognizedRequest error variant, send/recv try_ variants. -# -# Files: drivers/pcid/src/driver_handler.rs, drivers/pcid/src/driver_interface/bar.rs, -# drivers/pcid/src/driver_interface/cap.rs, drivers/pcid/src/driver_interface/config.rs, -# drivers/pcid/src/driver_interface/irq_helpers.rs, drivers/pcid/src/driver_interface/mod.rs, -# drivers/pcid/src/driver_interface/msi.rs, drivers/pcid/src/scheme.rs - -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/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/local/patches/base/P2-ps2d-improvements.patch b/local/patches/base/P2-ps2d-improvements.patch deleted file mode 100644 index 0897e0bfe8..0000000000 --- a/local/patches/base/P2-ps2d-improvements.patch +++ /dev/null @@ -1,384 +0,0 @@ -# P2-ps2d-improvements.patch -# -# PS/2 controller improvements: flush/retry logic, mouse state machine fixes. -# -# Covers: -# - ps2d/controller.rs: flush stale bytes, self-test with retry, AUX port test -# - ps2d/mouse.rs: ACK/RESEND/BAT constant names, resend handling, state machine fixes -# - ps2d/state.rs: non-fatal init error handling -# -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/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 -@@ -61,9 +61,11 @@ impl Ps2d { - pub fn new(input: ProducerHandle, 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; - let vmmouse = vm::enable(vmmouse_relative); - - // TODO: QEMU hack, maybe do this when Init timed out? diff --git a/local/patches/base/P2-storage-driver-mains.patch b/local/patches/base/P2-storage-driver-mains.patch deleted file mode 100644 index fb2b858375..0000000000 --- a/local/patches/base/P2-storage-driver-mains.patch +++ /dev/null @@ -1,625 +0,0 @@ -# P2-storage-driver-mains.patch -# Extract storage driver main.rs hardening: replace panic/unwrap/expect with -# proper error handling, debug_assert for invariant checks, and graceful exits. -# -# Files: drivers/storage/ahcid/src/main.rs, drivers/storage/ided/src/main.rs, -# drivers/storage/nvmed/src/main.rs, drivers/storage/virtio-blkd/src/main.rs - -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/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/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/local/patches/base/P2-storage-error-handling.patch b/local/patches/base/P2-storage-error-handling.patch deleted file mode 100644 index 3e715a0aa0..0000000000 --- a/local/patches/base/P2-storage-error-handling.patch +++ /dev/null @@ -1,601 +0,0 @@ -# P2-storage-error-handling.patch -# -# Storage driver error handling: replace unwrap()/expect()/panic!() with proper -# error propagation and graceful exits across AHCI, IDE, NVMe, and VirtIO block drivers. -# -# Covers: -# - ahcid/disk_ata.rs: replace unreachable!() with EIO error -# - ahcid/disk_atapi.rs: replace unreachable!() with EBADF error -# - ahcid/hba.rs: DMA allocation error handling -# - ided/ide.rs: assert→debug_assert, try_into error handling -# - nvmed/executor.rs: executor initialization error handling -# - nvmed/identify.rs: DMA allocation, unreachable!() fallback -# - nvmed/mod.rs: assert→debug_assert, unwrap→proper error/exit -# - nvmed/queues.rs: unreachable!()→safe fallback -# - virtio-blkd/scheme.rs: DMA allocation error handling, assert→if check -# -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,8 +178,11 @@ 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/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,8 +357,8 @@ 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/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,8 +145,8 @@ 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/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/local/patches/base/P2-usb-pm-and-drivers.patch b/local/patches/base/P2-usb-pm-and-drivers.patch index 3fd30d2fd1..dec0d1f60c 100644 --- a/local/patches/base/P2-usb-pm-and-drivers.patch +++ b/local/patches/base/P2-usb-pm-and-drivers.patch @@ -1,20 +1,6 @@ -# P2-usb-pm-and-drivers.patch -# -# USB power management and driver interface improvements: -# suspend/resume commands, SCSI driver enablement, PortPmState type, -# IRQ reactor staged port state fallback. -# -# Covers: -# - usbctl/main.rs: pm-state, suspend, resume subcommands -# - xhcid/drivers.toml: enable SCSI over USB driver (was commented out) -# - xhcid/driver_interface.rs: PortPmState enum, suspend/resume/port_pm_state methods -# - xhcid/irq_reactor.rs: staged_port_states fallback in with_ring/with_ring_mut -# -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() { +@@ -15,6 +15,9 @@ Command::new("port") .arg(Arg::new("PORT").num_args(1).required(true)) .subcommand(Command::new("status")) @@ -24,7 +10,7 @@ index 9b5773d9..232f7cfc 100644 .subcommand( Command::new("endpoint") .arg(Arg::new("ENDPOINT_NUM").num_args(1).required(true)) -@@ -38,7 +41,16 @@ fn main() { +@@ -38,6 +41,15 @@ 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()); @@ -39,13 +25,11 @@ index 9b5773d9..232f7cfc 100644 + 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,10 +1,9 @@ +@@ -1,9 +1,8 @@ -#TODO: causes XHCI errors -#[[drivers]] -#name = "SCSI over USB" @@ -61,16 +45,12 @@ index 83c90e23..470ec063 100644 [[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 { - } - } +@@ -446,6 +446,33 @@ -+#[repr(u8)] -+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] + #[repr(u8)] + #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub enum PortPmState { + Active, + Suspended, @@ -96,13 +76,18 @@ index 727f8d7e..82f839ae 100644 + } +} + - #[repr(u8)] - #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] ++#[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(()) + Disabled, + Enabled, +@@ -557,6 +584,16 @@ } + pub fn detach(&self) -> result::Result<(), XhciClientHandleError> { + let file = self.fd.openat("detach", libredox::flag::O_WRONLY, 0)?; ++ 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(&[])?; @@ -110,49 +95,17 @@ index 727f8d7e..82f839ae 100644 + } + 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,7 +619,11 @@ impl XhciClientHandle { - let string = self.read_to_string("state")?; - Ok(string.parse()?) + let _bytes_written = file.write(&[])?; + Ok(()) } -+ pub fn port_pm_state(&self) -> result::Result { -+ let string = self.read_to_string("pm_state")?; +@@ -580,6 +617,10 @@ + } + pub fn port_state(&self) -> result::Result { + 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/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/local/patches/base/P2-xhcid-remaining.patch b/local/patches/base/P2-xhcid-remaining.patch index 6e45e01499..9bde025e1d 100644 --- a/local/patches/base/P2-xhcid-remaining.patch +++ b/local/patches/base/P2-xhcid-remaining.patch @@ -1,16 +1,6 @@ -# P2-xhcid-remaining.patch -# Extract xhcid remaining hardening: MSI-X/MSI/legacy IRQ fallback, test hooks, -# port lifecycle management, staged port states, suspend/resume, endpoint -# configuration rollback, and power management. -# -# Files: drivers/usb/xhcid/src/main.rs, drivers/usb/xhcid/src/xhci/device_enumerator.rs, -# drivers/usb/xhcid/src/xhci/mod.rs, drivers/usb/xhcid/src/xhci/scheme.rs - -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; +@@ -33,7 +33,7 @@ use pcid_interface::irq_helpers::read_bsp_apic_id; #[cfg(target_arch = "x86_64")] use pcid_interface::irq_helpers::{ @@ -19,7 +9,7 @@ index d345a52f..562c580a 100644 }; use pcid_interface::{PciFeature, PciFeatureInfo, PciFunctionHandle}; -@@ -61,11 +61,24 @@ fn get_int_method(pcid_handle: &mut PciFunctionHandle) -> (Option, Interru +@@ -61,11 +61,24 @@ let has_msix = all_pci_features.iter().any(|feature| feature.is_msix()); if has_msix { @@ -36,19 +26,19 @@ index d345a52f..562c580a 100644 + log::error!("xhcid: failed to fetch MSI-X feature info: {err}"); + return (None, InterruptMethod::Polling); + } -+ }; + }; +- let mut info = unsafe { msix_info.map_and_mask_all(pcid_handle) }; + 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 +@@ -75,27 +88,53 @@ let table_entry_pointer = info.table_entry_pointer(k); @@ -108,7 +98,7 @@ index d345a52f..562c580a 100644 } else { // no interrupts at all (None, InterruptMethod::Polling) -@@ -109,7 +148,13 @@ fn get_int_method(pcid_handle: &mut PciFunctionHandle) -> (Option, Interru +@@ -109,7 +148,13 @@ if let Some(irq) = pci_config.func.legacy_interrupt_line { // legacy INTx# interrupt pins. @@ -123,11 +113,14 @@ index d345a52f..562c580a 100644 } else { // no interrupts at all (None, InterruptMethod::Polling) -@@ -136,23 +181,48 @@ fn daemon_with_context_size( +@@ -136,23 +181,48 @@ log::debug!("XHCI PCI CONFIG: {:?}", pci_config); - let address = unsafe { pcid_handle.map_bar(0) }.ptr.as_ptr() as usize; +- +- let (irq_file, interrupt_method) = (None, InterruptMethod::Polling); //get_int_method(&mut pcid_handle); +- //TODO: Fix interrupts. + let address = match unsafe { pcid_handle.try_map_bar(0) } { + Ok(bar) => bar.ptr.as_ptr() as usize, + Err(err) => { @@ -137,9 +130,7 @@ index d345a52f..562c580a 100644 + }; + + 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"), @@ -179,7 +170,7 @@ index d345a52f..562c580a 100644 daemon.ready(); -@@ -163,7 +233,10 @@ fn daemon_with_context_size( +@@ -163,7 +233,10 @@ handler .process_requests_blocking(&*hci) @@ -191,7 +182,7 @@ index d345a52f..562c580a 100644 } fn main() { -@@ -171,7 +244,13 @@ fn main() { +@@ -171,7 +244,13 @@ } fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { @@ -206,24 +197,22 @@ index d345a52f..562c580a 100644 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; +@@ -4,8 +4,10 @@ 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; + ++const DEFAULT_PORT_RESET_SETTLE_MS: u64 = 16; + pub struct DeviceEnumerationRequest { pub port_id: PortId, - } -@@ -28,7 +30,11 @@ impl DeviceEnumerator { +@@ -28,7 +30,11 @@ let request = match self.request_queue.recv() { Ok(req) => req, Err(err) => { @@ -236,7 +225,7 @@ index 74b9f732..493e79df 100644 } }; -@@ -38,7 +44,11 @@ impl DeviceEnumerator { +@@ -38,7 +44,11 @@ debug!("Device Enumerator request for port {}", port_id); let (len, flags) = { @@ -249,7 +238,7 @@ index 74b9f732..493e79df 100644 let len = ports.len(); -@@ -62,43 +72,52 @@ impl DeviceEnumerator { +@@ -62,43 +72,52 @@ //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) { @@ -274,6 +263,8 @@ index 74b9f732..493e79df 100644 //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); +- +- let mut ports = self.hci.ports.lock().unwrap(); + if let Err(err) = self.hci.reset_port(port_id) { + warn!( + "failed to reset port {} before enumeration; skipping attach: {}", @@ -281,8 +272,7 @@ index 74b9f732..493e79df 100644 + ); + continue; + } - -- let mut ports = self.hci.ports.lock().unwrap(); ++ + let mut ports = self + .hci + .ports @@ -293,18 +283,20 @@ index 74b9f732..493e79df 100644 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); ++ drop(ports); ++ ++ let flags = self.wait_for_port_enabled_state( ++ port_array_index, ++ Duration::from_millis(DEFAULT_PORT_RESET_SETTLE_MS), ++ ); ++ + let enabled_state = Self::port_is_enabled(&flags); if !enabled_state { @@ -317,7 +309,7 @@ index 74b9f732..493e79df 100644 } else { debug!( "Port {} is in the enabled state. Proceeding with enumeration", -@@ -131,13 +150,60 @@ impl DeviceEnumerator { +@@ -131,13 +150,60 @@ Ok(was_connected) => { if was_connected { info!("Device on port {} was detached", port_id); @@ -379,1627 +371,3 @@ index 74b9f732..493e79df 100644 + } + } } -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/local/patches/base/P3-acpi-power-dmi.patch b/local/patches/base/P3-acpi-power-dmi.patch deleted file mode 100644 index 887414a538..0000000000 --- a/local/patches/base/P3-acpi-power-dmi.patch +++ /dev/null @@ -1,1294 +0,0 @@ -diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs ---- a/drivers/acpid/src/acpi.rs -+++ b/drivers/acpid/src/acpi.rs -@@ -1,5 +1,6 @@ - use acpi::aml::object::{Object, WrappedObject}; - use acpi::aml::op_region::{RegionHandler, RegionSpace}; -+use libredox::Fd; - use rustc_hash::FxHashMap; - use std::convert::{TryFrom, TryInto}; - use std::error::Error; -@@ -228,6 +229,475 @@ - .field("header", &*self as &SdtHeader) - .field("extra_len", &self.data().len()) - .finish() -+ } -+} -+ -+#[derive(Clone, Debug, Default)] -+pub struct DmiInfo { -+ pub bios_vendor: Option, -+ pub bios_version: Option, -+ pub sys_vendor: Option, -+ pub board_vendor: Option, -+ pub board_name: Option, -+ pub board_version: Option, -+ pub product_name: Option, -+ pub product_version: Option, -+} -+ -+impl DmiInfo { -+ pub fn to_key_value_lines(&self) -> String { -+ let mut lines = Vec::new(); -+ -+ if let Some(value) = &self.bios_vendor { -+ lines.push(format!("bios_vendor={value}")); -+ } -+ if let Some(value) = &self.bios_version { -+ lines.push(format!("bios_version={value}")); -+ } -+ if let Some(value) = &self.sys_vendor { -+ lines.push(format!("sys_vendor={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.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}")); -+ } -+ -+ 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 {} -+ -+#[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, -+} -+ -+impl AcpiBattery { -+ pub fn is_charging(&self) -> bool { -+ self.state & 0x2 != 0 -+ } -+ -+ pub fn is_discharging(&self) -> bool { -+ self.state & 0x1 != 0 -+ } -+ -+ pub fn is_empty(&self) -> bool { -+ self.state & 0x4 != 0 -+ } -+ -+ pub fn is_full(&self) -> bool { -+ self.percentage.is_some_and(|percentage| percentage >= 99.0) -+ } -+} -+ -+#[derive(Clone, Debug, Default)] -+pub struct AcpiPowerSnapshot { -+ pub adapters: Vec, -+ pub batteries: Vec, -+} -+ -+impl AcpiPowerSnapshot { -+ pub fn adapter_status(&self) -> &'static str { -+ if self.adapters.iter().any(|adapter| adapter.online) { -+ "online" -+ } else { -+ "offline" -+ } -+ } -+ -+ pub fn battery_status(&self) -> &'static str { -+ if self.batteries.iter().any(AcpiBattery::is_charging) { -+ return "charging"; -+ } -+ if self.batteries.iter().any(AcpiBattery::is_discharging) { -+ return "discharging"; -+ } -+ if self.batteries.iter().any(AcpiBattery::is_empty) { -+ return "empty"; -+ } -+ if !self.batteries.is_empty() && self.batteries.iter().all(AcpiBattery::is_full) { -+ return "full"; -+ } -+ -+ "unknown" -+ } -+} -+ -+#[derive(Clone, Debug, Default)] -+pub struct AcpiPowerDevicePaths { -+ pub adapters: Vec, -+ pub batteries: Vec, -+} -+ -+#[derive(Debug, Error)] -+pub enum PowerQueryError { -+ #[error("AML bootstrap not complete")] -+ Unavailable, -+ #[error("ACPI power namespace unsupported")] -+ Unsupported, -+ #[error("AML error")] -+ Aml(#[from] AmlEvalError), -+} -+ -+fn checksum_ok(bytes: &[u8]) -> bool { -+ bytes -+ .iter() -+ .copied() -+ .fold(0u8, |acc, byte| acc.wrapping_add(byte)) -+ == 0 -+} -+ -+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -+fn scan_smbios2() -> Option<(usize, usize, Vec)> { -+ 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 = usize::from(entry.length); -+ -+ 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, -+ bytes[offset..offset + length].to_vec(), -+ )); -+ } -+ } -+ -+ offset += 16; -+ } -+ -+ None -+} -+ -+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -+fn scan_smbios3() -> Option<(usize, usize, Vec)> { -+ 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 = usize::from(entry.length); -+ -+ if offset + length <= bytes.len() && length >= header_size && checksum_ok(&bytes[offset..offset + length]) { -+ let table_address = usize::try_from(entry.table_address).ok()?; -+ let table_length = usize::try_from(entry.table_max_size).ok()?; -+ return Some(( -+ table_address, -+ table_length, -+ bytes[offset..offset + length].to_vec(), -+ )); -+ } -+ } -+ -+ 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(|byte| *byte == 0) { -+ if part.is_empty() { -+ break; -+ } -+ if current == index { -+ let value = String::from_utf8_lossy(part).trim().to_string(); -+ return (!value.is_empty()).then_some(value); -+ } -+ 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 info = DmiInfo::default(); -+ let mut offset = 0usize; -+ -+ while offset + mem::size_of::() <= bytes.len() { -+ let header = plain::from_bytes::( -+ &bytes[offset..offset + mem::size_of::()], -+ ) -+ .ok()?; -+ let formatted_len = usize::from(header.length); -+ 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; -+ } -+ -+ if string_end <= offset || string_end > bytes.len() { -+ break; -+ } -+ -+ let strings = &bytes[offset + formatted_len..string_end.saturating_sub(1)]; -+ -+ match header.kind { -+ 0 if formatted_len >= 0x06 => { -+ info.bios_vendor = smbios_string(strings, struct_bytes[0x04]); -+ 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, -+ _ => {} -+ } -+ -+ offset = string_end; -+ } -+ -+ (!info.to_key_value_lines().is_empty()).then_some(info) -+} -+ -+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -+fn load_dmi_data() -> (Option, Option>) { -+ let Some((table_addr, table_len, raw)) = scan_smbios3().or_else(scan_smbios2) else { -+ return (None, None); -+ }; -+ -+ ( -+ parse_smbios_table(table_addr, table_len), -+ Some(raw.into_boxed_slice()), -+ ) -+} -+ -+#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] -+fn load_dmi_data() -> (Option, Option>) { -+ (None, None) -+} -+ -+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)) - } - } - -@@ -560,6 +1030,8 @@ - dsdt: Option, - fadt: Option, - shutdown_s5: RwLock>, -+ dmi_info: Option, -+ dmi_raw: Option>, - - aml_symbols: RwLock, - -@@ -574,11 +1046,12 @@ - impl AcpiContext { - pub fn aml_eval( - &self, -+ pci_fd: Option<&Fd>, - symbol: AmlName, - args: Vec, - ) -> Result { - let mut symbols = self.aml_symbols.write(); -- let interpreter = symbols.aml_context_mut(None)?; -+ let interpreter = symbols.aml_context_mut(pci_fd)?; - interpreter.acquire_global_lock(16)?; - - let args = args -@@ -592,9 +1065,9 @@ - .collect::, AmlEvalError>>()?; - - let result = interpreter.evaluate(symbol, args); -- interpreter -- .release_global_lock() -- .expect("Failed to release GIL!"); //TODO: check if this should panic -+ if let Err(error) = interpreter.release_global_lock() { -+ log::error!("Failed to release AML global lock: {:?}", error); -+ } - - result - .map_err(AmlEvalError::from) -@@ -649,11 +1122,15 @@ - } - } - -+ let (dmi_info, dmi_raw) = load_dmi_data(); -+ - let mut this = Self { - tables, - dsdt: None, - fadt: None, - shutdown_s5: RwLock::new(None), -+ dmi_info, -+ dmi_raw, - - // Temporary values - aml_symbols: RwLock::new(AmlSymbols::new(aml_bootstrap, ec)), -@@ -735,11 +1212,155 @@ - self.sdt_order.write().push(Some(*signature)); - } - -- pub fn aml_lookup(&self, symbol: &str) -> Option { -- if let Ok(aml_symbols) = self.aml_symbols(None) { -+ pub fn dmi_info(&self) -> Option<&DmiInfo> { -+ self.dmi_info.as_ref() -+ } -+ -+ pub fn dmi_raw(&self) -> Option<&[u8]> { -+ self.dmi_raw.as_deref() -+ } -+ -+ pub fn aml_lookup(&self, pci_fd: Option<&Fd>, symbol: &str) -> Option { -+ if let Ok(aml_symbols) = self.aml_symbols(pci_fd) { - aml_symbols.lookup(symbol) - } else { - None -+ } -+ } -+ -+ pub fn power_object_paths(&self, pci_fd: Option<&Fd>) -> Result { -+ let mut aml_symbols = self.aml_symbols.write(); -+ let aml_context = aml_symbols.aml_context_mut(pci_fd).map_err(|error| match error { -+ AmlEvalError::NotInitialized => PowerQueryError::Unavailable, -+ other => PowerQueryError::Aml(other), -+ })?; -+ -+ let mut symbol_names = Vec::with_capacity(256); -+ aml_context -+ .namespace -+ .lock() -+ .traverse(|level_aml_name, level| { -+ 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) -+ { -+ symbol_names.push(aml_to_symbol(&aml_name)); -+ } -+ } -+ Ok(true) -+ }) -+ .map_err(AmlEvalError::from) -+ .map_err(PowerQueryError::Aml)?; -+ drop(aml_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(); -+ -+ Ok(AcpiPowerDevicePaths { -+ adapters: adapter_paths, -+ batteries: battery_paths, -+ }) -+ } -+ -+ pub fn power_snapshot(&self, pci_fd: Option<&Fd>) -> Result { -+ let paths = self.power_object_paths(pci_fd)?; -+ if paths.adapters.is_empty() && paths.batteries.is_empty() { -+ return Err(PowerQueryError::Unsupported); -+ } -+ -+ let mut snapshot = AcpiPowerSnapshot::default(); -+ -+ for path in paths.adapters { -+ let method_name = AmlName::from_str(&format!("\\{}.{}", path, "_PSR")) -+ .map_err(|_| PowerQueryError::Aml(AmlEvalError::DeserializationError))?; -+ match self.aml_eval(pci_fd, 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 {} due to _PSR eval failure: {:?}", path, error); -+ } -+ } -+ } -+ -+ for path in paths.batteries { -+ let mut battery = AcpiBattery { -+ id: symbol_leaf_id(&path), -+ path: path.clone(), -+ ..AcpiBattery::default() -+ }; -+ -+ match self.aml_eval( -+ pci_fd, -+ AmlName::from_str(&format!("\\{}.{}", path, "_BST")) -+ .map_err(|_| PowerQueryError::Aml(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(|_| PowerQueryError::Aml(AmlEvalError::DeserializationError))?; -+ match self.aml_eval(pci_fd, 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(PowerQueryError::Unavailable) -+ } else { -+ Ok(snapshot) - } - } - -diff --git a/drivers/acpid/src/scheme.rs b/drivers/acpid/src/scheme.rs ---- a/drivers/acpid/src/scheme.rs -+++ b/drivers/acpid/src/scheme.rs -@@ -21,7 +21,10 @@ - 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, -+ PowerQueryError, SdtSignature, -+}; - - pub struct AcpiScheme<'acpi, 'sock> { - ctx: &'acpi AcpiContext, -@@ -41,8 +44,151 @@ - Table(SdtSignature), - Symbols(RwLockReadGuard<'a, AmlSymbols>), - Symbol { name: String, description: String }, -+ DmiDir, -+ Dmi(Vec), -+ PowerDir, -+ PowerAdaptersDir, -+ PowerAdapterDir(String), -+ PowerBatteriesDir, -+ PowerBatteryDir(String), -+ PowerFile(Vec), - SchemeRoot, - RegisterPci, -+} -+ -+const DMI_DIRECTORY_ENTRIES: &[&str] = &[ -+ "bios_vendor", -+ "bios_version", -+ "sys_vendor", -+ "board_vendor", -+ "board_name", -+ "board_version", -+ "product_name", -+ "product_version", -+ "raw", -+]; -+ -+const POWER_ROOT_ENTRIES: &[(&str, DirentKind)] = &[ -+ ("status", DirentKind::Regular), -+ ("adapter", DirentKind::Regular), -+ ("battery", DirentKind::Regular), -+ ("adapters", DirentKind::Directory), -+ ("batteries", DirentKind::Directory), -+]; -+ -+fn dmi_match_all_contents(dmi_info: &DmiInfo) -> Vec { -+ dmi_info.to_key_value_lines().into_bytes() -+} -+ -+fn dmi_contents(dmi_info: Option<&DmiInfo>, dmi_raw: Option<&[u8]>, name: &str) -> Option> { -+ Some(match name { -+ "raw" => dmi_raw?.to_vec(), -+ "" | "match_all" => dmi_match_all_contents(dmi_info?), -+ "bios_vendor" => dmi_info?.bios_vendor.clone()?.into_bytes(), -+ "bios_version" => dmi_info?.bios_version.clone()?.into_bytes(), -+ "sys_vendor" | "system_vendor" => dmi_info?.sys_vendor.clone()?.into_bytes(), -+ "board_vendor" => dmi_info?.board_vendor.clone()?.into_bytes(), -+ "board_name" => dmi_info?.board_name.clone()?.into_bytes(), -+ "board_version" => dmi_info?.board_version.clone()?.into_bytes(), -+ "product_name" => dmi_info?.product_name.clone()?.into_bytes(), -+ "product_version" => dmi_info?.product_version.clone()?.into_bytes(), -+ _ => return None, -+ }) -+} -+ -+fn text_file_bytes(value: &str) -> Vec { -+ format!("{value}\n").into_bytes() -+} -+ -+fn power_bool_bytes(value: bool) -> Vec { -+ text_file_bytes(if value { "1" } else { "0" }) -+} -+ -+fn power_u64_bytes(value: u64) -> Vec { -+ format!("{value}\n").into_bytes() -+} -+ -+fn power_f64_bytes(value: f64) -> Vec { -+ format!("{value}\n").into_bytes() -+} -+ -+fn power_adapter_file_contents(adapter: &AcpiPowerAdapter, name: &str) -> Option> { -+ Some(match name { -+ "path" => text_file_bytes(&adapter.path), -+ "online" => power_bool_bytes(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" => text_file_bytes(&battery.path), -+ "state" => power_u64_bytes(battery.state), -+ "present_rate" => power_u64_bytes(battery.present_rate?), -+ "remaining_capacity" => power_u64_bytes(battery.remaining_capacity?), -+ "present_voltage" => power_u64_bytes(battery.present_voltage?), -+ "power_unit" => text_file_bytes(battery.power_unit.as_deref()?), -+ "design_capacity" => power_u64_bytes(battery.design_capacity?), -+ "last_full_capacity" => power_u64_bytes(battery.last_full_capacity?), -+ "design_voltage" => power_u64_bytes(battery.design_voltage?), -+ "technology" => text_file_bytes(battery.technology.as_deref()?), -+ "model" => text_file_bytes(battery.model.as_deref()?), -+ "serial" => text_file_bytes(battery.serial.as_deref()?), -+ "battery_type" => text_file_bytes(battery.battery_type.as_deref()?), -+ "oem_info" => text_file_bytes(battery.oem_info.as_deref()?), -+ "percentage" => power_f64_bytes(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 - } - - impl HandleKind<'_> { -@@ -53,6 +199,14 @@ - Self::Table(_) => false, - Self::Symbols(_) => true, - Self::Symbol { .. } => 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 +219,18 @@ - .ok_or(Error::new(EBADFD))? - .length(), - Self::Symbol { description, .. } => description.len(), -+ Self::Dmi(contents) => contents.len(), -+ Self::PowerFile(contents) => contents.len(), - // Directories -- Self::TopLevel | Self::Symbols(_) | Self::Tables => 0, -+ Self::TopLevel -+ | Self::Symbols(_) -+ | Self::Tables -+ | Self::DmiDir -+ | Self::PowerDir -+ | Self::PowerAdaptersDir -+ | Self::PowerAdapterDir(_) -+ | Self::PowerBatteriesDir -+ | Self::PowerBatteryDir(_) => 0, - Self::SchemeRoot | Self::RegisterPci => return Err(Error::new(EBADF)), - }) - } -@@ -79,6 +243,154 @@ - handles: HandleMap::new(), - pci_fd: None, - socket, -+ } -+ } -+ -+ fn power_snapshot(&self) -> Result { -+ self.ctx -+ .power_snapshot(self.pci_fd.as_ref()) -+ .map_err(|error| match error { -+ PowerQueryError::Unavailable | PowerQueryError::Unsupported => Error::new(ENOENT), -+ PowerQueryError::Aml(other) => { -+ log::warn!("Failed to build ACPI power snapshot: {:?}", other); -+ Error::new(EIO) -+ } -+ }) -+ } -+ -+ fn power_surface_counts(&self) -> (bool, usize, usize) { -+ let Ok(paths) = self.ctx.power_object_paths(self.pci_fd.as_ref()) else { -+ return (false, 0, 0); -+ }; -+ -+ ( -+ self.ctx.power_snapshot(self.pci_fd.as_ref()).is_ok(), -+ paths.batteries.len(), -+ paths.adapters.len(), -+ ) -+ } -+ -+ fn power_status_contents(&self) -> Vec { -+ let (available, battery_count, adapter_count) = self.power_surface_counts(); -+ format!( -+ "{{\"available\": {}, \"battery_count\": {}, \"adapter_count\": {}}}\n", -+ available, battery_count, adapter_count -+ ) -+ .into_bytes() -+ } -+ -+ fn power_adapter_summary_contents(&self) -> Vec { -+ let Ok(paths) = self.ctx.power_object_paths(self.pci_fd.as_ref()) else { -+ return text_file_bytes("unavailable"); -+ }; -+ if paths.adapters.is_empty() { -+ return text_file_bytes("unsupported"); -+ } -+ -+ match self.ctx.power_snapshot(self.pci_fd.as_ref()) { -+ Ok(snapshot) => text_file_bytes(snapshot.adapter_status()), -+ Err(_) => text_file_bytes("unavailable"), -+ } -+ } -+ -+ fn power_battery_summary_contents(&self) -> Vec { -+ let Ok(paths) = self.ctx.power_object_paths(self.pci_fd.as_ref()) else { -+ return text_file_bytes("unavailable"); -+ }; -+ if paths.batteries.is_empty() { -+ return text_file_bytes("unsupported"); -+ } -+ -+ match self.ctx.power_snapshot(self.pci_fd.as_ref()) { -+ Ok(snapshot) => text_file_bytes(snapshot.battery_status()), -+ Err(_) => text_file_bytes("unavailable"), -+ } -+ } -+ -+ fn power_handle(&self, path: &str) -> Result> { -+ let normalized = path.trim_matches('/'); -+ -+ if normalized.is_empty() { -+ return Ok(HandleKind::PowerDir); -+ } -+ if normalized == "status" { -+ return Ok(HandleKind::PowerFile(self.power_status_contents())); -+ } -+ if normalized == "adapter" { -+ return Ok(HandleKind::PowerFile(self.power_adapter_summary_contents())); -+ } -+ if normalized == "battery" { -+ return Ok(HandleKind::PowerFile(self.power_battery_summary_contents())); -+ } -+ 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))?, -+ )), - } - } - } -@@ -184,9 +496,9 @@ - HandleKind::SchemeRoot => { - // TODO: arrayvec - let components = { -- let mut v = arrayvec::ArrayVec::<&str, 3>::new(); -+ let mut v = arrayvec::ArrayVec::<&str, 4>::new(); - let it = path.split('/'); -- for component in it.take(3) { -+ for component in it.take(4) { - v.push(component); - } - -@@ -195,6 +507,25 @@ - - match &*components { - [""] => HandleKind::TopLevel, -+ ["dmi"] => { -+ if flag_dir || flag_stat || path.ends_with('/') { -+ HandleKind::DmiDir -+ } else { -+ HandleKind::Dmi( -+ dmi_contents(self.ctx.dmi_info(), self.ctx.dmi_raw(), "") -+ .ok_or(Error::new(ENOENT))?, -+ ) -+ } -+ } -+ ["dmi", ""] => HandleKind::DmiDir, -+ ["dmi", field] => HandleKind::Dmi( -+ dmi_contents(self.ctx.dmi_info(), self.ctx.dmi_raw(), field) -+ .ok_or(Error::new(ENOENT))?, -+ ), -+ ["power"] => HandleKind::PowerDir, -+ ["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, - -@@ -212,7 +543,7 @@ - } - - ["symbols", symbol] => { -- if let Some(description) = self.ctx.aml_lookup(symbol) { -+ if let Some(description) = self.ctx.aml_lookup(self.pci_fd.as_ref(), symbol) { - HandleKind::Symbol { - name: (*symbol).to_owned(), - description, -@@ -225,6 +556,16 @@ - _ => return Err(Error::new(ENOENT)), - } - } -+ HandleKind::DmiDir => { -+ if path.is_empty() { -+ HandleKind::DmiDir -+ } else { -+ HandleKind::Dmi( -+ dmi_contents(self.ctx.dmi_info(), self.ctx.dmi_raw(), path) -+ .ok_or(Error::new(ENOENT))?, -+ ) -+ } -+ } - HandleKind::Symbols(ref aml_symbols) => { - if let Some(description) = aml_symbols.lookup(path) { - HandleKind::Symbol { -@@ -233,6 +574,23 @@ - } - } else { - 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)), -@@ -309,6 +667,8 @@ - .ok_or(Error::new(EBADFD))? - .as_slice(), - HandleKind::Symbol { description, .. } => description.as_bytes(), -+ HandleKind::Dmi(contents) => contents.as_slice(), -+ HandleKind::PowerFile(contents) => contents.as_slice(), - _ => return Err(Error::new(EINVAL)), - }; - -@@ -328,13 +688,18 @@ - 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) in TOPLEVEL_ENTRIES -+ const TOPLEVEL_ENTRIES: &[(&str, DirentKind)] = &[ -+ ("tables", DirentKind::Directory), -+ ("symbols", DirentKind::Directory), -+ ("dmi", DirentKind::Directory), -+ ("power", DirentKind::Directory), -+ ]; -+ -+ for (idx, (name, kind)) in TOPLEVEL_ENTRIES - .iter() - .enumerate() - .skip(opaque_offset as usize) -@@ -343,7 +708,106 @@ - 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::PowerDir => { -+ 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 power_adapter_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::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, - })?; - } - } -@@ -419,11 +883,11 @@ - }; - - let Ok(aml_name) = AmlName::from_str(&to_aml_format(name)) else { -- log::error!("Failed to convert symbol name: "{name}" to aml name!"); -+ log::error!("Failed to convert symbol name: \"{name}\" to aml name!"); - return Err(Error::new(EBADF)); - }; - -- let Ok(result) = self.ctx.aml_eval(aml_name, args) else { -+ let Ok(result) = self.ctx.aml_eval(self.pci_fd.as_ref(), aml_name, args) else { - return Err(Error::new(EINVAL)); - }; - diff --git a/local/patches/base/P3-acpi-wave12-hardening.patch b/local/patches/base/P3-acpi-wave12-hardening.patch deleted file mode 100644 index b80c197ae4..0000000000 --- a/local/patches/base/P3-acpi-wave12-hardening.patch +++ /dev/null @@ -1,844 +0,0 @@ -diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs -index 94a1eb17..c8919290 100644 ---- a/drivers/acpid/src/acpi.rs -+++ b/drivers/acpid/src/acpi.rs -@@ -52,9 +52,7 @@ impl SdtHeader { - } - } - pub fn length(&self) -> usize { -- self.length -- .try_into() -- .expect("expected usize to be at least 32 bits") -+ self.length as usize - } - } - -@@ -132,6 +130,9 @@ impl Drop for PhysmapGuard { - pub struct Sdt(Arc<[u8]>); - - impl Sdt { -+ // SDT validation is split between parser and caller policy: -+ // - this parser only decides whether a given byte slice is structurally valid, -+ // - callers decide whether rejection is fatal (root [R|X]SDT) or degradable (child tables). - pub fn new(slice: Arc<[u8]>) -> Result { - let header = match plain::from_bytes::(&slice) { - Ok(header) => header, -@@ -233,6 +234,177 @@ impl fmt::Debug for Sdt { - pub struct Dsdt(Sdt); - pub struct Ssdt(Sdt); - -+#[derive(Clone, Copy, Debug)] -+pub enum AmlBootstrapMethod { -+ HwdEnv, -+ X86BiosFallback, -+} -+impl AmlBootstrapMethod { -+ fn as_str(self) -> &'static str { -+ match self { -+ Self::HwdEnv => "hwd RSDP_ADDR/RSDP_SIZE handoff", -+ Self::X86BiosFallback => "x86 BIOS fallback", -+ } -+ } -+} -+ -+#[derive(Clone, Debug)] -+pub struct AmlBootstrap { -+ rsdp_addr: usize, -+ rsdp_size: Option, -+ method: AmlBootstrapMethod, -+} -+impl AmlBootstrap { -+ pub fn from_env() -> Result> { -+ let rsdp_addr = usize::from_str_radix(&std::env::var("RSDP_ADDR")?, 16)?; -+ let rsdp_size = match std::env::var("RSDP_SIZE") { -+ Ok(size) => Some(usize::from_str_radix(&size, 16)?), -+ Err(std::env::VarError::NotPresent) => None, -+ Err(err) => return Err(Box::new(err)), -+ }; -+ -+ Ok(Self { -+ rsdp_addr, -+ rsdp_size, -+ method: AmlBootstrapMethod::HwdEnv, -+ }) -+ } -+ -+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -+ pub fn x86_bios_fallback() -> Result, Box> { -+ if let Some(rsdp_addr) = search_x86_bios_rsdp()? { -+ return Ok(Some(Self { -+ rsdp_addr, -+ rsdp_size: None, -+ method: AmlBootstrapMethod::X86BiosFallback, -+ })); -+ } -+ -+ Ok(None) -+ } -+ -+ #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] -+ pub fn x86_bios_fallback() -> Result, Box> { -+ Ok(None) -+ } -+ -+ pub fn log_bootstrap(&self) { -+ log::info!( -+ "acpid: AML bootstrap via {} (RSDP at {:#X})", -+ self.method.as_str(), -+ self.rsdp_addr -+ ); -+ -+ if let Some(rsdp_size) = self.rsdp_size { -+ log::debug!("acpid: AML bootstrap RSDP_SIZE={:#X}", rsdp_size); -+ } -+ } -+} -+ -+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -+const RSDP_SIGNATURE: &[u8; 8] = b"RSD PTR "; -+ -+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -+fn search_x86_bios_rsdp() -> Result, Box> { -+ let ebda_segment = read_u16_physical(0x40E)?; -+ let ebda_addr = usize::from(ebda_segment) << 4; -+ -+ if ebda_addr != 0 { -+ if let Some(rsdp_addr) = search_rsdp_region(ebda_addr, 1024)? { -+ return Ok(Some(rsdp_addr)); -+ } -+ } -+ -+ search_rsdp_region(0xE0000, 0x20000).map_err(Into::into) -+} -+ -+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -+fn read_u16_physical(physaddr: usize) -> std::io::Result { -+ let start_page = physaddr / PAGE_SIZE * PAGE_SIZE; -+ let page_offset = physaddr % PAGE_SIZE; -+ let map = PhysmapGuard::map(start_page, 1)?; -+ let bytes = map -+ .get(page_offset..page_offset + mem::size_of::()) -+ .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "short BIOS map"))?; -+ -+ Ok(u16::from_le_bytes([bytes[0], bytes[1]])) -+} -+ -+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -+fn search_rsdp_region(physaddr: usize, length: usize) -> std::io::Result> { -+ let start_page = physaddr / PAGE_SIZE * PAGE_SIZE; -+ let page_offset = physaddr % PAGE_SIZE; -+ let mapped_len = page_offset + length; -+ let page_count = mapped_len.div_ceil(PAGE_SIZE); -+ let map = PhysmapGuard::map(start_page, page_count)?; -+ let region = map.get(page_offset..page_offset + length).ok_or_else(|| { -+ std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "short BIOS RSDP search window") -+ })?; -+ -+ for candidate_offset in (0..=length.saturating_sub(20)).step_by(16) { -+ if region -+ .get(candidate_offset..candidate_offset + RSDP_SIGNATURE.len()) -+ != Some(&RSDP_SIGNATURE[..]) -+ { -+ continue; -+ } -+ -+ if rsdp_candidate_valid(®ion[candidate_offset..]) { -+ return Ok(Some(physaddr + candidate_offset)); -+ } -+ } -+ -+ Ok(None) -+} -+ -+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -+fn rsdp_candidate_valid(candidate: &[u8]) -> bool { -+ if candidate.len() < 20 || &candidate[..RSDP_SIGNATURE.len()] != RSDP_SIGNATURE { -+ return false; -+ } -+ -+ if checksum_is_zero(&candidate[..20]).is_err() { -+ return false; -+ } -+ -+ let revision = candidate[15]; -+ if revision < 2 { -+ return true; -+ } -+ -+ if candidate.len() < 36 { -+ return false; -+ } -+ -+ let declared_length = u32::from_le_bytes([candidate[20], candidate[21], candidate[22], candidate[23]]) -+ as usize; -+ if declared_length < 36 || candidate.len() < declared_length { -+ return false; -+ } -+ -+ checksum_is_zero(&candidate[..declared_length]).is_ok() -+} -+ -+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -+fn checksum_is_zero(bytes: &[u8]) -> Result<(), ()> { -+ let checksum = bytes -+ .iter() -+ .copied() -+ .fold(0_u8, |current_sum, item| current_sum.wrapping_add(item)); -+ -+ if checksum == 0 { -+ Ok(()) -+ } else { -+ Err(()) -+ } -+} -+ -+#[derive(Clone, Copy, Debug)] -+struct SleepTypeData { -+ slp_typa: u16, -+ slp_typb: u16, -+} -+ - // Current AML implementation builds the aml_context.namespace at startup, - // but the cache for symbols is lazy-loaded when someone - // reads from the acpi:/symbols scheme. -@@ -245,15 +417,20 @@ pub struct AmlSymbols { - symbol_cache: FxHashMap, - page_cache: Arc>, - aml_region_handlers: Vec<(RegionSpace, Box)>, -+ aml_bootstrap: Option, - } - - impl AmlSymbols { -- pub fn new(aml_region_handlers: Vec<(RegionSpace, Box)>) -> Self { -+ pub fn new( -+ aml_bootstrap: Option, -+ aml_region_handlers: Vec<(RegionSpace, Box)>, -+ ) -> Self { - Self { - aml_context: None, - symbol_cache: FxHashMap::default(), - page_cache: Arc::new(Mutex::new(AmlPageCache::default())), - aml_region_handlers, -+ aml_bootstrap, - } - } - -@@ -264,9 +441,12 @@ impl AmlSymbols { - 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 -- let rsdp_address = usize::from_str_radix(&std::env::var("RSDP_ADDR")?, 16)?; -+ let bootstrap = self -+ .aml_bootstrap -+ .as_ref() -+ .ok_or_else(|| std::io::Error::other("AML bootstrap unavailable"))?; - let tables = -- unsafe { AcpiTables::from_rsdp(handler.clone(), rsdp_address).map_err(format_err)? }; -+ unsafe { AcpiTables::from_rsdp(handler.clone(), bootstrap.rsdp_addr).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(..) { -@@ -316,7 +496,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) - { -@@ -379,6 +559,7 @@ pub struct AcpiContext { - tables: Vec, - dsdt: Option, - fadt: Option, -+ shutdown_s5: RwLock>, - - aml_symbols: RwLock, - -@@ -426,27 +607,56 @@ impl AcpiContext { - - pub fn init( - rxsdt_physaddrs: impl Iterator, -+ aml_bootstrap: Option, - 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"); -- -- log::trace!("TABLE AT {:#>08X}", physaddr); -- -- Sdt::load_from_physical(physaddr).expect("failed to load physical SDT") -- }) -- .collect::>(); -+ // Child-table validation policy: -+ // - checksum/length failures are degradable: warn, skip the table, continue boot, -+ // - malformed FADT is handled separately as "raw-table-only" mode for ACPI control paths, -+ // - MADT subtable interpretation is delegated to consumers, which must skip unknown entry -+ // types instead of treating them as daemon-fatal. -+ let mut tables = Vec::new(); -+ for physaddr in rxsdt_physaddrs { -+ let physaddr: usize = match physaddr.try_into() { -+ Ok(physaddr) => physaddr, -+ Err(_) => { -+ log::warn!( -+ "acpid: skipping ACPI table at {:#X}: physical address out of range", -+ physaddr -+ ); -+ continue; -+ } -+ }; -+ -+ match Sdt::load_from_physical(physaddr) { -+ Ok(table) => { -+ log::debug!( -+ "acpid: accepted ACPI table {} at {:#X}", -+ String::from_utf8_lossy(&table.signature), -+ physaddr -+ ); -+ tables.push(table); -+ } -+ Err(TablePhysLoadError::Validity(InvalidSdtError::BadChecksum)) => { -+ log::warn!( -+ "acpid: skipping ACPI table at {:#X}: checksum validation failed", -+ physaddr -+ ); -+ } -+ Err(err) => { -+ log::warn!("acpid: skipping ACPI table at {:#X}: {}", physaddr, err); -+ } -+ } -+ } - - let mut this = Self { - tables, - dsdt: None, - fadt: None, -+ shutdown_s5: RwLock::new(None), - - // Temporary values -- aml_symbols: RwLock::new(AmlSymbols::new(ec)), -+ aml_symbols: RwLock::new(AmlSymbols::new(aml_bootstrap, ec)), - - next_ctx: RwLock::new(0), - -@@ -581,55 +791,26 @@ impl AcpiContext { - let port = fadt.pm1a_control_block as u16; - let mut val = 1 << 13; - -- let aml_symbols = self.aml_symbols.read(); -- -- 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; -- } -- }; -- -- 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; -+ if self.shutdown_s5.read().is_none() { -+ match self.cache_shutdown_s5_from_ready_aml("existing AML context") { -+ Ok(true) | Ok(false) => {} -+ Err(err) => { -+ log::warn!("acpid: _S5 was not ready at shutdown: {}", err); - } -- }, -- 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; -- } -- }; -+ } - -- let slp_typa = match package[0].deref() { -- acpi::aml::object::Object::Integer(i) => i.to_owned(), -- _ => { -- log::error!("typa is not an Integer"); -- return; -- } -- }; -- let slp_typb = match package[1].deref() { -- acpi::aml::object::Object::Integer(i) => i.to_owned(), -- _ => { -- log::error!("typb is not an Integer"); -- return; -- } -+ let Some(sleep_types) = *self.shutdown_s5.read() else { -+ log::error!("Cannot set S-state, missing derived \\_S5 sleep types"); -+ return; - }; - -- log::trace!("Shutdown SLP_TYPa {:X}, SLP_TYPb {:X}", slp_typa, slp_typb); -- val |= slp_typa as u16; -+ log::trace!( -+ "Shutdown SLP_TYPa {:X}, SLP_TYPb {:X}", -+ sleep_types.slp_typa, -+ sleep_types.slp_typb -+ ); -+ val |= sleep_types.slp_typa; - - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - { -@@ -652,6 +833,86 @@ impl AcpiContext { - core::hint::spin_loop(); - } - } -+ -+ pub fn prime_shutdown_s5(&self, pci_fd: Option<&libredox::Fd>, source: &'static str) { -+ match self.cache_shutdown_s5(pci_fd, source) { -+ Ok(()) => {} -+ Err(err) => { -+ log::warn!("acpid: unable to derive _S5 from {}: {}", source, err); -+ } -+ } -+ } -+ -+ fn cache_shutdown_s5( -+ &self, -+ pci_fd: Option<&libredox::Fd>, -+ source: &'static str, -+ ) -> Result<(), String> { -+ if self.shutdown_s5.read().is_some() { -+ return Ok(()); -+ } -+ -+ let mut aml_symbols = self.aml_symbols.write(); -+ let aml_context = aml_symbols -+ .aml_context_mut(pci_fd) -+ .map_err(|err| format!("AML not ready: {err}"))?; -+ let sleep_types = extract_s5_sleep_types(aml_context)?; -+ -+ *self.shutdown_s5.write() = Some(sleep_types); -+ log::info!("acpid: _S5 derived from {}", source); -+ Ok(()) -+ } -+ -+ fn cache_shutdown_s5_from_ready_aml(&self, source: &'static str) -> Result { -+ if self.shutdown_s5.read().is_some() { -+ return Ok(true); -+ } -+ -+ let aml_symbols = self.aml_symbols.read(); -+ let Some(aml_context) = aml_symbols.aml_context.as_ref() else { -+ return Ok(false); -+ }; -+ -+ let sleep_types = extract_s5_sleep_types(aml_context)?; -+ drop(aml_symbols); -+ -+ *self.shutdown_s5.write() = Some(sleep_types); -+ log::info!("acpid: _S5 derived from {}", source); -+ Ok(true) -+ } -+} -+ -+fn extract_s5_sleep_types( -+ aml_context: &Interpreter, -+) -> Result { -+ let s5_aml_name = acpi::aml::namespace::AmlName::from_str("\\_S5") -+ .map_err(|error| format!("failed to build \\_S5 name: {error:?}"))?; -+ let s5 = aml_context -+ .namespace -+ .lock() -+ .get(s5_aml_name) -+ .map_err(|error| format!("missing \\_S5: {error:?}"))?; -+ let package = match s5.deref() { -+ acpi::aml::object::Object::Package(package) => package, -+ _ => return Err("\\_S5 is not a package".into()), -+ }; -+ -+ let slp_typa = extract_sleep_type(package.get(0), "SLP_TYPa")?; -+ let slp_typb = extract_sleep_type(package.get(1), "SLP_TYPb")?; -+ -+ Ok(SleepTypeData { slp_typa, slp_typb }) -+} -+ -+fn extract_sleep_type(value: Option<&WrappedObject>, label: &'static str) -> Result { -+ let Some(value) = value else { -+ return Err(format!("missing {label} in \\_S5 package")); -+ }; -+ -+ match value.deref() { -+ acpi::aml::object::Object::Integer(i) => u16::try_from(*i) -+ .map_err(|_| format!("{label} out of range for PM1 control register")), -+ _ => Err(format!("{label} is not an Integer")), -+ } - } - - #[repr(C, packed)] -@@ -760,45 +1021,66 @@ impl Deref for Fadt { - type Target = FadtStruct; - - fn deref(&self) -> &Self::Target { -- plain::from_bytes::(&self.0 .0) -- .expect("expected FADT struct to already be validated in Deref impl") -+ match plain::from_bytes::(&self.0 .0) { -+ Ok(fadt) => fadt, -+ Err(plain::Error::TooShort) => unreachable!( -+ "Fadt::new validates the minimum FADT size before constructing Fadt" -+ ), -+ Err(plain::Error::BadAlignment) => unreachable!( -+ "plain::from_bytes reported bad alignment, but FadtStruct is #[repr(packed)]" -+ ), -+ } - } - } - - impl Fadt { - pub fn new(sdt: Sdt) -> Option { -- if sdt.signature != *b"FACP" || sdt.length() < mem::size_of::() { -+ if sdt.signature != *b"FACP" || sdt.length() < mem::size_of::() { - return None; - } - Some(Fadt(sdt)) - } - - pub fn init(context: &mut AcpiContext) { -- let fadt_sdt = context -- .take_single_sdt(*b"FACP") -- .expect("expected ACPI to always have a FADT"); -+ // FADT policy: this table is mandatory for ACPI control services such as shutdown/reboot. -+ // If it is missing or malformed, acpid stays alive for diagnostics/raw tables but degrades -+ // into raw-table-only mode instead of crashing the boot. -+ let Some(fadt_sdt) = context.take_single_sdt(*b"FACP") else { -+ log::error!("acpid: missing FADT; booting without ACPI control services"); -+ return; -+ }; - - let fadt = match Fadt::new(fadt_sdt) { - Some(fadt) => fadt, - None => { -- log::error!("Failed to find FADT"); -+ log::error!("acpid: corrupt FADT; booting without ACPI control services"); - return; - } - }; - - let dsdt_ptr = match fadt.acpi_2_struct() { -- Some(fadt2) => usize::try_from(fadt2.x_dsdt).unwrap_or_else(|_| { -- usize::try_from(fadt.dsdt).expect("expected any given u32 to fit within usize") -- }), -- None => usize::try_from(fadt.dsdt).expect("expected any given u32 to fit within usize"), -+ Some(fadt2) if fadt2.x_dsdt != 0 => match usize::try_from(fadt2.x_dsdt) { -+ Ok(dsdt_ptr) => dsdt_ptr, -+ Err(_) => { -+ log::warn!( -+ "acpid: x_dsdt address out of range; falling back to 32-bit DSDT pointer" -+ ); -+ fadt.dsdt as usize -+ } -+ }, -+ _ => fadt.dsdt as usize, - }; - - log::debug!("FACP at {:X}", { dsdt_ptr }); - -- let dsdt_sdt = match Sdt::load_from_physical(fadt.dsdt as usize) { -+ let dsdt_sdt = match Sdt::load_from_physical(dsdt_ptr) { - Ok(dsdt) => dsdt, - Err(error) => { -- log::error!("Failed to load DSDT: {}", error); -+ log::error!( -+ "acpid: corrupt FADT/DSDT linkage (DSDT at {:#X}): booting without ACPI control services: {}", -+ dsdt_ptr, -+ error -+ ); - return; - } - }; -diff --git a/drivers/acpid/src/main.rs b/drivers/acpid/src/main.rs -index 059254b3..25566553 100644 ---- a/drivers/acpid/src/main.rs -+++ b/drivers/acpid/src/main.rs -@@ -3,6 +3,7 @@ use std::fs::File; - use std::mem; - use std::ops::ControlFlow; - use std::os::unix::io::AsRawFd; -+use std::process; - use std::sync::Arc; - - use ::acpi::aml::op_region::{RegionHandler, RegionSpace}; -@@ -28,94 +29,206 @@ fn daemon(daemon: daemon::Daemon) -> ! { - - log::info!("acpid start"); - -- let rxsdt_raw_data: Arc<[u8]> = std::fs::read("/scheme/kernel.acpi/rxsdt") -- .expect("acpid: failed to read `/scheme/kernel.acpi/rxsdt`") -- .into(); -+ let rxsdt_raw_data: Arc<[u8]> = match std::fs::read("/scheme/kernel.acpi/rxsdt") { -+ Ok(data) => data.into(), -+ Err(err) => { -+ log::error!("acpid: failed to read `/scheme/kernel.acpi/rxsdt`: {}", err); -+ process::exit(1); -+ } -+ }; - - if rxsdt_raw_data.is_empty() { - log::info!("System doesn't use ACPI"); - daemon.ready(); -- std::process::exit(0); -+ process::exit(0); - } - -- let sdt = self::acpi::Sdt::new(rxsdt_raw_data).expect("acpid: failed to parse [RX]SDT"); -+ // Root-table policy: if the kernel-provided [R|X]SDT is malformed, acpid cannot enumerate any -+ // firmware tables at all. That is fatal to this daemon, but it must fail with a logged exit -+ // rather than a panic on malformed firmware input. -+ let sdt = match self::acpi::Sdt::new(rxsdt_raw_data) { -+ Ok(sdt) => sdt, -+ Err(err) => { -+ log::error!("acpid: failed to parse kernel [R|X]SDT: {}", err); -+ process::exit(1); -+ } -+ }; -+ -+ // AML bootstrap contract: -+ // - preferred path: RSDP_ADDR[/RSDP_SIZE] inherited into acpid by the boot path, -+ // - x86 fallback: bounded BIOS RSDP search when that explicit handoff is absent or unusable. -+ let aml_bootstrap = match self::acpi::AmlBootstrap::from_env() { -+ Ok(bootstrap) => { -+ bootstrap.log_bootstrap(); -+ Some(bootstrap) -+ } -+ Err(err) => { -+ log::warn!( -+ "acpid: explicit AML bootstrap handoff unavailable ({}); trying x86 BIOS fallback", -+ err -+ ); - -- let mut thirty_two_bit; -- let mut sixty_four_bit; -+ match self::acpi::AmlBootstrap::x86_bios_fallback() { -+ Ok(Some(bootstrap)) => { -+ bootstrap.log_bootstrap(); -+ Some(bootstrap) -+ } -+ Ok(None) => { -+ log::warn!( -+ "acpid: AML bootstrap unavailable; continuing without AML-backed ACPI services" -+ ); -+ None -+ } -+ Err(err) => { -+ log::warn!( -+ "acpid: x86 BIOS AML bootstrap fallback failed ({}); continuing without AML-backed ACPI services", -+ err -+ ); -+ None -+ } -+ } -+ } -+ }; - -- let physaddrs_iter = match &sdt.signature { -+ let physaddrs = 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); -- -- &mut thirty_two_bit as &mut dyn Iterator -+ let chunks = sdt.data().chunks_exact(mem::size_of::()); -+ if !chunks.remainder().is_empty() { -+ log::error!("acpid: malformed RSDT payload length {}", sdt.data().len()); -+ process::exit(1); -+ } -+ -+ chunks -+ .map(|chunk| { -+ let chunk = <[u8; mem::size_of::()]>::try_from(chunk) -+ .map_err(|_| "invalid 32-bit RSDT entry width")?; -+ Ok(u64::from(u32::from_le_bytes(chunk))) -+ }) -+ .collect::, &str>>() - } - 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 chunks = sdt.data().chunks_exact(mem::size_of::()); -+ if !chunks.remainder().is_empty() { -+ log::error!("acpid: malformed XSDT payload length {}", sdt.data().len()); -+ process::exit(1); -+ } - -- &mut sixty_four_bit as &mut dyn Iterator -+ chunks -+ .map(|chunk| { -+ let chunk = <[u8; mem::size_of::()]>::try_from(chunk) -+ .map_err(|_| "invalid 64-bit XSDT entry width")?; -+ Ok(u64::from_le_bytes(chunk)) -+ }) -+ .collect::, &str>>() -+ } -+ _ => { -+ log::error!( -+ "acpid: expected kernel root table to be RSDT or XSDT, got {}", -+ String::from_utf8_lossy(&sdt.signature) -+ ); -+ process::exit(1); -+ } -+ }; -+ let physaddrs = match physaddrs { -+ Ok(physaddrs) => physaddrs, -+ Err(err) => { -+ log::error!("acpid: failed to decode root table pointers: {}", err); -+ process::exit(1); - } -- _ => panic!("acpid: expected [RX]SDT from kernel to be either of those"), - }; - - 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 acpi_context = self::acpi::AcpiContext::init(physaddrs.into_iter(), aml_bootstrap, region_handlers); - - // 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"); -+ if let Err(err) = common::acquire_port_io_rights() { -+ log::error!( -+ "acpid: failed to set I/O privilege level to Ring 3: {}", -+ err -+ ); -+ process::exit(1); -+ } - -- let shutdown_pipe = File::open("/scheme/kernel.acpi/kstop") -- .expect("acpid: failed to open `/scheme/kernel.acpi/kstop`"); -+ let shutdown_pipe = match File::open("/scheme/kernel.acpi/kstop") { -+ Ok(file) => file, -+ Err(err) => { -+ log::error!("acpid: failed to open `/scheme/kernel.acpi/kstop`: {}", err); -+ process::exit(1); -+ } -+ }; - -- 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 = match RawEventQueue::new() { -+ Ok(event_queue) => event_queue, -+ Err(err) => { -+ log::error!("acpid: failed to create event queue: {}", err); -+ process::exit(1); -+ } -+ }; -+ let socket = match Socket::nonblock() { -+ Ok(socket) => socket, -+ Err(err) => { -+ log::error!("acpid: failed to create acpi scheme socket: {}", err); -+ process::exit(1); -+ } -+ }; - - 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"); -- event_queue -- .subscribe(socket.inner().raw(), 1, EventFlags::READ) -- .expect("acpid: failed to register scheme socket for event queue"); -+ if let Err(err) = event_queue.subscribe(shutdown_pipe.as_raw_fd() as usize, 0, EventFlags::READ) -+ { -+ log::error!( -+ "acpid: failed to register shutdown pipe for event queue: {}", -+ err -+ ); -+ process::exit(1); -+ } -+ if let Err(err) = event_queue.subscribe(socket.inner().raw(), 1, EventFlags::READ) { -+ log::error!( -+ "acpid: failed to register scheme socket for event queue: {}", -+ err -+ ); -+ process::exit(1); -+ } - -- register_sync_scheme(&socket, "acpi", &mut scheme) -- .expect("acpid: failed to register acpi scheme to namespace"); -+ if let Err(err) = register_sync_scheme(&socket, "acpi", &mut scheme) { -+ log::error!("acpid: failed to register acpi scheme to namespace: {}", err); -+ process::exit(1); -+ } - - daemon.ready(); - -- libredox::call::setrens(0, 0).expect("acpid: failed to enter null namespace"); -+ if let Err(err) = libredox::call::setrens(0, 0) { -+ log::error!("acpid: failed to enter null namespace: {}", err); -+ process::exit(1); -+ } - - let mut mounted = true; - while mounted { -- let Some(event) = event_queue -- .next() -- .transpose() -- .expect("acpid: failed to read event file") -- else { -+ let event = match event_queue.next().transpose() { -+ Ok(event) => event, -+ Err(err) => { -+ log::error!("acpid: failed to read event file: {}", err); -+ process::exit(1); -+ } -+ }; -+ let Some(event) = event else { - break; - }; - - if event.fd == socket.inner().raw() { - loop { -- match handler -- .process_requests_nonblocking(&mut scheme) -- .expect("acpid: failed to process requests") -- { -+ match match handler.process_requests_nonblocking(&mut scheme) { -+ Ok(flow) => flow, -+ Err(err) => { -+ log::error!("acpid: failed to process requests: {}", err); -+ process::exit(1); -+ } -+ } { - ControlFlow::Continue(()) => {} - ControlFlow::Break(()) => break, - } -diff --git a/drivers/acpid/src/scheme.rs b/drivers/acpid/src/scheme.rs -index 5a5040c3..6e57624a 100644 ---- a/drivers/acpid/src/scheme.rs -+++ b/drivers/acpid/src/scheme.rs -@@ -474,6 +474,8 @@ impl SchemeSync for AcpiScheme<'_, '_> { - return Err(Error::new(EINVAL)); - } else { - self.pci_fd = Some(new_fd); -+ self.ctx -+ .prime_shutdown_s5(self.pci_fd.as_ref(), "PCI-backed AML handoff"); - } - - Ok(num_fds) diff --git a/local/patches/base/P3-pcid-aer-scheme.patch b/local/patches/base/P3-pcid-aer-scheme.patch deleted file mode 100644 index 9e38251895..0000000000 --- a/local/patches/base/P3-pcid-aer-scheme.patch +++ /dev/null @@ -1,398 +0,0 @@ ---- a/drivers/pcid/src/cfg_access/mod.rs -+++ b/drivers/pcid/src/cfg_access/mod.rs -@@ -349,7 +349,11 @@ - let bus_addr = self.bus_addr(address.segment(), address.bus())?; - Some(unsafe { bus_addr.add(Self::bus_addr_offset_in_dwords(address, offset)) }) - } -+ -+ pub fn has_extended_config(&self, address: PciAddress) -> bool { -+ self.mmio_addr(address, 0x100).is_some() -+ } - } - - impl ConfigRegionAccess for Pcie { ---- a/drivers/pcid/src/scheme.rs -+++ b/drivers/pcid/src/scheme.rs -@@ -5,12 +5,61 @@ - use redox_scheme::{CallerCtx, OpenResult}; - use scheme_utils::HandleMap; - use syscall::dirent::{DirEntry, DirentBuf, DirentKind}; --use syscall::error::{Error, Result, EACCES, EBADF, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR, EALREADY}; -+use syscall::error::{ -+ Error, Result, EACCES, EALREADY, EBADF, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR, EROFS, -+}; - use syscall::flag::{MODE_CHR, MODE_DIR, O_DIRECTORY, O_STAT}; - use syscall::schemev2::NewFdFlags; - use syscall::ENOLCK; - - use crate::cfg_access::Pcie; -+ -+const PCIE_EXTENDED_CAPABILITY_AER: u16 = 0x0001; -+ -+#[derive(Clone, Copy)] -+enum AerRegisterName { -+ UncorStatus, -+ UncorMask, -+ UncorSeverity, -+ CorStatus, -+ CorMask, -+ Cap, -+ HeaderLog, -+} -+ -+impl AerRegisterName { -+ fn from_path(path: &str) -> Option { -+ Some(match path { -+ "uncor_status" => Self::UncorStatus, -+ "uncor_mask" => Self::UncorMask, -+ "uncor_severity" => Self::UncorSeverity, -+ "cor_status" => Self::CorStatus, -+ "cor_mask" => Self::CorMask, -+ "cap" => Self::Cap, -+ "header_log" => Self::HeaderLog, -+ _ => return None, -+ }) -+ } -+ -+ const fn offset(self) -> u16 { -+ match self { -+ Self::UncorStatus => 0x00, -+ Self::UncorMask => 0x04, -+ Self::UncorSeverity => 0x08, -+ Self::CorStatus => 0x0C, -+ Self::CorMask => 0x10, -+ Self::Cap => 0x14, -+ Self::HeaderLog => 0x18, -+ } -+ } -+ -+ const fn len(self) -> usize { -+ match self { -+ Self::HeaderLog => 16, -+ _ => 4, -+ } -+ } -+} - - pub struct PciScheme { - handles: HandleMap, -@@ -20,13 +69,27 @@ - binds: HashMap, - } - enum Handle { -- TopLevel { entries: Vec }, -+ TopLevel { -+ entries: Vec, -+ }, - Access, -- Device, -- Channel { addr: PciAddress, st: ChannelState }, -+ Device { -+ addr: PciAddress, -+ }, -+ Channel { -+ addr: PciAddress, -+ st: ChannelState, -+ }, - SchemeRoot, - /// Represents an open handle to a device's bind endpoint -- Bind { addr: PciAddress }, -+ Bind { -+ addr: PciAddress, -+ }, -+ AerDir, -+ Aer { -+ addr: PciAddress, -+ register: AerRegisterName, -+ }, - /// Uevent surface for hotplug consumers. Opening uevent returns an object - /// from which device add/remove events can be read. Since pcid currently - /// only scans at startup, this surface is ready for hotplug polling consumers. -@@ -38,13 +101,23 @@ - } - impl Handle { - fn is_file(&self) -> bool { -- matches!(self, Self::Access | Self::Channel { .. } | Self::Bind { .. } | Self::Uevent) -+ matches!( -+ self, -+ Self::Access -+ | Self::Channel { .. } -+ | Self::Bind { .. } -+ | Self::Aer { .. } -+ | Self::Uevent -+ ) - } - fn is_dir(&self) -> bool { - !self.is_file() - } - fn requires_root(&self) -> bool { -- matches!(self, Self::Access | Self::Channel { .. } | Self::Bind { .. }) -+ matches!( -+ self, -+ Self::Access | Self::Channel { .. } | Self::Bind { .. } -+ ) - } - fn is_scheme_root(&self) -> bool { - matches!(self, Self::SchemeRoot) -@@ -57,6 +130,16 @@ - } - - const DEVICE_CONTENTS: &[&str] = &["channel", "bind"]; -+const DEVICE_AER_CONTENTS: &[&str] = &["channel", "bind", "aer"]; -+const AER_CONTENTS: &[&str] = &[ -+ "uncor_status", -+ "uncor_mask", -+ "uncor_severity", -+ "cor_status", -+ "cor_mask", -+ "cap", -+ "header_log", -+]; - - impl PciScheme { - pub fn access(&mut self) -> usize { -@@ -141,7 +224,12 @@ - - let (len, mode) = match handle.inner { - Handle::TopLevel { ref entries } => (entries.len(), MODE_DIR | 0o755), -- Handle::Device => (DEVICE_CONTENTS.len(), MODE_DIR | 0o755), -+ Handle::Device { addr } => ( -+ Self::device_entries(&self.pcie, addr).len(), -+ MODE_DIR | 0o755, -+ ), -+ Handle::AerDir => (AER_CONTENTS.len(), MODE_DIR | 0o755), -+ Handle::Aer { register, .. } => (register.len(), MODE_CHR | 0o444), - Handle::Access | Handle::Channel { .. } | Handle::Bind { .. } => (0, MODE_CHR | 0o600), - Handle::Uevent => (0, MODE_CHR | 0o644), - Handle::SchemeRoot => return Err(Error::new(EBADF)), -@@ -154,7 +242,7 @@ - &mut self, - id: usize, - buf: &mut [u8], -- _offset: u64, -+ offset: u64, - _fcntl_flags: u32, - _ctx: &CallerCtx, - ) -> Result { -@@ -166,11 +254,14 @@ - - match handle.inner { - Handle::TopLevel { .. } => Err(Error::new(EISDIR)), -- Handle::Device => Err(Error::new(EISDIR)), -+ Handle::Device { .. } | Handle::AerDir => Err(Error::new(EISDIR)), - Handle::Channel { - addr: _, - ref mut st, - } => Self::read_channel(st, buf), -+ Handle::Aer { addr, register } => { -+ Self::read_aer_register(&self.pcie, addr, register, buf, offset) -+ } - Handle::Uevent => { - // Uevent surface is ready for hotplug polling consumers. - // pcid currently only scans at startup, so return empty (EAGAIN would indicate no data available). -@@ -209,8 +300,15 @@ - } - return Ok(buf); - } -- Handle::Device => DEVICE_CONTENTS, -- Handle::Access | Handle::Channel { .. } | Handle::Bind { .. } | Handle::Uevent => return Err(Error::new(ENOTDIR)), -+ Handle::Device { addr } => Self::device_entries(&self.pcie, addr), -+ Handle::AerDir => AER_CONTENTS, -+ Handle::Access -+ | Handle::Channel { .. } -+ | Handle::Bind { .. } -+ | Handle::Aer { .. } -+ | Handle::Uevent => { -+ return Err(Error::new(ENOTDIR)); -+ } - Handle::SchemeRoot => return Err(Error::new(EBADF)), - }; - -@@ -243,6 +341,7 @@ - Handle::Channel { addr, ref mut st } => { - Self::write_channel(&self.pcie, &mut self.tree, addr, st, buf) - } -+ Handle::Aer { .. } => Err(Error::new(EROFS)), - - _ => Err(Error::new(EBADF)), - } -@@ -357,45 +456,151 @@ - binds: HashMap::new(), - } - } -- fn parse_after_pci_addr(&mut self, addr: PciAddress, after: &str, ctx: &CallerCtx) -> Result { -+ fn device_entries(pcie: &Pcie, addr: PciAddress) -> &'static [&'static str] { -+ if Self::find_pcie_extended_capability(pcie, addr, PCIE_EXTENDED_CAPABILITY_AER).is_some() { -+ DEVICE_AER_CONTENTS -+ } else { -+ DEVICE_CONTENTS -+ } -+ } -+ fn find_pcie_extended_capability( -+ pcie: &Pcie, -+ addr: PciAddress, -+ capability_id: u16, -+ ) -> Option { -+ if !pcie.has_extended_config(addr) { -+ return None; -+ } -+ -+ let mut offset = 0x100_u16; -+ -+ while offset <= 0xFFC { -+ let header = unsafe { pcie.read(addr, offset) }; -+ if header == 0 || header == u32::MAX { -+ return None; -+ } -+ -+ if (header & 0xFFFF) as u16 == capability_id { -+ return Some(offset); -+ } -+ -+ let next = ((header >> 20) & 0xFFF) as u16; -+ if next < 0x100 || next <= offset || next > 0xFFC || next % 4 != 0 { -+ return None; -+ } -+ offset = next; -+ } -+ -+ None -+ } -+ fn read_file_bytes(data: &[u8], buf: &mut [u8], offset: u64) -> Result { -+ let Ok(offset) = usize::try_from(offset) else { -+ return Ok(0); -+ }; -+ if offset >= data.len() { -+ return Ok(0); -+ } -+ -+ let count = std::cmp::min(buf.len(), data.len() - offset); -+ buf[..count].copy_from_slice(&data[offset..offset + count]); -+ Ok(count) -+ } -+ fn read_aer_register( -+ pcie: &Pcie, -+ addr: PciAddress, -+ register: AerRegisterName, -+ buf: &mut [u8], -+ offset: u64, -+ ) -> Result { -+ let Some(aer_base) = -+ Self::find_pcie_extended_capability(pcie, addr, PCIE_EXTENDED_CAPABILITY_AER) -+ else { -+ return Err(Error::new(ENOENT)); -+ }; -+ -+ let mut data = [0_u8; 16]; -+ for (index, chunk) in data[..register.len()].chunks_exact_mut(4).enumerate() { -+ let index = u16::try_from(index).map_err(|_| Error::new(EIO))?; -+ let value = unsafe { pcie.read(addr, aer_base + register.offset() + index * 4) }; -+ chunk.copy_from_slice(&value.to_le_bytes()); -+ } -+ -+ Self::read_file_bytes(&data[..register.len()], buf, offset) -+ } -+ fn parse_after_pci_addr( -+ &mut self, -+ addr: PciAddress, -+ after: &str, -+ ctx: &CallerCtx, -+ ) -> Result { - if after.chars().next().map_or(false, |c| c != '/') { - return Err(Error::new(ENOENT)); - } - let func = self.tree.get_mut(&addr).ok_or(Error::new(ENOENT))?; - - Ok(if after.is_empty() { -- Handle::Device -+ Handle::Device { addr } - } else { - let path = &after[1..]; - -- match path { -- "channel" => { -- if func.enabled { -- return Err(Error::new(ENOLCK)); -+ if path == "aer" { -+ if Self::find_pcie_extended_capability( -+ &self.pcie, -+ addr, -+ PCIE_EXTENDED_CAPABILITY_AER, -+ ) -+ .is_none() -+ { -+ return Err(Error::new(ENOENT)); -+ } -+ Handle::AerDir -+ } else if let Some(register_name) = path.strip_prefix("aer/") { -+ let register = -+ AerRegisterName::from_path(register_name).ok_or(Error::new(ENOENT))?; -+ if Self::find_pcie_extended_capability( -+ &self.pcie, -+ addr, -+ PCIE_EXTENDED_CAPABILITY_AER, -+ ) -+ .is_none() -+ { -+ return Err(Error::new(ENOENT)); -+ } -+ Handle::Aer { addr, register } -+ } else { -+ match path { -+ "channel" => { -+ if func.enabled { -+ return Err(Error::new(ENOLCK)); -+ } -+ func.inner.legacy_interrupt_line = crate::enable_function( -+ &self.pcie, -+ &mut func.endpoint_header, -+ &mut func.capabilities, -+ ); -+ func.enabled = true; -+ Handle::Channel { -+ addr, -+ st: ChannelState::AwaitingData, -+ } - } -- func.inner.legacy_interrupt_line = crate::enable_function( -- &self.pcie, -- &mut func.endpoint_header, -- &mut func.capabilities, -- ); -- func.enabled = true; -- Handle::Channel { -- addr, -- st: ChannelState::AwaitingData, -+ "bind" => { -+ let addr_str = format!("{}", addr); -+ if let Some(&owner_pid) = self.binds.get(&addr_str) { -+ log::info!( -+ "pcid: device {} already bound by pid {}", -+ addr_str, -+ owner_pid -+ ); -+ return Err(Error::new(EALREADY)); -+ } -+ let caller_pid = u32::try_from(ctx.pid).map_err(|_| Error::new(EINVAL))?; -+ self.binds.insert(addr_str.clone(), caller_pid); -+ log::info!("pcid: device {} bound by pid {}", addr_str, caller_pid); -+ Handle::Bind { addr } - } -- } -- "bind" => { -- let addr_str = format!("{}", addr); -- if let Some(&owner_pid) = self.binds.get(&addr_str) { -- log::info!("pcid: device {} already bound by pid {}", addr_str, owner_pid); -- return Err(Error::new(EALREADY)); -- } -- let caller_pid = ctx.pid; -- self.binds.insert(addr_str.clone(), caller_pid); -- log::info!("pcid: device {} bound by pid {}", addr_str, caller_pid); -- Handle::Bind { addr } -- } -- _ => return Err(Error::new(ENOENT)), -+ _ => return Err(Error::new(ENOENT)), -+ } - } - }) - } diff --git a/local/patches/base/P3-pcid-bind-scheme.patch b/local/patches/base/P3-pcid-bind-scheme.patch deleted file mode 100644 index 1cdf39f221..0000000000 --- a/local/patches/base/P3-pcid-bind-scheme.patch +++ /dev/null @@ -1,182 +0,0 @@ -diff --git a/drivers/pcid/src/scheme.rs b/drivers/pcid/src/scheme.rs -index bb9f39a3..06be6267 100644 ---- a/drivers/pcid/src/scheme.rs -+++ b/drivers/pcid/src/scheme.rs -@@ -1,11 +1,11 @@ --use std::collections::{BTreeMap, VecDeque}; -+use std::collections::{BTreeMap, HashMap, VecDeque}; - - use pci_types::{ConfigRegionAccess, PciAddress}; - use redox_scheme::scheme::SchemeSync; - use redox_scheme::{CallerCtx, OpenResult}; - use scheme_utils::HandleMap; - use syscall::dirent::{DirEntry, DirentBuf, DirentKind}; --use syscall::error::{Error, Result, EACCES, EBADF, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR}; -+use syscall::error::{Error, Result, EACCES, EBADF, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR, EALREADY}; - use syscall::flag::{MODE_CHR, MODE_DIR, O_DIRECTORY, O_STAT}; - use syscall::schemev2::NewFdFlags; - use syscall::ENOLCK; -@@ -16,6 +16,8 @@ pub struct PciScheme { - handles: HandleMap, - pub pcie: Pcie, - pub tree: BTreeMap, -+ /// Maps device address string (e.g. "0000:00:14.0") to owning PID -+ binds: HashMap, - } - enum Handle { - TopLevel { entries: Vec }, -@@ -23,6 +25,12 @@ enum Handle { - Device, - Channel { addr: PciAddress, st: ChannelState }, - SchemeRoot, -+ /// Represents an open handle to a device's bind endpoint -+ Bind { addr: PciAddress }, -+ /// Uevent surface for hotplug consumers. Opening uevent returns an object -+ /// from which device add/remove events can be read. Since pcid currently -+ /// only scans at startup, this surface is ready for hotplug polling consumers. -+ Uevent, - } - struct HandleWrapper { - inner: Handle, -@@ -30,14 +38,13 @@ struct HandleWrapper { - } - impl Handle { - fn is_file(&self) -> bool { -- matches!(self, Self::Access | Self::Channel { .. }) -+ matches!(self, Self::Access | Self::Channel { .. } | Self::Bind { .. } | Self::Uevent) - } - 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::Channel { .. } | Self::Bind { .. }) - } - fn is_scheme_root(&self) -> bool { - matches!(self, Self::SchemeRoot) -@@ -49,7 +56,7 @@ enum ChannelState { - AwaitingResponseRead(VecDeque), - } - --const DEVICE_CONTENTS: &[&str] = &["channel"]; -+const DEVICE_CONTENTS: &[&str] = &["channel", "bind"]; - - impl PciScheme { - pub fn access(&mut self) -> usize { -@@ -88,22 +95,25 @@ impl SchemeSync for PciScheme { - let path = path.trim_matches('/'); - - let handle = if path.is_empty() { -- Handle::TopLevel { -- entries: self -- .tree -- .iter() -- // FIXME remove replacement of : once the old scheme format is no longer supported. -- .map(|(addr, _)| format!("{}", addr).replace(':', "--")) -- .collect::>(), -- } -+ let mut entries: Vec = self -+ .tree -+ .iter() -+ // FIXME remove replacement of : once the old scheme format is no longer supported. -+ .map(|(addr, _)| format!("{}", addr).replace(':', "--")) -+ .collect(); -+ entries.push(String::from("uevent")); -+ entries.push(String::from("access")); -+ Handle::TopLevel { entries } - } else if path == "access" { - Handle::Access -+ } else if path == "uevent" { -+ Handle::Uevent - } else { - let idx = path.find('/').unwrap_or(path.len()); - let (addr_str, after) = path.split_at(idx); - let addr = parse_pci_addr(addr_str).ok_or(Error::new(ENOENT))?; - -- self.parse_after_pci_addr(addr, after)? -+ self.parse_after_pci_addr(addr, after, ctx)? - }; - - let stat = flags & O_STAT != 0; -@@ -132,7 +142,8 @@ 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::Access | Handle::Channel { .. } => (0, MODE_CHR | 0o600), -+ Handle::Access | Handle::Channel { .. } | Handle::Bind { .. } => (0, MODE_CHR | 0o600), -+ Handle::Uevent => (0, MODE_CHR | 0o644), - Handle::SchemeRoot => return Err(Error::new(EBADF)), - }; - stat.st_size = len as u64; -@@ -160,7 +171,13 @@ impl SchemeSync for PciScheme { - addr: _, - ref mut st, - } => Self::read_channel(st, buf), -- Handle::SchemeRoot => Err(Error::new(EBADF)), -+ Handle::Uevent => { -+ // Uevent surface is ready for hotplug polling consumers. -+ // pcid currently only scans at startup, so return empty (EAGAIN would indicate no data available). -+ // Consumers can poll and re-read to check for new events. -+ Ok(0) -+ } -+ Handle::SchemeRoot | Handle::Bind { .. } => Err(Error::new(EBADF)), - _ => Err(Error::new(EBADF)), - } - } -@@ -193,7 +210,7 @@ impl SchemeSync for PciScheme { - return Ok(buf); - } - Handle::Device => DEVICE_CONTENTS, -- Handle::Access | Handle::Channel { .. } => return Err(Error::new(ENOTDIR)), -+ Handle::Access | Handle::Channel { .. } | Handle::Bind { .. } | Handle::Uevent => return Err(Error::new(ENOTDIR)), - Handle::SchemeRoot => return Err(Error::new(EBADF)), - }; - -@@ -316,6 +333,16 @@ impl SchemeSync for PciScheme { - func.enabled = false; - } - } -+ Some(HandleWrapper { -+ inner: Handle::Bind { addr }, -+ .. -+ }) => { -+ let addr_str = format!("{}", addr); -+ if let Some(&owner_pid) = self.binds.get(&addr_str) { -+ log::info!("pcid: device {} unbound by pid {}", addr_str, owner_pid); -+ } -+ self.binds.remove(&addr_str); -+ } - _ => {} - } - } -@@ -327,9 +354,10 @@ impl PciScheme { - handles: HandleMap::new(), - pcie, - tree: BTreeMap::new(), -+ binds: HashMap::new(), - } - } -- fn parse_after_pci_addr(&mut self, addr: PciAddress, after: &str) -> Result { -+ fn parse_after_pci_addr(&mut self, addr: PciAddress, after: &str, ctx: &CallerCtx) -> Result { - if after.chars().next().map_or(false, |c| c != '/') { - return Err(Error::new(ENOENT)); - } -@@ -356,6 +384,17 @@ impl PciScheme { - st: ChannelState::AwaitingData, - } - } -+ "bind" => { -+ let addr_str = format!("{}", addr); -+ if let Some(&owner_pid) = self.binds.get(&addr_str) { -+ log::info!("pcid: device {} already bound by pid {}", addr_str, owner_pid); -+ return Err(Error::new(EALREADY)); -+ } -+ let caller_pid = ctx.pid; -+ self.binds.insert(addr_str.clone(), caller_pid); -+ log::info!("pcid: device {} bound by pid {}", addr_str, caller_pid); -+ Handle::Bind { addr } -+ } - _ => return Err(Error::new(ENOENT)), - } - }) diff --git a/local/patches/base/P3-pcid-uevent-format-fix.patch b/local/patches/base/P3-pcid-uevent-format-fix.patch deleted file mode 100644 index 90bb89aa1c..0000000000 --- a/local/patches/base/P3-pcid-uevent-format-fix.patch +++ /dev/null @@ -1,479 +0,0 @@ -diff --git a/drivers/pcid/src/scheme.rs b/drivers/pcid/src/scheme.rs -index bb9f39a3..b6f8711e 100644 ---- a/drivers/pcid/src/scheme.rs -+++ b/drivers/pcid/src/scheme.rs -@@ -1,28 +1,100 @@ --use std::collections::{BTreeMap, VecDeque}; -+use std::collections::{BTreeMap, HashMap, VecDeque}; -+use std::fmt::Write; - - use pci_types::{ConfigRegionAccess, PciAddress}; - use redox_scheme::scheme::SchemeSync; - use redox_scheme::{CallerCtx, OpenResult}; - use scheme_utils::HandleMap; - use syscall::dirent::{DirEntry, DirentBuf, DirentKind}; --use syscall::error::{Error, Result, EACCES, EBADF, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR}; -+use syscall::error::{ -+ Error, Result, EACCES, EALREADY, EBADF, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR, EROFS, -+}; - use syscall::flag::{MODE_CHR, MODE_DIR, O_DIRECTORY, O_STAT}; - use syscall::schemev2::NewFdFlags; - use syscall::ENOLCK; - - use crate::cfg_access::Pcie; - -+const PCIE_EXTENDED_CAPABILITY_AER: u16 = 0x0001; -+ -+#[derive(Clone, Copy)] -+enum AerRegisterName { -+ UncorStatus, -+ UncorMask, -+ UncorSeverity, -+ CorStatus, -+ CorMask, -+ Cap, -+ HeaderLog, -+} -+ -+impl AerRegisterName { -+ fn from_path(path: &str) -> Option { -+ Some(match path { -+ "uncor_status" => Self::UncorStatus, -+ "uncor_mask" => Self::UncorMask, -+ "uncor_severity" => Self::UncorSeverity, -+ "cor_status" => Self::CorStatus, -+ "cor_mask" => Self::CorMask, -+ "cap" => Self::Cap, -+ "header_log" => Self::HeaderLog, -+ _ => return None, -+ }) -+ } -+ -+ const fn offset(self) -> u16 { -+ match self { -+ Self::UncorStatus => 0x00, -+ Self::UncorMask => 0x04, -+ Self::UncorSeverity => 0x08, -+ Self::CorStatus => 0x0C, -+ Self::CorMask => 0x10, -+ Self::Cap => 0x14, -+ Self::HeaderLog => 0x18, -+ } -+ } -+ -+ const fn len(self) -> usize { -+ match self { -+ Self::HeaderLog => 16, -+ _ => 4, -+ } -+ } -+} -+ - pub struct PciScheme { - handles: HandleMap, - pub pcie: Pcie, - pub tree: BTreeMap, -+ /// Maps device address string (e.g. "0000:00:14.0") to owning PID -+ binds: HashMap, - } - enum Handle { -- TopLevel { entries: Vec }, -+ TopLevel { -+ entries: Vec, -+ }, - Access, -- Device, -- Channel { addr: PciAddress, st: ChannelState }, -+ Device { -+ addr: PciAddress, -+ }, -+ Channel { -+ addr: PciAddress, -+ st: ChannelState, -+ }, - SchemeRoot, -+ /// Represents an open handle to a device's bind endpoint -+ Bind { -+ addr: PciAddress, -+ }, -+ AerDir, -+ Aer { -+ addr: PciAddress, -+ register: AerRegisterName, -+ }, -+ /// Uevent surface for hotplug consumers. Opening uevent returns an object -+ /// from which device add/remove events can be read. Since pcid currently -+ /// only scans at startup, this surface is ready for hotplug polling consumers. -+ Uevent, - } - struct HandleWrapper { - inner: Handle, -@@ -30,14 +102,23 @@ struct HandleWrapper { - } - impl Handle { - fn is_file(&self) -> bool { -- matches!(self, Self::Access | Self::Channel { .. }) -+ matches!( -+ self, -+ Self::Access -+ | Self::Channel { .. } -+ | Self::Bind { .. } -+ | Self::Aer { .. } -+ | Self::Uevent -+ ) - } - 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::Channel { .. } | Self::Bind { .. } -+ ) - } - fn is_scheme_root(&self) -> bool { - matches!(self, Self::SchemeRoot) -@@ -49,7 +130,17 @@ enum ChannelState { - AwaitingResponseRead(VecDeque), - } - --const DEVICE_CONTENTS: &[&str] = &["channel"]; -+const DEVICE_CONTENTS: &[&str] = &["channel", "bind"]; -+const DEVICE_AER_CONTENTS: &[&str] = &["channel", "bind", "aer"]; -+const AER_CONTENTS: &[&str] = &[ -+ "uncor_status", -+ "uncor_mask", -+ "uncor_severity", -+ "cor_status", -+ "cor_mask", -+ "cap", -+ "header_log", -+]; - - impl PciScheme { - pub fn access(&mut self) -> usize { -@@ -88,22 +179,25 @@ impl SchemeSync for PciScheme { - let path = path.trim_matches('/'); - - let handle = if path.is_empty() { -- Handle::TopLevel { -- entries: self -- .tree -- .iter() -- // FIXME remove replacement of : once the old scheme format is no longer supported. -- .map(|(addr, _)| format!("{}", addr).replace(':', "--")) -- .collect::>(), -- } -+ let mut entries: Vec = self -+ .tree -+ .iter() -+ // FIXME remove replacement of : once the old scheme format is no longer supported. -+ .map(|(addr, _)| format!("{}", addr).replace(':', "--")) -+ .collect(); -+ entries.push(String::from("uevent")); -+ entries.push(String::from("access")); -+ Handle::TopLevel { entries } - } else if path == "access" { - Handle::Access -+ } else if path == "uevent" { -+ Handle::Uevent - } else { - let idx = path.find('/').unwrap_or(path.len()); - let (addr_str, after) = path.split_at(idx); - let addr = parse_pci_addr(addr_str).ok_or(Error::new(ENOENT))?; - -- self.parse_after_pci_addr(addr, after)? -+ self.parse_after_pci_addr(addr, after, ctx)? - }; - - let stat = flags & O_STAT != 0; -@@ -131,8 +225,14 @@ 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::Access | Handle::Channel { .. } => (0, MODE_CHR | 0o600), -+ Handle::Device { addr } => ( -+ Self::device_entries(&self.pcie, addr).len(), -+ MODE_DIR | 0o755, -+ ), -+ Handle::AerDir => (AER_CONTENTS.len(), MODE_DIR | 0o755), -+ Handle::Aer { register, .. } => (register.len(), MODE_CHR | 0o444), -+ Handle::Access | Handle::Channel { .. } | Handle::Bind { .. } => (0, MODE_CHR | 0o600), -+ Handle::Uevent => (0, MODE_CHR | 0o644), - Handle::SchemeRoot => return Err(Error::new(EBADF)), - }; - stat.st_size = len as u64; -@@ -143,7 +243,7 @@ impl SchemeSync for PciScheme { - &mut self, - id: usize, - buf: &mut [u8], -- _offset: u64, -+ offset: u64, - _fcntl_flags: u32, - _ctx: &CallerCtx, - ) -> Result { -@@ -155,12 +255,45 @@ impl SchemeSync for PciScheme { - - match handle.inner { - Handle::TopLevel { .. } => Err(Error::new(EISDIR)), -- Handle::Device => Err(Error::new(EISDIR)), -+ Handle::Device { .. } | Handle::AerDir => Err(Error::new(EISDIR)), - Handle::Channel { - addr: _, - ref mut st, - } => Self::read_channel(st, buf), -- Handle::SchemeRoot => Err(Error::new(EBADF)), -+ Handle::Aer { addr, register } => { -+ Self::read_aer_register(&self.pcie, addr, register, buf, offset) -+ } -+ Handle::Uevent => { -+ // Uevent surface for hotplug polling consumers. -+ // pcid currently only scans at startup, so return the current -+ // device tree as "add" events. Consumers can poll and re-read -+ // to check for new events. -+ let mut o = String::new(); -+ for (a, f) in &self.tree { -+ let _ = write!( -+ o, -+ "add device {:02x}:{:02x}.{:x}.{:x} vendor=0x{:04x} device=0x{:04x} class=0x{:02x}.{:02x}\n", -+ a.segment(), -+ a.bus(), -+ a.device(), -+ a.function(), -+ f.inner.full_device_id.vendor_id, -+ f.inner.full_device_id.device_id, -+ f.inner.full_device_id.class, -+ f.inner.full_device_id.subclass -+ ); -+ } -+ let b = o.as_bytes(); -+ let s = offset as usize; -+ if s < b.len() { -+ let n = (b.len() - s).min(buf.len()); -+ buf[..n].copy_from_slice(&b[s..s + n]); -+ Ok(n) -+ } else { -+ Ok(0) -+ } -+ } -+ Handle::SchemeRoot | Handle::Bind { .. } => Err(Error::new(EBADF)), - _ => Err(Error::new(EBADF)), - } - } -@@ -192,8 +325,15 @@ impl SchemeSync for PciScheme { - } - return Ok(buf); - } -- Handle::Device => DEVICE_CONTENTS, -- Handle::Access | Handle::Channel { .. } => return Err(Error::new(ENOTDIR)), -+ Handle::Device { addr } => Self::device_entries(&self.pcie, addr), -+ Handle::AerDir => AER_CONTENTS, -+ Handle::Access -+ | Handle::Channel { .. } -+ | Handle::Bind { .. } -+ | Handle::Aer { .. } -+ | Handle::Uevent => { -+ return Err(Error::new(ENOTDIR)); -+ } - Handle::SchemeRoot => return Err(Error::new(EBADF)), - }; - -@@ -226,6 +366,7 @@ impl SchemeSync for PciScheme { - Handle::Channel { addr, ref mut st } => { - Self::write_channel(&self.pcie, &mut self.tree, addr, st, buf) - } -+ Handle::Aer { .. } => Err(Error::new(EROFS)), - - _ => Err(Error::new(EBADF)), - } -@@ -316,6 +457,16 @@ impl SchemeSync for PciScheme { - func.enabled = false; - } - } -+ Some(HandleWrapper { -+ inner: Handle::Bind { addr }, -+ .. -+ }) => { -+ let addr_str = format!("{}", addr); -+ if let Some(&owner_pid) = self.binds.get(&addr_str) { -+ log::info!("pcid: device {} unbound by pid {}", addr_str, owner_pid); -+ } -+ self.binds.remove(&addr_str); -+ } - _ => {} - } - } -@@ -327,36 +478,154 @@ impl PciScheme { - handles: HandleMap::new(), - pcie, - tree: BTreeMap::new(), -+ binds: HashMap::new(), -+ } -+ } -+ fn device_entries(pcie: &Pcie, addr: PciAddress) -> &'static [&'static str] { -+ if Self::find_pcie_extended_capability(pcie, addr, PCIE_EXTENDED_CAPABILITY_AER).is_some() { -+ DEVICE_AER_CONTENTS -+ } else { -+ DEVICE_CONTENTS - } - } -- fn parse_after_pci_addr(&mut self, addr: PciAddress, after: &str) -> Result { -+ fn find_pcie_extended_capability( -+ pcie: &Pcie, -+ addr: PciAddress, -+ capability_id: u16, -+ ) -> Option { -+ if !pcie.has_extended_config(addr) { -+ return None; -+ } -+ -+ let mut offset = 0x100_u16; -+ -+ while offset <= 0xFFC { -+ let header = unsafe { pcie.read(addr, offset) }; -+ if header == 0 || header == u32::MAX { -+ return None; -+ } -+ -+ if (header & 0xFFFF) as u16 == capability_id { -+ return Some(offset); -+ } -+ -+ let next = ((header >> 20) & 0xFFF) as u16; -+ if next < 0x100 || next <= offset || next > 0xFFC || next % 4 != 0 { -+ return None; -+ } -+ offset = next; -+ } -+ -+ None -+ } -+ fn read_file_bytes(data: &[u8], buf: &mut [u8], offset: u64) -> Result { -+ let Ok(offset) = usize::try_from(offset) else { -+ return Ok(0); -+ }; -+ if offset >= data.len() { -+ return Ok(0); -+ } -+ -+ let count = std::cmp::min(buf.len(), data.len() - offset); -+ buf[..count].copy_from_slice(&data[offset..offset + count]); -+ Ok(count) -+ } -+ fn read_aer_register( -+ pcie: &Pcie, -+ addr: PciAddress, -+ register: AerRegisterName, -+ buf: &mut [u8], -+ offset: u64, -+ ) -> Result { -+ let Some(aer_base) = -+ Self::find_pcie_extended_capability(pcie, addr, PCIE_EXTENDED_CAPABILITY_AER) -+ else { -+ return Err(Error::new(ENOENT)); -+ }; -+ -+ let mut data = [0_u8; 16]; -+ for (index, chunk) in data[..register.len()].chunks_exact_mut(4).enumerate() { -+ let index = u16::try_from(index).map_err(|_| Error::new(EIO))?; -+ let value = unsafe { pcie.read(addr, aer_base + register.offset() + index * 4) }; -+ chunk.copy_from_slice(&value.to_le_bytes()); -+ } -+ -+ Self::read_file_bytes(&data[..register.len()], buf, offset) -+ } -+ fn parse_after_pci_addr( -+ &mut self, -+ addr: PciAddress, -+ after: &str, -+ ctx: &CallerCtx, -+ ) -> Result { - if after.chars().next().map_or(false, |c| c != '/') { - return Err(Error::new(ENOENT)); - } - let func = self.tree.get_mut(&addr).ok_or(Error::new(ENOENT))?; - - Ok(if after.is_empty() { -- Handle::Device -+ Handle::Device { addr } - } else { - let path = &after[1..]; - -- match path { -- "channel" => { -- if func.enabled { -- return Err(Error::new(ENOLCK)); -+ if path == "aer" { -+ if Self::find_pcie_extended_capability( -+ &self.pcie, -+ addr, -+ PCIE_EXTENDED_CAPABILITY_AER, -+ ) -+ .is_none() -+ { -+ return Err(Error::new(ENOENT)); -+ } -+ Handle::AerDir -+ } else if let Some(register_name) = path.strip_prefix("aer/") { -+ let register = -+ AerRegisterName::from_path(register_name).ok_or(Error::new(ENOENT))?; -+ if Self::find_pcie_extended_capability( -+ &self.pcie, -+ addr, -+ PCIE_EXTENDED_CAPABILITY_AER, -+ ) -+ .is_none() -+ { -+ return Err(Error::new(ENOENT)); -+ } -+ Handle::Aer { addr, register } -+ } else { -+ match path { -+ "channel" => { -+ if func.enabled { -+ return Err(Error::new(ENOLCK)); -+ } -+ func.inner.legacy_interrupt_line = crate::enable_function( -+ &self.pcie, -+ &mut func.endpoint_header, -+ &mut func.capabilities, -+ ); -+ func.enabled = true; -+ Handle::Channel { -+ addr, -+ st: ChannelState::AwaitingData, -+ } - } -- func.inner.legacy_interrupt_line = crate::enable_function( -- &self.pcie, -- &mut func.endpoint_header, -- &mut func.capabilities, -- ); -- func.enabled = true; -- Handle::Channel { -- addr, -- st: ChannelState::AwaitingData, -+ "bind" => { -+ let addr_str = format!("{}", addr); -+ if let Some(&owner_pid) = self.binds.get(&addr_str) { -+ log::info!( -+ "pcid: device {} already bound by pid {}", -+ addr_str, -+ owner_pid -+ ); -+ return Err(Error::new(EALREADY)); -+ } -+ let caller_pid = u32::try_from(ctx.pid).map_err(|_| Error::new(EINVAL))?; -+ self.binds.insert(addr_str.clone(), caller_pid); -+ log::info!("pcid: device {} bound by pid {}", addr_str, caller_pid); -+ Handle::Bind { addr } - } -+ _ => return Err(Error::new(ENOENT)), - } -- _ => return Err(Error::new(ENOENT)), - } - }) - } diff --git a/local/patches/base/P4-login-rate-limit.patch b/local/patches/base/P4-login-rate-limit.patch deleted file mode 100644 index 0b4b574750..0000000000 --- a/local/patches/base/P4-login-rate-limit.patch +++ /dev/null @@ -1,43 +0,0 @@ ---- a/src/bin/login.rs 2026-05-03 09:46:36.866954421 +0100 -+++ /mnt/data/homes/kellito/Builds/rbos/b/src/bin/login.rs 2026-05-03 09:46:36.867061170 +0100 -@@ -120,6 +120,7 @@ - pub fn main() { - let mut stdout = io::stdout(); - let mut stderr = io::stderr(); -+ let mut consecutive_failures: u32 = 0; - - let _args = clap_app!(login => - (author: "Jeremy Soller, Jose Narvaez") -@@ -133,6 +134,10 @@ - } - - loop { -+ if consecutive_failures >= 3 { -+ let delay_secs = std::cmp::min(consecutive_failures as u64, 30); -+ std::thread::sleep(std::time::Duration::from_secs(delay_secs)); -+ } - let user = liner::Context::new() - .read_line( - liner::Prompt::from("\x1B[1mredox login:\x1B[0m "), -@@ -150,11 +155,13 @@ - None => { - stdout.write(b"\nLogin incorrect\n").r#try(&mut stderr); - stdout.write(b"\n").r#try(&mut stderr); -+ consecutive_failures += 1; - stdout.flush().r#try(&mut stderr); - continue; - } - Some(user) => { - if user.is_passwd_blank() { -+ consecutive_failures = 0; - if let Ok(mut motd) = File::open(MOTD_FILE) { - io::copy(&mut motd, &mut stdout).r#try(&mut stderr); - stdout.flush().r#try(&mut stderr); -@@ -185,6 +192,7 @@ - stdout.flush().r#try(&mut stderr); - - if user.verify_passwd(&password) { -+ consecutive_failures = 0; - if let Ok(mut motd) = File::open(MOTD_FILE) { - io::copy(&mut motd, &mut stdout).r#try(&mut stderr); - stdout.flush().r#try(&mut stderr); diff --git a/local/patches/base/P5-init-daemon-panic-hardening.patch b/local/patches/base/P5-init-daemon-panic-hardening.patch deleted file mode 100644 index efcd5d818a..0000000000 --- a/local/patches/base/P5-init-daemon-panic-hardening.patch +++ /dev/null @@ -1,685 +0,0 @@ -diff --git a/drivers/acpid/src/main.rs b/drivers/acpid/src/main.rs -index 059254b3..a3f5f996 100644 ---- a/drivers/acpid/src/main.rs -+++ b/drivers/acpid/src/main.rs -@@ -3,6 +3,7 @@ use std::fs::File; - use std::mem; - use std::ops::ControlFlow; - use std::os::unix::io::AsRawFd; -+use std::process; - use std::sync::Arc; - - use ::acpi::aml::op_region::{RegionHandler, RegionSpace}; -@@ -17,6 +18,58 @@ mod ec; - - mod scheme; - -+fn parse_physaddrs(sdt: &self::acpi::Sdt) -> Vec { -+ match &sdt.signature { -+ b"RSDT" => { -+ let chunks = sdt.data().chunks_exact(mem::size_of::()); -+ if !chunks.remainder().is_empty() { -+ eprintln!( -+ "acpid: malformed RSDT length {}: expected 4-byte entries", -+ sdt.data().len() -+ ); -+ process::exit(1); -+ } -+ -+ chunks -+ .map(|chunk| match <[u8; mem::size_of::()]>::try_from(chunk) { -+ Ok(bytes) => u32::from_le_bytes(bytes) as u64, -+ Err(_) => { -+ eprintln!("acpid: failed to decode RSDT physical address entry"); -+ process::exit(1); -+ } -+ }) -+ .collect() -+ } -+ b"XSDT" => { -+ let chunks = sdt.data().chunks_exact(mem::size_of::()); -+ if !chunks.remainder().is_empty() { -+ eprintln!( -+ "acpid: malformed XSDT length {}: expected 8-byte entries", -+ sdt.data().len() -+ ); -+ process::exit(1); -+ } -+ -+ chunks -+ .map(|chunk| match <[u8; mem::size_of::()]>::try_from(chunk) { -+ Ok(bytes) => u64::from_le_bytes(bytes), -+ Err(_) => { -+ eprintln!("acpid: failed to decode XSDT physical address entry"); -+ process::exit(1); -+ } -+ }) -+ .collect() -+ } -+ signature => { -+ eprintln!( -+ "acpid: expected [RX]SDT from kernel, got {:?}", -+ String::from_utf8_lossy(signature) -+ ); -+ process::exit(1); -+ } -+ } -+} -+ - fn daemon(daemon: daemon::Daemon) -> ! { - common::setup_logging( - "misc", -@@ -29,7 +82,10 @@ fn daemon(daemon: daemon::Daemon) -> ! { - log::info!("acpid start"); - - let rxsdt_raw_data: Arc<[u8]> = std::fs::read("/scheme/kernel.acpi/rxsdt") -- .expect("acpid: failed to read `/scheme/kernel.acpi/rxsdt`") -+ .unwrap_or_else(|err| { -+ eprintln!("acpid: failed to read `/scheme/kernel.acpi/rxsdt`: {err}"); -+ process::exit(1); -+ }) - .into(); - - if rxsdt_raw_data.is_empty() { -@@ -38,84 +94,84 @@ fn daemon(daemon: daemon::Daemon) -> ! { - std::process::exit(0); - } - -- let sdt = self::acpi::Sdt::new(rxsdt_raw_data).expect("acpid: failed to parse [RX]SDT"); -- -- let mut thirty_two_bit; -- let mut sixty_four_bit; -- -- let physaddrs_iter = 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); -- -- &mut thirty_two_bit as &mut dyn Iterator -- } -- 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)); -- -- &mut sixty_four_bit as &mut dyn Iterator -- } -- _ => panic!("acpid: expected [RX]SDT from kernel to be either of those"), -- }; -+ let sdt = self::acpi::Sdt::new(rxsdt_raw_data).unwrap_or_else(|err| { -+ eprintln!("acpid: failed to parse [RX]SDT: {err}"); -+ process::exit(1); -+ }); -+ let physaddrs = parse_physaddrs(&sdt); - - 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 acpi_context = self::acpi::AcpiContext::init(physaddrs.into_iter(), region_handlers); - - // 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().unwrap_or_else(|err| { -+ eprintln!("acpid: failed to set I/O privilege level to Ring 3: {err}"); -+ process::exit(1); -+ }); - - let shutdown_pipe = File::open("/scheme/kernel.acpi/kstop") -- .expect("acpid: failed to open `/scheme/kernel.acpi/kstop`"); -- -- let mut event_queue = RawEventQueue::new().expect("acpid: failed to create event queue"); -- let socket = Socket::nonblock().expect("acpid: failed to create disk scheme"); -+ .unwrap_or_else(|err| { -+ eprintln!("acpid: failed to open `/scheme/kernel.acpi/kstop`: {err}"); -+ process::exit(1); -+ }); -+ -+ let mut event_queue = RawEventQueue::new().unwrap_or_else(|err| { -+ eprintln!("acpid: failed to create event queue: {err}"); -+ process::exit(1); -+ }); -+ let socket = Socket::nonblock().unwrap_or_else(|err| { -+ eprintln!("acpid: failed to create disk scheme: {err}"); -+ process::exit(1); -+ }); - - 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"); -+ .unwrap_or_else(|err| { -+ eprintln!("acpid: failed to register shutdown pipe for event queue: {err}"); -+ process::exit(1); -+ }); - event_queue - .subscribe(socket.inner().raw(), 1, EventFlags::READ) -- .expect("acpid: failed to register scheme socket for event queue"); -+ .unwrap_or_else(|err| { -+ eprintln!("acpid: failed to register scheme socket for event queue: {err}"); -+ process::exit(1); -+ }); - - register_sync_scheme(&socket, "acpi", &mut scheme) -- .expect("acpid: failed to register acpi scheme to namespace"); -+ .unwrap_or_else(|err| { -+ eprintln!("acpid: failed to register acpi scheme to namespace: {err}"); -+ process::exit(1); -+ }); - - daemon.ready(); - -- libredox::call::setrens(0, 0).expect("acpid: failed to enter null namespace"); -+ libredox::call::setrens(0, 0).unwrap_or_else(|err| { -+ eprintln!("acpid: failed to enter null namespace: {err}"); -+ process::exit(1); -+ }); - - let mut mounted = true; - while mounted { -- let Some(event) = event_queue -- .next() -- .transpose() -- .expect("acpid: failed to read event file") -- else { -+ let Some(event) = event_queue.next().transpose().unwrap_or_else(|err| { -+ eprintln!("acpid: failed to read event file: {err}"); -+ process::exit(1); -+ }) else { - break; - }; - - if event.fd == socket.inner().raw() { - loop { -- match handler -- .process_requests_nonblocking(&mut scheme) -- .expect("acpid: failed to process requests") -- { -+ match handler.process_requests_nonblocking(&mut scheme).unwrap_or_else(|err| { -+ eprintln!("acpid: failed to process requests: {err}"); -+ process::exit(1); -+ }) { - ControlFlow::Continue(()) => {} - ControlFlow::Break(()) => break, - } -@@ -134,7 +190,8 @@ fn daemon(daemon: daemon::Daemon) -> ! { - - acpi_context.set_global_s_state(5); - -- unreachable!("System should have shut down before this is entered"); -+ eprintln!("acpid: system did not shut down after requesting S5"); -+ process::exit(1); - } - - fn main() { -diff --git a/drivers/pcid/src/main.rs b/drivers/pcid/src/main.rs -index 61cd9a78..18ee18ab 100644 ---- a/drivers/pcid/src/main.rs -+++ b/drivers/pcid/src/main.rs -@@ -3,6 +3,7 @@ - #![feature(non_exhaustive_omitted_patterns_lint)] - - use std::collections::BTreeMap; -+use std::process; - - use log::{debug, info, trace, warn}; - use pci_types::capability::PciCapability; -@@ -42,7 +43,16 @@ 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 port.try_into() { -+ Ok(port) => bars[i as usize] = PciBar::Port(port), -+ Err(_) => { -+ warn!( -+ "pcid: skipping invalid I/O BAR port {port:#x} on {}", -+ endpoint_header.header().address() -+ ); -+ bars[i as usize] = PciBar::None; -+ } -+ }, - Some(TyBar::Memory32 { - address, - size, -@@ -251,7 +261,10 @@ 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 = redox_scheme::Socket::create().unwrap_or_else(|err| { -+ eprintln!("pcid: failed to open pci scheme socket: {err}"); -+ process::exit(1); -+ }); - let handler = Blocking::new(&socket, 16); - - { -@@ -259,17 +272,27 @@ fn daemon(daemon: daemon::Daemon) -> ! { - Ok(register_pci) => { - let access_id = scheme.access(); - -- let access_fd = 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"); -+ match socket.create_this_scheme_fd(0, access_id, syscall::O_RDWR, 0) { -+ Ok(access_fd) => { -+ let access_bytes = access_fd.to_ne_bytes(); -+ if let Err(err) = register_pci.call_wo( -+ &access_bytes, -+ syscall::CallFlags::WRITE | syscall::CallFlags::FD, -+ &[], -+ ) { -+ warn!( -+ "pcid: failed to send pci_fd to acpid (error: {}). Running without ACPI integration.", -+ err -+ ); -+ } -+ } -+ Err(err) => { -+ warn!( -+ "pcid: failed to issue acpid registration resource (error: {}). Running without ACPI integration.", -+ err -+ ); -+ } -+ } - } - Err(err) => { - if err.errno() == libredox::errno::ENODEV { -@@ -305,13 +328,20 @@ fn daemon(daemon: daemon::Daemon) -> ! { - debug!("Enumeration complete, now starting pci scheme"); - - register_sync_scheme(&socket, "pci", &mut scheme) -- .expect("failed to register pci scheme to namespace"); -+ .unwrap_or_else(|err| { -+ eprintln!("pcid: failed to register pci scheme to namespace: {err}"); -+ process::exit(1); -+ }); - - let _ = daemon.ready(); - -- handler -- .process_requests_blocking(scheme) -- .expect("pcid: failed to process requests"); -+ match handler.process_requests_blocking(scheme) { -+ Ok(never) => match never {}, -+ Err(err) => { -+ eprintln!("pcid: failed to process requests: {err}"); -+ process::exit(1); -+ } -+ } - } - - fn scan_device( -@@ -323,6 +353,7 @@ fn scan_device( - ) { - for func_num in 0..8 { - let header = TyPciHeader::new(PciAddress::new(0, bus_num, dev_num, func_num)); -+ let header_address = header.address(); - - let (vendor_id, device_id) = header.id(pcie); - if vendor_id == 0xffff && device_id == 0xffff { -@@ -344,21 +375,40 @@ fn scan_device( - revision, - }; - -- info!("PCI {} {}", header.address(), full_device_id.display()); -+ info!("PCI {} {}", header_address, full_device_id.display()); - - let has_multiple_functions = header.has_multiple_functions(pcie); - - match header.header_type(pcie) { - HeaderType::Endpoint => { -+ let endpoint_header = match EndpointHeader::from_header(header, pcie) { -+ Some(endpoint_header) => endpoint_header, -+ None => { -+ warn!( -+ "pcid: failed to parse endpoint header for {}", -+ header_address, -+ ); -+ continue; -+ } -+ }; - handle_parsed_header( - pcie, - tree, -- EndpointHeader::from_header(header, pcie).unwrap(), -+ endpoint_header, - full_device_id, - ); - } - HeaderType::PciPciBridge => { -- let bridge_header = PciPciBridgeHeader::from_header(header, pcie).unwrap(); -+ let bridge_header = match PciPciBridgeHeader::from_header(header, pcie) { -+ Some(bridge_header) => bridge_header, -+ None => { -+ warn!( -+ "pcid: failed to parse bridge header for {}", -+ header_address, -+ ); -+ continue; -+ } -+ }; - bus_nums.push(bridge_header.secondary_bus_number(pcie)); - } - ty => { -diff --git a/init/src/main.rs b/init/src/main.rs -index 5682cf44..cd270a6e 100644 ---- a/init/src/main.rs -+++ b/init/src/main.rs -@@ -1,6 +1,7 @@ - use std::collections::BTreeMap; - use std::ffi::OsString; - use std::path::Path; -+use std::time::Duration; - use std::{env, fs, io}; - - use libredox::flag::{O_RDONLY, O_WRONLY}; -@@ -166,19 +167,36 @@ fn main() { - } - }; - for entry in entries { -+ let Some(unit_name) = entry.file_name().and_then(|name| name.to_str()) else { -+ eprintln!( -+ "init: skipping config entry with invalid filename: {}", -+ entry.display() -+ ); -+ continue; -+ }; - scheduler.schedule_start_and_report_errors( - &mut unit_store, -- UnitId(entry.file_name().unwrap().to_str().unwrap().to_owned()), -+ UnitId(unit_name.to_owned()), - ); - } - }; - - scheduler.step(&mut unit_store, &mut init_config); - -- libredox::call::setrens(0, 0).expect("init: failed to enter null namespace"); -+ if let Err(err) = libredox::call::setrens(0, 0) { -+ eprintln!("init: failed to enter null namespace: {err}"); -+ std::process::exit(1); -+ } - - loop { - let mut status = 0; -- libredox::call::waitpid(0, &mut status, 0).unwrap(); -+ match libredox::call::waitpid(0, &mut status, 0) { -+ Ok(_) => {} -+ Err(err) if err.errno() == libredox::errno::EINTR => continue, -+ Err(err) => { -+ eprintln!("init: waitpid failed: {err}"); -+ std::thread::sleep(Duration::from_millis(100)); -+ } -+ } - } - } -diff --git a/init/src/scheduler.rs b/init/src/scheduler.rs -index d42a4e57..3b8d10b0 100644 ---- a/init/src/scheduler.rs -+++ b/init/src/scheduler.rs -@@ -43,7 +43,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() { -+ if unit_store -+ .try_unit(&unit_id) -+ .is_ok_and(|unit| !unit.conditions_met()) -+ { - continue; - } - -@@ -62,7 +65,10 @@ impl Scheduler { - - match job.kind { - JobKind::Start => { -- let unit = unit_store.unit_mut(&job.unit); -+ let Ok(unit) = unit_store.try_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 { -diff --git a/init/src/service.rs b/init/src/service.rs -index ed0023e9..827ae275 100644 ---- a/init/src/service.rs -+++ b/init/src/service.rs -@@ -3,13 +3,24 @@ use std::ffi::OsString; - use std::io::Read; - use std::os::fd::{AsRawFd, OwnedFd}; - use std::os::unix::process::CommandExt; --use std::process::Command; -+use std::process::{Child, Command}; - use std::{env, io}; - - use serde::Deserialize; - - use crate::script::subst_env; - -+fn terminate_child(child: &mut Child, command: &str) { -+ if let Err(err) = child.kill() { -+ if err.kind() != io::ErrorKind::InvalidInput { -+ eprintln!("init: failed to terminate {command}: {err}"); -+ } -+ } -+ if let Err(err) = child.wait() { -+ eprintln!("init: failed to reap {command}: {err}"); -+ } -+} -+ - #[derive(Clone, Debug, Deserialize)] - #[serde(deny_unknown_fields)] - pub struct Service { -@@ -37,7 +48,8 @@ pub enum ServiceType { - impl Service { - pub fn spawn(&self, base_envs: &BTreeMap) { - let mut command = Command::new(&self.cmd); -- command.args(self.args.iter().map(|arg| subst_env(arg))); -+ let resolved_args: Vec = self.args.iter().map(|arg| subst_env(arg)).collect(); -+ command.args(&resolved_args); - command.env_clear(); - for env in &self.inherit_envs { - if let Some(value) = env::var_os(env) { -@@ -45,14 +57,25 @@ impl Service { - } - } - command.envs(base_envs).envs(&self.envs); -+ let command_display = if resolved_args.is_empty() { -+ self.cmd.clone() -+ } else { -+ format!("{} {}", self.cmd, resolved_args.join(" ")) -+ }; - -- let (mut read_pipe, write_pipe) = io::pipe().unwrap(); -+ let (mut read_pipe, write_pipe) = match io::pipe().map_err(|err| { -+ eprintln!("init: failed to create readiness pipe for {command_display}: {err}"); -+ err -+ }) { -+ Ok(pair) => pair, -+ Err(_) => return, -+ }; - 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); -+ eprintln!("init: failed to execute {command_display}: {err}"); - return; - } - }; -@@ -61,10 +84,10 @@ impl Service { - ServiceType::Notify => match read_pipe.read_exact(&mut [0]) { - Ok(()) => {} - Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => { -- eprintln!("init: {command:?} exited without notifying readiness"); -+ eprintln!("init: {command_display} exited without notifying readiness"); - } - Err(err) => { -- eprintln!("init: failed to wait for {command:?}: {err}"); -+ eprintln!("init: failed to wait for {command_display}: {err}"); - } - }, - ServiceType::Scheme(scheme) => { -@@ -80,7 +103,7 @@ impl Service { - errno: syscall::EINTR, - }) => continue, - Ok(0) => { -- eprintln!("init: {command:?} exited without notifying readiness"); -+ eprintln!("init: {command_display} exited without notifying readiness"); - return; - } - Ok(1) => break, -@@ -89,26 +112,40 @@ impl Service { - return; - } - Err(err) => { -- eprintln!("init: failed to wait for {command:?}: {err}"); -+ eprintln!("init: failed to wait for {command_display}: {err}"); - return; - } - } - } - -- 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_display}: {err}"); -+ terminate_child(&mut child, &command_display); -+ return; -+ } -+ }; -+ 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_display}: {err}" -+ ); -+ terminate_child(&mut child, &command_display); -+ return; -+ } - } - ServiceType::Oneshot => { - drop(read_pipe); - match child.wait() { - Ok(exit_status) => { - if !exit_status.success() { -- eprintln!("init: {command:?} failed with {exit_status}"); -+ eprintln!("init: {command_display} failed with {exit_status}"); - } - } - Err(err) => { -- eprintln!("init: failed to wait for {:?}: {}", command, err) -+ eprintln!("init: failed to wait for {command_display}: {err}") - } - } - } -diff --git a/init/src/unit.rs b/init/src/unit.rs -index 98053cb2..bd998394 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,15 @@ 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()); -+ match self.try_unit(&unit) { -+ Ok(unit) => { -+ for dep in &unit.info.requires_weak { -+ pending_units.push(dep.clone()); -+ } -+ } -+ Err(err) => { -+ errors.push(err); -+ } - } - } - } -@@ -94,12 +107,34 @@ impl UnitStore { - loaded_units - } - -+ pub fn try_unit(&self, unit: &UnitId) -> Result<&Unit, String> { -+ self.units -+ .get(unit) -+ .ok_or_else(|| format!("unit {} not found in store", unit.0)) -+ } -+ -+ // Keep the legacy infallible accessors for compatibility while scheduler/load paths -+ // use the fallible helpers to avoid panicking on missing units. -+ #[allow(dead_code)] - pub fn unit(&self, unit: &UnitId) -> &Unit { -- self.units.get(unit).unwrap() -+ self.try_unit(unit).unwrap_or_else(|err| { -+ eprintln!("init: {err}"); -+ std::process::exit(1); -+ }) -+ } -+ -+ pub fn try_unit_mut(&mut self, unit: &UnitId) -> Result<&mut Unit, String> { -+ self.units -+ .get_mut(unit) -+ .ok_or_else(|| format!("unit {} not found in store", unit.0)) - } - -+ #[allow(dead_code)] - pub fn unit_mut(&mut self, unit: &UnitId) -> &mut Unit { -- self.units.get_mut(unit).unwrap() -+ self.try_unit_mut(unit).unwrap_or_else(|err| { -+ eprintln!("init: {err}"); -+ std::process::exit(1); -+ }) - } - } - -@@ -180,7 +215,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/local/patches/base/P5-init-supervisor-restart.patch b/local/patches/base/P5-init-supervisor-restart.patch deleted file mode 100644 index a5325b5974..0000000000 --- a/local/patches/base/P5-init-supervisor-restart.patch +++ /dev/null @@ -1,633 +0,0 @@ -diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs -index 94a1eb17e..3521bfc7b 100644 ---- a/drivers/acpid/src/acpi.rs -+++ b/drivers/acpid/src/acpi.rs -@@ -136,9 +136,10 @@ impl Sdt { - let header = match plain::from_bytes::(&slice) { - Ok(header) => header, - Err(plain::Error::TooShort) => return Err(InvalidSdtError::InvalidSize), -- Err(plain::Error::BadAlignment) => panic!( -- "plain::from_bytes failed due to alignment, but SdtHeader is #[repr(packed)]!" -- ), -+ Err(plain::Error::BadAlignment) => { -+ log::error!("acpid: plain::from_bytes failed due to alignment, but SdtHeader is #[repr(packed)] - internal inconsistency"); -+ return Err(InvalidSdtError::InvalidSize); -+ } - }; - - if header.length() != slice.len() { -diff --git a/drivers/acpid/src/main.rs b/drivers/acpid/src/main.rs -index 059254b3e..8f99f2ea9 100644 ---- a/drivers/acpid/src/main.rs -+++ b/drivers/acpid/src/main.rs -@@ -28,9 +28,13 @@ fn daemon(daemon: daemon::Daemon) -> ! { - - log::info!("acpid start"); - -- let rxsdt_raw_data: Arc<[u8]> = std::fs::read("/scheme/kernel.acpi/rxsdt") -- .expect("acpid: failed to read `/scheme/kernel.acpi/rxsdt`") -- .into(); -+ let rxsdt_raw_data: Arc<[u8]> = match std::fs::read("/scheme/kernel.acpi/rxsdt") { -+ Ok(data) => data.into(), -+ Err(err) => { -+ log::error!("acpid: failed to read `/scheme/kernel.acpi/rxsdt`: {}", err); -+ std::process::exit(1); -+ } -+ }; - - if rxsdt_raw_data.is_empty() { - log::info!("System doesn't use ACPI"); -@@ -38,7 +42,13 @@ fn daemon(daemon: daemon::Daemon) -> ! { - std::process::exit(0); - } - -- let sdt = self::acpi::Sdt::new(rxsdt_raw_data).expect("acpid: failed to parse [RX]SDT"); -+ let sdt = match self::acpi::Sdt::new(rxsdt_raw_data) { -+ Ok(sdt) => sdt, -+ Err(err) => { -+ log::error!("acpid: failed to parse [RX]SDT: {:?}", err); -+ std::process::exit(1); -+ } -+ }; - - let mut thirty_two_bit; - let mut sixty_four_bit; -@@ -64,7 +74,10 @@ fn daemon(daemon: daemon::Daemon) -> ! { - - &mut sixty_four_bit as &mut dyn Iterator - } -- _ => panic!("acpid: expected [RX]SDT from kernel to be either of those"), -+ _ => { -+ log::error!("acpid: expected [RX]SDT from kernel to be RSDT or XSDT"); -+ std::process::exit(1); -+ } - }; - - let region_handlers: Vec<(RegionSpace, Box)> = vec![ -@@ -75,49 +88,84 @@ fn daemon(daemon: daemon::Daemon) -> ! { - - // 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"); -+ if let Err(err) = common::acquire_port_io_rights() { -+ log::error!("acpid: failed to set I/O privilege level to Ring 3: {:?}", err); -+ std::process::exit(1); -+ } - -- let shutdown_pipe = File::open("/scheme/kernel.acpi/kstop") -- .expect("acpid: failed to open `/scheme/kernel.acpi/kstop`"); -+ let shutdown_pipe = match File::open("/scheme/kernel.acpi/kstop") { -+ Ok(f) => f, -+ Err(err) => { -+ log::error!("acpid: failed to open `/scheme/kernel.acpi/kstop`: {}", err); -+ std::process::exit(1); -+ } -+ }; - -- 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 = match RawEventQueue::new() { -+ Ok(q) => q, -+ Err(err) => { -+ log::error!("acpid: failed to create event queue: {:?}", err); -+ std::process::exit(1); -+ } -+ }; -+ let socket = match Socket::nonblock() { -+ Ok(s) => s, -+ Err(err) => { -+ log::error!("acpid: failed to create scheme socket: {:?}", err); -+ std::process::exit(1); -+ } -+ }; - - let mut scheme = self::scheme::AcpiScheme::new(&acpi_context, &socket); - let mut handler = Blocking::new(&socket, 16); - -- event_queue -+ if let Err(err) = event_queue - .subscribe(shutdown_pipe.as_raw_fd() as usize, 0, EventFlags::READ) -- .expect("acpid: failed to register shutdown pipe for event queue"); -- event_queue -+ { -+ log::error!("acpid: failed to register shutdown pipe for event queue: {:?}", err); -+ std::process::exit(1); -+ } -+ if let Err(err) = event_queue - .subscribe(socket.inner().raw(), 1, EventFlags::READ) -- .expect("acpid: failed to register scheme socket for event queue"); -+ { -+ log::error!("acpid: failed to register scheme socket for event queue: {:?}", err); -+ std::process::exit(1); -+ } - -- register_sync_scheme(&socket, "acpi", &mut scheme) -- .expect("acpid: failed to register acpi scheme to namespace"); -+ if let Err(err) = register_sync_scheme(&socket, "acpi", &mut scheme) { -+ log::error!("acpid: failed to register acpi scheme to namespace: {:?}", err); -+ std::process::exit(1); -+ } - - daemon.ready(); - -- libredox::call::setrens(0, 0).expect("acpid: failed to enter null namespace"); -+ if let Err(err) = libredox::call::setrens(0, 0) { -+ log::error!("acpid: failed to enter null namespace: {}", err); -+ std::process::exit(1); -+ } - - let mut mounted = true; - while mounted { -- let Some(event) = event_queue -- .next() -- .transpose() -- .expect("acpid: failed to read event file") -- else { -- break; -+ let event = match event_queue.next().transpose() { -+ Ok(Some(ev)) => ev, -+ Ok(None) => break, -+ Err(err) => { -+ log::error!("acpid: failed to read event file: {:?}", err); -+ break; -+ } - }; - - if event.fd == socket.inner().raw() { - loop { -- match handler -- .process_requests_nonblocking(&mut scheme) -- .expect("acpid: failed to process requests") -- { -- ControlFlow::Continue(()) => {} -- ControlFlow::Break(()) => break, -+ match handler.process_requests_nonblocking(&mut scheme) { -+ Ok(flow) => match flow { -+ ControlFlow::Continue(()) => {} -+ ControlFlow::Break(()) => break, -+ }, -+ Err(err) => { -+ log::error!("acpid: failed to process requests: {:?}", err); -+ break; -+ } - } - } - } else if event.fd == shutdown_pipe.as_raw_fd() as usize { -diff --git a/drivers/pcid/src/main.rs b/drivers/pcid/src/main.rs -index 61cd9a787..cad33114b 100644 ---- a/drivers/pcid/src/main.rs -+++ b/drivers/pcid/src/main.rs -@@ -4,7 +4,7 @@ - - use std::collections::BTreeMap; - --use log::{debug, info, trace, warn}; -+use log::{debug, error, info, trace, warn}; - use pci_types::capability::PciCapability; - use pci_types::{ - Bar as TyBar, CommandRegister, EndpointHeader, HeaderType, PciAddress, -@@ -259,17 +259,25 @@ 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( -+ { -+ Ok(fd) => Some(fd), -+ Err(err) => { -+ warn!("pcid: failed to issue acpi resource fd: {:?}", err); -+ None -+ } -+ }; -+ if let Some(access_fd) = access_fd { -+ let access_bytes = access_fd.to_ne_bytes(); -+ if let Err(err) = register_pci.call_wo( - &access_bytes, - syscall::CallFlags::WRITE | syscall::CallFlags::FD, - &[], -- ) -- .expect("failed to send pci_fd to acpid"); -+ ) { -+ warn!("pcid: failed to send pci_fd to acpid: {:?}", err); -+ } -+ } - } - Err(err) => { - if err.errno() == libredox::errno::ENODEV { -@@ -304,14 +312,17 @@ fn daemon(daemon: daemon::Daemon) -> ! { - } - debug!("Enumeration complete, now starting pci scheme"); - -- 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) { -+ 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"); -+ handler.process_requests_blocking(scheme).unwrap_or_else(|err| { -+ error!("pcid: failed to process requests: {:?}", err); -+ std::process::exit(1); -+ }); - } - - fn scan_device( -diff --git a/init/src/main.rs b/init/src/main.rs -index 5682cf445..72c97f53c 100644 ---- a/init/src/main.rs -+++ b/init/src/main.rs -@@ -166,19 +166,29 @@ fn main() { - } - }; - for entry in entries { -+ let Some(file_name) = entry.file_name().and_then(|n| n.to_str()) else { -+ eprintln!("init: skipping entry with invalid filename: {}", entry.display()); -+ continue; -+ }; - scheduler.schedule_start_and_report_errors( - &mut unit_store, -- UnitId(entry.file_name().unwrap().to_str().unwrap().to_owned()), -+ UnitId(file_name.to_owned()), - ); - } - }; - - scheduler.step(&mut unit_store, &mut init_config); - -- libredox::call::setrens(0, 0).expect("init: failed to enter null namespace"); -+ if let Err(err) = libredox::call::setrens(0, 0) { -+ eprintln!("init: failed to enter null namespace: {}", err); -+ return; -+ } - - loop { - let mut status = 0; -- libredox::call::waitpid(0, &mut status, 0).unwrap(); -+ match libredox::call::waitpid(0, &mut status, 0) { -+ Ok(()) => {} -+ Err(err) => eprintln!("init: waitpid error: {}", err), -+ } - } - } -diff --git a/init/src/scheduler.rs b/init/src/scheduler.rs -index d42a4e570..333e0e20e 100644 ---- a/init/src/scheduler.rs -+++ b/init/src/scheduler.rs -@@ -1,7 +1,16 @@ - use std::collections::VecDeque; -+use std::io::Read; -+use std::os::fd::AsRawFd; -+use std::os::unix::process::CommandExt; -+use std::process::Command; -+use std::time::Duration; -+use std::{env, io}; - - use crate::InitConfig; --use crate::unit::{Unit, UnitId, UnitKind, UnitStore}; -+use crate::service::ServiceType; -+use crate::unit::{RestartPolicy, UnitId, UnitKind, UnitStore}; -+ -+const MAX_DEPENDENCY_WAIT_RETRIES: u32 = 1000; - - pub struct Scheduler { - pending: VecDeque, -@@ -10,10 +19,12 @@ pub struct Scheduler { - struct Job { - unit: UnitId, - kind: JobKind, -+ dep_retries: u32, - } - - enum JobKind { - Start, -+ Restart { backoff: Duration }, - } - - impl Scheduler { -@@ -50,37 +61,97 @@ impl Scheduler { - self.pending.push_back(Job { - unit: unit_id, - kind: JobKind::Start, -+ dep_retries: 0, - }); - } - } - - pub fn step(&mut self, unit_store: &mut UnitStore, init_config: &mut InitConfig) { - 'a: loop { -- let Some(job) = self.pending.pop_front() else { -+ let Some(mut job) = self.pending.pop_front() else { - return; - }; - - match job.kind { - JobKind::Start => { -- let unit = unit_store.unit_mut(&job.unit); -+ let unit = unit_store.unit(&job.unit); - -+ let timeout_secs = unit.info.dependency_timeout_secs; -+ let mut deps_pending = false; - for dep in &unit.info.requires_weak { - for pending_job in &self.pending { - if &pending_job.unit == dep { -- self.pending.push_back(job); -- continue 'a; -+ deps_pending = true; -+ break; - } - } -+ if deps_pending { -+ break; -+ } - } - -- run(unit, init_config); -+ if deps_pending { -+ if timeout_secs > 0 { -+ job.dep_retries += 1; -+ let max_retries = timeout_secs * 100; // ~10ms per retry -+ if job.dep_retries > max_retries as u32 { -+ eprintln!( -+ "init: {}: dependency timeout after {}s, failing", -+ job.unit.0, timeout_secs -+ ); -+ continue; -+ } -+ } else if job.dep_retries >= MAX_DEPENDENCY_WAIT_RETRIES { -+ eprintln!( -+ "init: {}: dependency wait exceeded {} retries, failing", -+ job.unit.0, MAX_DEPENDENCY_WAIT_RETRIES -+ ); -+ continue; -+ } -+ job.dep_retries += 1; -+ self.pending.push_back(job); -+ continue 'a; -+ } -+ -+ if let Err(restart) = run(unit_store, &job.unit, init_config) { -+ if let Some(backoff) = restart { -+ self.pending.push_back(Job { -+ unit: job.unit.clone(), -+ kind: JobKind::Restart { backoff }, -+ dep_retries: 0, -+ }); -+ } -+ } -+ } -+ JobKind::Restart { backoff } => { -+ std::thread::sleep(backoff); -+ let next_backoff = (backoff * 2).min(Duration::from_secs(60)); -+ if let Err(restart) = run(unit_store, &job.unit, init_config) { -+ if let Some(_next) = restart { -+ self.pending.push_back(Job { -+ unit: job.unit, -+ kind: JobKind::Restart { -+ backoff: next_backoff, -+ }, -+ dep_retries: 0, -+ }); -+ } -+ } - } - } - } - } - } - --fn run(unit: &mut Unit, config: &mut InitConfig) { -+fn run( -+ unit_store: &UnitStore, -+ unit_id: &UnitId, -+ config: &mut InitConfig, -+) -> Result<(), Option> { -+ let unit = unit_store.unit(unit_id); -+ -+ let restart_policy = unit.info.restart; -+ - match &unit.kind { - UnitKind::LegacyScript { script } => { - for cmd in script.clone() { -@@ -89,11 +160,12 @@ fn run(unit: &mut Unit, config: &mut InitConfig) { - } - cmd.run(config); - } -+ Ok(()) - } - UnitKind::Service { service } => { - if config.skip_cmd.contains(&service.cmd) { - eprintln!("Skipping '{} {}'", service.cmd, service.args.join(" ")); -- return; -+ return Ok(()); - } - if config.log_debug { - eprintln!( -@@ -102,7 +174,44 @@ fn run(unit: &mut Unit, config: &mut InitConfig) { - service.cmd, - ); - } -- service.spawn(&config.envs); -+ -+ let mut command = Command::new(&service.cmd); -+ command.args(&service.args); -+ command.env_clear(); -+ for env in &service.inherit_envs { -+ if let Some(value) = env::var_os(env) { -+ command.env(env, value); -+ } -+ } -+ command.envs(config.envs.iter().map(|(k, v)| (k.as_str(), v.as_os_str()))); -+ -+ let (read_pipe, write_pipe) = match io::pipe() { -+ Ok(p) => p, -+ Err(err) => { -+ eprintln!("init: pipe failed for {}: {}", service.cmd, err); -+ return Err(restart_signal(restart_policy)); -+ } -+ }; -+ -+ let write_fd: std::os::fd::OwnedFd = write_pipe.into(); -+ unsafe { -+ command.env("INIT_NOTIFY", format!("{}", write_fd.as_raw_fd())); -+ command.pre_exec(move || { -+ if unsafe { libc::fcntl(write_fd.as_raw_fd(), libc::F_SETFD, 0) } == -1 { -+ Err(io::Error::last_os_error()) -+ } else { -+ Ok(()) -+ } -+ }); -+ } -+ -+ let status = service_spawn_status(read_pipe, command, &service.type_, &service.cmd); -+ -+ match status { -+ SpawnStatus::Success => Ok(()), -+ SpawnStatus::Failed => Err(restart_signal(restart_policy)), -+ SpawnStatus::Async => Ok(()), -+ } - } - UnitKind::Target {} => { - if config.log_debug { -@@ -111,6 +220,113 @@ fn run(unit: &mut Unit, config: &mut InitConfig) { - unit.info.description.as_ref().unwrap_or(&unit.id.0), - ); - } -+ Ok(()) -+ } -+ } -+} -+ -+enum SpawnStatus { -+ Success, -+ Failed, -+ Async, -+} -+ -+fn restart_signal(policy: RestartPolicy) -> Option { -+ match policy { -+ RestartPolicy::No => None, -+ RestartPolicy::OnFailure | RestartPolicy::Always => Some(Duration::from_secs(1)), -+ } -+} -+ -+fn service_spawn_status( -+ mut read_pipe: impl Read + AsRawFd, -+ mut command: Command, -+ service_type: &ServiceType, -+ cmd: &str, -+) -> SpawnStatus { -+ let mut child = match command.spawn() { -+ Ok(child) => child, -+ Err(err) => { -+ eprintln!("init: failed to execute {}: {}", cmd, err); -+ return SpawnStatus::Failed; -+ } -+ }; -+ -+ match service_type { -+ ServiceType::Notify => match read_pipe.read_exact(&mut [0]) { -+ Ok(()) => SpawnStatus::Success, -+ Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => { -+ eprintln!("init: {cmd} exited without notifying readiness"); -+ SpawnStatus::Failed -+ } -+ Err(err) => { -+ eprintln!("init: failed to wait for {cmd}: {err}"); -+ SpawnStatus::Failed -+ } -+ }, -+ ServiceType::Scheme(scheme) => { -+ let scheme = scheme.clone(); -+ let mut new_fd = usize::MAX; -+ let res = loop { -+ match syscall::call_ro( -+ read_pipe.as_raw_fd() as usize, -+ unsafe { plain::as_mut_bytes(&mut new_fd) }, -+ syscall::CallFlags::FD | syscall::CallFlags::FD_UPPER, -+ &[], -+ ) { -+ Err(syscall::Error { -+ errno: syscall::EINTR, -+ }) => continue, -+ Ok(0) => break SpawnStatus::Failed, -+ Ok(1) => break SpawnStatus::Success, -+ Ok(n) => { -+ eprintln!("init: incorrect amount of fds {n} returned from {cmd}"); -+ break SpawnStatus::Failed; -+ } -+ Err(err) => { -+ eprintln!("init: failed to wait for {cmd}: {err}"); -+ break SpawnStatus::Failed; -+ } -+ } -+ }; -+ -+ if matches!(res, SpawnStatus::Success) { -+ match libredox::call::getns() { -+ Ok(current_namespace_fd) => { -+ if let Err(err) = libredox::call::register_scheme_to_ns( -+ current_namespace_fd, -+ &scheme, -+ new_fd, -+ ) { -+ eprintln!("init: scheme registration failed for {cmd}: {err}"); -+ return SpawnStatus::Failed; -+ } -+ } -+ Err(err) => { -+ eprintln!("init: getns failed for {cmd}: {err}"); -+ return SpawnStatus::Failed; -+ } -+ } -+ } -+ res -+ } -+ ServiceType::Oneshot => { -+ drop(read_pipe); -+ match child.wait() { -+ Ok(exit_status) => { -+ if !exit_status.success() { -+ eprintln!("init: {cmd} failed with {exit_status}"); -+ SpawnStatus::Failed -+ } else { -+ SpawnStatus::Success -+ } -+ } -+ Err(err) => { -+ eprintln!("init: failed to wait for {cmd}: {err}"); -+ SpawnStatus::Failed -+ } -+ } - } -+ ServiceType::OneshotAsync => SpawnStatus::Async, - } - } -diff --git a/init/src/unit.rs b/init/src/unit.rs -index 98053cb2d..414b92d17 100644 ---- a/init/src/unit.rs -+++ b/init/src/unit.rs -@@ -125,6 +125,25 @@ pub struct UnitInfo { - pub condition_architecture: Option>, - // FIXME replace this with hwd reading from the devicetree - pub condition_board: Option>, -+ /// Restart policy for the service (only applies to Service units) -+ #[serde(default)] -+ pub restart: RestartPolicy, -+ /// Maximum time in seconds to wait for dependencies before failing (0 = no timeout) -+ #[serde(default)] -+ pub dependency_timeout_secs: u64, -+} -+ -+/// Restart policy for managed services -+#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Default)] -+#[serde(rename_all = "kebab-case")] -+pub enum RestartPolicy { -+ /// Never restart the service (default) -+ #[default] -+ No, -+ /// Restart on failure (non-zero exit or crash) -+ OnFailure, -+ /// Always restart (on any exit) -+ Always, - } - - fn true_bool() -> bool { -@@ -190,6 +209,8 @@ impl Unit { - requires_weak: script.1, - condition_architecture: None, - condition_board: None, -+ restart: RestartPolicy::No, -+ dependency_timeout_secs: 0, - }, - kind: UnitKind::LegacyScript { script: script.0 }, - }); diff --git a/local/patches/base/P6-cpufreqd-real-impl.patch b/local/patches/base/P6-cpufreqd-real-impl.patch index 11486f6a27..50766dbdb0 100644 --- a/local/patches/base/P6-cpufreqd-real-impl.patch +++ b/local/patches/base/P6-cpufreqd-real-impl.patch @@ -1,177 +1,170 @@ -0a1,176 -> use std::env; -> use std::fs; -> use std::io::{Read, Write}; -> use std::thread; -> use std::time::{Duration, Instant}; -> use log::{info, warn, error, LevelFilter}; -> -> const IA32_PERF_CTL: u32 = 0x199; -> const IA32_PERF_STATUS: u32 = 0x198; -> const POLL_INTERVAL_MS: u64 = 100; -> -> struct StderrLogger; -> impl log::Log for StderrLogger { -> fn enabled(&self, m: &log::Metadata) -> bool { m.level() <= LevelFilter::Info } -> fn log(&self, r: &log::Record) { eprintln!("[{}] cpufreqd: {}", r.level(), r.args()); } -> fn flush(&self) {} -> } -> -> #[derive(Clone, Copy, PartialEq)] -> enum Governor { Performance, Powersave, Ondemand } -> -> struct PState { freq_mhz: u32, power_mw: u32, latency_us: u32, ctl_value: u64 } -> -> struct CpuState { id: u32, current_pstate: usize, load: f64 } -> -> fn read_msr(cpu: u32, msr: u32) -> Option { -> let path = format!("/dev/cpu/{}/msr", cpu); -> let mut f = fs::OpenOptions::new().read(true).open(&path).ok()?; -> let mut buf = [0u8; 8]; -> f.read_exact(&mut buf).ok()?; -> Some(u64::from_ne_bytes(buf)) -> } -> -> fn write_msr(cpu: u32, msr: u32, value: u64) -> bool { -> let path = format!("/dev/cpu/{}/msr", cpu); -> fs::OpenOptions::new().write(true).open(&path) -> .and_then(|mut f| f.write_all(&value.to_ne_bytes())) -> .is_ok() -> } -> -> fn read_acpi_pss(cpu: u32) -> Vec { -> let path = format!("/scheme/acpi/processor/CPU{}/pss", cpu); -> let data = fs::read_to_string(&path).unwrap_or_default(); -> let mut states = Vec::new(); -> for line in data.lines() { -> let parts: Vec<&str> = line.split_whitespace().collect(); -> if parts.len() >= 6 { -> if let (Ok(freq), Ok(power), Ok(latency), Ok(ctl)) = ( -> parts[0].parse::(), parts[2].parse::(), -> parts[4].parse::(), u64::from_str_radix(parts[5], 16) -> ) { -> states.push(PState { freq_mhz: freq, power_mw: power, latency_us: latency, ctl_value: ctl }); -> } -> } -> } -> if states.is_empty() { -> states.push(PState { freq_mhz: 2400, power_mw: 35000, latency_us: 10, ctl_value: 0x1a00 }); -> states.push(PState { freq_mhz: 1200, power_mw: 15000, latency_us: 10, ctl_value: 0x0d00 }); -> } -> states -> } -> -> fn detect_cpus() -> Vec { -> let mut cpus = Vec::new(); -> if let Ok(entries) = fs::read_dir("/dev/cpu") { -> for entry in entries.flatten() { -> if let Ok(name) = entry.file_name().into_string() { -> if let Ok(id) = name.parse::() { -> cpus.push(id); -> } -> } -> } -> } -> if cpus.is_empty() { cpus.push(0); } -> cpus -> } -> -> fn measure_cpu_load(cpu: u32, prev: &mut (u64, u64)) -> f64 { -> let path = format!("/scheme/sys/cpu/{}/stat", cpu); -> if let Ok(data) = fs::read_to_string(&path) { -> let parts: Vec = data.split_whitespace().filter_map(|s| s.parse().ok()).collect(); -> if parts.len() >= 4 { -> let total: u64 = parts.iter().sum(); -> let idle = parts.get(3).copied().unwrap_or(0); -> let prev_total = prev.0; -> let prev_idle = prev.1; -> *prev = (total, idle); -> if total > prev_total { -> let total_delta = total - prev_total; -> let idle_delta = idle.saturating_sub(prev_idle); -> return 1.0 - (idle_delta as f64 / total_delta as f64); -> } -> } -> } -> 0.0 -> } -> -> fn choose_pstate(governor: Governor, pstates: &[PState], current: usize, load: f64) -> usize { -> match governor { -> Governor::Performance => 0, -> Governor::Powersave => pstates.len() - 1, -> Governor::Ondemand => { -> if load > 0.8 && current > 0 { current - 1 } -> else if load < 0.3 && current + 1 < pstates.len() { current + 1 } -> else { current } -> } -> } -> } -> -> fn main() { -> log::set_logger(&StderrLogger).ok(); -> log::set_max_level(LevelFilter::Info); -> -> let governor = match env::var("CPUFREQ_GOVERNOR").unwrap_or_else(|_| "ondemand".to_string()).as_str() { -> "performance" => Governor::Performance, -> "powersave" => Governor::Powersave, -> _ => Governor::Ondemand, -> }; -> -> let cpus = detect_cpus(); -> info!("detected {} CPU(s)", cpus.len()); -> -> let all_pstates: Vec> = cpus.iter().map(|cpu| read_acpi_pss(*cpu)).collect(); -> if all_pstates.iter().all(|p| p.is_empty()) { -> error!("no P-states found, cannot scale frequency"); -> return; -> } -> -> let mut cpu_states: Vec = cpus.iter().enumerate().map(|(i, &id)| { -> CpuState { id, current_pstate: 0, load: 0.0 } -> }).collect(); -> let mut prev_stats: Vec<(u64, u64)> = vec![(0, 0); cpus.len()]; -> -> info!("governor={:?}, {} P-states available", governor, all_pstates[0].len()); -> for (i, p) in all_pstates[0].iter().enumerate() { -> info!(" P{}: {} MHz, {} mW, latency {} us", i, p.freq_mhz, p.power_mw, p.latency_us); -> } -> -> for (i, cs) in cpu_states.iter_mut().enumerate() { -> let pstates = &all_pstates[i]; -> if !pstates.is_empty() { -> let ctl = pstates[0].ctl_value; -> if write_msr(cs.id, IA32_PERF_CTL, ctl) { -> info!("CPU{}: set P0 ({} MHz)", cs.id, pstates[0].freq_mhz); -> } else { -> warn!("CPU{}: MSR write failed, trying ACPI path", cs.id); -> } -> } -> } -> -> let poll = Duration::from_millis(POLL_INTERVAL_MS); -> -> loop { -> thread::sleep(poll); -> -> for (i, cs) in cpu_states.iter_mut().enumerate() { -> let load = measure_cpu_load(cs.id, &mut prev_stats[i]); -> cs.load = load; -> -> let pstates = &all_pstates[i]; -> if pstates.is_empty() { continue; } -> -> let new_idx = choose_pstate(governor, pstates, cs.current_pstate, load); -> if new_idx != cs.current_pstate { -> let ctl = pstates[new_idx].ctl_value; -> if write_msr(cs.id, IA32_PERF_CTL, ctl) { -> info!("CPU{}: P{}→P{} ({}→{} MHz, load={:.1}%)", -> cs.id, cs.current_pstate, new_idx, -> pstates[cs.current_pstate].freq_mhz, pstates[new_idx].freq_mhz, -> load * 100.0); -> cs.current_pstate = new_idx; -> } -> } -> } -> } -> } +--- a/drivers/cpufreqd/src/main.rs ++++ b/drivers/cpufreqd/src/main.rs +@@ -0,0 +1,167 @@ ++use std::env; ++use std::fs; ++use std::io::{Read, Write}; ++use std::thread; ++use std::time::{Duration, Instant}; ++ ++const IA32_PERF_CTL: u32 = 0x199; ++const IA32_PERF_STATUS: u32 = 0x198; ++const POLL_INTERVAL_MS: u64 = 100; ++ ++#[derive(Clone, Copy, PartialEq)] ++enum Governor { Performance, Powersave, Ondemand } ++ ++struct PState { freq_mhz: u32, power_mw: u32, latency_us: u32, ctl_value: u64 } ++ ++struct CpuState { id: u32, current_pstate: usize, load: f64 } ++ ++fn read_msr(cpu: u32, msr: u32) -> Option { ++ let path = format!("/dev/cpu/{}/msr", cpu); ++ let mut f = fs::OpenOptions::new().read(true).open(&path).ok()?; ++ let mut buf = [0u8; 8]; ++ f.read_exact(&mut buf).ok()?; ++ Some(u64::from_ne_bytes(buf)) ++} ++ ++fn write_msr(cpu: u32, msr: u32, value: u64) -> bool { ++ let path = format!("/dev/cpu/{}/msr", cpu); ++ fs::OpenOptions::new().write(true).open(&path) ++ .and_then(|mut f| f.write_all(&value.to_ne_bytes())) ++ .is_ok() ++} ++ ++fn read_acpi_pss(cpu: u32) -> Vec { ++ let path = format!("/scheme/acpi/processor/CPU{}/pss", cpu); ++ let data = fs::read_to_string(&path).unwrap_or_default(); ++ let mut states = Vec::new(); ++ for line in data.lines() { ++ let parts: Vec<&str> = line.split_whitespace().collect(); ++ if parts.len() >= 6 { ++ if let (Ok(freq), Ok(power), Ok(latency), Ok(ctl)) = ( ++ parts[0].parse::(), parts[2].parse::(), ++ parts[4].parse::(), u64::from_str_radix(parts[5], 16) ++ ) { ++ states.push(PState { freq_mhz: freq, power_mw: power, latency_us: latency, ctl_value: ctl }); ++ } ++ } ++ } ++ if states.is_empty() { ++ states.push(PState { freq_mhz: 2400, power_mw: 35000, latency_us: 10, ctl_value: 0x1a00 }); ++ states.push(PState { freq_mhz: 1200, power_mw: 15000, latency_us: 10, ctl_value: 0x0d00 }); ++ } ++ states ++} ++ ++fn detect_cpus() -> Vec { ++ let mut cpus = Vec::new(); ++ if let Ok(entries) = fs::read_dir("/dev/cpu") { ++ for entry in entries.flatten() { ++ if let Ok(name) = entry.file_name().into_string() { ++ if let Ok(id) = name.parse::() { ++ cpus.push(id); ++ } ++ } ++ } ++ } ++ if cpus.is_empty() { cpus.push(0); } ++ cpus ++} ++ ++fn measure_cpu_load(cpu: u32, prev: &mut (u64, u64)) -> f64 { ++ let path = format!("/scheme/sys/cpu/{}/stat", cpu); ++ if let Ok(data) = fs::read_to_string(&path) { ++ let parts: Vec = data.split_whitespace().filter_map(|s| s.parse().ok()).collect(); ++ if parts.len() >= 4 { ++ let total: u64 = parts.iter().sum(); ++ let idle = parts.get(3).copied().unwrap_or(0); ++ let prev_total = prev.0; ++ let prev_idle = prev.1; ++ *prev = (total, idle); ++ if total > prev_total { ++ let total_delta = total - prev_total; ++ let idle_delta = idle.saturating_sub(prev_idle); ++ return 1.0 - (idle_delta as f64 / total_delta as f64); ++ } ++ } ++ } ++ 0.0 ++} ++ ++fn choose_pstate(governor: Governor, pstates: &[PState], current: usize, load: f64) -> usize { ++ match governor { ++ Governor::Performance => 0, ++ Governor::Powersave => pstates.len() - 1, ++ Governor::Ondemand => { ++ if load > 0.8 && current > 0 { current - 1 } ++ else if load < 0.3 && current + 1 < pstates.len() { current + 1 } ++ else { current } ++ } ++ } ++} ++ ++fn main() { ++ eprintln!("cpufreqd: started"); ++ ++ let governor = match env::var("CPUFREQ_GOVERNOR").unwrap_or_else(|_| "ondemand".to_string()).as_str() { ++ "performance" => Governor::Performance, ++ "powersave" => Governor::Powersave, ++ _ => Governor::Ondemand, ++ }; ++ ++ let cpus = detect_cpus(); ++ eprintln!("cpufreqd: detected {} CPU(s)", cpus.len()); ++ ++ let all_pstates: Vec> = cpus.iter().map(|cpu| read_acpi_pss(*cpu)).collect(); ++ if all_pstates.iter().all(|p| p.is_empty()) { ++ eprintln!("cpufreqd: no P-states found, cannot scale frequency"); ++ return; ++ } ++ ++ let mut cpu_states: Vec = cpus.iter().enumerate().map(|(i, &id)| { ++ CpuState { id, current_pstate: 0, load: 0.0 } ++ }).collect(); ++ let mut prev_stats: Vec<(u64, u64)> = vec![(0, 0); cpus.len()]; ++ ++ eprintln!("cpufreqd: governor={:?}, {} P-states available", governor, all_pstates[0].len()); ++ for (i, p) in all_pstates[0].iter().enumerate() { ++ eprintln!("cpufreqd: P{}: {} MHz, {} mW, latency {} us", i, p.freq_mhz, p.power_mw, p.latency_us); ++ } ++ ++ for (i, cs) in cpu_states.iter_mut().enumerate() { ++ let pstates = &all_pstates[i]; ++ if !pstates.is_empty() { ++ let ctl = pstates[0].ctl_value; ++ if write_msr(cs.id, IA32_PERF_CTL, ctl) { ++ eprintln!("cpufreqd: CPU{}: set P0 ({} MHz)", cs.id, pstates[0].freq_mhz); ++ } else { ++ eprintln!("cpufreqd: CPU{}: MSR write failed, trying ACPI path", cs.id); ++ } ++ } ++ } ++ ++ let poll = Duration::from_millis(POLL_INTERVAL_MS); ++ ++ loop { ++ thread::sleep(poll); ++ ++ for (i, cs) in cpu_states.iter_mut().enumerate() { ++ let load = measure_cpu_load(cs.id, &mut prev_stats[i]); ++ cs.load = load; ++ ++ let pstates = &all_pstates[i]; ++ if pstates.is_empty() { continue; } ++ ++ let new_idx = choose_pstate(governor, pstates, cs.current_pstate, load); ++ if new_idx != cs.current_pstate { ++ let ctl = pstates[new_idx].ctl_value; ++ if write_msr(cs.id, IA32_PERF_CTL, ctl) { ++ eprintln!("cpufreqd: CPU{}: P{}→P{} ({}→{} MHz, load={:.1}%)", ++ cs.id, cs.current_pstate, new_idx, ++ pstates[cs.current_pstate].freq_mhz, pstates[new_idx].freq_mhz, ++ load * 100.0); ++ cs.current_pstate = new_idx; ++ } ++ } ++ } ++ } ++} diff --git a/local/patches/base/P6-driver-phase0-phase2.patch b/local/patches/base/P6-driver-phase0-phase2.patch deleted file mode 100644 index e0c82419a7..0000000000 --- a/local/patches/base/P6-driver-phase0-phase2.patch +++ /dev/null @@ -1,89 +0,0 @@ -diff --git a/drivers/audio/ihdad/src/main.rs b/drivers/audio/ihdad/src/main.rs -index 31a2add7..a75a0a35 100755 ---- a/drivers/audio/ihdad/src/main.rs -+++ b/drivers/audio/ihdad/src/main.rs -@@ -57,7 +57,15 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - EventQueue::::new().expect("ihdad: Could not create event queue."); - let socket = Socket::nonblock().expect("ihdad: failed to create socket"); - let mut device = unsafe { -- hda::IntelHDA::new(address, vend_prod).expect("ihdad: failed to allocate device") -+ match hda::IntelHDA::new(address, vend_prod) { -+ Ok(dev) => dev, -+ Err(e) => { -+ log::error!("ihdad: failed to initialize HDA device (err {}), exiting gracefully", e); -+ log::info!("ihdad: this is expected in virtual environments without functional HDA hardware"); -+ daemon.ready(); -+ return loop {}; -+ } -+ } - }; - let mut readiness_based = ReadinessBased::new(&socket, 16); - -diff --git a/drivers/net/e1000d/src/main.rs b/drivers/net/e1000d/src/main.rs -index 373ea9b3..2e4cf579 100644 ---- a/drivers/net/e1000d/src/main.rs -+++ b/drivers/net/e1000d/src/main.rs -@@ -70,15 +70,15 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - - libredox::call::setrens(0, 0).expect("e1000d: failed to enter null namespace"); - -- scheme.tick().unwrap(); -+ scheme.tick().expect("e1000d: initial scheme tick failed"); - - for event in event_queue.map(|e| e.expect("e1000d: failed to get event")) { - match event.user_data { - Source::Irq => { - let mut irq = [0; 8]; -- irq_file.read(&mut irq).unwrap(); -+ irq_file.read(&mut irq).expect("e1000d: failed to read IRQ from kernel"); - if unsafe { scheme.adapter().irq() } { -- irq_file.write(&mut irq).unwrap(); -+ irq_file.write(&mut irq).expect("e1000d: failed to acknowledge IRQ"); - - scheme.tick().expect("e1000d: failed to handle IRQ") - } -diff --git a/drivers/net/rtl8168d/src/main.rs b/drivers/net/rtl8168d/src/main.rs -index 1d9963a3..08efda6c 100644 ---- a/drivers/net/rtl8168d/src/main.rs -+++ b/drivers/net/rtl8168d/src/main.rs -@@ -81,33 +81,33 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - Source::Irq, - event::EventFlags::READ, - ) -- .unwrap(); -+ .expect("rtl8168d: failed to subscribe to scheme event"); - event_queue - .subscribe( - scheme.event_handle().raw(), - Source::Scheme, - event::EventFlags::READ, - ) -- .unwrap(); -+ .expect("rtl8168d: failed to subscribe to scheme event"); - - libredox::call::setrens(0, 0).expect("rtl8168d: failed to enter null namespace"); - -- scheme.tick().unwrap(); -+ scheme.tick().expect("rtl8168d: scheme tick failed"); - - for event in event_queue.map(|e| e.expect("rtl8168d: failed to get next event")) { - match event.user_data { - Source::Irq => { - let mut irq = [0; 8]; -- irq_file.irq_handle().read(&mut irq).unwrap(); -+ irq_file.irq_handle().read(&mut irq).expect("rtl8168d: failed to read IRQ"); - //TODO: This may be causing spurious interrupts - if unsafe { scheme.adapter_mut().irq() } { -- irq_file.irq_handle().write(&mut irq).unwrap(); -+ irq_file.irq_handle().write(&mut irq).expect("rtl8168d: failed to acknowledge IRQ"); - -- scheme.tick().unwrap(); -+ scheme.tick().expect("rtl8168d: scheme tick failed"); - } - } - Source::Scheme => { -- scheme.tick().unwrap(); -+ scheme.tick().expect("rtl8168d: scheme tick failed"); - } - } - } diff --git a/local/patches/base/P6-e1000d-msi-migration.patch b/local/patches/base/P6-e1000d-msi-migration.patch deleted file mode 100644 index 052e00b851..0000000000 --- a/local/patches/base/P6-e1000d-msi-migration.patch +++ /dev/null @@ -1,63 +0,0 @@ -diff --git a/drivers/net/e1000d/src/main.rs b/drivers/net/e1000d/src/main.rs -index 373ea9b3..f62eaad7 100644 ---- a/drivers/net/e1000d/src/main.rs -+++ b/drivers/net/e1000d/src/main.rs -@@ -3,6 +3,7 @@ use std::os::unix::io::AsRawFd; - - use driver_network::NetworkScheme; - use event::{user_data, EventQueue}; -+use pcid_interface::irq_helpers::pci_allocate_interrupt_vector; - use pcid_interface::PciFunctionHandle; - - pub mod device; -@@ -25,14 +26,11 @@ 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_vector = pci_allocate_interrupt_vector(&mut pcid_handle, "e1000d"); - - log::info!("E1000 {}", pci_config.func.display()); - -- let mut irq_file = irq.irq_handle("e1000d"); -+ let irq_file = irq_vector.irq_handle(); - - let address = unsafe { pcid_handle.map_bar(0) }.ptr.as_ptr() as usize; - -@@ -53,9 +51,11 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - - let event_queue = EventQueue::::new().expect("e1000d: failed to create event queue"); - -+ let irq_fd = irq_vector.irq_handle().as_raw_fd(); -+ - event_queue - .subscribe( -- irq_file.as_raw_fd() as usize, -+ irq_fd as usize, - Source::Irq, - event::EventFlags::READ, - ) -@@ -70,15 +70,17 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - - libredox::call::setrens(0, 0).expect("e1000d: failed to enter null namespace"); - -- scheme.tick().unwrap(); -+ scheme.tick().expect("e1000d: tick failed"); -+ -+ let mut irq_file = irq_vector.irq_handle(); - - for event in event_queue.map(|e| e.expect("e1000d: failed to get event")) { - match event.user_data { - Source::Irq => { - let mut irq = [0; 8]; -- irq_file.read(&mut irq).unwrap(); -+ irq_file.read(&mut irq).expect("e1000d: IRQ read failed"); - if unsafe { scheme.adapter().irq() } { -- irq_file.write(&mut irq).unwrap(); -+ irq_file.write(&mut irq).expect("e1000d: IRQ ack failed"); - - scheme.tick().expect("e1000d: failed to handle IRQ") - } diff --git a/local/patches/base/P9-init-debug-logging.patch b/local/patches/base/P9-init-debug-logging.patch deleted file mode 100644 index 3b76a1b48d..0000000000 --- a/local/patches/base/P9-init-debug-logging.patch +++ /dev/null @@ -1,32 +0,0 @@ ---- a/init/src/main.rs -+++ b/init/src/main.rs -@@ -157,13 +157,17 @@ - for entry in entries { -+ status_ok(&format!("RB_LOAD {}", entry.display())); - scheduler.schedule_start_and_report_errors( - &mut unit_store, - UnitId(entry.file_name().unwrap().to_str().unwrap().to_owned()), - ); - } -+ status_ok(&format!("RB_STORE {} units", unit_store.units.len())); - }; - - scheduler.step(&mut unit_store, &mut init_config); -+ status_ok("RB_STEP_DONE"); - - libredox::call::setrens(0, 0).expect("init: failed to enter null namespace"); - ---- a/init/src/scheduler.rs -+++ b/init/src/scheduler.rs -@@ -64,9 +64,11 @@ - JobKind::Start => { - let unit = unit_store.unit_mut(&job.unit); -+ status_ok(&format!("RB_RUN {}", unit.id.0)); - - for dep in &unit.info.requires_weak { - for pending_job in &self.pending { - if &pending_job.unit == dep { -+ status_ok(&format!("RB_DEFER {} (dep {} pending)", unit.id.0, dep.0)); - self.pending.push_back(job); - continue 'a'; - } diff --git a/local/patches/base/P9-init-debug-scheduler.patch b/local/patches/base/P9-init-debug-scheduler.patch deleted file mode 100644 index bb076ca45a..0000000000 --- a/local/patches/base/P9-init-debug-scheduler.patch +++ /dev/null @@ -1,11 +0,0 @@ -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/P9-init-scheduler-debug.patch b/local/patches/base/P9-init-scheduler-debug.patch deleted file mode 100644 index 65883c798c..0000000000 --- a/local/patches/base/P9-init-scheduler-debug.patch +++ /dev/null @@ -1,36 +0,0 @@ ---- a/init/src/scheduler.rs 2026-05-04 22:01:55.238384585 +0100 -+++ b/init/src/scheduler.rs 2026-05-04 22:37:31.323354810 +0100 -@@ -6,6 +6,7 @@ - - pub struct Scheduler { - pending: VecDeque, -+ completed: VecDeque, - } - - struct Job { -@@ -21,6 +22,7 @@ - pub fn new() -> Scheduler { - Scheduler { - pending: VecDeque::new(), -+ completed: VecDeque::new(), - } - } - -@@ -66,6 +68,9 @@ - let unit = unit_store.unit_mut(&job.unit); - - for dep in &unit.info.requires_weak { -+ if self.completed.contains(dep) { -+ continue; -+ } - for pending_job in &self.pending { - if &pending_job.unit == dep { - self.pending.push_back(job); -@@ -75,6 +80,7 @@ - } - - run(unit, init_config); -+ self.completed.push_back(job.unit); - } - } - } diff --git a/local/patches/base/P0-daemon-init-notify-graceful.patch b/local/patches/base/absorbed/P0-daemon-init-notify-graceful.patch similarity index 100% rename from local/patches/base/P0-daemon-init-notify-graceful.patch rename to local/patches/base/absorbed/P0-daemon-init-notify-graceful.patch diff --git a/local/patches/base/P0-daemon-silence-init-notify.patch b/local/patches/base/absorbed/P0-daemon-silence-init-notify.patch similarity index 100% rename from local/patches/base/P0-daemon-silence-init-notify.patch rename to local/patches/base/absorbed/P0-daemon-silence-init-notify.patch diff --git a/local/patches/base/P0-driver-api-migration-fixes.patch b/local/patches/base/absorbed/P0-driver-api-migration-fixes.patch similarity index 100% rename from local/patches/base/P0-driver-api-migration-fixes.patch rename to local/patches/base/absorbed/P0-driver-api-migration-fixes.patch diff --git a/local/patches/base/P2-daemon-hardening.patch b/local/patches/base/absorbed/P2-daemon-hardening.patch similarity index 100% rename from local/patches/base/P2-daemon-hardening.patch rename to local/patches/base/absorbed/P2-daemon-hardening.patch diff --git a/recipes/core/base/drivers/acpi-resource/Cargo.toml b/recipes/core/base/drivers/acpi-resource/Cargo.toml new file mode 100644 index 0000000000..f30c6d023c --- /dev/null +++ b/recipes/core/base/drivers/acpi-resource/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "acpi-resource" +description = "Shared ACPI resource template decoder" +version = "0.0.1" +authors = ["Red Bear OS"] +repository = "https://gitlab.redox-os.org/redox-os/drivers" +categories = ["hardware-support"] +license = "MIT/Apache-2.0" +edition = "2021" + +[dependencies] +serde.workspace = true +thiserror.workspace = true diff --git a/recipes/core/base/drivers/acpi-resource/src/lib.rs b/recipes/core/base/drivers/acpi-resource/src/lib.rs new file mode 100644 index 0000000000..57ae4b4b86 --- /dev/null +++ b/recipes/core/base/drivers/acpi-resource/src/lib.rs @@ -0,0 +1,688 @@ +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +const SMALL_IRQ: u8 = 0x20; +const SMALL_END_TAG: u8 = 0x78; + +const LARGE_MEMORY32: u8 = 0x85; +const LARGE_FIXED_MEMORY32: u8 = 0x86; +const LARGE_ADDRESS32: u8 = 0x87; +const LARGE_EXTENDED_IRQ: u8 = 0x89; +const LARGE_ADDRESS64: u8 = 0x8A; +const LARGE_GPIO: u8 = 0x8C; +const LARGE_SERIAL_BUS: u8 = 0x8E; + +const SERIAL_BUS_I2C: u8 = 1; +const I2C_TYPE_DATA_LEN: usize = 6; + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum InterruptTrigger { + Edge, + Level, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum InterruptPolarity { + ActiveHigh, + ActiveLow, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum AddressResourceType { + MemoryRange, + IoRange, + BusNumberRange, + Unknown(u8), +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct ResourceSource { + pub index: u8, + pub source: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct IrqDescriptor { + pub interrupts: Vec, + pub triggering: InterruptTrigger, + pub polarity: InterruptPolarity, + pub shareable: bool, + pub wake_capable: bool, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct ExtendedIrqDescriptor { + pub producer_consumer: bool, + pub interrupts: Vec, + pub triggering: InterruptTrigger, + pub polarity: InterruptPolarity, + pub shareable: bool, + pub wake_capable: bool, + pub resource_source: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct GpioDescriptor { + pub revision_id: u8, + pub producer_consumer: bool, + pub pin_config: u8, + pub shareable: bool, + pub wake_capable: bool, + pub io_restriction: u8, + pub triggering: InterruptTrigger, + pub polarity: InterruptPolarity, + pub drive_strength: u16, + pub debounce_timeout: u16, + pub pins: Vec, + pub resource_source: Option, + pub vendor_data: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct I2cSerialBusDescriptor { + pub revision_id: u8, + pub producer_consumer: bool, + pub slave_mode: bool, + pub connection_sharing: bool, + pub type_revision_id: u8, + pub access_mode_10bit: bool, + pub slave_address: u16, + pub connection_speed: u32, + pub resource_source: Option, + pub vendor_data: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct Memory32RangeDescriptor { + pub write_protect: bool, + pub minimum: u32, + pub maximum: u32, + pub alignment: u32, + pub address_length: u32, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct FixedMemory32Descriptor { + pub write_protect: bool, + pub address: u32, + pub address_length: u32, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct Address32Descriptor { + pub resource_type: AddressResourceType, + pub producer_consumer: bool, + pub decode: bool, + pub min_address_fixed: bool, + pub max_address_fixed: bool, + pub specific_flags: u8, + pub granularity: u32, + pub minimum: u32, + pub maximum: u32, + pub translation_offset: u32, + pub address_length: u32, + pub resource_source: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct Address64Descriptor { + pub resource_type: AddressResourceType, + pub producer_consumer: bool, + pub decode: bool, + pub min_address_fixed: bool, + pub max_address_fixed: bool, + pub specific_flags: u8, + pub granularity: u64, + pub minimum: u64, + pub maximum: u64, + pub translation_offset: u64, + pub address_length: u64, + pub resource_source: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum ResourceDescriptor { + Irq(IrqDescriptor), + ExtendedIrq(ExtendedIrqDescriptor), + GpioInt(GpioDescriptor), + GpioIo(GpioDescriptor), + I2cSerialBus(I2cSerialBusDescriptor), + Memory32Range(Memory32RangeDescriptor), + FixedMemory32(FixedMemory32Descriptor), + Address32(Address32Descriptor), + Address64(Address64Descriptor), +} + +#[derive(Debug, Error, PartialEq, Eq)] +pub enum ResourceDecodeError { + #[error("descriptor at offset {offset} overruns the resource template")] + TruncatedDescriptor { offset: usize }, + + #[error("unsupported small descriptor length {length} for tag {tag:#04x} at offset {offset}")] + InvalidSmallLength { + offset: usize, + tag: u8, + length: usize, + }, + + #[error("descriptor {descriptor} at offset {offset} is shorter than {minimum} bytes")] + InvalidLargeLength { + offset: usize, + descriptor: &'static str, + minimum: usize, + }, + + #[error("descriptor {descriptor} at offset {offset} has an invalid internal offset")] + InvalidInternalOffset { + offset: usize, + descriptor: &'static str, + }, +} + +pub fn decode_resource_template( + bytes: &[u8], +) -> Result, ResourceDecodeError> { + let mut resources = Vec::new(); + let mut offset = 0usize; + + while offset < bytes.len() { + let descriptor = *bytes + .get(offset) + .ok_or(ResourceDecodeError::TruncatedDescriptor { offset })?; + + if descriptor & 0x80 == 0 { + let length = usize::from(descriptor & 0x07); + let end = offset + 1 + length; + let desc = bytes + .get(offset..end) + .ok_or(ResourceDecodeError::TruncatedDescriptor { offset })?; + let body = &desc[1..]; + + match descriptor & 0x78 { + SMALL_IRQ => resources.push(ResourceDescriptor::Irq(parse_irq(body, offset)?)), + SMALL_END_TAG => break, + _ => {} + } + + offset = end; + continue; + } + + let length = usize::from(read_u16(bytes, offset + 1)?); + let end = offset + 3 + length; + let desc = bytes + .get(offset..end) + .ok_or(ResourceDecodeError::TruncatedDescriptor { offset })?; + let body = &desc[3..]; + + match descriptor { + LARGE_MEMORY32 => resources.push(ResourceDescriptor::Memory32Range(parse_memory32( + body, offset, + )?)), + LARGE_FIXED_MEMORY32 => resources.push(ResourceDescriptor::FixedMemory32( + parse_fixed_memory32(body, offset)?, + )), + LARGE_ADDRESS32 => { + resources.push(ResourceDescriptor::Address32(parse_address32( + desc, body, offset, + )?)); + } + LARGE_ADDRESS64 => { + resources.push(ResourceDescriptor::Address64(parse_address64( + desc, body, offset, + )?)); + } + LARGE_EXTENDED_IRQ => resources.push(ResourceDescriptor::ExtendedIrq( + parse_extended_irq(desc, body, offset)?, + )), + LARGE_GPIO => { + let (is_interrupt, descriptor) = parse_gpio(desc, body, offset)?; + resources.push(if is_interrupt { + ResourceDescriptor::GpioInt(descriptor) + } else { + ResourceDescriptor::GpioIo(descriptor) + }); + } + LARGE_SERIAL_BUS => { + if let Some(descriptor) = parse_i2c_serial_bus(desc, body, offset)? { + resources.push(ResourceDescriptor::I2cSerialBus(descriptor)); + } + } + _ => {} + } + + offset = end; + } + + Ok(resources) +} + +fn parse_irq(body: &[u8], offset: usize) -> Result { + if body.len() != 2 && body.len() != 3 { + return Err(ResourceDecodeError::InvalidSmallLength { + offset, + tag: SMALL_IRQ, + length: body.len(), + }); + } + + let mask = u16::from_le_bytes([body[0], body[1]]); + let flags = body.get(2).copied().unwrap_or(0); + let interrupts = (0..16) + .filter(|irq| mask & (1 << irq) != 0) + .map(|irq| irq as u8) + .collect(); + + Ok(IrqDescriptor { + interrupts, + triggering: if flags & 0x01 != 0 { + InterruptTrigger::Level + } else { + InterruptTrigger::Edge + }, + polarity: if flags & 0x08 != 0 { + InterruptPolarity::ActiveLow + } else { + InterruptPolarity::ActiveHigh + }, + shareable: flags & 0x10 != 0, + wake_capable: flags & 0x20 != 0, + }) +} + +fn parse_extended_irq( + desc: &[u8], + body: &[u8], + offset: usize, +) -> Result { + ensure_length(body, 2, offset, "ExtendedIrq")?; + + let flags = body[0]; + let count = usize::from(body[1]); + let ints_len = count * 4; + ensure_length(body, 2 + ints_len, offset, "ExtendedIrq")?; + + let interrupts = (0..count) + .map(|index| read_u32(body, 2 + index * 4)) + .collect::, _>>()?; + let resource_source = if body.len() > 2 + ints_len { + Some(parse_source_inline(&body[2 + ints_len..])) + } else { + None + }; + + let _ = desc; + + Ok(ExtendedIrqDescriptor { + producer_consumer: flags & 0x01 != 0, + triggering: if flags & 0x02 != 0 { + InterruptTrigger::Level + } else { + InterruptTrigger::Edge + }, + polarity: if flags & 0x04 != 0 { + InterruptPolarity::ActiveLow + } else { + InterruptPolarity::ActiveHigh + }, + shareable: flags & 0x08 != 0, + wake_capable: flags & 0x10 != 0, + interrupts, + resource_source, + }) +} + +fn parse_gpio( + desc: &[u8], + body: &[u8], + offset: usize, +) -> Result<(bool, GpioDescriptor), ResourceDecodeError> { + ensure_length(body, 20, offset, "Gpio")?; + + let connection_type = body[1]; + let flags = read_u16(body, 2)?; + let int_flags = read_u16(body, 4)?; + let pin_table_offset = usize::from(read_u16(body, 11)?); + let resource_source_index = body[13]; + let resource_source_offset = usize::from(read_u16(body, 14)?); + let vendor_offset = usize::from(read_u16(body, 16)?); + let vendor_length = usize::from(read_u16(body, 18)?); + + let pins_end = min_nonzero([resource_source_offset, vendor_offset, desc.len()]); + let pins = parse_u16_list(desc, pin_table_offset, pins_end, offset, "Gpio")?; + let resource_source = parse_source_absolute( + desc, + resource_source_offset, + min_nonzero([vendor_offset, desc.len()]), + resource_source_index, + offset, + "Gpio", + )?; + let vendor_data = parse_blob_absolute(desc, vendor_offset, vendor_length, offset, "Gpio")?; + + Ok(( + connection_type == 0, + GpioDescriptor { + revision_id: body[0], + producer_consumer: flags & 0x0001 != 0, + pin_config: body[6], + shareable: int_flags & 0x0008 != 0, + wake_capable: int_flags & 0x0010 != 0, + io_restriction: (int_flags & 0x0003) as u8, + triggering: if int_flags & 0x0001 != 0 { + InterruptTrigger::Level + } else { + InterruptTrigger::Edge + }, + polarity: if int_flags & 0x0002 != 0 { + InterruptPolarity::ActiveLow + } else { + InterruptPolarity::ActiveHigh + }, + drive_strength: read_u16(body, 7)?, + debounce_timeout: read_u16(body, 9)?, + pins, + resource_source, + vendor_data, + }, + )) +} + +fn parse_i2c_serial_bus( + desc: &[u8], + body: &[u8], + offset: usize, +) -> Result, ResourceDecodeError> { + ensure_length(body, 15, offset, "SerialBus")?; + if body[2] != SERIAL_BUS_I2C { + return Ok(None); + } + + let type_data_length = usize::from(read_u16(body, 7)?); + if type_data_length < I2C_TYPE_DATA_LEN { + return Err(ResourceDecodeError::InvalidLargeLength { + offset, + descriptor: "I2cSerialBus", + minimum: 15, + }); + } + + let vendor_length = type_data_length - I2C_TYPE_DATA_LEN; + let vendor_data = parse_blob_absolute(desc, 18, vendor_length, offset, "I2cSerialBus")?; + let resource_source = parse_source_absolute( + desc, + 12 + type_data_length, + desc.len(), + body[1], + offset, + "I2cSerialBus", + )?; + + Ok(Some(I2cSerialBusDescriptor { + revision_id: body[0], + producer_consumer: body[3] & 0x02 != 0, + slave_mode: body[3] & 0x01 != 0, + connection_sharing: body[3] & 0x04 != 0, + type_revision_id: body[6], + access_mode_10bit: read_u16(body, 4)? & 0x0001 != 0, + connection_speed: read_u32(body, 9)?, + slave_address: read_u16(body, 13)?, + resource_source, + vendor_data, + })) +} + +fn parse_memory32( + body: &[u8], + offset: usize, +) -> Result { + ensure_length(body, 17, offset, "Memory32Range")?; + Ok(Memory32RangeDescriptor { + write_protect: body[0] & 0x01 != 0, + minimum: read_u32(body, 1)?, + maximum: read_u32(body, 5)?, + alignment: read_u32(body, 9)?, + address_length: read_u32(body, 13)?, + }) +} + +fn parse_fixed_memory32( + body: &[u8], + offset: usize, +) -> Result { + ensure_length(body, 9, offset, "FixedMemory32")?; + Ok(FixedMemory32Descriptor { + write_protect: body[0] & 0x01 != 0, + address: read_u32(body, 1)?, + address_length: read_u32(body, 5)?, + }) +} + +fn parse_address32( + desc: &[u8], + body: &[u8], + offset: usize, +) -> Result { + ensure_length(body, 23, offset, "Address32")?; + Ok(Address32Descriptor { + resource_type: parse_address_type(body[0]), + producer_consumer: body[1] & 0x01 != 0, + decode: body[1] & 0x02 != 0, + min_address_fixed: body[1] & 0x04 != 0, + max_address_fixed: body[1] & 0x08 != 0, + specific_flags: body[2], + granularity: read_u32(body, 3)?, + minimum: read_u32(body, 7)?, + maximum: read_u32(body, 11)?, + translation_offset: read_u32(body, 15)?, + address_length: read_u32(body, 19)?, + resource_source: if desc.len() > 26 { + parse_source_absolute(desc, 26, desc.len(), desc[26], offset, "Address32")? + } else { + None + }, + }) +} + +fn parse_address64( + desc: &[u8], + body: &[u8], + offset: usize, +) -> Result { + ensure_length(body, 43, offset, "Address64")?; + Ok(Address64Descriptor { + resource_type: parse_address_type(body[0]), + producer_consumer: body[1] & 0x01 != 0, + decode: body[1] & 0x02 != 0, + min_address_fixed: body[1] & 0x04 != 0, + max_address_fixed: body[1] & 0x08 != 0, + specific_flags: body[2], + granularity: read_u64(body, 3)?, + minimum: read_u64(body, 11)?, + maximum: read_u64(body, 19)?, + translation_offset: read_u64(body, 27)?, + address_length: read_u64(body, 35)?, + resource_source: if desc.len() > 46 { + parse_source_absolute(desc, 46, desc.len(), desc[46], offset, "Address64")? + } else { + None + }, + }) +} + +fn ensure_length( + body: &[u8], + minimum: usize, + offset: usize, + descriptor: &'static str, +) -> Result<(), ResourceDecodeError> { + if body.len() < minimum { + return Err(ResourceDecodeError::InvalidLargeLength { + offset, + descriptor, + minimum, + }); + } + Ok(()) +} + +fn parse_source_inline(bytes: &[u8]) -> ResourceSource { + let index = bytes.first().copied().unwrap_or(0); + let source = bytes.get(1..).map(parse_nul_string).unwrap_or_default(); + ResourceSource { index, source } +} + +fn parse_source_absolute( + desc: &[u8], + start: usize, + end: usize, + index: u8, + offset: usize, + descriptor: &'static str, +) -> Result, ResourceDecodeError> { + if start == 0 || start >= end || start > desc.len() { + return Ok(None); + } + let slice = desc + .get(start..end) + .ok_or(ResourceDecodeError::InvalidInternalOffset { offset, descriptor })?; + Ok(Some(ResourceSource { + index, + source: parse_nul_string(slice), + })) +} + +fn parse_blob_absolute( + desc: &[u8], + start: usize, + length: usize, + offset: usize, + descriptor: &'static str, +) -> Result, ResourceDecodeError> { + if start == 0 || length == 0 { + return Ok(Vec::new()); + } + let end = start + length; + Ok(desc + .get(start..end) + .ok_or(ResourceDecodeError::InvalidInternalOffset { offset, descriptor })? + .to_vec()) +} + +fn parse_u16_list( + desc: &[u8], + start: usize, + end: usize, + offset: usize, + descriptor: &'static str, +) -> Result, ResourceDecodeError> { + if start == 0 || start >= end || start > desc.len() { + return Ok(Vec::new()); + } + let slice = desc + .get(start..end) + .ok_or(ResourceDecodeError::InvalidInternalOffset { offset, descriptor })?; + if slice.len() % 2 != 0 { + return Err(ResourceDecodeError::InvalidInternalOffset { offset, descriptor }); + } + slice + .chunks_exact(2) + .map(|chunk| Ok(u16::from_le_bytes([chunk[0], chunk[1]]))) + .collect() +} + +fn parse_nul_string(bytes: &[u8]) -> String { + let end = bytes + .iter() + .position(|byte| *byte == 0) + .unwrap_or(bytes.len()); + String::from_utf8_lossy(&bytes[..end]).to_string() +} + +fn parse_address_type(value: u8) -> AddressResourceType { + match value { + 0 => AddressResourceType::MemoryRange, + 1 => AddressResourceType::IoRange, + 2 => AddressResourceType::BusNumberRange, + other => AddressResourceType::Unknown(other), + } +} + +fn read_u16(bytes: &[u8], offset: usize) -> Result { + let slice = bytes + .get(offset..offset + 2) + .ok_or(ResourceDecodeError::TruncatedDescriptor { offset })?; + Ok(u16::from_le_bytes([slice[0], slice[1]])) +} + +fn read_u32(bytes: &[u8], offset: usize) -> Result { + let slice = bytes + .get(offset..offset + 4) + .ok_or(ResourceDecodeError::TruncatedDescriptor { offset })?; + Ok(u32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]])) +} + +fn read_u64(bytes: &[u8], offset: usize) -> Result { + let slice = bytes + .get(offset..offset + 8) + .ok_or(ResourceDecodeError::TruncatedDescriptor { offset })?; + Ok(u64::from_le_bytes([ + slice[0], slice[1], slice[2], slice[3], slice[4], slice[5], slice[6], slice[7], + ])) +} + +fn min_nonzero(values: [usize; N]) -> usize { + values + .into_iter() + .filter(|value| *value != 0) + .min() + .unwrap_or(0) +} + +#[cfg(test)] +mod tests { + use super::{decode_resource_template, ResourceDescriptor}; + + #[test] + fn decodes_small_irq_descriptor() { + let resources = decode_resource_template(&[0x23, 0x0A, 0x00, 0x19, 0x79, 0x00]).unwrap(); + + assert!(matches!( + &resources[0], + ResourceDescriptor::Irq(descriptor) + if descriptor.interrupts == vec![1, 3] + && descriptor.shareable + && descriptor.wake_capable == false + )); + } + + #[test] + fn decodes_i2c_serial_bus_descriptor() { + let template = [ + 0x8E, 0x14, 0x00, 0x01, 0x02, 0x01, 0x02, 0x00, 0x00, 0x01, 0x06, 0x00, 0x80, 0x1A, + 0x06, 0x00, 0x15, 0x00, b'I', b'2', b'C', b'0', 0x00, 0x79, 0x00, + ]; + let resources = decode_resource_template(&template).unwrap(); + + assert!(matches!( + &resources[0], + ResourceDescriptor::I2cSerialBus(descriptor) + if descriptor.connection_speed == 400_000 + && descriptor.slave_address == 0x15 + && descriptor.resource_source.as_ref().map(|source| source.source.as_str()) + == Some("I2C0") + )); + } + + #[test] + fn decodes_gpio_interrupt_descriptor() { + let template = [ + 0x8C, 0x1B, 0x00, 0x01, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, + 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, b'\\', b'_', b'S', b'B', + 0x00, 0x79, 0x00, + ]; + let resources = decode_resource_template(&template).unwrap(); + + assert!(matches!(&resources[0], ResourceDescriptor::GpioInt(_))); + } +} diff --git a/recipes/core/base/drivers/gpio/gpiod/Cargo.toml b/recipes/core/base/drivers/gpio/gpiod/Cargo.toml new file mode 100644 index 0000000000..7e087bd776 --- /dev/null +++ b/recipes/core/base/drivers/gpio/gpiod/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "gpiod" +description = "GPIO controller registry daemon" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow.workspace = true +log.workspace = true +redox_syscall = { workspace = true, features = ["std"] } +libredox.workspace = true +redox-scheme.workspace = true +ron.workspace = true +serde.workspace = true + +common = { path = "../../common" } +daemon = { path = "../../../daemon" } +scheme-utils = { path = "../../../scheme-utils" } + +[lints] +workspace = true diff --git a/recipes/core/base/drivers/gpio/gpiod/src/main.rs b/recipes/core/base/drivers/gpio/gpiod/src/main.rs new file mode 100644 index 0000000000..41618ba1ac --- /dev/null +++ b/recipes/core/base/drivers/gpio/gpiod/src/main.rs @@ -0,0 +1,496 @@ +use std::collections::BTreeMap; +use std::process; + +use anyhow::{Context, Result}; +use redox_scheme::scheme::SchemeSync; +use redox_scheme::{CallerCtx, OpenResult, Socket}; +use scheme_utils::{Blocking, HandleMap}; +use serde::{Deserialize, Serialize}; +use syscall::schemev2::NewFdFlags; +use syscall::{Error as SysError, EACCES, EBADF, EINVAL, ENOENT}; + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct GpioControllerInfo { + pub id: u32, + pub name: String, + pub pin_count: usize, + pub supports_interrupt: bool, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum GpioControlRequest { + RegisterController { info: GpioControllerInfo }, + ReadPin { controller_id: u32, pin: u32 }, + WritePin { controller_id: u32, pin: u32, value: bool }, + ConfigurePin { controller_id: u32, pin: u32, config: PinConfig }, + ListControllers, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct PinConfig { + pub direction: PinDirection, + pub pull: PullMode, + pub interrupt_mode: Option, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum PinDirection { + Input, + Output, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum PullMode { + None, + Up, + Down, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum InterruptMode { + EdgeRising, + EdgeFalling, + EdgeBoth, + LevelHigh, + LevelLow, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +enum GpioControlResponse { + ControllerRegistered { id: u32 }, + Controllers(Vec), + Controller(GpioControllerInfo), + PinValue(bool), + Ack, + Error(String), +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum PinOpKind { + Read, + Write, + Configure, +} + +enum Handle { + SchemeRoot, + Register { pending: Vec }, + Provider { controller_id: u32, pending: Vec }, + ControllersDir { pending: Vec }, + ControllerDetail { id: u32, pending: Vec }, + PinOp { kind: PinOpKind, pending: Vec }, +} + +struct ControllerEntry { + info: GpioControllerInfo, + provider_handle: usize, +} + +struct GpioDaemon { + handles: HandleMap, + controllers: BTreeMap, + next_id: u32, +} + +impl GpioDaemon { + fn new() -> Self { + Self { + handles: HandleMap::new(), + controllers: BTreeMap::new(), + next_id: 0, + } + } + + fn controller_list(&self) -> Vec { + self.controllers + .values() + .map(|entry| entry.info.clone()) + .collect() + } + + fn serialize_response(response: &GpioControlResponse) -> syscall::Result> { + ron::ser::to_string(response) + .map(|text| text.into_bytes()) + .map_err(|err| { + log::error!("gpiod: failed to serialize control response: {err}"); + SysError::new(EINVAL) + }) + } + + fn deserialize_request(buf: &[u8]) -> syscall::Result { + let text = std::str::from_utf8(buf).map_err(|err| { + log::warn!("gpiod: invalid UTF-8 request payload: {err}"); + SysError::new(EINVAL) + })?; + + ron::from_str(text).map_err(|err| { + log::warn!("gpiod: failed to decode control request: {err}"); + SysError::new(EINVAL) + }) + } + + fn set_pending_response(handle: &mut Handle, response: GpioControlResponse) -> syscall::Result<()> { + let pending = Self::serialize_response(&response)?; + Self::set_pending_bytes(handle, pending) + } + + fn set_pending_bytes(handle: &mut Handle, pending: Vec) -> syscall::Result<()> { + match handle { + Handle::Register { pending: slot } + | Handle::Provider { pending: slot, .. } + | Handle::ControllersDir { pending: slot } + | Handle::ControllerDetail { pending: slot, .. } + | Handle::PinOp { pending: slot, .. } => { + *slot = pending; + Ok(()) + } + Handle::SchemeRoot => Err(SysError::new(EBADF)), + } + } + + fn copy_pending(handle: &mut Handle, buf: &mut [u8], offset: u64) -> syscall::Result { + let pending = match handle { + Handle::Register { pending } + | Handle::Provider { pending, .. } + | Handle::ControllersDir { pending } + | Handle::ControllerDetail { pending, .. } + | Handle::PinOp { pending, .. } => pending, + Handle::SchemeRoot => return Err(SysError::new(EBADF)), + }; + + let offset = usize::try_from(offset).map_err(|_| SysError::new(EINVAL))?; + if offset >= pending.len() { + return Ok(0); + } + + let copy_len = buf.len().min(pending.len() - offset); + buf[..copy_len].copy_from_slice(&pending[offset..offset + copy_len]); + Ok(copy_len) + } + + fn validate_pin_target( + &self, + controller_id: u32, + pin: u32, + ) -> std::result::Result { + let entry = self + .controllers + .get(&controller_id) + .ok_or_else(|| format!("unknown controller {controller_id}"))?; + if usize::try_from(pin) + .ok() + .filter(|pin| *pin < entry.info.pin_count) + .is_none() + { + return Err(format!( + "pin {pin} is out of range for controller {} (pin_count={})", + entry.info.name, entry.info.pin_count + )); + } + Ok(entry.info.clone()) + } +} + +impl SchemeSync for GpioDaemon { + fn scheme_root(&mut self) -> syscall::Result { + Ok(self.handles.insert(Handle::SchemeRoot)) + } + + fn openat( + &mut self, + dirfd: usize, + path: &str, + _flags: usize, + _fcntl_flags: u32, + _ctx: &CallerCtx, + ) -> syscall::Result { + let handle = self.handles.get(dirfd)?; + let segments = path.trim_matches('/'); + + let new_handle = match handle { + Handle::SchemeRoot => { + if segments.is_empty() { + return Err(SysError::new(EINVAL)); + } + + let mut parts = segments.split('/'); + match parts.next() { + Some("register") if parts.next().is_none() => Handle::Register { + pending: Vec::new(), + }, + Some("controllers") => match parts.next() { + None => Handle::ControllersDir { + pending: Vec::new(), + }, + Some(id) if parts.next().is_none() => Handle::ControllerDetail { + id: id.parse::().map_err(|_| SysError::new(EINVAL))?, + pending: Vec::new(), + }, + _ => return Err(SysError::new(EINVAL)), + }, + Some("read_pin") if parts.next().is_none() => Handle::PinOp { + kind: PinOpKind::Read, + pending: Vec::new(), + }, + Some("write_pin") if parts.next().is_none() => Handle::PinOp { + kind: PinOpKind::Write, + pending: Vec::new(), + }, + Some("configure_pin") if parts.next().is_none() => Handle::PinOp { + kind: PinOpKind::Configure, + pending: Vec::new(), + }, + _ => return Err(SysError::new(ENOENT)), + } + } + Handle::ControllersDir { .. } => { + if segments.is_empty() { + return Err(SysError::new(EINVAL)); + } + + Handle::ControllerDetail { + id: segments.parse::().map_err(|_| SysError::new(EINVAL))?, + pending: Vec::new(), + } + } + _ => return Err(SysError::new(EACCES)), + }; + + let fd = self.handles.insert(new_handle); + Ok(OpenResult::ThisScheme { + number: fd, + flags: NewFdFlags::empty(), + }) + } + + fn read( + &mut self, + id: usize, + buf: &mut [u8], + offset: u64, + _fcntl_flags: u32, + _ctx: &CallerCtx, + ) -> syscall::Result { + let controllers = self.controller_list(); + let detail = match self.handles.get(id)? { + Handle::ControllerDetail { id, .. } => self.controllers.get(id).map(|entry| entry.info.clone()), + _ => None, + }; + + let handle = self.handles.get_mut(id)?; + match handle { + Handle::ControllersDir { pending } if pending.is_empty() => { + *pending = Self::serialize_response(&GpioControlResponse::Controllers(controllers))?; + } + Handle::ControllerDetail { id, pending } if pending.is_empty() => { + let info = detail.ok_or(SysError::new(ENOENT))?; + *pending = Self::serialize_response(&GpioControlResponse::Controller(info))?; + log::debug!("gpiod: served controller detail for id={id}"); + } + _ => {} + } + + Self::copy_pending(handle, buf, offset) + } + + fn write( + &mut self, + id: usize, + buf: &[u8], + _offset: u64, + _fcntl_flags: u32, + _ctx: &CallerCtx, + ) -> syscall::Result { + let request = Self::deserialize_request(buf)?; + + match request { + GpioControlRequest::RegisterController { mut info } => { + if !matches!(self.handles.get(id)?, Handle::Register { .. }) { + return Err(SysError::new(EINVAL)); + } + + let controller_id = self.next_id; + self.next_id = self.next_id.checked_add(1).ok_or(SysError::new(EINVAL))?; + info.id = controller_id; + self.controllers.insert( + controller_id, + ControllerEntry { + info: info.clone(), + provider_handle: id, + }, + ); + + let handle = self.handles.get_mut(id)?; + *handle = Handle::Provider { + controller_id, + pending: Self::serialize_response(&GpioControlResponse::ControllerRegistered { + id: controller_id, + })?, + }; + + log::info!( + "RB_GPIOD_CONTROLLER_REGISTERED id={} name={} pin_count={} supports_interrupt={}", + info.id, + info.name, + info.pin_count, + info.supports_interrupt, + ); + Ok(buf.len()) + } + GpioControlRequest::ListControllers => { + let controllers = self.controller_list(); + let handle = self.handles.get_mut(id)?; + Self::set_pending_response(handle, GpioControlResponse::Controllers(controllers))?; + Ok(buf.len()) + } + GpioControlRequest::ReadPin { controller_id, pin } => { + let validation = self.validate_pin_target(controller_id, pin); + let handle = self.handles.get_mut(id)?; + match handle { + Handle::PinOp { + kind: PinOpKind::Read, + .. + } => { + match validation { + Ok(info) => { + log::info!( + "RB_GPIOD_PIN_READ controller_id={} name={} pin={} routed=stub", + controller_id, + info.name, + pin, + ); + Self::set_pending_response(handle, GpioControlResponse::PinValue(false))?; + } + Err(message) => { + Self::set_pending_response(handle, GpioControlResponse::Error(message))?; + } + } + Ok(buf.len()) + } + _ => Err(SysError::new(EINVAL)), + } + } + GpioControlRequest::WritePin { + controller_id, + pin, + value, + } => { + let validation = self.validate_pin_target(controller_id, pin); + let handle = self.handles.get_mut(id)?; + match handle { + Handle::PinOp { + kind: PinOpKind::Write, + .. + } => { + match validation { + Ok(info) => { + log::info!( + "RB_GPIOD_PIN_WRITE controller_id={} name={} pin={} value={} routed=stub", + controller_id, + info.name, + pin, + value, + ); + Self::set_pending_response(handle, GpioControlResponse::Ack)?; + } + Err(message) => { + Self::set_pending_response(handle, GpioControlResponse::Error(message))?; + } + } + Ok(buf.len()) + } + _ => Err(SysError::new(EINVAL)), + } + } + GpioControlRequest::ConfigurePin { + controller_id, + pin, + config, + } => { + let validation = self.validate_pin_target(controller_id, pin); + let handle = self.handles.get_mut(id)?; + match handle { + Handle::PinOp { + kind: PinOpKind::Configure, + .. + } => { + match validation { + Ok(info) => { + log::info!( + "RB_GPIOD_PIN_CONFIG controller_id={} name={} pin={} direction={:?} pull={:?} interrupt={:?} routed=stub", + controller_id, + info.name, + pin, + config.direction, + config.pull, + config.interrupt_mode, + ); + Self::set_pending_response(handle, GpioControlResponse::Ack)?; + } + Err(message) => { + Self::set_pending_response(handle, GpioControlResponse::Error(message))?; + } + } + Ok(buf.len()) + } + _ => Err(SysError::new(EINVAL)), + } + } + } + } + + fn on_close(&mut self, id: usize) { + let Some(handle) = self.handles.remove(id) else { + return; + }; + if let Handle::Provider { controller_id, .. } = handle { + if let Some(entry) = self.controllers.remove(&controller_id) { + log::info!( + "RB_GPIOD_CONTROLLER_REMOVED id={} name={} provider_handle={}", + controller_id, + entry.info.name, + entry.provider_handle, + ); + } + } + } +} + +fn run_daemon(daemon: daemon::SchemeDaemon) -> Result<()> { + let socket = Socket::create().context("failed to create gpio scheme socket")?; + let mut scheme = GpioDaemon::new(); + let handler = Blocking::new(&socket, 16); + + daemon + .ready_sync_scheme(&socket, &mut scheme) + .context("failed to publish gpio scheme root")?; + + log::info!("RB_GPIOD_SCHEMA version=1"); + + libredox::call::setrens(0, 0).context("failed to enter null namespace")?; + + handler + .process_requests_blocking(scheme) + .context("failed to process gpiod requests")?; +} + +fn daemon_runner(daemon: daemon::SchemeDaemon) -> ! { + if let Err(err) = run_daemon(daemon) { + log::error!("gpiod: {err:#}"); + process::exit(1); + } + + process::exit(0); +} + +fn main() { + common::setup_logging( + "gpio", + "gpio", + "gpiod", + common::output_level(), + common::file_level(), + ); + + daemon::SchemeDaemon::new(daemon_runner); +} diff --git a/recipes/core/base/drivers/gpio/i2c-gpio-expanderd/Cargo.toml b/recipes/core/base/drivers/gpio/i2c-gpio-expanderd/Cargo.toml new file mode 100644 index 0000000000..3e168e968f --- /dev/null +++ b/recipes/core/base/drivers/gpio/i2c-gpio-expanderd/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "i2c-gpio-expanderd" +description = "I2C GPIO expander bridge daemon" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow.workspace = true +log.workspace = true +redox_syscall = { workspace = true, features = ["std"] } +libredox.workspace = true +serde.workspace = true +ron.workspace = true + +acpi-resource = { path = "../../acpi-resource" } +common = { path = "../../common" } +daemon = { path = "../../../daemon" } +i2c-interface = { path = "../../i2c/i2c-interface" } + +[lints] +workspace = true diff --git a/recipes/core/base/drivers/gpio/i2c-gpio-expanderd/src/main.rs b/recipes/core/base/drivers/gpio/i2c-gpio-expanderd/src/main.rs new file mode 100644 index 0000000000..d457d2df6d --- /dev/null +++ b/recipes/core/base/drivers/gpio/i2c-gpio-expanderd/src/main.rs @@ -0,0 +1,454 @@ +use std::collections::BTreeMap; +use std::fs::{self, File, OpenOptions}; +use std::io::{Read, Write}; +use std::path::Path; +use std::process; + +use acpi_resource::{GpioDescriptor, I2cSerialBusDescriptor, ResourceDescriptor}; +use anyhow::{Context, Result}; +use i2c_interface::{ + I2cAdapterInfo, I2cControlRequest, I2cControlResponse, I2cTransferRequest, + I2cTransferResponse, I2cTransferSegment, +}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize)] +struct AmlSymbol { + name: String, + value: AmlValue, +} + +#[derive(Debug, Deserialize)] +enum AmlValue { + Integer(u64), + String(String), +} + +#[derive(Clone, Debug)] +struct ExpanderResources { + i2c: I2cSerialBusDescriptor, + pin_count: usize, + gpio_int_count: usize, + gpio_io_count: usize, +} + +#[derive(Debug)] +struct ExpanderDescriptor { + device: String, + hid: String, + resources: ExpanderResources, +} + +struct RegisteredExpander { + _registration: File, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +struct GpioControllerInfo { + id: u32, + name: String, + pin_count: usize, + supports_interrupt: bool, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +enum GpioControlRequest { + RegisterController { info: GpioControllerInfo }, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +enum GpioControlResponse { + ControllerRegistered { id: u32 }, + Error(String), +} + +fn main() { + common::setup_logging( + "gpio", + "i2c-gpio-expander", + "i2c-gpio-expanderd", + common::output_level(), + common::file_level(), + ); + + daemon::Daemon::new(daemon_runner); +} + +fn daemon_runner(daemon: daemon::Daemon) -> ! { + if let Err(err) = daemon_main(daemon) { + log::error!("i2c-gpio-expanderd: {err:#}"); + process::exit(1); + } + + process::exit(0); +} + +fn daemon_main(daemon: daemon::Daemon) -> Result<()> { + let expanders = discover_expanders().context("failed to discover ACPI I2C GPIO expanders")?; + if expanders.is_empty() { + log::info!("i2c-gpio-expanderd: no probable ACPI I2C GPIO expanders found"); + } + + let adapters = list_i2c_adapters().unwrap_or_else(|err| { + log::warn!("i2c-gpio-expanderd: unable to query i2cd adapters: {err:#}"); + Vec::new() + }); + + let mut registered = Vec::new(); + for expander in expanders { + match register_expander(expander, &adapters) { + Ok(expander) => registered.push(expander), + Err(err) => log::warn!("i2c-gpio-expanderd: expander registration skipped: {err:#}"), + } + } + + daemon.ready(); + libredox::call::setrens(0, 0).context("failed to enter null namespace")?; + + log::info!("i2c-gpio-expanderd: registered {} expander(s)", registered.len()); + + loop { + std::thread::park(); + } +} + +fn discover_expanders() -> Result> { + let mut matched = BTreeMap::new(); + + 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!("i2c-gpio-expanderd: ACPI symbols are not ready yet"); + return Ok(Vec::new()); + } + Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"), + }; + + for entry in entries { + let entry = entry.context("failed to read ACPI symbol directory entry")?; + let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else { + continue; + }; + if !file_name.ends_with("_HID") && !file_name.ends_with("_CID") { + continue; + } + + let Some(id) = read_symbol_id(&entry.path())? else { + continue; + }; + if is_excluded_device_id(&id) { + continue; + } + + let Some(device) = file_name + .strip_suffix("_HID") + .or_else(|| file_name.strip_suffix("_CID")) + .map(str::to_owned) + else { + continue; + }; + + let resources = match read_expander_resources(&device) { + Ok(resources) => resources, + Err(err) => { + log::debug!("i2c-gpio-expanderd: skipping {device}: {err:#}"); + continue; + } + }; + if resources.gpio_int_count == 0 && resources.gpio_io_count == 0 { + continue; + } + + matched.entry(device).or_insert((id, resources)); + } + + let mut expanders = Vec::new(); + for (device, (hid, resources)) in matched { + expanders.push(ExpanderDescriptor { + device, + hid, + resources, + }); + } + Ok(expanders) +} + +fn read_symbol_id(path: &Path) -> Result> { + let contents = fs::read_to_string(path) + .with_context(|| format!("failed to read ACPI symbol {}", path.display()))?; + let symbol = match ron::from_str::(&contents) { + Ok(symbol) => symbol, + Err(err) => { + log::debug!( + "i2c-gpio-expanderd: skipping {} because the symbol payload was not a scalar ID: {err}", + path.display(), + ); + return Ok(None); + } + }; + + let id = match symbol.value { + AmlValue::Integer(integer) => eisa_id_from_integer(integer), + AmlValue::String(string) => string, + }; + + log::debug!("i2c-gpio-expanderd: {} -> {id}", symbol.name); + Ok(Some(id)) +} + +fn read_expander_resources(device: &str) -> Result { + let contents = fs::read_to_string(format!("/scheme/acpi/resources/{device}")) + .with_context(|| format!("failed to read /scheme/acpi/resources/{device}"))?; + let resources = ron::from_str::>(&contents) + .with_context(|| format!("failed to decode RON resources for {device}"))?; + + let mut i2c = None; + let mut pin_count = 0usize; + let mut gpio_int_count = 0usize; + let mut gpio_io_count = 0usize; + + for resource in resources { + match resource { + ResourceDescriptor::I2cSerialBus(bus) if i2c.is_none() => i2c = Some(bus), + ResourceDescriptor::GpioInt(descriptor) => { + gpio_int_count += 1; + pin_count = pin_count.max(pin_count_from_descriptor(&descriptor)); + } + ResourceDescriptor::GpioIo(descriptor) => { + gpio_io_count += 1; + pin_count = pin_count.max(pin_count_from_descriptor(&descriptor)); + } + _ => {} + } + } + + Ok(ExpanderResources { + i2c: i2c.context("no I2cSerialBus resource was found")?, + pin_count, + gpio_int_count, + gpio_io_count, + }) +} + +fn pin_count_from_descriptor(descriptor: &GpioDescriptor) -> usize { + descriptor + .pins + .iter() + .copied() + .max() + .map(|pin| usize::from(pin).saturating_add(1)) + .unwrap_or(0) +} + +fn is_excluded_device_id(id: &str) -> bool { + matches!( + id, + "PNP0C50" + | "ACPI0C50" + | "INT34C5" + | "INTC1055" + | "INT33C2" + | "INT33C3" + | "INT3432" + | "INT3433" + | "INTC10EF" + | "AMDI0010" + | "AMDI0019" + | "AMDI0510" + | "PNP0CA0" + | "AMDI0042" + ) || id.starts_with("ELAN") + || id.starts_with("CYAP") + || id.starts_with("SYNA") +} + +fn register_expander(expander: ExpanderDescriptor, adapters: &[I2cAdapterInfo]) -> Result { + let ExpanderDescriptor { + device, + hid, + resources, + } = expander; + + let adapter_name = resources + .i2c + .resource_source + .as_ref() + .map(|source| source.source.clone()) + .filter(|source| !source.is_empty()) + .unwrap_or_else(|| String::from("ACPI-I2C")); + let adapter = match match_i2c_adapter(adapters, &adapter_name) { + Some(adapter) => Some(adapter.clone()), + None => { + log::warn!( + "i2c-gpio-expanderd: unable to resolve I2C adapter {} for {}", + adapter_name, + device, + ); + None + } + }; + + if let Some(adapter) = adapter.as_ref() { + if let Err(err) = probe_expander(adapter, &adapter_name, resources.i2c.slave_address) { + log::warn!( + "i2c-gpio-expanderd: expander {} probe on {}@{:04x} failed: {err:#}", + device, + adapter_name, + resources.i2c.slave_address, + ); + } + } + + let info = GpioControllerInfo { + id: 0, + name: format!("i2c-gpio-expander:{device}"), + pin_count: resources.pin_count, + supports_interrupt: resources.gpio_int_count > 0, + }; + let mut registration = register_with_gpiod(&info) + .with_context(|| format!("failed to register {device} with gpiod"))?; + let response = read_gpio_registration_response(&mut registration) + .with_context(|| format!("failed to read gpiod registration response for {device}"))?; + + match response { + GpioControlResponse::ControllerRegistered { id } => { + log::info!( + "RB_I2C_GPIO_EXPANDERD_DEVICE device={} hid={} controller_id={} adapter={} addr={:04x} pin_count={} gpio_int={} gpio_io={}", + device, + hid, + id, + adapter_name, + resources.i2c.slave_address, + info.pin_count, + resources.gpio_int_count, + resources.gpio_io_count, + ); + } + GpioControlResponse::Error(message) => { + anyhow::bail!("gpiod rejected expander {device}: {message}"); + } + } + + Ok(RegisteredExpander { + _registration: registration, + }) +} + +fn list_i2c_adapters() -> Result> { + let mut file = OpenOptions::new() + .read(true) + .write(true) + .open("/scheme/i2c/adapters") + .context("failed to open /scheme/i2c/adapters")?; + + let payload = ron::ser::to_string(&I2cControlRequest::ListAdapters) + .context("failed to encode I2C list-adapters request")?; + file.write_all(payload.as_bytes()) + .context("failed to request I2C adapter list")?; + + let response = read_i2c_control_response(&mut file)?; + match response { + I2cControlResponse::AdapterList(adapters) => Ok(adapters), + I2cControlResponse::Error(message) => anyhow::bail!("i2cd returned an error: {message}"), + other => anyhow::bail!("unexpected i2cd list-adapters response: {other:?}"), + } +} + +fn match_i2c_adapter<'a>(adapters: &'a [I2cAdapterInfo], wanted: &str) -> Option<&'a I2cAdapterInfo> { + adapters + .iter() + .find(|adapter| adapter.name == wanted) + .or_else(|| adapters.iter().find(|adapter| adapter.name.ends_with(wanted))) + .or_else(|| adapters.iter().find(|adapter| wanted.ends_with(&adapter.name))) +} + +fn probe_expander(adapter: &I2cAdapterInfo, adapter_name: &str, address: u16) -> Result { + let request = I2cTransferRequest { + adapter: adapter_name.to_string(), + segments: vec![I2cTransferSegment::read(address, 1)], + stop: true, + }; + + let mut file = OpenOptions::new() + .read(true) + .write(true) + .open("/scheme/i2c/transfer") + .context("failed to open /scheme/i2c/transfer")?; + let payload = ron::ser::to_string(&I2cControlRequest::Transfer { + adapter_id: adapter.id, + request, + }) + .context("failed to encode I2C expander probe request")?; + file.write_all(payload.as_bytes()) + .context("failed to send I2C expander probe request")?; + + let response = read_i2c_control_response(&mut file)?; + match response { + I2cControlResponse::TransferResult(result) => { + if !result.ok { + let detail = result + .error + .clone() + .unwrap_or_else(|| String::from("unknown I2C transfer failure")); + anyhow::bail!("I2C probe failed: {detail}"); + } + Ok(result) + } + I2cControlResponse::Error(message) => anyhow::bail!("i2cd returned an error: {message}"), + other => anyhow::bail!("unexpected I2C transfer response: {other:?}"), + } +} + +fn register_with_gpiod(info: &GpioControllerInfo) -> Result { + let mut file = OpenOptions::new() + .read(true) + .write(true) + .open("/scheme/gpio/register") + .context("failed to open /scheme/gpio/register")?; + let payload = ron::ser::to_string(&GpioControlRequest::RegisterController { info: info.clone() }) + .context("failed to encode GPIO controller registration")?; + file.write_all(payload.as_bytes()) + .context("failed to send GPIO controller registration")?; + Ok(file) +} + +fn read_gpio_registration_response(file: &mut File) -> Result { + let mut buffer = vec![0_u8; 4096]; + let count = file + .read(&mut buffer) + .context("failed to read GPIO registration response")?; + buffer.truncate(count); + let text = std::str::from_utf8(&buffer).context("GPIO registration response was not UTF-8")?; + ron::from_str(text).context("failed to decode GPIO registration response") +} + +fn read_i2c_control_response(file: &mut File) -> Result { + let mut buffer = vec![0_u8; 4096]; + let count = file + .read(&mut buffer) + .context("failed to read I2C control response")?; + buffer.truncate(count); + let text = std::str::from_utf8(&buffer).context("I2C control response was not UTF-8")?; + let trimmed = text.trim(); + if trimmed.is_empty() { + return Ok(I2cControlResponse::AdapterList(Vec::new())); + } + ron::from_str(trimmed).context("failed to decode I2C control response") +} + +fn eisa_id_from_integer(integer: u64) -> String { + let vendor = integer & 0xFFFF; + let device = (integer >> 16) & 0xFFFF; + let vendor_rev = ((vendor & 0xFF) << 8) | (vendor >> 8); + let vendor_1 = (((vendor_rev >> 10) & 0x1F) as u8 + 64) as char; + let vendor_2 = (((vendor_rev >> 5) & 0x1F) as u8 + 64) as char; + let vendor_3 = (((vendor_rev >> 0) & 0x1F) as u8 + 64) as char; + let device_1 = (device >> 4) & 0xF; + let device_2 = (device >> 0) & 0xF; + let device_3 = (device >> 12) & 0xF; + let device_4 = (device >> 8) & 0xF; + + format!( + "{vendor_1}{vendor_2}{vendor_3}{device_1:01X}{device_2:01X}{device_3:01X}{device_4:01X}" + ) +} diff --git a/recipes/core/base/drivers/gpio/intel-gpiod/Cargo.toml b/recipes/core/base/drivers/gpio/intel-gpiod/Cargo.toml new file mode 100644 index 0000000000..f5ac93554a --- /dev/null +++ b/recipes/core/base/drivers/gpio/intel-gpiod/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "intel-gpiod" +description = "Intel ACPI GPIO registrar daemon" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow.workspace = true +log.workspace = true +redox_syscall = { workspace = true, features = ["std"] } +libredox.workspace = true +serde.workspace = true +ron.workspace = true + +acpi-resource = { path = "../../acpi-resource" } +common = { path = "../../common" } +daemon = { path = "../../../daemon" } + +[lints] +workspace = true diff --git a/recipes/core/base/drivers/gpio/intel-gpiod/src/main.rs b/recipes/core/base/drivers/gpio/intel-gpiod/src/main.rs new file mode 100644 index 0000000000..78e6099076 --- /dev/null +++ b/recipes/core/base/drivers/gpio/intel-gpiod/src/main.rs @@ -0,0 +1,401 @@ +use std::collections::BTreeMap; +use std::fs::{self, File, OpenOptions}; +use std::io::{Read, Write}; +use std::path::Path; +use std::process; + +use acpi_resource::{ + AddressResourceType, ExtendedIrqDescriptor, FixedMemory32Descriptor, GpioDescriptor, + IrqDescriptor, Memory32RangeDescriptor, ResourceDescriptor, +}; +use anyhow::{Context, Result}; +use common::{MemoryType, PhysBorrowed, Prot}; +use serde::{Deserialize, Serialize}; + +const SUPPORTED_IDS: &[&str] = &["INT34C5", "INTC1055"]; + +const PADNFGPIO_OWN_BASE: usize = 0x20; +const PADNFGPIO_PADCFG_BASE: usize = 0x700; +const GPI_INT_STATUS: usize = 0x100; +const GPI_INT_EN: usize = 0x120; +const INTEL_GPIO_MMIO_WINDOW: usize = PADNFGPIO_PADCFG_BASE + core::mem::size_of::(); + +#[derive(Debug, Deserialize)] +struct AmlSymbol { + name: String, + value: AmlValue, +} + +#[derive(Debug, Deserialize)] +enum AmlValue { + Integer(u64), + String(String), +} + +#[derive(Clone, Debug)] +struct ControllerResources { + mmio_base: usize, + mmio_len: usize, + pin_count: usize, + supports_interrupt: bool, + gpio_int_count: usize, + gpio_io_count: usize, +} + +#[derive(Debug)] +struct ControllerDescriptor { + device: String, + hid: String, + resources: ControllerResources, +} + +struct RegisteredController { + _mmio: Option, + _registration: File, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +struct GpioControllerInfo { + id: u32, + name: String, + pin_count: usize, + supports_interrupt: bool, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +enum GpioControlRequest { + RegisterController { info: GpioControllerInfo }, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +enum GpioControlResponse { + ControllerRegistered { id: u32 }, + Error(String), +} + +fn main() { + common::setup_logging( + "gpio", + "intel-gpio", + "intel-gpiod", + common::output_level(), + common::file_level(), + ); + + daemon::Daemon::new(daemon_runner); +} + +fn daemon_runner(daemon: daemon::Daemon) -> ! { + if let Err(err) = daemon_main(daemon) { + log::error!("intel-gpiod: {err:#}"); + process::exit(1); + } + + process::exit(0); +} + +fn daemon_main(daemon: daemon::Daemon) -> Result<()> { + common::init(); + + let controllers = + discover_controllers(SUPPORTED_IDS).context("failed to discover Intel GPIO controllers")?; + if controllers.is_empty() { + log::info!("intel-gpiod: no supported Intel GPIO ACPI controllers found"); + } + + let mut registered = Vec::new(); + for controller in controllers { + match register_controller(controller) { + Ok(controller) => registered.push(controller), + Err(err) => log::warn!("intel-gpiod: controller registration skipped: {err:#}"), + } + } + + daemon.ready(); + libredox::call::setrens(0, 0).context("failed to enter null namespace")?; + + log::info!("intel-gpiod: registered {} controller(s)", registered.len()); + + loop { + std::thread::park(); + } +} + +fn discover_controllers(supported_ids: &[&str]) -> Result> { + let mut matched = BTreeMap::new(); + + 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!("intel-gpiod: ACPI symbols are not ready yet"); + return Ok(Vec::new()); + } + Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"), + }; + + for entry in entries { + let entry = entry.context("failed to read ACPI symbol directory entry")?; + let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else { + continue; + }; + if !file_name.ends_with("_HID") && !file_name.ends_with("_CID") { + continue; + } + + let Some(id) = read_symbol_id(&entry.path())? else { + continue; + }; + if !supported_ids.iter().any(|candidate| *candidate == id) { + continue; + } + + let device = file_name + .strip_suffix("_HID") + .or_else(|| file_name.strip_suffix("_CID")) + .map(str::to_owned); + if let Some(device) = device { + matched.entry(device).or_insert(id); + } + } + + let mut controllers = Vec::new(); + for (device, hid) in matched { + let resources = read_controller_resources(&device) + .with_context(|| format!("failed to read resources for {device}"))?; + controllers.push(ControllerDescriptor { + device, + hid, + resources, + }); + } + + Ok(controllers) +} + +fn read_symbol_id(path: &Path) -> Result> { + let contents = fs::read_to_string(path) + .with_context(|| format!("failed to read ACPI symbol {}", path.display()))?; + let symbol = match ron::from_str::(&contents) { + Ok(symbol) => symbol, + Err(err) => { + log::debug!( + "intel-gpiod: skipping {} because the symbol payload was not a scalar ID: {err}", + path.display(), + ); + return Ok(None); + } + }; + + let id = match symbol.value { + AmlValue::Integer(integer) => eisa_id_from_integer(integer), + AmlValue::String(string) => string, + }; + + log::debug!("intel-gpiod: {} -> {id}", symbol.name); + Ok(Some(id)) +} + +fn read_controller_resources(device: &str) -> Result { + let contents = fs::read_to_string(format!("/scheme/acpi/resources/{device}")) + .with_context(|| format!("failed to read /scheme/acpi/resources/{device}"))?; + let resources = ron::from_str::>(&contents) + .with_context(|| format!("failed to decode RON resources for {device}"))?; + + let mut mmio = None; + let mut supports_interrupt = false; + let mut gpio_int_count = 0usize; + let mut gpio_io_count = 0usize; + let mut pin_count = 0usize; + + for resource in &resources { + match resource { + ResourceDescriptor::FixedMemory32(FixedMemory32Descriptor { + address, + address_length, + .. + }) if mmio.is_none() => { + mmio = Some(( + *address as usize, + (*address_length as usize).max(INTEL_GPIO_MMIO_WINDOW), + )); + } + ResourceDescriptor::Memory32Range(Memory32RangeDescriptor { + minimum, + maximum, + address_length, + .. + }) if mmio.is_none() && maximum >= minimum => { + let span = maximum.saturating_sub(*minimum).saturating_add(1) as usize; + mmio = Some(( + *minimum as usize, + span.max((*address_length as usize).max(INTEL_GPIO_MMIO_WINDOW)), + )); + } + ResourceDescriptor::Address32(descriptor) + if mmio.is_none() + && matches!(descriptor.resource_type, AddressResourceType::MemoryRange) => + { + mmio = Some(( + descriptor.minimum as usize, + (descriptor.address_length as usize).max(INTEL_GPIO_MMIO_WINDOW), + )); + } + ResourceDescriptor::Address64(descriptor) + if mmio.is_none() + && matches!(descriptor.resource_type, AddressResourceType::MemoryRange) => + { + let base = usize::try_from(descriptor.minimum) + .context("64-bit MMIO base does not fit in usize")?; + let len = usize::try_from(descriptor.address_length) + .context("64-bit MMIO length does not fit in usize")?; + mmio = Some((base, len.max(INTEL_GPIO_MMIO_WINDOW))); + } + ResourceDescriptor::Irq(IrqDescriptor { interrupts, .. }) => { + supports_interrupt |= !interrupts.is_empty(); + } + ResourceDescriptor::ExtendedIrq(ExtendedIrqDescriptor { interrupts, .. }) => { + supports_interrupt |= !interrupts.is_empty(); + } + ResourceDescriptor::GpioInt(descriptor) => { + gpio_int_count += 1; + supports_interrupt = true; + pin_count = pin_count.max(pin_count_from_descriptor(descriptor)); + } + ResourceDescriptor::GpioIo(descriptor) => { + gpio_io_count += 1; + pin_count = pin_count.max(pin_count_from_descriptor(descriptor)); + } + _ => {} + } + } + + let (mmio_base, mmio_len) = mmio.context("no MMIO resource was found")?; + Ok(ControllerResources { + mmio_base, + mmio_len, + pin_count, + supports_interrupt, + gpio_int_count, + gpio_io_count, + }) +} + +fn pin_count_from_descriptor(descriptor: &GpioDescriptor) -> usize { + descriptor + .pins + .iter() + .copied() + .max() + .map(|pin| usize::from(pin).saturating_add(1)) + .unwrap_or(0) +} + +fn register_controller(controller: ControllerDescriptor) -> Result { + let ControllerDescriptor { + device, + hid, + resources, + } = controller; + + let mmio = match PhysBorrowed::map( + resources.mmio_base, + resources.mmio_len, + Prot::RW, + MemoryType::Uncacheable, + ) { + Ok(mapping) => Some(mapping), + Err(err) => { + log::warn!( + "intel-gpiod: failed to map MMIO for {device} ({:#x}, len {:#x}): {err}", + resources.mmio_base, + resources.mmio_len, + ); + None + } + }; + + log::info!( + "intel-gpiod: discovered {device} hid={hid} mmio={:#x}+{:#x} pin_count={} gpio_int={} gpio_io={} supports_interrupt={}", + resources.mmio_base, + resources.mmio_len, + resources.pin_count, + resources.gpio_int_count, + resources.gpio_io_count, + resources.supports_interrupt, + ); + log::debug!( + "intel-gpiod: register model own={PADNFGPIO_OWN_BASE:#x} padcfg={PADNFGPIO_PADCFG_BASE:#x} gpi_int_status={GPI_INT_STATUS:#x} gpi_int_en={GPI_INT_EN:#x}", + ); + + let info = GpioControllerInfo { + id: 0, + name: format!("intel-gpio:{device}"), + pin_count: resources.pin_count, + supports_interrupt: resources.supports_interrupt, + }; + let mut registration = register_with_gpiod(&info) + .with_context(|| format!("failed to register {device} with gpiod"))?; + let response = read_registration_response(&mut registration) + .with_context(|| format!("failed to read gpiod registration response for {device}"))?; + + match response { + GpioControlResponse::ControllerRegistered { id } => { + log::info!( + "RB_INTEL_GPIOD_DEVICE device={} hid={} controller_id={} pin_count={} supports_interrupt={}", + device, + hid, + id, + info.pin_count, + info.supports_interrupt, + ); + } + GpioControlResponse::Error(message) => { + anyhow::bail!("gpiod rejected Intel GPIO controller {device}: {message}"); + } + } + + Ok(RegisteredController { + _mmio: mmio, + _registration: registration, + }) +} + +fn register_with_gpiod(info: &GpioControllerInfo) -> Result { + let mut file = OpenOptions::new() + .read(true) + .write(true) + .open("/scheme/gpio/register") + .context("failed to open /scheme/gpio/register")?; + let payload = ron::ser::to_string(&GpioControlRequest::RegisterController { info: info.clone() }) + .context("failed to encode GPIO controller registration")?; + file.write_all(payload.as_bytes()) + .context("failed to send GPIO controller registration")?; + Ok(file) +} + +fn read_registration_response(file: &mut File) -> Result { + let mut buffer = vec![0_u8; 4096]; + let count = file + .read(&mut buffer) + .context("failed to read GPIO registration response")?; + buffer.truncate(count); + let text = std::str::from_utf8(&buffer).context("GPIO registration response was not UTF-8")?; + ron::from_str(text).context("failed to decode GPIO registration response") +} + +fn eisa_id_from_integer(integer: u64) -> String { + let vendor = integer & 0xFFFF; + let device = (integer >> 16) & 0xFFFF; + let vendor_rev = ((vendor & 0xFF) << 8) | (vendor >> 8); + let vendor_1 = (((vendor_rev >> 10) & 0x1F) as u8 + 64) as char; + let vendor_2 = (((vendor_rev >> 5) & 0x1F) as u8 + 64) as char; + let vendor_3 = (((vendor_rev >> 0) & 0x1F) as u8 + 64) as char; + let device_1 = (device >> 4) & 0xF; + let device_2 = (device >> 0) & 0xF; + let device_3 = (device >> 12) & 0xF; + let device_4 = (device >> 8) & 0xF; + + format!( + "{vendor_1}{vendor_2}{vendor_3}{device_1:01X}{device_2:01X}{device_3:01X}{device_4:01X}" + ) +} diff --git a/recipes/core/base/drivers/i2c/amd-mp2-i2cd/Cargo.toml b/recipes/core/base/drivers/i2c/amd-mp2-i2cd/Cargo.toml new file mode 100644 index 0000000000..357ca948c7 --- /dev/null +++ b/recipes/core/base/drivers/i2c/amd-mp2-i2cd/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "amd-mp2-i2cd" +description = "AMD MP2 PCI I2C controller driver" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow.workspace = true +log.workspace = true +redox_syscall = { workspace = true, features = ["std"] } +libredox.workspace = true +serde.workspace = true +ron.workspace = true + +acpi-resource = { path = "../../acpi-resource" } +common = { path = "../../common" } +daemon = { path = "../../../daemon" } +i2c-interface = { path = "../i2c-interface" } +pcid = { path = "../../pcid" } + +[lints] +workspace = true diff --git a/recipes/core/base/drivers/i2c/amd-mp2-i2cd/src/main.rs b/recipes/core/base/drivers/i2c/amd-mp2-i2cd/src/main.rs new file mode 100644 index 0000000000..925b45e75f --- /dev/null +++ b/recipes/core/base/drivers/i2c/amd-mp2-i2cd/src/main.rs @@ -0,0 +1,106 @@ +use std::fs::{File, OpenOptions}; +use std::io::{Read, Write}; +use std::process; + +use anyhow::{Context, Result}; +use i2c_interface::{I2cAdapterInfo, I2cControlRequest, I2cControlResponse}; +use pcid_interface::PciFunctionHandle; + +const MP2_MAILBOX_STATUS: usize = 0x00; +const MP2_MAILBOX_COMMAND: usize = 0x04; +const MP2_MAILBOX_ARGUMENT0: usize = 0x08; +const MP2_MAILBOX_ARGUMENT1: usize = 0x0C; + +fn main() { + pcid_interface::pci_daemon(daemon_runner); +} + +fn daemon_runner(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { + if let Err(err) = daemon_main(daemon, &mut pcid_handle) { + log::error!("amd-mp2-i2cd: {err:#}"); + process::exit(1); + } + + process::exit(0); +} + +fn daemon_main(daemon: daemon::Daemon, pcid_handle: &mut PciFunctionHandle) -> Result<()> { + let pci_config = pcid_handle.config(); + let log_name = format!("{}_amd-mp2-i2c", pci_config.func.name()); + + common::setup_logging( + "bus", + "i2c", + &log_name, + common::output_level(), + common::file_level(), + ); + + let mapped_bar = unsafe { pcid_handle.map_bar(0) }; + let bar_addr = mapped_bar.ptr.as_ptr() as usize; + let bar_size = mapped_bar.bar_size; + + log::info!( + "amd-mp2-i2cd: {} BAR0={:#x}+{:#x} mapped={:p}+{:#x}", + pci_config.func.display(), + bar_addr, + bar_size, + mapped_bar.ptr.as_ptr(), + mapped_bar.bar_size, + ); + log::debug!( + "amd-mp2-i2cd: MP2 mailbox regs status={MP2_MAILBOX_STATUS:#x} cmd={MP2_MAILBOX_COMMAND:#x} arg0={MP2_MAILBOX_ARGUMENT0:#x} arg1={MP2_MAILBOX_ARGUMENT1:#x}", + ); + + let info = I2cAdapterInfo { + id: 0, + name: format!("amd-mp2:{}", pci_config.func.name()), + max_transaction_size: 0, + supports_10bit_addr: false, + }; + let mut registration = register_adapter(&info) + .context("failed to register AMD MP2 controller with i2cd")?; + let response = read_registration_response(&mut registration) + .context("failed to read AMD MP2 i2cd registration response")?; + + match response { + I2cControlResponse::AdapterRegistered { id } => { + log::info!("amd-mp2-i2cd: controller registered with i2cd as adapter {id}"); + } + other => anyhow::bail!("unexpected i2cd registration response: {other:?}"), + } + + daemon.ready(); + libredox::call::setrens(0, 0).context("failed to enter null namespace")?; + + let _keep_registration = registration; + let _keep_pcid = pcid_handle; + + loop { + std::thread::park(); + } +} + +fn register_adapter(info: &I2cAdapterInfo) -> Result { + let mut file = OpenOptions::new() + .read(true) + .write(true) + .open("/scheme/i2c/register") + .context("failed to open /scheme/i2c/register")?; + let payload = ron::ser::to_string(&I2cControlRequest::RegisterAdapter { info: info.clone() }) + .context("failed to encode AMD MP2 I2C registration payload")?; + file.write_all(payload.as_bytes()) + .context("failed to send AMD MP2 I2C registration payload")?; + Ok(file) +} + +fn read_registration_response(file: &mut File) -> Result { + let mut buffer = vec![0_u8; 4096]; + let count = file + .read(&mut buffer) + .context("failed to read AMD MP2 I2C registration response")?; + buffer.truncate(count); + let text = std::str::from_utf8(&buffer) + .context("AMD MP2 I2C registration response was not UTF-8")?; + ron::from_str(text).context("failed to decode AMD MP2 I2C registration response") +} diff --git a/recipes/core/base/drivers/i2c/dw-acpi-i2cd/Cargo.toml b/recipes/core/base/drivers/i2c/dw-acpi-i2cd/Cargo.toml new file mode 100644 index 0000000000..a90b48cce3 --- /dev/null +++ b/recipes/core/base/drivers/i2c/dw-acpi-i2cd/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "dw-acpi-i2cd" +description = "Generic DesignWare ACPI I2C controller driver" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow.workspace = true +log.workspace = true +redox_syscall = { workspace = true, features = ["std"] } +libredox.workspace = true +serde.workspace = true +ron.workspace = true + +acpi-resource = { path = "../../acpi-resource" } +common = { path = "../../common" } +daemon = { path = "../../../daemon" } +i2c-interface = { path = "../i2c-interface" } + +[lints] +workspace = true diff --git a/recipes/core/base/drivers/i2c/dw-acpi-i2cd/src/main.rs b/recipes/core/base/drivers/i2c/dw-acpi-i2cd/src/main.rs new file mode 100644 index 0000000000..b22a27739d --- /dev/null +++ b/recipes/core/base/drivers/i2c/dw-acpi-i2cd/src/main.rs @@ -0,0 +1,361 @@ +use std::collections::BTreeMap; +use std::fs::{self, File, OpenOptions}; +use std::io::{Read, Write}; +use std::path::Path; +use std::process; + +use acpi_resource::{ + AddressResourceType, ExtendedIrqDescriptor, FixedMemory32Descriptor, I2cSerialBusDescriptor, + IrqDescriptor, Memory32RangeDescriptor, ResourceDescriptor, +}; +use anyhow::{Context, Result}; +use common::{MemoryType, PhysBorrowed, Prot}; +use i2c_interface::{I2cAdapterInfo, I2cControlRequest, I2cControlResponse}; +use serde::Deserialize; + +const SUPPORTED_IDS: &[&str] = &["80860F41", "808622C1", "AMDI0010", "AMDI0019", "AMDI0510"]; + +const DW_IC_CON: usize = 0x00; +const DW_IC_TAR: usize = 0x04; +const DW_IC_SS_SCL_HCNT: usize = 0x14; +const DW_IC_SS_SCL_LCNT: usize = 0x18; +const DW_IC_DATA_CMD: usize = 0x10; +const DW_IC_INTR_MASK: usize = 0x30; +const DW_IC_CLR_INTR: usize = 0x40; +const DW_IC_ENABLE: usize = 0x6C; +const DW_IC_STATUS: usize = 0x70; +const DW_MMIO_WINDOW: usize = DW_IC_STATUS + core::mem::size_of::(); + +#[derive(Debug, Deserialize)] +struct AmlSymbol { + name: String, + value: AmlValue, +} + +#[derive(Debug, Deserialize)] +enum AmlValue { + Integer(u64), + String(String), +} + +#[derive(Clone, Debug)] +struct ControllerResources { + mmio_base: usize, + mmio_len: usize, + irq: Option, + serial_bus: Option, +} + +#[derive(Debug)] +struct ControllerDescriptor { + device: String, + hid: String, + resources: ControllerResources, +} + +struct RegisteredController { + _mmio: Option, + _registration: File, +} + +fn main() { + common::setup_logging( + "bus", + "i2c", + "dw-acpi-i2cd", + common::output_level(), + common::file_level(), + ); + + daemon::Daemon::new(daemon_runner); +} + +fn daemon_runner(daemon: daemon::Daemon) -> ! { + if let Err(err) = daemon_main(daemon) { + log::error!("dw-acpi-i2cd: {err:#}"); + process::exit(1); + } + + process::exit(0); +} + +fn daemon_main(daemon: daemon::Daemon) -> Result<()> { + common::init(); + + let controllers = discover_controllers(SUPPORTED_IDS) + .context("failed to discover DesignWare ACPI I2C controllers")?; + if controllers.is_empty() { + log::info!("dw-acpi-i2cd: no supported ACPI controllers found"); + } + + let mut registered = Vec::new(); + for controller in controllers { + match register_controller("dw-acpi", controller) { + Ok(controller) => registered.push(controller), + Err(err) => log::warn!("dw-acpi-i2cd: controller registration skipped: {err:#}"), + } + } + + daemon.ready(); + libredox::call::setrens(0, 0).context("failed to enter null namespace")?; + + log::info!("dw-acpi-i2cd: registered {} controller(s)", registered.len()); + + loop { + std::thread::park(); + } +} + +fn discover_controllers(supported_ids: &[&str]) -> Result> { + let mut matched = BTreeMap::new(); + + 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!("dw-acpi-i2cd: ACPI symbols are not ready yet"); + return Ok(Vec::new()); + } + Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"), + }; + + for entry in entries { + let entry = entry.context("failed to read ACPI symbol directory entry")?; + let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else { + continue; + }; + if !file_name.ends_with("_HID") && !file_name.ends_with("_CID") { + continue; + } + + let Some(id) = read_symbol_id(&entry.path())? else { + continue; + }; + if !supported_ids.iter().any(|candidate| *candidate == id) { + continue; + } + + let device = file_name + .strip_suffix("_HID") + .or_else(|| file_name.strip_suffix("_CID")) + .map(str::to_owned); + if let Some(device) = device { + matched.entry(device).or_insert(id); + } + } + + let mut controllers = Vec::new(); + for (device, hid) in matched { + let resources = read_controller_resources(&device) + .with_context(|| format!("failed to read resources for {device}"))?; + controllers.push(ControllerDescriptor { + device, + hid, + resources, + }); + } + + Ok(controllers) +} + +fn read_symbol_id(path: &Path) -> Result> { + let contents = fs::read_to_string(path) + .with_context(|| format!("failed to read ACPI symbol {}", path.display()))?; + let symbol = match ron::from_str::(&contents) { + Ok(symbol) => symbol, + Err(err) => { + log::debug!( + "dw-acpi-i2cd: skipping {} because the symbol payload was not a scalar ID: {err}", + path.display(), + ); + return Ok(None); + } + }; + + let id = match symbol.value { + AmlValue::Integer(integer) => eisa_id_from_integer(integer), + AmlValue::String(string) => string, + }; + + log::debug!("dw-acpi-i2cd: {} -> {id}", symbol.name); + Ok(Some(id)) +} + +fn read_controller_resources(device: &str) -> Result { + let contents = fs::read_to_string(format!("/scheme/acpi/resources/{device}")) + .with_context(|| format!("failed to read /scheme/acpi/resources/{device}"))?; + let resources = ron::from_str::>(&contents) + .with_context(|| format!("failed to decode RON resources for {device}"))?; + + let mut mmio = None; + let mut irq = None; + let mut serial_bus = None; + + for resource in &resources { + match resource { + ResourceDescriptor::FixedMemory32(FixedMemory32Descriptor { + address, + address_length, + .. + }) if mmio.is_none() => { + mmio = Some((*address as usize, (*address_length as usize).max(DW_MMIO_WINDOW))); + } + ResourceDescriptor::Memory32Range(Memory32RangeDescriptor { + minimum, + maximum, + address_length, + .. + }) if mmio.is_none() && maximum >= minimum => { + let span = maximum.saturating_sub(*minimum).saturating_add(1) as usize; + mmio = Some(( + *minimum as usize, + span.max((*address_length as usize).max(DW_MMIO_WINDOW)), + )); + } + ResourceDescriptor::Address32(descriptor) + if mmio.is_none() + && matches!(descriptor.resource_type, AddressResourceType::MemoryRange) => + { + mmio = Some(( + descriptor.minimum as usize, + (descriptor.address_length as usize).max(DW_MMIO_WINDOW), + )); + } + ResourceDescriptor::Address64(descriptor) + if mmio.is_none() + && matches!(descriptor.resource_type, AddressResourceType::MemoryRange) => + { + let base = usize::try_from(descriptor.minimum) + .context("64-bit MMIO base does not fit in usize")?; + let len = usize::try_from(descriptor.address_length) + .context("64-bit MMIO length does not fit in usize")?; + mmio = Some((base, len.max(DW_MMIO_WINDOW))); + } + ResourceDescriptor::Irq(IrqDescriptor { interrupts, .. }) if irq.is_none() => { + irq = interrupts.first().copied().map(u32::from); + } + ResourceDescriptor::ExtendedIrq(ExtendedIrqDescriptor { interrupts, .. }) + if irq.is_none() => + { + irq = interrupts.first().copied(); + } + ResourceDescriptor::I2cSerialBus(descriptor) if serial_bus.is_none() => { + serial_bus = Some(descriptor.clone()); + } + _ => {} + } + } + + let (mmio_base, mmio_len) = mmio.context("no MMIO resource was found")?; + Ok(ControllerResources { + mmio_base, + mmio_len, + irq, + serial_bus, + }) +} + +fn register_controller(prefix: &str, controller: ControllerDescriptor) -> Result { + let ControllerDescriptor { + device, + hid, + resources, + } = controller; + + let mmio = match PhysBorrowed::map( + resources.mmio_base, + resources.mmio_len, + Prot::RW, + MemoryType::Uncacheable, + ) { + Ok(mapping) => Some(mapping), + Err(err) => { + log::warn!( + "dw-acpi-i2cd: failed to map MMIO for {device} ({:#x}, len {:#x}): {err}", + resources.mmio_base, + resources.mmio_len, + ); + None + } + }; + + log::info!( + "dw-acpi-i2cd: discovered {device} hid={hid} mmio={:#x}+{:#x} irq={:?}", + resources.mmio_base, + resources.mmio_len, + resources.irq, + ); + log::debug!( + "dw-acpi-i2cd: DesignWare regs con={DW_IC_CON:#x} tar={DW_IC_TAR:#x} data_cmd={DW_IC_DATA_CMD:#x} intr_mask={DW_IC_INTR_MASK:#x} clr_intr={DW_IC_CLR_INTR:#x} enable={DW_IC_ENABLE:#x} ss_hcnt={DW_IC_SS_SCL_HCNT:#x} ss_lcnt={DW_IC_SS_SCL_LCNT:#x}", + ); + + let info = I2cAdapterInfo { + id: 0, + name: format!("{prefix}:{device}"), + max_transaction_size: 0, + supports_10bit_addr: resources + .serial_bus + .as_ref() + .map(|bus| bus.access_mode_10bit) + .unwrap_or(false), + }; + let mut registration = register_adapter(&info) + .with_context(|| format!("failed to register {device} with i2cd"))?; + let response = read_registration_response(&mut registration) + .with_context(|| format!("failed to read i2cd registration response for {device}"))?; + + match response { + I2cControlResponse::AdapterRegistered { id } => { + log::info!("dw-acpi-i2cd: adapter {device} registered with i2cd as {id}"); + } + other => { + anyhow::bail!("unexpected i2cd registration response for {device}: {other:?}"); + } + } + + Ok(RegisteredController { + _mmio: mmio, + _registration: registration, + }) +} + +fn register_adapter(info: &I2cAdapterInfo) -> Result { + let mut file = OpenOptions::new() + .read(true) + .write(true) + .open("/scheme/i2c/register") + .context("failed to open /scheme/i2c/register")?; + let payload = ron::ser::to_string(&I2cControlRequest::RegisterAdapter { info: info.clone() }) + .context("failed to encode I2C adapter registration")?; + file.write_all(payload.as_bytes()) + .context("failed to send I2C adapter registration")?; + Ok(file) +} + +fn read_registration_response(file: &mut File) -> Result { + let mut buffer = vec![0_u8; 4096]; + let count = file + .read(&mut buffer) + .context("failed to read I2C registration response")?; + buffer.truncate(count); + let text = std::str::from_utf8(&buffer).context("I2C registration response was not UTF-8")?; + ron::from_str(text).context("failed to decode I2C registration response") +} + +fn eisa_id_from_integer(integer: u64) -> String { + let vendor = integer & 0xFFFF; + let device = (integer >> 16) & 0xFFFF; + let vendor_rev = ((vendor & 0xFF) << 8) | (vendor >> 8); + let vendor_1 = (((vendor_rev >> 10) & 0x1F) as u8 + 64) as char; + let vendor_2 = (((vendor_rev >> 5) & 0x1F) as u8 + 64) as char; + let vendor_3 = (((vendor_rev >> 0) & 0x1F) as u8 + 64) as char; + let device_1 = (device >> 4) & 0xF; + let device_2 = (device >> 0) & 0xF; + let device_3 = (device >> 12) & 0xF; + let device_4 = (device >> 8) & 0xF; + + format!( + "{vendor_1}{vendor_2}{vendor_3}{device_1:01X}{device_2:01X}{device_3:01X}{device_4:01X}" + ) +} diff --git a/recipes/core/base/drivers/i2c/i2c-interface/Cargo.toml b/recipes/core/base/drivers/i2c/i2c-interface/Cargo.toml new file mode 100644 index 0000000000..fa9bbe4f85 --- /dev/null +++ b/recipes/core/base/drivers/i2c/i2c-interface/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "i2c-interface" +description = "Shared I2C transfer and registry types" +version = "0.1.0" +edition = "2021" + +[dependencies] +serde.workspace = true +redox_syscall = { workspace = true, features = ["std"] } + +[lints] +workspace = true diff --git a/recipes/core/base/drivers/i2c/i2c-interface/src/lib.rs b/recipes/core/base/drivers/i2c/i2c-interface/src/lib.rs new file mode 100644 index 0000000000..9e5ab4448e --- /dev/null +++ b/recipes/core/base/drivers/i2c/i2c-interface/src/lib.rs @@ -0,0 +1,92 @@ +use serde::{Deserialize, Serialize}; + +pub use syscall; + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum I2cTransferOp { + Write(Vec), + Read(usize), +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct I2cTransferSegment { + pub address: u16, + pub ten_bit_address: bool, + pub op: I2cTransferOp, +} + +impl I2cTransferSegment { + pub fn write(address: u16, payload: impl Into>) -> Self { + Self { + address, + ten_bit_address: false, + op: I2cTransferOp::Write(payload.into()), + } + } + + pub fn read(address: u16, len: usize) -> Self { + Self { + address, + ten_bit_address: false, + op: I2cTransferOp::Read(len), + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct I2cTransferRequest { + pub adapter: String, + pub segments: Vec, + pub stop: bool, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct I2cTransferResponse { + pub ok: bool, + pub read_data: Vec>, + pub error: Option, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct I2cAdapterRegistration { + pub name: String, + pub description: String, + pub acpi_companion: Option, + pub slave_address_override: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct I2cAdapterInfo { + pub id: u32, + pub name: String, + pub max_transaction_size: usize, + pub supports_10bit_addr: bool, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum I2cTransferStatus { + Ok, + Nack, + Timeout, + Error, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum I2cControlRequest { + RegisterAdapter { info: I2cAdapterInfo }, + OpenAdapter { id: u32 }, + Transfer { + adapter_id: u32, + request: I2cTransferRequest, + }, + ListAdapters, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum I2cControlResponse { + AdapterRegistered { id: u32 }, + AdapterOpened, + TransferResult(I2cTransferResponse), + AdapterList(Vec), + Error(String), +} diff --git a/recipes/core/base/drivers/i2c/i2cd/Cargo.toml b/recipes/core/base/drivers/i2c/i2cd/Cargo.toml new file mode 100644 index 0000000000..0fdd6911fe --- /dev/null +++ b/recipes/core/base/drivers/i2c/i2cd/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "i2cd" +description = "I2C adapter registry scheme daemon" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow.workspace = true +log.workspace = true +redox_syscall = { workspace = true, features = ["std"] } +libredox.workspace = true +redox-scheme.workspace = true +serde.workspace = true +ron.workspace = true + +common = { path = "../../common" } +daemon = { path = "../../../daemon" } +scheme-utils = { path = "../../../scheme-utils" } +i2c-interface = { path = "../i2c-interface" } + +[lints] +workspace = true diff --git a/recipes/core/base/drivers/i2c/i2cd/src/main.rs b/recipes/core/base/drivers/i2c/i2cd/src/main.rs new file mode 100644 index 0000000000..b7b7d89aab --- /dev/null +++ b/recipes/core/base/drivers/i2c/i2cd/src/main.rs @@ -0,0 +1,377 @@ +use std::collections::BTreeMap; +use std::process; + +use anyhow::{Context, Result}; +use i2c_interface::{ + I2cAdapterInfo, I2cControlRequest, I2cControlResponse, I2cTransferRequest, + I2cTransferResponse, +}; +use redox_scheme::scheme::SchemeSync; +use redox_scheme::{CallerCtx, OpenResult, Socket}; +use scheme_utils::{Blocking, HandleMap}; +use syscall::schemev2::NewFdFlags; +use syscall::{Error as SysError, EACCES, EBADF, EINVAL, ENOENT}; + +enum Handle { + SchemeRoot, + Register { pending: Vec }, + Provider { adapter_id: u32, pending: Vec }, + Adapters { pending: Vec }, + AdapterDetail { id: u32, pending: Vec }, + Transfer { pending: Vec }, +} + +struct AdapterEntry { + info: I2cAdapterInfo, + provider_handle: usize, +} + +struct I2cDaemon { + handles: HandleMap, + adapters: BTreeMap, + next_id: u32, +} + +impl I2cDaemon { + fn new() -> Self { + Self { + handles: HandleMap::new(), + adapters: BTreeMap::new(), + next_id: 0, + } + } + + fn adapter_list(&self) -> Vec { + self.adapters.values().map(|entry| entry.info.clone()).collect() + } + + fn serialize_response(response: &I2cControlResponse) -> syscall::Result> { + ron::ser::to_string(response) + .map(|text| text.into_bytes()) + .map_err(|err| { + log::error!("i2cd: failed to serialize control response: {err}"); + SysError::new(EINVAL) + }) + } + + fn deserialize_request(buf: &[u8]) -> syscall::Result { + let text = std::str::from_utf8(buf).map_err(|err| { + log::warn!("i2cd: invalid UTF-8 control payload: {err}"); + SysError::new(EINVAL) + })?; + + ron::from_str(text).map_err(|err| { + log::warn!("i2cd: failed to decode control request: {err}"); + SysError::new(EINVAL) + }) + } + + fn set_pending_response(handle: &mut Handle, response: I2cControlResponse) -> syscall::Result<()> { + let pending = Self::serialize_response(&response)?; + match handle { + Handle::Register { pending: slot } + | Handle::Provider { pending: slot, .. } + | Handle::Adapters { pending: slot } + | Handle::AdapterDetail { pending: slot, .. } + | Handle::Transfer { pending: slot } => { + *slot = pending; + Ok(()) + } + Handle::SchemeRoot => Err(SysError::new(EBADF)), + } + } + + fn queue_adapter_list(handle: &mut Handle, adapters: Vec) -> syscall::Result<()> { + Self::set_pending_response(handle, I2cControlResponse::AdapterList(adapters)) + } + + fn queue_transfer_stub( + handle: &mut Handle, + adapter: &I2cAdapterInfo, + request: &I2cTransferRequest, + ) -> syscall::Result<()> { + let write_bytes = request + .segments + .iter() + .filter_map(|segment| match &segment.op { + i2c_interface::I2cTransferOp::Write(bytes) => Some(bytes.len()), + i2c_interface::I2cTransferOp::Read(_) => None, + }) + .sum::(); + let read_segments = request + .segments + .iter() + .filter(|segment| matches!(segment.op, i2c_interface::I2cTransferOp::Read(_))) + .count(); + + log::info!( + "i2cd: routing transfer to adapter {} ({}) name={} segments={} write_bytes={} read_segments={} stop={} (stubbed)", + adapter.id, + adapter.name, + request.adapter, + request.segments.len(), + write_bytes, + read_segments, + request.stop, + ); + + Self::set_pending_response( + handle, + I2cControlResponse::TransferResult(I2cTransferResponse { + ok: false, + read_data: Vec::new(), + error: Some(String::from("I2C controller transfer path is not implemented yet")), + }), + ) + } + + fn copy_pending(handle: &mut Handle, buf: &mut [u8], offset: u64) -> syscall::Result { + let pending = match handle { + Handle::Register { pending } + | Handle::Provider { pending, .. } + | Handle::Adapters { pending } + | Handle::AdapterDetail { pending, .. } + | Handle::Transfer { pending } => pending, + Handle::SchemeRoot => return Err(SysError::new(EBADF)), + }; + + let offset = usize::try_from(offset).map_err(|_| SysError::new(EINVAL))?; + if offset >= pending.len() { + return Ok(0); + } + + let copy_len = buf.len().min(pending.len() - offset); + buf[..copy_len].copy_from_slice(&pending[offset..offset + copy_len]); + Ok(copy_len) + } +} + +impl SchemeSync for I2cDaemon { + fn scheme_root(&mut self) -> syscall::Result { + Ok(self.handles.insert(Handle::SchemeRoot)) + } + + fn openat( + &mut self, + dirfd: usize, + path: &str, + _flags: usize, + _fcntl_flags: u32, + _ctx: &CallerCtx, + ) -> syscall::Result { + let handle = self.handles.get(dirfd)?; + let segments = path.trim_matches('/'); + + let new_handle = match handle { + Handle::SchemeRoot => { + if segments.is_empty() { + return Err(SysError::new(EINVAL)); + } + + let mut parts = segments.split('/'); + match parts.next() { + Some("register") if parts.next().is_none() => Handle::Register { + pending: Vec::new(), + }, + Some("adapters") => match parts.next() { + None => Handle::Adapters { + pending: Vec::new(), + }, + Some(id) if parts.next().is_none() => { + let id = id.parse::().map_err(|_| SysError::new(EINVAL))?; + Handle::AdapterDetail { + id, + pending: Vec::new(), + } + } + _ => return Err(SysError::new(EINVAL)), + }, + Some("transfer") if parts.next().is_none() => Handle::Transfer { + pending: Vec::new(), + }, + _ => return Err(SysError::new(ENOENT)), + } + } + Handle::Adapters { .. } => { + if segments.is_empty() { + return Err(SysError::new(EINVAL)); + } + + let id = segments.parse::().map_err(|_| SysError::new(EINVAL))?; + Handle::AdapterDetail { + id, + pending: Vec::new(), + } + } + _ => return Err(SysError::new(EACCES)), + }; + + let fd = self.handles.insert(new_handle); + Ok(OpenResult::ThisScheme { + number: fd, + flags: NewFdFlags::empty(), + }) + } + + fn read( + &mut self, + id: usize, + buf: &mut [u8], + offset: u64, + _fcntl_flags: u32, + _ctx: &CallerCtx, + ) -> syscall::Result { + let adapters = self.adapter_list(); + let handle = self.handles.get_mut(id)?; + + match handle { + Handle::Adapters { pending } if pending.is_empty() => { + *pending = Self::serialize_response(&I2cControlResponse::AdapterList(adapters))?; + } + Handle::AdapterDetail { id, pending } if pending.is_empty() => { + let info = self + .adapters + .get(id) + .map(|entry| entry.info.clone()) + .ok_or(SysError::new(ENOENT))?; + *pending = Self::serialize_response(&I2cControlResponse::AdapterList(vec![info]))?; + } + _ => {} + } + + Self::copy_pending(handle, buf, offset) + } + + fn write( + &mut self, + id: usize, + buf: &[u8], + _offset: u64, + _fcntl_flags: u32, + _ctx: &CallerCtx, + ) -> syscall::Result { + let request = Self::deserialize_request(buf)?; + + match request { + I2cControlRequest::RegisterAdapter { mut info } => { + let adapter_id = self.next_id; + self.next_id = self.next_id.checked_add(1).ok_or(SysError::new(EINVAL))?; + info.id = adapter_id; + + self.adapters.insert( + adapter_id, + AdapterEntry { + info: info.clone(), + provider_handle: id, + }, + ); + + let handle = self.handles.get_mut(id)?; + *handle = Handle::Provider { + adapter_id, + pending: Self::serialize_response(&I2cControlResponse::AdapterRegistered { + id: adapter_id, + })?, + }; + + log::info!( + "RB_I2CD_ADAPTER_REGISTERED id={} name={} max_transaction_size={} supports_10bit_addr={}", + info.id, + info.name, + info.max_transaction_size, + info.supports_10bit_addr, + ); + Ok(buf.len()) + } + I2cControlRequest::ListAdapters => { + let adapters = self.adapter_list(); + let handle = self.handles.get_mut(id)?; + Self::queue_adapter_list(handle, adapters)?; + Ok(buf.len()) + } + I2cControlRequest::OpenAdapter { id: adapter_id } => { + if !self.adapters.contains_key(&adapter_id) { + return Err(SysError::new(ENOENT)); + } + + let handle = self.handles.get_mut(id)?; + match handle { + Handle::Adapters { .. } | Handle::AdapterDetail { .. } => { + Self::set_pending_response(handle, I2cControlResponse::AdapterOpened)?; + Ok(buf.len()) + } + _ => Err(SysError::new(EINVAL)), + } + } + I2cControlRequest::Transfer { + adapter_id, + request, + } => { + let entry = self.adapters.get(&adapter_id).ok_or(SysError::new(ENOENT))?; + log::debug!( + "i2cd: transfer requested for adapter {} via provider fd {}", + adapter_id, + entry.provider_handle, + ); + + let adapter_info = entry.info.clone(); + let handle = self.handles.get_mut(id)?; + match handle { + Handle::Transfer { .. } => { + Self::queue_transfer_stub(handle, &adapter_info, &request)?; + Ok(buf.len()) + } + _ => Err(SysError::new(EINVAL)), + } + } + } + } + + fn on_close(&mut self, id: usize) { + let Some(handle) = self.handles.remove(id) else { + return; + }; + if let Handle::Provider { adapter_id, .. } = handle { + self.adapters.remove(&adapter_id); + } + } +} + +fn run_daemon(daemon: daemon::SchemeDaemon) -> Result<()> { + let socket = Socket::create().context("failed to create i2c scheme socket")?; + let mut scheme = I2cDaemon::new(); + let handler = Blocking::new(&socket, 16); + + daemon + .ready_sync_scheme(&socket, &mut scheme) + .context("failed to publish i2c scheme root")?; + + log::info!("RB_I2CD_SCHEMA"); + + libredox::call::setrens(0, 0).context("failed to enter null namespace")?; + + handler + .process_requests_blocking(scheme) + .context("failed to process i2cd requests")?; +} + +fn daemon_runner(daemon: daemon::SchemeDaemon) -> ! { + if let Err(err) = run_daemon(daemon) { + log::error!("i2cd: {err:#}"); + process::exit(1); + } + + process::exit(0); +} + +fn main() { + common::setup_logging( + "bus", + "i2c", + "i2cd", + common::output_level(), + common::file_level(), + ); + + daemon::SchemeDaemon::new(daemon_runner); +} diff --git a/recipes/core/base/drivers/i2c/intel-lpss-i2cd/Cargo.toml b/recipes/core/base/drivers/i2c/intel-lpss-i2cd/Cargo.toml new file mode 100644 index 0000000000..0e74cf94d6 --- /dev/null +++ b/recipes/core/base/drivers/i2c/intel-lpss-i2cd/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "intel-lpss-i2cd" +description = "Intel LPSS ACPI I2C controller driver" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow.workspace = true +log.workspace = true +redox_syscall = { workspace = true, features = ["std"] } +libredox.workspace = true +serde.workspace = true +ron.workspace = true + +acpi-resource = { path = "../../acpi-resource" } +common = { path = "../../common" } +daemon = { path = "../../../daemon" } +i2c-interface = { path = "../i2c-interface" } + +[lints] +workspace = true diff --git a/recipes/core/base/drivers/i2c/intel-lpss-i2cd/src/main.rs b/recipes/core/base/drivers/i2c/intel-lpss-i2cd/src/main.rs new file mode 100644 index 0000000000..ca3ead4374 --- /dev/null +++ b/recipes/core/base/drivers/i2c/intel-lpss-i2cd/src/main.rs @@ -0,0 +1,361 @@ +use std::collections::BTreeMap; +use std::fs::{self, File, OpenOptions}; +use std::io::{Read, Write}; +use std::path::Path; +use std::process; + +use acpi_resource::{ + AddressResourceType, ExtendedIrqDescriptor, FixedMemory32Descriptor, I2cSerialBusDescriptor, + IrqDescriptor, Memory32RangeDescriptor, ResourceDescriptor, +}; +use anyhow::{Context, Result}; +use common::{MemoryType, PhysBorrowed, Prot}; +use i2c_interface::{I2cAdapterInfo, I2cControlRequest, I2cControlResponse}; +use serde::Deserialize; + +const SUPPORTED_IDS: &[&str] = &["INT33C2", "INT33C3", "INT3432", "INT3433", "INTC10EF"]; + +const DW_IC_CON: usize = 0x00; +const DW_IC_TAR: usize = 0x04; +const DW_IC_SS_SCL_HCNT: usize = 0x14; +const DW_IC_SS_SCL_LCNT: usize = 0x18; +const DW_IC_DATA_CMD: usize = 0x10; +const DW_IC_INTR_MASK: usize = 0x30; +const DW_IC_CLR_INTR: usize = 0x40; +const DW_IC_ENABLE: usize = 0x6C; +const DW_IC_STATUS: usize = 0x70; +const DW_MMIO_WINDOW: usize = DW_IC_STATUS + core::mem::size_of::(); + +#[derive(Debug, Deserialize)] +struct AmlSymbol { + name: String, + value: AmlValue, +} + +#[derive(Debug, Deserialize)] +enum AmlValue { + Integer(u64), + String(String), +} + +#[derive(Clone, Debug)] +struct ControllerResources { + mmio_base: usize, + mmio_len: usize, + irq: Option, + serial_bus: Option, +} + +#[derive(Debug)] +struct ControllerDescriptor { + device: String, + hid: String, + resources: ControllerResources, +} + +struct RegisteredController { + _mmio: Option, + _registration: File, +} + +fn main() { + common::setup_logging( + "bus", + "i2c", + "intel-lpss-i2cd", + common::output_level(), + common::file_level(), + ); + + daemon::Daemon::new(daemon_runner); +} + +fn daemon_runner(daemon: daemon::Daemon) -> ! { + if let Err(err) = daemon_main(daemon) { + log::error!("intel-lpss-i2cd: {err:#}"); + process::exit(1); + } + + process::exit(0); +} + +fn daemon_main(daemon: daemon::Daemon) -> Result<()> { + common::init(); + + let controllers = discover_controllers(SUPPORTED_IDS) + .context("failed to discover Intel LPSS ACPI I2C controllers")?; + if controllers.is_empty() { + log::info!("intel-lpss-i2cd: no supported ACPI controllers found"); + } + + let mut registered = Vec::new(); + for controller in controllers { + match register_controller("intel-lpss", controller) { + Ok(controller) => registered.push(controller), + Err(err) => log::warn!("intel-lpss-i2cd: controller registration skipped: {err:#}"), + } + } + + daemon.ready(); + libredox::call::setrens(0, 0).context("failed to enter null namespace")?; + + log::info!("intel-lpss-i2cd: registered {} controller(s)", registered.len()); + + loop { + std::thread::park(); + } +} + +fn discover_controllers(supported_ids: &[&str]) -> Result> { + let mut matched = BTreeMap::new(); + + 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!("intel-lpss-i2cd: ACPI symbols are not ready yet"); + return Ok(Vec::new()); + } + Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"), + }; + + for entry in entries { + let entry = entry.context("failed to read ACPI symbol directory entry")?; + let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else { + continue; + }; + if !file_name.ends_with("_HID") && !file_name.ends_with("_CID") { + continue; + } + + let Some(id) = read_symbol_id(&entry.path())? else { + continue; + }; + if !supported_ids.iter().any(|candidate| *candidate == id) { + continue; + } + + let device = file_name + .strip_suffix("_HID") + .or_else(|| file_name.strip_suffix("_CID")) + .map(str::to_owned); + if let Some(device) = device { + matched.entry(device).or_insert(id); + } + } + + let mut controllers = Vec::new(); + for (device, hid) in matched { + let resources = read_controller_resources(&device) + .with_context(|| format!("failed to read resources for {device}"))?; + controllers.push(ControllerDescriptor { + device, + hid, + resources, + }); + } + + Ok(controllers) +} + +fn read_symbol_id(path: &Path) -> Result> { + let contents = fs::read_to_string(path) + .with_context(|| format!("failed to read ACPI symbol {}", path.display()))?; + let symbol = match ron::from_str::(&contents) { + Ok(symbol) => symbol, + Err(err) => { + log::debug!( + "intel-lpss-i2cd: skipping {} because the symbol payload was not a scalar ID: {err}", + path.display(), + ); + return Ok(None); + } + }; + + let id = match symbol.value { + AmlValue::Integer(integer) => eisa_id_from_integer(integer), + AmlValue::String(string) => string, + }; + + log::debug!("intel-lpss-i2cd: {} -> {id}", symbol.name); + Ok(Some(id)) +} + +fn read_controller_resources(device: &str) -> Result { + let contents = fs::read_to_string(format!("/scheme/acpi/resources/{device}")) + .with_context(|| format!("failed to read /scheme/acpi/resources/{device}"))?; + let resources = ron::from_str::>(&contents) + .with_context(|| format!("failed to decode RON resources for {device}"))?; + + let mut mmio = None; + let mut irq = None; + let mut serial_bus = None; + + for resource in &resources { + match resource { + ResourceDescriptor::FixedMemory32(FixedMemory32Descriptor { + address, + address_length, + .. + }) if mmio.is_none() => { + mmio = Some((*address as usize, (*address_length as usize).max(DW_MMIO_WINDOW))); + } + ResourceDescriptor::Memory32Range(Memory32RangeDescriptor { + minimum, + maximum, + address_length, + .. + }) if mmio.is_none() && maximum >= minimum => { + let span = maximum.saturating_sub(*minimum).saturating_add(1) as usize; + mmio = Some(( + *minimum as usize, + span.max((*address_length as usize).max(DW_MMIO_WINDOW)), + )); + } + ResourceDescriptor::Address32(descriptor) + if mmio.is_none() + && matches!(descriptor.resource_type, AddressResourceType::MemoryRange) => + { + mmio = Some(( + descriptor.minimum as usize, + (descriptor.address_length as usize).max(DW_MMIO_WINDOW), + )); + } + ResourceDescriptor::Address64(descriptor) + if mmio.is_none() + && matches!(descriptor.resource_type, AddressResourceType::MemoryRange) => + { + let base = usize::try_from(descriptor.minimum) + .context("64-bit MMIO base does not fit in usize")?; + let len = usize::try_from(descriptor.address_length) + .context("64-bit MMIO length does not fit in usize")?; + mmio = Some((base, len.max(DW_MMIO_WINDOW))); + } + ResourceDescriptor::Irq(IrqDescriptor { interrupts, .. }) if irq.is_none() => { + irq = interrupts.first().copied().map(u32::from); + } + ResourceDescriptor::ExtendedIrq(ExtendedIrqDescriptor { interrupts, .. }) + if irq.is_none() => + { + irq = interrupts.first().copied(); + } + ResourceDescriptor::I2cSerialBus(descriptor) if serial_bus.is_none() => { + serial_bus = Some(descriptor.clone()); + } + _ => {} + } + } + + let (mmio_base, mmio_len) = mmio.context("no MMIO resource was found")?; + Ok(ControllerResources { + mmio_base, + mmio_len, + irq, + serial_bus, + }) +} + +fn register_controller(prefix: &str, controller: ControllerDescriptor) -> Result { + let ControllerDescriptor { + device, + hid, + resources, + } = controller; + + let mmio = match PhysBorrowed::map( + resources.mmio_base, + resources.mmio_len, + Prot::RW, + MemoryType::Uncacheable, + ) { + Ok(mapping) => Some(mapping), + Err(err) => { + log::warn!( + "intel-lpss-i2cd: failed to map MMIO for {device} ({:#x}, len {:#x}): {err}", + resources.mmio_base, + resources.mmio_len, + ); + None + } + }; + + log::info!( + "intel-lpss-i2cd: discovered {device} hid={hid} mmio={:#x}+{:#x} irq={:?}", + resources.mmio_base, + resources.mmio_len, + resources.irq, + ); + log::debug!( + "intel-lpss-i2cd: DesignWare regs con={DW_IC_CON:#x} tar={DW_IC_TAR:#x} data_cmd={DW_IC_DATA_CMD:#x} intr_mask={DW_IC_INTR_MASK:#x} clr_intr={DW_IC_CLR_INTR:#x} enable={DW_IC_ENABLE:#x} ss_hcnt={DW_IC_SS_SCL_HCNT:#x} ss_lcnt={DW_IC_SS_SCL_LCNT:#x}", + ); + + let info = I2cAdapterInfo { + id: 0, + name: format!("{prefix}:{device}"), + max_transaction_size: 0, + supports_10bit_addr: resources + .serial_bus + .as_ref() + .map(|bus| bus.access_mode_10bit) + .unwrap_or(false), + }; + let mut registration = register_adapter(&info) + .with_context(|| format!("failed to register {device} with i2cd"))?; + let response = read_registration_response(&mut registration) + .with_context(|| format!("failed to read i2cd registration response for {device}"))?; + + match response { + I2cControlResponse::AdapterRegistered { id } => { + log::info!("intel-lpss-i2cd: adapter {device} registered with i2cd as {id}"); + } + other => { + anyhow::bail!("unexpected i2cd registration response for {device}: {other:?}"); + } + } + + Ok(RegisteredController { + _mmio: mmio, + _registration: registration, + }) +} + +fn register_adapter(info: &I2cAdapterInfo) -> Result { + let mut file = OpenOptions::new() + .read(true) + .write(true) + .open("/scheme/i2c/register") + .context("failed to open /scheme/i2c/register")?; + let payload = ron::ser::to_string(&I2cControlRequest::RegisterAdapter { info: info.clone() }) + .context("failed to encode I2C adapter registration")?; + file.write_all(payload.as_bytes()) + .context("failed to send I2C adapter registration")?; + Ok(file) +} + +fn read_registration_response(file: &mut File) -> Result { + let mut buffer = vec![0_u8; 4096]; + let count = file + .read(&mut buffer) + .context("failed to read I2C registration response")?; + buffer.truncate(count); + let text = std::str::from_utf8(&buffer).context("I2C registration response was not UTF-8")?; + ron::from_str(text).context("failed to decode I2C registration response") +} + +fn eisa_id_from_integer(integer: u64) -> String { + let vendor = integer & 0xFFFF; + let device = (integer >> 16) & 0xFFFF; + let vendor_rev = ((vendor & 0xFF) << 8) | (vendor >> 8); + let vendor_1 = (((vendor_rev >> 10) & 0x1F) as u8 + 64) as char; + let vendor_2 = (((vendor_rev >> 5) & 0x1F) as u8 + 64) as char; + let vendor_3 = (((vendor_rev >> 0) & 0x1F) as u8 + 64) as char; + let device_1 = (device >> 4) & 0xF; + let device_2 = (device >> 0) & 0xF; + let device_3 = (device >> 12) & 0xF; + let device_4 = (device >> 8) & 0xF; + + format!( + "{vendor_1}{vendor_2}{vendor_3}{device_1:01X}{device_2:01X}{device_3:01X}{device_4:01X}" + ) +} diff --git a/recipes/core/base/drivers/input/i2c-hidd/Cargo.toml b/recipes/core/base/drivers/input/i2c-hidd/Cargo.toml new file mode 100644 index 0000000000..db7b3f03ba --- /dev/null +++ b/recipes/core/base/drivers/input/i2c-hidd/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "i2c-hidd" +description = "I2C HID client daemon" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow.workspace = true +log.workspace = true +orbclient.workspace = true +redox_syscall = { workspace = true, features = ["std"] } +libredox.workspace = true +redox-scheme.workspace = true +ron.workspace = true +serde.workspace = true + +acpi-resource = { path = "../../acpi-resource" } +amlserde = { path = "../../amlserde" } +common = { path = "../../common" } +daemon = { path = "../../../daemon" } +i2c-interface = { path = "../../i2c/i2c-interface" } +inputd = { path = "../../inputd" } +scheme-utils = { path = "../../../scheme-utils" } + +[lints] +workspace = true diff --git a/recipes/core/base/drivers/input/i2c-hidd/src/acpi.rs b/recipes/core/base/drivers/input/i2c-hidd/src/acpi.rs new file mode 100644 index 0000000000..1132154cf1 --- /dev/null +++ b/recipes/core/base/drivers/input/i2c-hidd/src/acpi.rs @@ -0,0 +1,307 @@ +use acpi_resource::{GpioDescriptor, ResourceDescriptor}; +use amlserde::{AmlSerde, AmlSerdeValue}; +use anyhow::{anyhow, bail, Context, Result}; +use libredox::flag::{O_CLOEXEC, O_RDWR}; +use std::collections::BTreeSet; +use std::fs::{self, OpenOptions}; +use std::io::{ErrorKind, Read}; + +use crate::quirks::ProbeFailureQuirk; + +const I2C_HID_DSM_GUID: [u8; 16] = [ + 0xF7, 0xF6, 0xDF, 0x3C, 0x67, 0x42, 0x55, 0x45, 0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x41, 0x76, +]; + +#[derive(Clone, Debug)] +pub struct I2cBinding { + pub adapter: String, + pub address: u16, +} + +#[derive(Clone, Debug)] +pub struct AcpiDeviceResources { + pub i2c: I2cBinding, + pub irq: Option, + pub gpio_int: Vec, + pub gpio_io: Vec, +} + +pub fn scan_acpi_i2c_hid_devices() -> Result> { + let entries = match fs::read_dir("/scheme/acpi/symbols") { + Ok(entries) => entries, + Err(err) if err.kind() == ErrorKind::WouldBlock || err.raw_os_error() == Some(11) => { + return Ok(Vec::new()); + } + Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"), + }; + + let mut devices = BTreeSet::new(); + for entry in entries { + let entry = entry.context("failed to enumerate ACPI symbol entry")?; + let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else { + continue; + }; + if !file_name.ends_with("._HID") && !file_name.ends_with("._CID") { + continue; + } + + let symbol = read_aml_symbol(&file_name) + .with_context(|| format!("failed to read ACPI symbol {file_name}"))?; + let Some(id) = decode_hardware_id(&symbol.value) else { + continue; + }; + + if matches!(id.as_str(), "PNP0C50" | "ACPI0C50") { + let device = symbol + .name + .strip_suffix("._HID") + .or_else(|| symbol.name.strip_suffix("._CID")) + .unwrap_or(&symbol.name) + .trim_start_matches('\\') + .trim_matches('/') + .replace('/', "."); + if !device.is_empty() { + devices.insert(device); + } + } + } + + Ok(devices.into_iter().collect()) +} + +pub fn read_decoded_resources(path: &str) -> Result { + let resource_path = format!("/scheme/acpi/resources/{}", normalize_device_path(path)); + let serialized = fs::read_to_string(&resource_path) + .with_context(|| format!("failed to read {resource_path}"))?; + let descriptors: Vec = ron::from_str(&serialized) + .with_context(|| format!("invalid ACPI resources in {resource_path}"))?; + + let mut i2c = None; + let mut irq = None; + let mut gpio_int = Vec::new(); + let mut gpio_io = Vec::new(); + + for descriptor in descriptors { + match descriptor { + ResourceDescriptor::I2cSerialBus(bus) => { + if i2c.is_none() { + let adapter = bus + .resource_source + .as_ref() + .map(|source| source.source.clone()) + .filter(|source| !source.is_empty()) + .unwrap_or_else(|| "ACPI-I2C".to_string()); + i2c = Some(I2cBinding { + adapter, + address: bus.slave_address, + }); + } + } + ResourceDescriptor::Irq(descriptor) => { + if irq.is_none() { + irq = descriptor.interrupts.first().copied().map(u32::from); + } + } + ResourceDescriptor::ExtendedIrq(descriptor) => { + if irq.is_none() { + irq = descriptor.interrupts.first().copied(); + } + } + ResourceDescriptor::GpioInt(descriptor) => gpio_int.push(descriptor), + ResourceDescriptor::GpioIo(descriptor) => gpio_io.push(descriptor), + _ => {} + } + } + + let mut resources = AcpiDeviceResources { + i2c: i2c.ok_or_else(|| anyhow!("no I2cSerialBus resource in _CRS"))?, + irq, + gpio_int, + gpio_io, + }; + + if let Some(override_address) = companion_icrs_override(path)? { + log::info!( + "{}: applying THC companion ICRS override {:04x} -> {:04x}", + path, + resources.i2c.address, + override_address + ); + resources.i2c.address = override_address; + } + + Ok(resources) +} + +pub fn prepare_acpi_device(path: &str) -> Result<()> { + let sta = evaluate_integer_method(path, "_STA").ok(); + if let Some(sta) = sta { + if sta & 0x01 == 0 { + bail!("ACPI device is not present according to _STA={sta:#x}"); + } + } + + let _ = evaluate_method(path, "_PS0", &[]); + let _ = evaluate_method(path, "_INI", &[]); + Ok(()) +} + +pub fn recover_acpi_device( + path: &str, + resources: &AcpiDeviceResources, + quirk: Option<&ProbeFailureQuirk>, +) -> Result<()> { + let _ = evaluate_method(path, "_PS3", &[]); + + if let Some(quirk) = quirk { + if !resources.gpio_io.is_empty() { + log::warn!( + "{}: applying GPIO probe-failure recovery quirk {} vendor={:?} product={:?} board={:?} across {} GPIO IO resources", + path, + quirk.name, + quirk.system_vendor, + quirk.product_name, + quirk.board_name, + resources.gpio_io.len() + ); + } else { + log::warn!( + "{}: quirk {} vendor={:?} product={:?} board={:?} matched but no GPIO IO resource was exposed", + path, + quirk.name + , + quirk.system_vendor, + quirk.product_name, + quirk.board_name + ); + } + } + + let _ = evaluate_method(path, "_PS0", &[]); + let _ = evaluate_method(path, "_INI", &[]); + Ok(()) +} + +pub fn hid_descriptor_address(path: &str) -> Result { + let args = [ + AmlSerdeValue::Buffer(I2C_HID_DSM_GUID.to_vec()), + AmlSerdeValue::Integer(1), + AmlSerdeValue::Integer(1), + AmlSerdeValue::Package { + contents: Vec::new(), + }, + ]; + + match evaluate_method(path, "_DSM", &args) { + Ok(AmlSerdeValue::Integer(value)) => { + return u16::try_from(value).context("_DSM descriptor address out of range") + } + Ok(other) => log::warn!( + "{}._DSM returned unexpected value {:?}; retrying fallback index", + path, + other + ), + Err(err) => log::warn!( + "{}._DSM function 1 failed: {err}; retrying function 0", + path + ), + } + + let fallback = [ + AmlSerdeValue::Buffer(I2C_HID_DSM_GUID.to_vec()), + AmlSerdeValue::Integer(1), + AmlSerdeValue::Integer(0), + AmlSerdeValue::Package { + contents: Vec::new(), + }, + ]; + + match evaluate_method(path, "_DSM", &fallback)? { + AmlSerdeValue::Integer(value) => { + u16::try_from(value).context("fallback _DSM descriptor address out of range") + } + other => bail!("fallback _DSM returned unexpected value {other:?}"), + } +} + +fn companion_icrs_override(path: &str) -> Result> { + let value = match evaluate_integer_method(path, "ICRS") { + Ok(value) => value, + Err(_) => return Ok(None), + }; + Ok(Some( + u16::try_from(value).context("ICRS override out of range")?, + )) +} + +pub fn evaluate_integer_method(path: &str, method: &str) -> Result { + match evaluate_method(path, method, &[])? { + AmlSerdeValue::Integer(value) => Ok(value), + other => bail!( + "{}.{} returned non-integer AML value {other:?}", + path, + method + ), + } +} + +pub fn evaluate_method(path: &str, method: &str, args: &[AmlSerdeValue]) -> Result { + let symbol_name = format!("{}.{}", normalize_device_path(path), method); + let symbol_path = format!("/scheme/acpi/symbols/{symbol_name}"); + let fd = libredox::Fd::open(&symbol_path, O_RDWR | O_CLOEXEC, 0) + .with_context(|| format!("failed to open {symbol_path} for ACPI evaluation"))?; + + let serialized = ron::to_string(args) + .with_context(|| format!("failed to serialize ACPI arguments for {symbol_name}"))?; + let mut payload = serialized.into_bytes(); + payload.resize(payload.len() + 4096, 0); + + let used = libredox::call::call_ro(fd.raw(), &mut payload, syscall::CallFlags::empty(), &[]) + .with_context(|| format!("ACPI evaluation failed for {symbol_name}"))?; + let response = std::str::from_utf8(&payload[..used]) + .with_context(|| format!("invalid UTF-8 ACPI response for {symbol_name}"))?; + ron::from_str(response) + .with_context(|| format!("failed to decode ACPI response for {symbol_name}")) +} + +fn read_aml_symbol(file_name: &str) -> Result { + let path = format!("/scheme/acpi/symbols/{file_name}"); + let mut file = OpenOptions::new() + .read(true) + .open(&path) + .with_context(|| format!("failed to open {path}"))?; + let mut ron_text = String::new(); + file.read_to_string(&mut ron_text) + .with_context(|| format!("failed to read {path}"))?; + ron::from_str(&ron_text).with_context(|| format!("failed to decode {path}")) +} + +fn decode_hardware_id(value: &AmlSerdeValue) -> Option { + match value { + AmlSerdeValue::String(value) => Some(value.clone()), + AmlSerdeValue::Integer(integer) => { + let vendor = integer & 0xFFFF; + let device = (integer >> 16) & 0xFFFF; + let vendor_rev = ((vendor & 0xFF) << 8) | (vendor >> 8); + let vendor_1 = (((vendor_rev >> 10) & 0x1f) as u8 + 64) as char; + let vendor_2 = (((vendor_rev >> 5) & 0x1f) as u8 + 64) as char; + let vendor_3 = (((vendor_rev >> 0) & 0x1f) as u8 + 64) as char; + let device_1 = (device >> 4) & 0xF; + let device_2 = (device >> 0) & 0xF; + let device_3 = (device >> 12) & 0xF; + let device_4 = (device >> 8) & 0xF; + + Some(format!( + "{}{}{}{:01X}{:01X}{:01X}{:01X}", + vendor_1, vendor_2, vendor_3, device_1, device_2, device_3, device_4 + )) + } + _ => None, + } +} + +pub fn normalize_device_path(path: &str) -> String { + path.trim_start_matches('\\') + .trim_matches('/') + .replace('/', ".") +} diff --git a/recipes/core/base/drivers/input/i2c-hidd/src/hid.rs b/recipes/core/base/drivers/input/i2c-hidd/src/hid.rs new file mode 100644 index 0000000000..dd18e36c58 --- /dev/null +++ b/recipes/core/base/drivers/input/i2c-hidd/src/hid.rs @@ -0,0 +1,195 @@ +use std::fs::OpenOptions; +use std::io::{Read, Write}; + +use anyhow::{bail, Context, Result}; +use i2c_interface::{I2cTransferRequest, I2cTransferResponse, I2cTransferSegment}; +use serde::{Deserialize, Serialize}; + +use crate::acpi::I2cBinding; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct HidDescriptor { + pub hid_desc_length: u16, + pub bcd_version: u16, + pub report_desc_length: u16, + pub report_desc_register: u16, + pub input_register: u16, + pub max_input_length: u16, + pub output_register: u16, + pub max_output_length: u16, + pub command_register: u16, + pub data_register: u16, +} + +#[derive(Clone, Debug, Default)] +pub struct ReportDescriptorSummary { + pub has_keyboard_page: bool, + pub has_pointer_page: bool, + pub report_ids: bool, +} + +#[derive(Clone, Debug)] +pub struct I2cAdapterClient { + binding: I2cBinding, +} + +impl I2cAdapterClient { + pub fn new(binding: I2cBinding) -> Self { + Self { binding } + } + pub fn transfer(&self, segments: Vec) -> Result { + let request = I2cTransferRequest { + adapter: self.binding.adapter.clone(), + segments, + stop: true, + }; + + let serialized = ron::to_string(&request).context("failed to serialize I2C request")?; + let mut handle = OpenOptions::new() + .read(true) + .write(true) + .open("/scheme/i2c/transfer") + .context("failed to open /scheme/i2c/transfer")?; + handle + .write_all(serialized.as_bytes()) + .context("failed to write I2C transfer request")?; + + let mut response = String::new(); + handle + .read_to_string(&mut response) + .context("failed to read I2C transfer response")?; + + let transfer: I2cTransferResponse = + ron::from_str(&response).context("failed to decode I2C transfer response")?; + if !transfer.ok { + bail!( + "I2C transfer failed: {}", + transfer + .error + .unwrap_or_else(|| "unspecified transfer error".to_string()) + ); + } + Ok(transfer) + } + + pub fn write_read(&self, address: u16, write_data: &[u8], read_len: usize) -> Result> { + let response = self.transfer(vec![ + I2cTransferSegment::write(address, write_data.to_vec()), + I2cTransferSegment::read(address, read_len), + ])?; + + response + .read_data + .last() + .cloned() + .ok_or_else(|| anyhow::anyhow!("I2C transfer returned no readable segment payload")) + } +} + +pub fn fetch_hid_descriptor( + adapter: &I2cAdapterClient, + address: u16, + hid_desc_addr: u16, +) -> Result { + let prefix = adapter + .write_read(address, &hid_desc_addr.to_le_bytes(), 2) + .context("failed to read HID descriptor length prefix")?; + if prefix.len() < 2 { + bail!("short HID descriptor prefix: {} bytes", prefix.len()); + } + + let hid_desc_length = u16::from_le_bytes([prefix[0], prefix[1]]); + if hid_desc_length < 18 { + bail!("invalid HID descriptor length {hid_desc_length}"); + } + + let raw = adapter + .write_read( + address, + &hid_desc_addr.to_le_bytes(), + usize::from(hid_desc_length), + ) + .context("failed to read full HID descriptor")?; + parse_hid_descriptor(&raw) +} + +pub fn fetch_report_descriptor( + adapter: &I2cAdapterClient, + address: u16, + desc: &HidDescriptor, +) -> Result> { + adapter + .write_read( + address, + &desc.report_desc_register.to_le_bytes(), + usize::from(desc.report_desc_length), + ) + .context("failed to read HID report descriptor") +} + +pub fn stream_input_reports( + adapter: &I2cAdapterClient, + address: u16, + desc: &HidDescriptor, + report_desc: &[u8], + sink: &mut crate::input::InputForwarder, +) -> Result<()> { + let summary = summarize_report_descriptor(report_desc); + let input_len = usize::from(desc.max_input_length.max(4)); + + loop { + let report = adapter + .write_read(address, &desc.input_register.to_le_bytes(), input_len) + .context("failed to fetch I2C HID input report")?; + sink.forward_report(&summary, &report)?; + } +} + +fn parse_hid_descriptor(bytes: &[u8]) -> Result { + if bytes.len() < 18 { + bail!("short HID descriptor: {} bytes", bytes.len()); + } + + Ok(HidDescriptor { + hid_desc_length: le16(bytes, 0)?, + bcd_version: le16(bytes, 2)?, + report_desc_length: le16(bytes, 4)?, + report_desc_register: le16(bytes, 6)?, + input_register: le16(bytes, 8)?, + max_input_length: le16(bytes, 10)?, + output_register: le16(bytes, 12)?, + max_output_length: le16(bytes, 14)?, + command_register: le16(bytes, 16)?, + data_register: if bytes.len() >= 20 { + le16(bytes, 18)? + } else { + 0 + }, + }) +} + +fn summarize_report_descriptor(report_desc: &[u8]) -> ReportDescriptorSummary { + let mut summary = ReportDescriptorSummary::default(); + + for window in report_desc.windows(2) { + match window { + [0x05, 0x01] => summary.has_pointer_page = true, + [0x05, 0x07] => summary.has_keyboard_page = true, + [0x85, _] => summary.report_ids = true, + _ => {} + } + } + + if !summary.has_keyboard_page && !summary.has_pointer_page { + summary.has_pointer_page = true; + } + + summary +} + +fn le16(bytes: &[u8], offset: usize) -> Result { + let slice = bytes + .get(offset..offset + 2) + .ok_or_else(|| anyhow::anyhow!("short LE16 field at offset {offset}"))?; + Ok(u16::from_le_bytes([slice[0], slice[1]])) +} diff --git a/recipes/core/base/drivers/input/i2c-hidd/src/input.rs b/recipes/core/base/drivers/input/i2c-hidd/src/input.rs new file mode 100644 index 0000000000..432a0782f6 --- /dev/null +++ b/recipes/core/base/drivers/input/i2c-hidd/src/input.rs @@ -0,0 +1,175 @@ +use std::collections::BTreeSet; + +use anyhow::Result; +use inputd::ProducerHandle; +use orbclient::{ + ButtonEvent, KeyEvent, MouseRelativeEvent, ScrollEvent, K_ALT, K_ALT_GR, K_BKSP, K_BRACE_CLOSE, + K_BRACE_OPEN, K_CAPS, K_COMMA, K_ENTER, K_EQUALS, K_ESC, K_LEFT_CTRL, K_LEFT_SHIFT, + K_LEFT_SUPER, K_MINUS, K_PERIOD, K_QUOTE, K_RIGHT_CTRL, K_RIGHT_SHIFT, K_RIGHT_SUPER, + K_SEMICOLON, K_SLASH, K_SPACE, K_TAB, K_TICK, +}; + +use crate::hid::ReportDescriptorSummary; + +pub struct InputForwarder { + producer: ProducerHandle, + keyboard_state: BTreeSet, + last_buttons: u8, +} + +impl InputForwarder { + pub fn new() -> Result { + Ok(Self { + producer: ProducerHandle::new()?, + keyboard_state: BTreeSet::new(), + last_buttons: 0, + }) + } + + pub fn forward_report( + &mut self, + summary: &ReportDescriptorSummary, + report: &[u8], + ) -> Result<()> { + if report.is_empty() { + return Ok(()); + } + + if summary.has_keyboard_page && report.len() >= 8 { + self.forward_boot_keyboard(report)?; + return Ok(()); + } + + if summary.has_pointer_page && report.len() >= 3 { + self.forward_boot_pointer(report)?; + return Ok(()); + } + + Ok(()) + } + + fn forward_boot_keyboard(&mut self, report: &[u8]) -> Result<()> { + let modifiers = report[0]; + for (bit, scancode) in [ + (0_u8, K_LEFT_CTRL), + (1, K_LEFT_SHIFT), + (2, K_ALT), + (3, K_LEFT_SUPER), + (4, K_RIGHT_CTRL), + (5, K_RIGHT_SHIFT), + (6, K_ALT_GR), + (7, K_RIGHT_SUPER), + ] { + self.producer.write_event( + KeyEvent { + character: '\0', + scancode, + pressed: modifiers & (1 << bit) != 0, + } + .to_event(), + )?; + } + + let current = report[2..8] + .iter() + .copied() + .filter(|code| *code != 0) + .collect::>(); + + for code in current.difference(&self.keyboard_state) { + if let Some(scancode) = map_boot_keyboard_usage(*code) { + self.producer.write_event( + KeyEvent { + character: '\0', + scancode, + pressed: true, + } + .to_event(), + )?; + } + } + for code in self.keyboard_state.difference(¤t) { + if let Some(scancode) = map_boot_keyboard_usage(*code) { + self.producer.write_event( + KeyEvent { + character: '\0', + scancode, + pressed: false, + } + .to_event(), + )?; + } + } + + self.keyboard_state = current; + Ok(()) + } + + fn forward_boot_pointer(&mut self, report: &[u8]) -> Result<()> { + let dx = i8::from_ne_bytes([report[1]]) as i32; + let dy = i8::from_ne_bytes([report[2]]) as i32; + if dx != 0 || dy != 0 { + self.producer + .write_event(MouseRelativeEvent { dx, dy }.to_event())?; + } + + if let Some(scroll) = report.get(3).copied() { + let scroll = i8::from_ne_bytes([scroll]) as i32; + if scroll != 0 { + self.producer + .write_event(ScrollEvent { x: 0, y: scroll }.to_event())?; + } + } + + let buttons = report[0] & 0x07; + for index in 0..3 { + let mask = 1 << index; + if (buttons & mask) != (self.last_buttons & mask) { + self.producer.write_event( + ButtonEvent { + left: buttons & 0x01 != 0, + middle: buttons & 0x04 != 0, + right: buttons & 0x02 != 0, + } + .to_event(), + )?; + break; + } + } + self.last_buttons = buttons; + Ok(()) + } +} + +fn map_boot_keyboard_usage(usage: u8) -> Option { + Some(match usage { + 0x04..=0x1D => b'a' + (usage - 0x04), + 0x1E => b'1', + 0x1F => b'2', + 0x20 => b'3', + 0x21 => b'4', + 0x22 => b'5', + 0x23 => b'6', + 0x24 => b'7', + 0x25 => b'8', + 0x26 => b'9', + 0x27 => b'0', + 0x28 => K_ENTER, + 0x29 => K_ESC, + 0x2A => K_BKSP, + 0x2B => K_TAB, + 0x2C => K_SPACE, + 0x2D => K_MINUS, + 0x2E => K_EQUALS, + 0x2F => K_BRACE_OPEN, + 0x30 => K_BRACE_CLOSE, + 0x33 => K_SEMICOLON, + 0x34 => K_QUOTE, + 0x35 => K_TICK, + 0x36 => K_COMMA, + 0x37 => K_PERIOD, + 0x38 => K_SLASH, + 0x39 => K_CAPS, + _ => return None, + }) +} diff --git a/recipes/core/base/drivers/input/i2c-hidd/src/main.rs b/recipes/core/base/drivers/input/i2c-hidd/src/main.rs new file mode 100644 index 0000000000..88270e37b8 --- /dev/null +++ b/recipes/core/base/drivers/input/i2c-hidd/src/main.rs @@ -0,0 +1,114 @@ +use std::process; +use std::thread; +use std::time::Duration; + +use anyhow::{Context, Result}; + +mod acpi; +mod hid; +mod input; +mod quirks; + +use acpi::{ + hid_descriptor_address, prepare_acpi_device, read_decoded_resources, recover_acpi_device, + scan_acpi_i2c_hid_devices, +}; +use hid::{fetch_hid_descriptor, fetch_report_descriptor, stream_input_reports, I2cAdapterClient}; +use input::InputForwarder; +use quirks::match_probe_failure_quirk; + +fn main() { + daemon::Daemon::new(daemon); +} + +fn daemon(daemon: daemon::Daemon) -> ! { + common::setup_logging( + "input", + "i2c-hid", + "i2c-hidd", + common::output_level(), + common::file_level(), + ); + + if let Err(err) = run(daemon) { + log::error!("RB_I2C_HIDD_BLOCKER stage=startup error={err:#}"); + process::exit(1); + } + + process::exit(0); +} + +fn run(daemon: daemon::Daemon) -> Result<()> { + log::info!("RB_I2C_HIDD_SCHEMA version=1"); + + let devices = scan_acpi_i2c_hid_devices().context("failed to scan ACPI I2C HID devices")?; + if devices.is_empty() { + log::warn!("RB_I2C_HIDD_BLOCKER stage=scan error=no PNP0C50/ACPI0C50 devices found"); + } + + let mut workers = Vec::new(); + for device in devices { + log::info!("RB_I2C_HIDD_SNAPSHOT device={device}"); + workers.push(thread::spawn(move || { + if let Err(err) = bind_device(&device) { + log::error!("RB_I2C_HIDD_BLOCKER device={} error={:#}", device, err); + } + })); + } + + daemon.ready(); + + if workers.is_empty() { + loop { + thread::sleep(Duration::from_secs(5)); + } + } + + for worker in workers { + let _ = worker.join(); + } + Ok(()) +} + +pub fn bind_device(device_path: &str) -> Result<()> { + prepare_acpi_device(device_path) + .with_context(|| format!("failed to prepare ACPI device {device_path}"))?; + + let resources = read_decoded_resources(device_path) + .with_context(|| format!("failed to decode _CRS for {device_path}"))?; + log::info!( + "RB_I2C_HIDD_SNAPSHOT device={} adapter={} addr={:04x} irq={:?} gpio_int={} gpio_io={}", + device_path, + resources.i2c.adapter, + resources.i2c.address, + resources.irq, + resources.gpio_int.len(), + resources.gpio_io.len() + ); + + let hid_desc_addr = hid_descriptor_address(device_path) + .with_context(|| format!("failed to evaluate _DSM for {device_path}"))?; + let adapter = I2cAdapterClient::new(resources.i2c.clone()); + let hid_desc = fetch_hid_descriptor(&adapter, resources.i2c.address, hid_desc_addr) + .with_context(|| format!("failed to fetch HID descriptor for {device_path}"))?; + let report_desc = fetch_report_descriptor(&adapter, resources.i2c.address, &hid_desc) + .with_context(|| format!("failed to fetch report descriptor for {device_path}"))?; + let mut forwarder = InputForwarder::new().context("failed to connect to inputd producer")?; + + match stream_input_reports( + &adapter, + resources.i2c.address, + &hid_desc, + &report_desc, + &mut forwarder, + ) { + Ok(()) => Ok(()), + Err(err) => { + let quirk = + match_probe_failure_quirk().context("failed to evaluate DMI recovery quirks")?; + recover_acpi_device(device_path, &resources, quirk.as_ref()) + .with_context(|| format!("failed ACPI recovery for {device_path}"))?; + Err(err).with_context(|| format!("streaming input reports failed for {device_path}")) + } + } +} diff --git a/recipes/core/base/drivers/input/i2c-hidd/src/quirks.rs b/recipes/core/base/drivers/input/i2c-hidd/src/quirks.rs new file mode 100644 index 0000000000..450cb19b1b --- /dev/null +++ b/recipes/core/base/drivers/input/i2c-hidd/src/quirks.rs @@ -0,0 +1,88 @@ +use std::fs; + +use anyhow::{Context, Result}; +use serde::Deserialize; + +#[derive(Clone, Debug)] +pub struct ProbeFailureQuirk { + pub name: String, + pub system_vendor: Option, + pub product_name: Option, + pub board_name: Option, +} + +#[derive(Clone, Debug, Default, Deserialize)] +struct ProbeFailureQuirkFile { + quirks: Vec, +} + +#[derive(Clone, Debug, Deserialize)] +struct ProbeFailureQuirkEntry { + name: String, + system_vendor: Option, + product_name: Option, + board_name: Option, +} + +#[derive(Default)] +struct DmiSnapshot { + system_vendor: String, + product_name: String, + board_name: String, +} + +pub fn match_probe_failure_quirk() -> Result> { + let snapshot = read_dmi_snapshot()?; + for entry in load_quirks()? { + if field_matches(&entry.system_vendor, &snapshot.system_vendor) + && field_matches(&entry.product_name, &snapshot.product_name) + && field_matches(&entry.board_name, &snapshot.board_name) + { + return Ok(Some(ProbeFailureQuirk { + name: entry.name, + system_vendor: entry.system_vendor, + product_name: entry.product_name, + board_name: entry.board_name, + })); + } + } + + Ok(None) +} + +fn load_quirks() -> Result> { + let path = "/etc/i2c-hidd-quirks.ron"; + let text = match fs::read_to_string(path) { + Ok(text) => text, + Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(Vec::new()), + Err(err) => return Err(err).with_context(|| format!("failed to read {path}")), + }; + + let file: ProbeFailureQuirkFile = + ron::from_str(&text).with_context(|| format!("failed to decode {path}"))?; + Ok(file.quirks) +} + +fn read_dmi_snapshot() -> Result { + Ok(DmiSnapshot { + system_vendor: read_dmi_field("system_vendor")?, + product_name: read_dmi_field("product_name")?, + board_name: read_dmi_field("board_name")?, + }) +} + +fn read_dmi_field(field: &str) -> Result { + let path = format!("/scheme/acpi/dmi/{field}"); + match fs::read_to_string(&path) { + Ok(value) => Ok(value.trim().to_string()), + Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(String::new()), + Err(err) => Err(err).with_context(|| format!("failed to read {path}")), + } +} + +fn field_matches(expected: &Option, actual: &str) -> bool { + expected + .as_deref() + .map(|expected| actual.eq_ignore_ascii_case(expected)) + .unwrap_or(true) +} diff --git a/recipes/core/base/drivers/input/intel-thc-hidd/Cargo.toml b/recipes/core/base/drivers/input/intel-thc-hidd/Cargo.toml new file mode 100644 index 0000000000..f6aa224832 --- /dev/null +++ b/recipes/core/base/drivers/input/intel-thc-hidd/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "intel-thc-hidd" +description = "Intel THC QuickI2C HID transport daemon" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow.workspace = true +log.workspace = true +pci_types = "0.10.1" +redox_syscall = { workspace = true, features = ["std"] } +libredox.workspace = true +redox-scheme.workspace = true +ron.workspace = true +serde.workspace = true + +acpi-resource = { path = "../../acpi-resource" } +amlserde = { path = "../../amlserde" } +common = { path = "../../common" } +daemon = { path = "../../../daemon" } +i2c-interface = { path = "../../i2c/i2c-interface" } +pcid = { path = "../../pcid" } +scheme-utils = { path = "../../../scheme-utils" } + +[lints] +workspace = true diff --git a/recipes/core/base/drivers/input/intel-thc-hidd/src/main.rs b/recipes/core/base/drivers/input/intel-thc-hidd/src/main.rs new file mode 100644 index 0000000000..423977e05d --- /dev/null +++ b/recipes/core/base/drivers/input/intel-thc-hidd/src/main.rs @@ -0,0 +1,260 @@ +use std::collections::BTreeSet; +use std::fs::{self, OpenOptions}; +use std::io::Read; +use std::process; +use std::thread; +use std::time::Duration; + +use acpi_resource::ResourceDescriptor; +use amlserde::{AmlSerde, AmlSerdeValue}; +use anyhow::{bail, Context, Result}; +use libredox::flag::{O_CLOEXEC, O_RDWR}; +use pcid_interface::PciFunctionHandle; + +mod quicki2c; +mod thc; + +use quicki2c::QuickI2cTransport; +use thc::{ThcController, SUPPORTED_PCI_IDS}; + +fn main() { + pcid_interface::pci_daemon(daemon); +} + +fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { + common::setup_logging( + "input", + "intel-thc", + "intel-thc-hidd", + common::output_level(), + common::file_level(), + ); + + if let Err(err) = run(daemon, &mut pcid_handle) { + log::error!("RB_THC_HIDD_FATAL error={err:#}"); + process::exit(1); + } + + process::exit(0); +} + +fn run(daemon: daemon::Daemon, pcid_handle: &mut PciFunctionHandle) -> Result<()> { + log::info!("RB_THC_HIDD_SCHEMA version=1"); + + let pci_config = pcid_handle.config(); + let id = ( + pci_config.func.full_device_id.vendor_id, + pci_config.func.full_device_id.device_id, + ); + if !SUPPORTED_PCI_IDS.contains(&id) { + bail!("unsupported Intel THC PCI device {:04x}:{:04x}", id.0, id.1); + } + + pcid_handle.enable_device(); + let bar = unsafe { pcid_handle.map_bar(0) }; + let controller = ThcController::new(bar.ptr.as_ptr(), bar.bar_size) + .context("failed to create THC controller")?; + + let companion = resolve_acpi_companion(&pci_config.func.addr) + .context("failed to resolve ACPI companion for THC device")?; + let override_address = companion + .as_deref() + .map(companion_slave_address_override) + .transpose() + .context("failed to evaluate THC slave-address override")? + .flatten(); + let hid_devices = scan_bound_i2c_hid_devices(companion.as_deref()) + .context("failed to scan PNP0C50 devices for THC controller")?; + + let effective_address = override_address.unwrap_or(0x0015); + let transport = QuickI2cTransport::new(controller, effective_address); + transport.prime_controller(); + transport.emulate_transfer(&[]); + log::debug!("RB_THC_HIDD status={:#x}", transport.status()); + + match transport.register_with_i2cd(companion.as_deref(), override_address) { + Ok(()) => {} + Err(err) => { + log::warn!("RB_THC_HIDD registration error={err:#}"); + } + } + + log::info!( + "RB_THC_HIDD pci={} companion={:?} override={:?} hid_devices={}", + pci_config.func.name(), + companion, + override_address, + hid_devices.len() + ); + + daemon.ready(); + + loop { + thread::sleep(Duration::from_secs(5)); + } +} + +fn resolve_acpi_companion(addr: &pci_types::PciAddress) -> Result> { + let entries = + fs::read_dir("/scheme/acpi/symbols").context("failed to read /scheme/acpi/symbols")?; + let expected_adr = (u64::from(addr.device()) << 16) | u64::from(addr.function()); + + for entry in entries { + let entry = entry.context("failed to enumerate ACPI symbol entry")?; + let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else { + continue; + }; + if !file_name.ends_with("._ADR") { + continue; + } + + let symbol = read_aml_symbol(&file_name)?; + if !matches!(symbol.value, AmlSerdeValue::Integer(value) if value == expected_adr) { + continue; + } + + let device = symbol + .name + .strip_suffix("._ADR") + .unwrap_or(&symbol.name) + .trim_start_matches('\\') + .replace('/', "."); + return Ok(Some(device)); + } + + Ok(None) +} + +fn companion_slave_address_override(path: &str) -> Result> { + let icrs = evaluate_integer_method(path, "ICRS").ok(); + let isub = evaluate_integer_method(path, "ISUB").ok(); + Ok(icrs + .or(isub) + .map(|value| u16::try_from(value)) + .transpose() + .context("THC ACPI override out of range")?) +} + +fn scan_bound_i2c_hid_devices(companion: Option<&str>) -> Result> { + let entries = + fs::read_dir("/scheme/acpi/symbols").context("failed to read /scheme/acpi/symbols")?; + let mut devices = BTreeSet::new(); + + for entry in entries { + let entry = entry.context("failed to enumerate ACPI HID entry")?; + let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else { + continue; + }; + if !file_name.ends_with("._HID") && !file_name.ends_with("._CID") { + continue; + } + + let symbol = read_aml_symbol(&file_name)?; + let is_hid = matches!( + decode_hardware_id(&symbol.value).as_deref(), + Some("PNP0C50" | "ACPI0C50") + ); + if !is_hid { + continue; + } + + let device = symbol + .name + .strip_suffix("._HID") + .or_else(|| symbol.name.strip_suffix("._CID")) + .unwrap_or(&symbol.name) + .trim_start_matches('\\') + .replace('/', "."); + if let Some(companion) = companion { + if !is_bound_to_companion(&device, companion)? { + continue; + } + } + devices.insert(device); + } + + Ok(devices.into_iter().collect()) +} + +fn is_bound_to_companion(device: &str, companion: &str) -> Result { + let resource_path = format!("/scheme/acpi/resources/{device}"); + let serialized = match fs::read_to_string(&resource_path) { + Ok(serialized) => serialized, + Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(false), + Err(err) => return Err(err).with_context(|| format!("failed to read {resource_path}")), + }; + + let resources: Vec = + ron::from_str(&serialized).with_context(|| format!("failed to decode {resource_path}"))?; + Ok(resources.into_iter().any(|resource| match resource { + ResourceDescriptor::I2cSerialBus(bus) => bus + .resource_source + .as_ref() + .map(|source| source.source == companion) + .unwrap_or(false), + _ => false, + })) +} + +fn evaluate_integer_method(path: &str, method: &str) -> Result { + let symbol_name = format!("{}.{}", normalize_device_path(path), method); + let symbol_path = format!("/scheme/acpi/symbols/{symbol_name}"); + let fd = libredox::Fd::open(&symbol_path, O_RDWR | O_CLOEXEC, 0) + .with_context(|| format!("failed to open {symbol_path}"))?; + + let mut payload = ron::to_string(&Vec::::new()) + .context("failed to serialize ACPI call arguments")? + .into_bytes(); + payload.resize(payload.len() + 2048, 0); + let used = libredox::call::call_ro(fd.raw(), &mut payload, syscall::CallFlags::empty(), &[]) + .with_context(|| format!("ACPI evaluation failed for {symbol_name}"))?; + let response = std::str::from_utf8(&payload[..used]) + .with_context(|| format!("invalid UTF-8 ACPI response for {symbol_name}"))?; + match ron::from_str::(response) + .with_context(|| format!("failed to decode ACPI response for {symbol_name}"))? + { + AmlSerdeValue::Integer(value) => Ok(value), + other => bail!("{}.{} returned non-integer value {other:?}", path, method), + } +} + +fn read_aml_symbol(file_name: &str) -> Result { + let path = format!("/scheme/acpi/symbols/{file_name}"); + let mut file = OpenOptions::new() + .read(true) + .open(&path) + .with_context(|| format!("failed to open {path}"))?; + let mut ron_text = String::new(); + file.read_to_string(&mut ron_text) + .with_context(|| format!("failed to read {path}"))?; + ron::from_str(&ron_text).with_context(|| format!("failed to decode {path}")) +} + +fn decode_hardware_id(value: &AmlSerdeValue) -> Option { + match value { + AmlSerdeValue::String(value) => Some(value.clone()), + AmlSerdeValue::Integer(integer) => { + let vendor = integer & 0xFFFF; + let device = (integer >> 16) & 0xFFFF; + let vendor_rev = ((vendor & 0xFF) << 8) | (vendor >> 8); + let vendor_1 = (((vendor_rev >> 10) & 0x1f) as u8 + 64) as char; + let vendor_2 = (((vendor_rev >> 5) & 0x1f) as u8 + 64) as char; + let vendor_3 = (((vendor_rev >> 0) & 0x1f) as u8 + 64) as char; + let device_1 = (device >> 4) & 0xF; + let device_2 = (device >> 0) & 0xF; + let device_3 = (device >> 12) & 0xF; + let device_4 = (device >> 8) & 0xF; + Some(format!( + "{}{}{}{:01X}{:01X}{:01X}{:01X}", + vendor_1, vendor_2, vendor_3, device_1, device_2, device_3, device_4 + )) + } + _ => None, + } +} + +fn normalize_device_path(path: &str) -> String { + path.trim_start_matches('\\') + .trim_matches('/') + .replace('/', ".") +} diff --git a/recipes/core/base/drivers/input/intel-thc-hidd/src/quicki2c.rs b/recipes/core/base/drivers/input/intel-thc-hidd/src/quicki2c.rs new file mode 100644 index 0000000000..721f0be3b8 --- /dev/null +++ b/recipes/core/base/drivers/input/intel-thc-hidd/src/quicki2c.rs @@ -0,0 +1,86 @@ +use std::fs::OpenOptions; +use std::io::Write; + +use anyhow::{Context, Result}; +use i2c_interface::{I2cAdapterRegistration, I2cTransferSegment}; + +use crate::thc::ThcController; + +const QUICKI2C_OPCODE_WRITE: u32 = 0x1; +const QUICKI2C_OPCODE_READ: u32 = 0x2; + +pub struct QuickI2cTransport { + controller: ThcController, + slave_address: u16, +} + +impl QuickI2cTransport { + pub fn new(controller: ThcController, slave_address: u16) -> Self { + Self { + controller, + slave_address, + } + } + + pub fn prime_controller(&self) { + self.controller.initialize_quicki2c_mode(); + } + + pub fn emulate_transfer(&self, segments: &[I2cTransferSegment]) { + for segment in segments { + match &segment.op { + i2c_interface::I2cTransferOp::Write(data) => { + self.controller.program_subip_transaction( + QUICKI2C_OPCODE_WRITE, + segment.address, + data.len(), + ); + for (index, chunk) in data.chunks(4).enumerate() { + let mut word = [0_u8; 4]; + word[..chunk.len()].copy_from_slice(chunk); + self.controller + .write_subip_data(index * 4, u32::from_le_bytes(word)); + } + } + i2c_interface::I2cTransferOp::Read(len) => { + self.controller.program_subip_transaction( + QUICKI2C_OPCODE_READ, + segment.address, + *len, + ); + let _ = self.controller.read_subip_data(0); + } + } + } + } + + pub fn status(&self) -> u32 { + self.controller.status() + } + + pub fn register_with_i2cd( + &self, + acpi_companion: Option<&str>, + override_address: Option, + ) -> Result<()> { + let registration = I2cAdapterRegistration { + name: "intel-thc-quicki2c".to_string(), + description: format!( + "Intel THC QuickI2C adapter for slave {:04x}", + self.slave_address + ), + acpi_companion: acpi_companion.map(str::to_owned), + slave_address_override: override_address, + }; + let payload = + ron::to_string(®istration).context("failed to serialize i2cd registration")?; + + let mut file = OpenOptions::new() + .write(true) + .open("/scheme/i2c/register") + .context("failed to open /scheme/i2c/register")?; + file.write_all(payload.as_bytes()) + .context("failed to write i2cd adapter registration")?; + Ok(()) + } +} diff --git a/recipes/core/base/drivers/input/intel-thc-hidd/src/thc.rs b/recipes/core/base/drivers/input/intel-thc-hidd/src/thc.rs new file mode 100644 index 0000000000..e06a6f8a6d --- /dev/null +++ b/recipes/core/base/drivers/input/intel-thc-hidd/src/thc.rs @@ -0,0 +1,78 @@ +use std::ptr::NonNull; + +use anyhow::{bail, Result}; + +pub const SUPPORTED_PCI_IDS: &[(u16, u16)] = &[ + (0x8086, 0x7eb8), + (0x8086, 0x7eb9), + (0x8086, 0x7ebd), + (0x8086, 0x7ebe), + (0x8086, 0xa8b8), + (0x8086, 0xa8b9), +]; + +pub const REG_CONTROL: usize = 0x0000; +pub const REG_STATUS: usize = 0x0004; +pub const REG_MODE: usize = 0x0010; +pub const REG_SUBIP_OPCODE: usize = 0x0800; +pub const REG_SUBIP_ADDRESS: usize = 0x0804; +pub const REG_SUBIP_LENGTH: usize = 0x0808; +pub const REG_SUBIP_DOORBELL: usize = 0x080C; +pub const REG_SUBIP_DATA: usize = 0x0810; + +#[derive(Clone, Copy)] +pub struct ThcController { + base: NonNull, + len: usize, +} + +impl ThcController { + pub fn new(base: *mut u8, len: usize) -> Result { + let Some(base) = NonNull::new(base) else { + bail!("THC BAR mapping returned null base pointer"); + }; + Ok(Self { base, len }) + } + + pub fn initialize_quicki2c_mode(&self) { + self.write32(REG_MODE, 0x1); + self.write32(REG_CONTROL, 0x1); + } + + pub fn status(&self) -> u32 { + self.read32(REG_STATUS) + } + + pub fn program_subip_transaction(&self, opcode: u32, address: u16, len: usize) { + self.write32(REG_SUBIP_OPCODE, opcode); + self.write32(REG_SUBIP_ADDRESS, u32::from(address)); + self.write32(REG_SUBIP_LENGTH, len as u32); + self.write32(REG_SUBIP_DOORBELL, 1); + } + + pub fn write_subip_data(&self, offset: usize, value: u32) { + self.write32(REG_SUBIP_DATA + offset, value); + } + + pub fn read_subip_data(&self, offset: usize) -> u32 { + self.read32(REG_SUBIP_DATA + offset) + } + + fn read32(&self, offset: usize) -> u32 { + if offset + 4 > self.len { + return 0; + } + + let ptr = unsafe { self.base.as_ptr().add(offset).cast::() }; + unsafe { ptr.read_volatile() } + } + + fn write32(&self, offset: usize, value: u32) { + if offset + 4 > self.len { + return; + } + + let ptr = unsafe { self.base.as_ptr().add(offset).cast::() }; + unsafe { ptr.write_volatile(value) }; + } +} diff --git a/recipes/core/base/drivers/thermald/Cargo.toml b/recipes/core/base/drivers/thermald/Cargo.toml new file mode 100644 index 0000000000..65255ebaa0 --- /dev/null +++ b/recipes/core/base/drivers/thermald/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "thermald" +version = "0.1.0" +edition = "2021" + +[dependencies] +log.workspace = true +anyhow.workspace = true +common = { path = "../common" } + +[lints] +workspace = true diff --git a/recipes/core/base/drivers/thermald/src/main.rs b/recipes/core/base/drivers/thermald/src/main.rs new file mode 100644 index 0000000000..3db4dd55ba --- /dev/null +++ b/recipes/core/base/drivers/thermald/src/main.rs @@ -0,0 +1,30 @@ +use anyhow::{Context, Result}; +use std::{thread, time}; + +fn read_temp() -> Option { + for zone in 0..4 { + let path = format!("/scheme/acpi/thermal_zone/{}/temperature", zone); + if let Ok(data) = std::fs::read_to_string(&path) { + if let Ok(mv) = data.trim().parse::() { + return Some(mv as f32 / 1000.0); + } + } + } + None +} + +fn main() -> Result<()> { + common::setup_logging("system", "thermald", "thermald", + common::output_level(), common::file_level()); + log::info!("thermald: started"); + loop { + if let Some(temp) = read_temp() { + if temp > 85.0 { + log::error!("thermald: CRITICAL {:.1}C", temp); + } else if temp > 70.0 { + log::warn!("thermald: WARNING {:.1}C", temp); + } + } + thread::sleep(time::Duration::from_secs(5)); + } +} diff --git a/recipes/core/base/drivers/usb/ucsid/Cargo.toml b/recipes/core/base/drivers/usb/ucsid/Cargo.toml new file mode 100644 index 0000000000..1a6833e500 --- /dev/null +++ b/recipes/core/base/drivers/usb/ucsid/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "ucsid" +description = "USB-C UCSI topology daemon" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow.workspace = true +log.workspace = true +redox_syscall = { workspace = true, features = ["std"] } +libredox.workspace = true +redox-scheme.workspace = true +serde.workspace = true +ron.workspace = true + +acpi-resource = { path = "../../acpi-resource" } +common = { path = "../../common" } +daemon = { path = "../../../daemon" } +i2c-interface = { path = "../../i2c/i2c-interface" } +scheme-utils = { path = "../../../scheme-utils" } + +[lints] +workspace = true diff --git a/recipes/core/base/drivers/usb/ucsid/src/main.rs b/recipes/core/base/drivers/usb/ucsid/src/main.rs new file mode 100644 index 0000000000..d00cfbcfe1 --- /dev/null +++ b/recipes/core/base/drivers/usb/ucsid/src/main.rs @@ -0,0 +1,839 @@ +use std::collections::BTreeMap; +use std::fs::{self, File, OpenOptions}; +use std::io::{Read, Write}; +use std::path::Path; +use std::process; + +use acpi_resource::{ + AddressResourceType, FixedMemory32Descriptor, I2cSerialBusDescriptor, Memory32RangeDescriptor, + ResourceDescriptor, +}; +use anyhow::{bail, Context, Result}; +use i2c_interface::{ + I2cAdapterInfo, I2cControlRequest, I2cControlResponse, I2cTransferRequest, + I2cTransferSegment, +}; +use libredox::flag::{O_CLOEXEC, O_RDWR}; +use redox_scheme::scheme::SchemeSync; +use redox_scheme::{CallerCtx, OpenResult, Socket}; +use scheme_utils::{Blocking, HandleMap}; +use serde::{Deserialize, Serialize}; +use syscall::schemev2::NewFdFlags; +use syscall::{Error as SysError, EACCES, EBADF, EINVAL, ENOENT}; + +const SUPPORTED_IDS: &[&str] = &["PNP0CA0", "AMDI0042"]; +const GET_CAPABILITY: u8 = 0x01; +const GET_CONNECTOR_STATUS: u8 = 0x10; +const UCSI_RESPONSE_HEADER_LEN: usize = 4; +const UCSI_CAPABILITY_READ_LEN: usize = 20; +const UCSI_CONNECTOR_STATUS_READ_LEN: usize = 20; +const MAX_CONNECTOR_PROBE: u8 = 8; + +#[derive(Debug, Deserialize)] +struct AmlSymbol { + name: String, + value: AmlValue, +} + +#[derive(Debug, Deserialize)] +enum AmlValue { + Integer(u64), + String(String), +} + +#[derive(Clone, Copy, Debug)] +struct UcsiCommand { + command: u8, + data_length: u8, + specific_data: [u8; 6], +} + +impl UcsiCommand { + fn new(command: u8, data_length: u8, specific_data: [u8; 6]) -> Self { + Self { + command, + data_length, + specific_data, + } + } + + fn as_bytes(self) -> [u8; 8] { + let mut bytes = [0_u8; 8]; + bytes[0] = self.command; + bytes[1] = self.data_length; + bytes[2..].copy_from_slice(&self.specific_data); + bytes + } +} + +#[derive(Clone, Copy, Debug)] +struct UcsiResponseHeader { + _status: u16, + data_length: u16, +} + +impl UcsiResponseHeader { + fn parse(bytes: &[u8]) -> Option { + let header = bytes.get(..UCSI_RESPONSE_HEADER_LEN)?; + Some(Self { + _status: u16::from_le_bytes([header[0], header[1]]), + data_length: u16::from_le_bytes([header[2], header[3]]), + }) + } +} + +#[derive(Clone, Debug)] +struct DiscoveredUcsiDevice { + name: String, + hid: String, + transport: UcsiTransport, + dsm_probe: bool, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +enum UcsiTransport { + I2c { + adapter: String, + address: u16, + ten_bit_address: bool, + }, + Mmio { + base: usize, + len: usize, + }, + Unknown, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +struct UcsiCapability { + connector_count: u8, + supports_usb_pd: bool, + supports_alt_modes: bool, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +struct UcsiConnectorSummary { + device: String, + connector_number: u8, + connected: bool, + data_role: String, + power_direction: String, + input_critical: bool, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +struct UcsiDeviceSummary { + name: String, + hid: String, + transport: UcsiTransport, + capability: Option, + connectors: Vec, + dsm_probe: bool, + issues: Vec, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +struct UcsiSummary { + schema_version: u32, + device_count: usize, + connector_count: usize, + input_critical_ports: usize, + devices: Vec, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +struct UcsiHealth { + healthy: bool, + scanned_devices: usize, + responsive_devices: usize, + issues: Vec, +} + +struct UcsiState { + summary: UcsiSummary, + connectors: Vec, + health: UcsiHealth, +} + +enum Handle { + SchemeRoot, + Summary { pending: Vec }, + Connectors { pending: Vec }, + Health { pending: Vec }, +} + +struct UcsiScheme { + handles: HandleMap, + state: UcsiState, +} + +impl UcsiScheme { + fn new(state: UcsiState) -> Self { + Self { + handles: HandleMap::new(), + state, + } + } + + fn serialize_payload(value: &T) -> syscall::Result> { + ron::ser::to_string(value) + .map(|text| text.into_bytes()) + .map_err(|err| { + log::error!("ucsid: failed to serialize scheme payload: {err}"); + SysError::new(EINVAL) + }) + } + + fn set_pending(handle: &mut Handle, pending: Vec) -> syscall::Result<()> { + match handle { + Handle::Summary { pending: slot } + | Handle::Connectors { pending: slot } + | Handle::Health { pending: slot } => { + *slot = pending; + Ok(()) + } + Handle::SchemeRoot => Err(SysError::new(EBADF)), + } + } + + fn copy_pending(handle: &mut Handle, buf: &mut [u8], offset: u64) -> syscall::Result { + let pending = match handle { + Handle::Summary { pending } + | Handle::Connectors { pending } + | Handle::Health { pending } => pending, + Handle::SchemeRoot => return Err(SysError::new(EBADF)), + }; + + let offset = usize::try_from(offset).map_err(|_| SysError::new(EINVAL))?; + if offset >= pending.len() { + return Ok(0); + } + + let copy_len = buf.len().min(pending.len() - offset); + buf[..copy_len].copy_from_slice(&pending[offset..offset + copy_len]); + Ok(copy_len) + } +} + +impl SchemeSync for UcsiScheme { + fn scheme_root(&mut self) -> syscall::Result { + Ok(self.handles.insert(Handle::SchemeRoot)) + } + + fn openat( + &mut self, + dirfd: usize, + path: &str, + _flags: usize, + _fcntl_flags: u32, + _ctx: &CallerCtx, + ) -> syscall::Result { + if !matches!(self.handles.get(dirfd)?, Handle::SchemeRoot) { + return Err(SysError::new(EACCES)); + } + + let handle = match path.trim_matches('/') { + "summary" => Handle::Summary { + pending: Vec::new(), + }, + "connectors" => Handle::Connectors { + pending: Vec::new(), + }, + "health" => Handle::Health { + pending: Vec::new(), + }, + "" => return Err(SysError::new(EINVAL)), + _ => return Err(SysError::new(ENOENT)), + }; + + let fd = self.handles.insert(handle); + Ok(OpenResult::ThisScheme { + number: fd, + flags: NewFdFlags::empty(), + }) + } + + fn read( + &mut self, + id: usize, + buf: &mut [u8], + offset: u64, + _fcntl_flags: u32, + _ctx: &CallerCtx, + ) -> syscall::Result { + let payload = match self.handles.get(id)? { + Handle::Summary { pending } if pending.is_empty() => { + Some(Self::serialize_payload(&self.state.summary)?) + } + Handle::Connectors { pending } if pending.is_empty() => { + Some(Self::serialize_payload(&self.state.connectors)?) + } + Handle::Health { pending } if pending.is_empty() => { + log::info!( + "RB_UCSID_HEALTH healthy={} scanned_devices={} responsive_devices={} issues={}", + self.state.health.healthy, + self.state.health.scanned_devices, + self.state.health.responsive_devices, + self.state.health.issues.len(), + ); + Some(Self::serialize_payload(&self.state.health)?) + } + _ => None, + }; + + let handle = self.handles.get_mut(id)?; + if let Some(payload) = payload { + Self::set_pending(handle, payload)?; + } + Self::copy_pending(handle, buf, offset) + } +} + +fn main() { + common::setup_logging( + "usb", + "ucsi", + "ucsid", + common::output_level(), + common::file_level(), + ); + + daemon::SchemeDaemon::new(daemon_runner); +} + +fn daemon_runner(daemon: daemon::SchemeDaemon) -> ! { + if let Err(err) = run_daemon(daemon) { + log::error!("ucsid: {err:#}"); + process::exit(1); + } + + process::exit(0); +} + +fn run_daemon(daemon: daemon::SchemeDaemon) -> Result<()> { + log::info!("RB_UCSID_SCHEMA version=1"); + + let state = build_state().context("failed to build UCSI device snapshot")?; + let socket = Socket::create().context("failed to create ucsi scheme socket")?; + let mut scheme = UcsiScheme::new(state); + let handler = Blocking::new(&socket, 16); + + daemon + .ready_sync_scheme(&socket, &mut scheme) + .context("failed to publish ucsi scheme root")?; + + libredox::call::setrens(0, 0).context("failed to enter null namespace")?; + + handler + .process_requests_blocking(scheme) + .context("failed to process ucsid requests")?; +} + +fn build_state() -> Result { + let adapters = list_i2c_adapters().unwrap_or_else(|err| { + log::warn!("ucsid: failed to query i2cd adapters: {err:#}"); + Vec::new() + }); + let devices = discover_ucsi_devices().context("failed to discover ACPI UCSI devices")?; + + let mut summaries = Vec::new(); + let mut connectors = Vec::new(); + let mut issues = Vec::new(); + let mut responsive_devices = 0usize; + + for device in devices { + log::info!( + "RB_UCSID_DEVICE name={} hid={} transport={:?} dsm_probe={}", + device.name, + device.hid, + device.transport, + device.dsm_probe, + ); + let summary = summarize_device(device, &adapters) + .context("failed to summarize discovered UCSI device")?; + if summary.capability.is_some() { + responsive_devices += 1; + } + issues.extend(summary.issues.iter().cloned()); + connectors.extend(summary.connectors.iter().cloned()); + summaries.push(summary); + } + + let summary = UcsiSummary { + schema_version: 1, + device_count: summaries.len(), + connector_count: connectors.len(), + input_critical_ports: connectors.iter().filter(|connector| connector.input_critical).count(), + devices: summaries, + }; + let health = UcsiHealth { + healthy: issues.is_empty(), + scanned_devices: summary.device_count, + responsive_devices, + issues, + }; + + log::info!( + "RB_UCSID_SUMMARY devices={} connectors={} input_critical_ports={} healthy={}", + summary.device_count, + summary.connector_count, + summary.input_critical_ports, + health.healthy, + ); + + Ok(UcsiState { + summary, + connectors, + health, + }) +} + +fn discover_ucsi_devices() -> Result> { + let mut matched = BTreeMap::new(); + + 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!("ucsid: ACPI symbols are not ready yet"); + return Ok(Vec::new()); + } + Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"), + }; + + for entry in entries { + let entry = entry.context("failed to read ACPI symbol directory entry")?; + let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else { + continue; + }; + if !file_name.ends_with("_HID") && !file_name.ends_with("_CID") { + continue; + } + + let Some(id) = read_symbol_id(&entry.path())? else { + continue; + }; + if !SUPPORTED_IDS.iter().any(|candidate| *candidate == id) { + continue; + } + + let Some(device) = file_name + .strip_suffix("_HID") + .or_else(|| file_name.strip_suffix("_CID")) + .map(str::to_owned) + else { + continue; + }; + matched.entry(device).or_insert(id); + } + + let mut devices = Vec::new(); + for (device, hid) in matched { + let transport = read_ucsi_transport(&device) + .with_context(|| format!("failed to decode transport resources for {device}"))?; + let dsm_probe = bounded_dsm_probe(&device).unwrap_or_else(|err| { + log::debug!("ucsid: bounded _DSM probe failed for {device}: {err:#}"); + false + }); + devices.push(DiscoveredUcsiDevice { + name: device, + hid, + transport, + dsm_probe, + }); + } + + Ok(devices) +} + +fn summarize_device(device: DiscoveredUcsiDevice, adapters: &[I2cAdapterInfo]) -> Result { + let mut issues = Vec::new(); + let capability = match &device.transport { + UcsiTransport::I2c { + adapter, + address, + ten_bit_address, + } => match match_i2c_adapter(adapters, adapter) { + Some(adapter_info) => match execute_ucsi_i2c_command( + adapter_info, + adapter, + *address, + *ten_bit_address, + UcsiCommand::new(GET_CAPABILITY, 0, [0; 6]), + UCSI_CAPABILITY_READ_LEN, + ) { + Ok(bytes) => parse_ucsi_payload(&bytes) + .and_then(|(_header, payload)| parse_capability(payload)) + .or_else(|| { + issues.push(format!( + "{}: GET_CAPABILITY returned an unexpected payload", + device.name + )); + None + }), + Err(err) => { + issues.push(format!("{}: GET_CAPABILITY failed: {err:#}", device.name)); + None + } + }, + None => { + issues.push(format!( + "{}: no i2cd adapter matched ACPI source {}", + device.name, adapter + )); + None + } + }, + UcsiTransport::Mmio { base, len } => { + issues.push(format!( + "{}: MMIO UCSI transport discovered at {base:#x}+{len:#x} but command execution is not implemented yet", + device.name, + )); + None + } + UcsiTransport::Unknown => { + issues.push(format!( + "{}: no supported UCSI transport was decoded from ACPI resources", + device.name, + )); + None + } + }; + + let connector_count = capability + .as_ref() + .map(|capability| capability.connector_count.min(MAX_CONNECTOR_PROBE)) + .unwrap_or(0); + let mut connectors = Vec::new(); + for connector in 1..=connector_count { + match query_connector_status(&device, adapters, connector) { + Ok(connector_summary) => connectors.push(connector_summary), + Err(err) => issues.push(format!( + "{}: GET_CONNECTOR_STATUS({connector}) failed: {err:#}", + device.name, + )), + } + } + + Ok(UcsiDeviceSummary { + name: device.name, + hid: device.hid, + transport: device.transport, + capability, + connectors, + dsm_probe: device.dsm_probe, + issues, + }) +} + +fn read_ucsi_transport(device: &str) -> Result { + let contents = fs::read_to_string(format!("/scheme/acpi/resources/{device}")) + .with_context(|| format!("failed to read /scheme/acpi/resources/{device}"))?; + let resources = ron::from_str::>(&contents) + .with_context(|| format!("failed to decode RON resources for {device}"))?; + + let mut i2c = None::; + let mut mmio = None::<(usize, usize)>; + + for resource in resources { + match resource { + ResourceDescriptor::I2cSerialBus(bus) if i2c.is_none() => i2c = Some(bus), + ResourceDescriptor::FixedMemory32(FixedMemory32Descriptor { + address, + address_length, + .. + }) if mmio.is_none() => { + mmio = Some((address as usize, address_length as usize)); + } + ResourceDescriptor::Memory32Range(Memory32RangeDescriptor { + minimum, + maximum, + address_length, + .. + }) if mmio.is_none() && maximum >= minimum => { + let span = maximum.saturating_sub(minimum).saturating_add(1) as usize; + mmio = Some((minimum as usize, span.max(address_length as usize))); + } + ResourceDescriptor::Address32(descriptor) + if mmio.is_none() + && matches!(descriptor.resource_type, AddressResourceType::MemoryRange) => + { + mmio = Some((descriptor.minimum as usize, descriptor.address_length as usize)); + } + ResourceDescriptor::Address64(descriptor) + if mmio.is_none() + && matches!(descriptor.resource_type, AddressResourceType::MemoryRange) => + { + let base = usize::try_from(descriptor.minimum) + .context("64-bit MMIO base does not fit in usize")?; + let len = usize::try_from(descriptor.address_length) + .context("64-bit MMIO length does not fit in usize")?; + mmio = Some((base, len)); + } + _ => {} + } + } + + if let Some(bus) = i2c { + let adapter = bus + .resource_source + .as_ref() + .map(|source| source.source.clone()) + .filter(|source| !source.is_empty()) + .unwrap_or_else(|| String::from("ACPI-I2C")); + return Ok(UcsiTransport::I2c { + adapter, + address: bus.slave_address, + ten_bit_address: bus.access_mode_10bit, + }); + } + if let Some((base, len)) = mmio { + return Ok(UcsiTransport::Mmio { base, len }); + } + Ok(UcsiTransport::Unknown) +} + +fn bounded_dsm_probe(device: &str) -> Result { + let symbol_name = format!("{}.{}", normalize_device_path(device), "_DSM"); + let symbol_path = format!("/scheme/acpi/symbols/{symbol_name}"); + let fd = match libredox::Fd::open(&symbol_path, O_RDWR | O_CLOEXEC, 0) { + Ok(fd) => fd, + Err(err) => { + log::debug!("ucsid: {} has no callable _DSM: {err}", device); + return Ok(false); + } + }; + + let mut payload = ron::to_string(&Vec::::new()) + .context("failed to serialize bounded _DSM probe arguments")? + .into_bytes(); + payload.resize(payload.len() + 1024, 0); + match libredox::call::call_ro(fd.raw(), &mut payload, syscall::CallFlags::empty(), &[]) { + Ok(_) => Ok(true), + Err(err) => { + log::debug!("ucsid: bounded _DSM probe for {} failed: {err}", device); + Ok(false) + } + } +} + +fn parse_capability(payload: &[u8]) -> Option { + let connector_count = *payload.first()?; + let flags = payload.get(1).copied().unwrap_or(0); + Some(UcsiCapability { + connector_count, + supports_usb_pd: flags & 0x01 != 0, + supports_alt_modes: flags & 0x02 != 0, + }) +} + +fn query_connector_status( + device: &DiscoveredUcsiDevice, + adapters: &[I2cAdapterInfo], + connector: u8, +) -> Result { + match &device.transport { + UcsiTransport::I2c { + adapter, + address, + ten_bit_address, + } => { + let adapter_info = match_i2c_adapter(adapters, adapter).with_context(|| { + format!("no i2cd adapter matched ACPI source {} for {}", adapter, device.name) + })?; + let bytes = execute_ucsi_i2c_command( + adapter_info, + adapter, + *address, + *ten_bit_address, + UcsiCommand::new(GET_CONNECTOR_STATUS, 1, [connector, 0, 0, 0, 0, 0]), + UCSI_CONNECTOR_STATUS_READ_LEN, + )?; + let (_header, payload) = parse_ucsi_payload(&bytes) + .with_context(|| format!("{}: malformed connector-status response", device.name))?; + Ok(parse_connector_summary(&device.name, connector, payload)) + } + UcsiTransport::Mmio { base, len } => bail!( + "MMIO connector-status transport is not implemented yet for {:#x}+{:#x}", + base, + len, + ), + UcsiTransport::Unknown => bail!("unknown UCSI transport"), + } +} + +fn parse_connector_summary(device_name: &str, connector: u8, payload: &[u8]) -> UcsiConnectorSummary { + let state = payload.first().copied().unwrap_or(0); + let connected = state & 0x01 != 0; + let power_direction = if state & 0x02 != 0 { "source" } else { "sink" }; + let data_role = if state & 0x04 != 0 { "dfp" } else { "ufp" }; + UcsiConnectorSummary { + device: device_name.to_string(), + connector_number: connector, + connected, + data_role: data_role.to_string(), + power_direction: power_direction.to_string(), + input_critical: classify_input_critical(device_name), + } +} + +fn classify_input_critical(device_name: &str) -> bool { + let normalized = device_name.to_ascii_lowercase(); + normalized.contains("kbd") + || normalized.contains("key") + || normalized.contains("touch") + || normalized.contains("thc") +} + +fn parse_ucsi_payload(bytes: &[u8]) -> Option<(UcsiResponseHeader, &[u8])> { + let header = UcsiResponseHeader::parse(bytes)?; + let body = bytes.get(UCSI_RESPONSE_HEADER_LEN..)?; + let body_len = usize::from(header.data_length).min(body.len()); + Some((header, &body[..body_len])) +} + +fn execute_ucsi_i2c_command( + adapter: &I2cAdapterInfo, + adapter_name: &str, + address: u16, + ten_bit_address: bool, + command: UcsiCommand, + read_len: usize, +) -> Result> { + let request = I2cTransferRequest { + adapter: adapter_name.to_string(), + segments: vec![ + I2cTransferSegment { + address, + ten_bit_address, + op: i2c_interface::I2cTransferOp::Write(command.as_bytes().to_vec()), + }, + I2cTransferSegment { + address, + ten_bit_address, + op: i2c_interface::I2cTransferOp::Read(read_len), + }, + ], + stop: true, + }; + + let mut file = OpenOptions::new() + .read(true) + .write(true) + .open("/scheme/i2c/transfer") + .context("failed to open /scheme/i2c/transfer")?; + let payload = ron::ser::to_string(&I2cControlRequest::Transfer { + adapter_id: adapter.id, + request, + }) + .context("failed to encode UCSI I2C transfer request")?; + file.write_all(payload.as_bytes()) + .context("failed to send UCSI I2C transfer request")?; + + let response = read_i2c_control_response(&mut file)?; + match response { + I2cControlResponse::TransferResult(result) => { + if !result.ok { + let detail = result + .error + .clone() + .unwrap_or_else(|| String::from("unknown I2C transfer failure")); + bail!("UCSI I2C transfer failed: {detail}"); + } + result + .read_data + .into_iter() + .next() + .context("UCSI I2C transfer returned no response payload") + } + I2cControlResponse::Error(message) => bail!("i2cd returned an error: {message}"), + other => bail!("unexpected i2cd transfer response: {other:?}"), + } +} + +fn list_i2c_adapters() -> Result> { + let mut file = OpenOptions::new() + .read(true) + .write(true) + .open("/scheme/i2c/adapters") + .context("failed to open /scheme/i2c/adapters")?; + + let payload = ron::ser::to_string(&I2cControlRequest::ListAdapters) + .context("failed to encode I2C list-adapters request")?; + file.write_all(payload.as_bytes()) + .context("failed to request I2C adapter list")?; + + let response = read_i2c_control_response(&mut file)?; + match response { + I2cControlResponse::AdapterList(adapters) => Ok(adapters), + I2cControlResponse::Error(message) => bail!("i2cd returned an error: {message}"), + other => bail!("unexpected i2cd list-adapters response: {other:?}"), + } +} + +fn match_i2c_adapter<'a>(adapters: &'a [I2cAdapterInfo], wanted: &str) -> Option<&'a I2cAdapterInfo> { + adapters + .iter() + .find(|adapter| adapter.name == wanted) + .or_else(|| adapters.iter().find(|adapter| adapter.name.ends_with(wanted))) + .or_else(|| adapters.iter().find(|adapter| wanted.ends_with(&adapter.name))) +} + +fn read_i2c_control_response(file: &mut File) -> Result { + let mut buffer = vec![0_u8; 4096]; + let count = file + .read(&mut buffer) + .context("failed to read I2C control response")?; + buffer.truncate(count); + let text = std::str::from_utf8(&buffer).context("I2C control response was not UTF-8")?; + let trimmed = text.trim(); + if trimmed.is_empty() { + return Ok(I2cControlResponse::AdapterList(Vec::new())); + } + ron::from_str(trimmed).context("failed to decode I2C control response") +} + +fn read_symbol_id(path: &Path) -> Result> { + let contents = fs::read_to_string(path) + .with_context(|| format!("failed to read ACPI symbol {}", path.display()))?; + let symbol = match ron::from_str::(&contents) { + Ok(symbol) => symbol, + Err(err) => { + log::debug!( + "ucsid: skipping {} because the symbol payload was not a scalar ID: {err}", + path.display(), + ); + return Ok(None); + } + }; + + let id = match symbol.value { + AmlValue::Integer(integer) => eisa_id_from_integer(integer), + AmlValue::String(string) => string, + }; + + log::debug!("ucsid: {} -> {id}", symbol.name); + Ok(Some(id)) +} + +fn normalize_device_path(path: &str) -> String { + path.trim_start_matches('\\') + .trim_matches('/') + .replace('/', ".") +} + +fn eisa_id_from_integer(integer: u64) -> String { + let vendor = integer & 0xFFFF; + let device = (integer >> 16) & 0xFFFF; + let vendor_rev = ((vendor & 0xFF) << 8) | (vendor >> 8); + let vendor_1 = (((vendor_rev >> 10) & 0x1F) as u8 + 64) as char; + let vendor_2 = (((vendor_rev >> 5) & 0x1F) as u8 + 64) as char; + let vendor_3 = (((vendor_rev >> 0) & 0x1F) as u8 + 64) as char; + let device_1 = (device >> 4) & 0xF; + let device_2 = (device >> 0) & 0xF; + let device_3 = (device >> 12) & 0xF; + let device_4 = (device >> 8) & 0xF; + + format!( + "{vendor_1}{vendor_2}{vendor_3}{device_1:01X}{device_2:01X}{device_3:01X}{device_4:01X}" + ) +} diff --git a/recipes/core/base/init.d/05_boot_essential.target b/recipes/core/base/init.d/05_boot_essential.target new file mode 100644 index 0000000000..58a08491b7 --- /dev/null +++ b/recipes/core/base/init.d/05_boot_essential.target @@ -0,0 +1,3 @@ +[unit] +description = "Boot essential services target" +requires_weak = ["00_base.target"] diff --git a/recipes/core/base/init.d/12_boot_late.target b/recipes/core/base/init.d/12_boot_late.target new file mode 100644 index 0000000000..7ded5b1448 --- /dev/null +++ b/recipes/core/base/init.d/12_boot_late.target @@ -0,0 +1,3 @@ +[unit] +description = "Boot late stage target" +requires_weak = ["05_boot_essential.target"] diff --git a/recipes/core/base/init.d/12_dbus.service b/recipes/core/base/init.d/12_dbus.service new file mode 100644 index 0000000000..0c64eb1c31 --- /dev/null +++ b/recipes/core/base/init.d/12_dbus.service @@ -0,0 +1,8 @@ +[unit] +description = "D-Bus system bus" +requires_weak = ["12_boot_late.target", "00_ipcd.service"] + +[service] +cmd = "dbus-daemon" +args = ["--system", "--nopidfile"] +type = "oneshot_async" diff --git a/recipes/core/base/init.d/13_seatd.service b/recipes/core/base/init.d/13_seatd.service new file mode 100644 index 0000000000..d985278c29 --- /dev/null +++ b/recipes/core/base/init.d/13_seatd.service @@ -0,0 +1,8 @@ +[unit] +description = "seatd seat management" +requires_weak = ["12_dbus.service"] + +[service] +cmd = "/usr/bin/seatd" +args = ["-l", "info"] +type = "oneshot_async" diff --git a/recipes/core/base/init.d/29_activate_console.service b/recipes/core/base/init.d/29_activate_console.service new file mode 100644 index 0000000000..c5f227e924 --- /dev/null +++ b/recipes/core/base/init.d/29_activate_console.service @@ -0,0 +1,8 @@ +[unit] +description = "Activate console VT" +requires_weak = ["00_base.target"] + +[service] +cmd = "inputd" +args = ["-A", "2"] +type = "oneshot_async" diff --git a/recipes/core/base/init.d/30_console.service b/recipes/core/base/init.d/30_console.service new file mode 100644 index 0000000000..49afdd2661 --- /dev/null +++ b/recipes/core/base/init.d/30_console.service @@ -0,0 +1,8 @@ +[unit] +description = "Console getty on VT2" +requires_weak = ["29_activate_console.service"] + +[service] +cmd = "getty" +args = ["2"] +type = "oneshot_async" diff --git a/recipes/core/base/init.d/30_thermald.service b/recipes/core/base/init.d/30_thermald.service new file mode 100644 index 0000000000..4402500bb5 --- /dev/null +++ b/recipes/core/base/init.d/30_thermald.service @@ -0,0 +1,7 @@ +[unit] +description = "CPU Thermal Monitor" +requires_weak = ["00_base.target"] + +[service] +cmd = "thermald" +type = "oneshot_async" diff --git a/recipes/core/base/init.d/31_debug_console.service b/recipes/core/base/init.d/31_debug_console.service new file mode 100644 index 0000000000..a61bed9857 --- /dev/null +++ b/recipes/core/base/init.d/31_debug_console.service @@ -0,0 +1,8 @@ +[unit] +description = "Debug console on VT3" +requires_weak = ["29_activate_console.service"] + +[service] +cmd = "getty" +args = ["3"] +type = "oneshot_async" diff --git a/recipes/core/base/init.initfs.d/30_redox-drm.service b/recipes/core/base/init.initfs.d/30_redox-drm.service new file mode 100644 index 0000000000..ba380bf2ec --- /dev/null +++ b/recipes/core/base/init.initfs.d/30_redox-drm.service @@ -0,0 +1,8 @@ +[unit] +description = "DRM/KMS Display Driver" +requires_weak = ["20_graphics.target", "40_hwd.service", "40_pcid-spawner-initfs.service"] +condition_architecture = ["x86", "x86_64"] + +[service] +cmd = "redox-drm" +type = "notify" diff --git a/recipes/core/base/init.initfs.d/45_usbscsid.service b/recipes/core/base/init.initfs.d/45_usbscsid.service new file mode 100644 index 0000000000..1279def82d --- /dev/null +++ b/recipes/core/base/init.initfs.d/45_usbscsid.service @@ -0,0 +1,8 @@ +[unit] +description = "USB Mass Storage Driver" +requires_weak = ["40_drivers.target"] +condition_architecture = ["x86", "x86_64"] + +[service] +cmd = "usbscsid" +type = "notify" diff --git a/recipes/core/base/init.initfs.d/60_smolnetd.service b/recipes/core/base/init.initfs.d/60_smolnetd.service new file mode 100644 index 0000000000..d77dc13f83 --- /dev/null +++ b/recipes/core/base/init.initfs.d/60_smolnetd.service @@ -0,0 +1,7 @@ +[unit] +description = "Network Stack" +requires_weak = ["40_drivers.target"] + +[service] +cmd = "smolnetd" +type = "notify" diff --git a/recipes/core/base/init.initfs.d/61_dhcpd.service b/recipes/core/base/init.initfs.d/61_dhcpd.service new file mode 100644 index 0000000000..37379761b4 --- /dev/null +++ b/recipes/core/base/init.initfs.d/61_dhcpd.service @@ -0,0 +1,7 @@ +[unit] +description = "DHCP Client" +requires_weak = ["60_smolnetd.service"] + +[service] +cmd = "dhcpd" +type = "oneshot_async" diff --git a/recipes/core/base/init/src/color.rs b/recipes/core/base/init/src/color.rs new file mode 100644 index 0000000000..db792c441f --- /dev/null +++ b/recipes/core/base/init/src/color.rs @@ -0,0 +1,27 @@ +pub fn status_ok(msg: &str) { + eprintln!("[ OK ] {msg}"); +} + +pub fn status_fail(msg: &str) { + eprintln!("[ FAILED ] {msg}"); +} + +pub fn status_skip(msg: &str) { + eprintln!("[ SKIP ] {msg}"); +} + +pub fn init_error(msg: &str) { + eprintln!("init: {msg}"); +} + +pub fn init_warn(msg: &str) { + eprintln!("init: {msg}"); +} + +pub fn init_info(msg: &str) { + eprintln!("init: {msg}"); +} + +pub fn init_debug(msg: &str) { + eprintln!("init: {msg}"); +} diff --git a/recipes/core/base/net/e1000d/src/itr.rs b/recipes/core/base/net/e1000d/src/itr.rs new file mode 100644 index 0000000000..8d2e52bdfd --- /dev/null +++ b/recipes/core/base/net/e1000d/src/itr.rs @@ -0,0 +1,9 @@ +pub const ITR_LOW_LATENCY: u32 = 64; pub const ITR_BULK: u32 = 256; pub const ITR_DEFAULT: u32 = 800; +#[derive(Clone,Copy,PartialEq)] pub enum ItrState { LowLatency, Moderate, Bulk } +pub struct ItrTracker { state: ItrState, current_itr: u32, packets_since_update: u32 } +impl ItrTracker { pub const fn new() -> Self { Self { state: ItrState::LowLatency, current_itr: ITR_LOW_LATENCY, packets_since_update: 0 } } +pub fn record_packet(&mut self, bytes: usize) { self.packets_since_update += 1; let _ = bytes; } +pub fn update(&mut self) -> u32 { let new_state = if self.packets_since_update < 8 { ItrState::LowLatency } else if self.packets_since_update < 64 { ItrState::Moderate } else { ItrState::Bulk }; +if new_state != self.state { self.state = new_state; self.current_itr = match self.state { ItrState::LowLatency => ITR_LOW_LATENCY, ItrState::Moderate => ITR_DEFAULT, ItrState::Bulk => ITR_BULK }; } +self.packets_since_update = 0; self.current_itr } +pub fn current_itr(&self) -> u32 { self.current_itr } } diff --git a/recipes/core/base/net/rtl8168d/src/phy.rs b/recipes/core/base/net/rtl8168d/src/phy.rs new file mode 100644 index 0000000000..b2f1e0cd52 --- /dev/null +++ b/recipes/core/base/net/rtl8168d/src/phy.rs @@ -0,0 +1,5 @@ +#[derive(Clone,Copy,PartialEq,Debug)] pub enum ChipVersion { Rtl8168b, Rtl8168c, Rtl8168cp, Rtl8168d, Rtl8168dp, Rtl8168e, Rtl8168evl, Rtl8168f, Rtl8168g, Rtl8168h, Rtl8168ep, Unknown } +pub fn identify_chip(rev: u8, mac0: u32, _m1: u32, _m2: u32, _m3: u32, _m4: u32) -> ChipVersion { match ((mac0>>20)&0x7, rev) { (0,_)=>ChipVersion::Rtl8168b, (1,0x00..=0x01)=>ChipVersion::Rtl8168c, (1,0x02)=>ChipVersion::Rtl8168cp, (2,_)=>ChipVersion::Rtl8168d, (3,r) if r<=0x02=>ChipVersion::Rtl8168e, (3,_)=>ChipVersion::Rtl8168evl, (4,_)=>ChipVersion::Rtl8168f, (5,_)=>ChipVersion::Rtl8168g, (6,_)=>ChipVersion::Rtl8168h, (7,_)=>ChipVersion::Rtl8168ep, _=>ChipVersion::Unknown } } +pub mod phy_regs { pub const BMCR: u32 = 0x00; pub const BMSR: u32 = 0x01; pub const BMCR_RESET: u16 = 1<<15; pub const BMCR_AUTONEG_ENABLE: u16 = 1<<12; pub const BMSR_LINK_STATUS: u16 = 1<<2; } +pub fn phy_link_up(read: &dyn Fn(u32)->u16) -> bool { read(phy_regs::BMSR) & phy_regs::BMSR_LINK_STATUS != 0 } +pub fn phy_reset(write: &dyn Fn(u32,u16), read: &dyn Fn(u32)->u16) -> bool { write(phy_regs::BMCR, phy_regs::BMCR_RESET); for _ in 0..500 { if read(phy_regs::BMCR) & phy_regs::BMCR_RESET == 0 { return true; } } false } diff --git a/recipes/core/base/recipe.toml b/recipes/core/base/recipe.toml index 5ceac38cf2..7a92e20dc2 100644 --- a/recipes/core/base/recipe.toml +++ b/recipes/core/base/recipe.toml @@ -8,27 +8,20 @@ patches = [ "P0-dhcpd-auto-iface.patch", "P0-procmgr-sigchld-debug.patch", "P0-pcid-mcfg-diagnostics.patch", -# TODO: P6-e1000d-msi-migration.patch conflicts with P6-driver-main-fixes -# (both touch e1000d/src/main.rs). Merge into P6-driver-main-fixes on rebase. -# TODO: P1 patches (11) exist in local/patches/base/ but need rebase -# after redox.patch removal. ACPI (5), PCI/IRQ (3), xHCI (3) — 1144 lines. -# P1-acpid-acpi-core.patch -# P1-acpid-ec-runtime.patch -# P1-acpid-power-enumeration.patch -# P1-acpid-runtime-hardening.patch -# P1-acpid-scheme-surface.patch -# P1-pcid-uevent-surface.patch -# P1-pci-irq-wave1-3.patch -# P1-pci-irq-wave1-5.patch -# P1-xhcid-device-lifecycle.patch -# P1-xhcid-port-pm-read-fix.patch -# P1-xhcid-uevent-logging.patch -# TODO: P5 patches (2) exist in local/patches/base/ but need rebase. -# P5-init-daemon-panic-hardening.patch -# P5-init-supervisor-restart.patch + "P0-ihdgd-intel-gpu-ids.patch", + # P1: acpid EC runtime and AML physmem hardening (narrow ACPI runtime patches) + "P1-acpid-ec-runtime.patch", + "P1-acpid-runtime-hardening.patch", + # Stale patches needing recreation: P1-pcid-uevent-surface, P2-boot-runtime-fixes, + # P2-hwd-misc, P2-pcid-cfg-access, P3-xhci-device-hardening, P6-cpufreqd-real-impl "P2-i2c-gpio-ucsi-drivers.patch", "P0-i2c-control-response-empty.patch", "P2-ihdad-graceful-init.patch", + "P2-boot-logging.patch", + "P2-init-acpid-wiring.patch", + "P2-hwd-remove-acpid-spawn.patch", + "P2-initfs-pcid-service.patch", + "P2-misc-daemon-fixes.patch", "P9-fix-so-pecred.patch", "P3-inputd-keymap-bridge.patch", "P3-ps2d-led-feedback.patch", @@ -39,6 +32,8 @@ patches = [ "P4-logd-persistent-logging.patch", "P4-acpi-shutdown-hardening.patch", "P4-acpi-s3-sleep.patch", + "P4-pcid-public-client-channel.patch", + "P4-pcid-spawner-pci-coordinate-env.patch", "P4-initfs-usb-drm-services.patch", "P4-initfs-release-virtio-gpu.patch", "P4-initfs-network-services.patch", @@ -50,7 +45,7 @@ patches = [ "P6-driver-main-fixes.patch", "P6-driver-new-modules.patch", "P9-init-scheduler-completed.patch", - + "P2-pcid-acpid-graceful-fd.patch", ] [package] diff --git a/recipes/core/base/storage/ahcid/src/ahci/ncq.rs b/recipes/core/base/storage/ahcid/src/ahci/ncq.rs new file mode 100644 index 0000000000..474f11d0f8 --- /dev/null +++ b/recipes/core/base/storage/ahcid/src/ahci/ncq.rs @@ -0,0 +1,12 @@ +use core::sync::atomic::{AtomicU32, Ordering}; +pub const NCQ_MAX_DEPTH: usize = 32; +pub struct NcqState { pub sactive: AtomicU32, pub pending: AtomicU32 } +impl NcqState { pub const fn new() -> Self { Self { sactive: AtomicU32::new(0), pending: AtomicU32::new(0) } } +pub fn allocate_tag(&self) -> Option { let active = self.pending.load(Ordering::Acquire); let free = !active; if free == 0 { return None; } let tag = free.trailing_zeros(); let mask = 1u32 << tag; self.pending.fetch_or(mask, Ordering::AcqRel); self.sactive.fetch_or(mask, Ordering::AcqRel); Some(tag) } +pub fn complete_tag(&self, tag: u32) { let mask = 1u32 << tag; self.sactive.fetch_and(!mask, Ordering::AcqRel); self.pending.fetch_and(!mask, Ordering::AcqRel); } +pub fn has_pending(&self) -> bool { self.pending.load(Ordering::Acquire) != 0 } } +pub fn build_ncq_read_fis(tag: u32, lba: u64, sector_count: u16) -> [u32; 5] { let mut f = [0u32;5]; f[0]=0x0000_8027; f[1]=0x0060|((sector_count as u32&0xFF)<<24); f[2]=(lba as u32&0xFF)|(((lba>>8) as u32&0xFF)<<8); let mid=((lba>>16)as u32&0xFF)|((tag&0x1F)<<3); f[3]=mid|(((lba>>24)as u32&0xFF)<<8)|(((lba>>32)as u32&0xFF)<<16)|(((lba>>40)as u32&0xFF)<<24); f[4]=(((sector_count>>8)as u32&0xFF)<<16)|(((sector_count>>8)as u32&0xFF)<<24); f } +pub fn build_ncq_write_fis(tag: u32, lba: u64, sector_count: u16) -> [u32; 5] { let mut f = build_ncq_read_fis(tag,lba,sector_count); f[1]=(f[1]&!0xFF00)|0x6100; f } +pub fn process_ncq_completions(old_sactive: u32, new_sactive: u32, ncq: &NcqState, completed: &mut [u32;NCQ_MAX_DEPTH]) -> usize { let mask = old_sactive & !new_sactive; if mask == 0 { return 0; } let mut count = 0; let mut m = mask; while m != 0 { let tag = m.trailing_zeros(); ncq.complete_tag(tag); completed[count]=tag; count+=1; m&=m-1; } count } +pub fn drive_supports_ncq(id: &[u16;256]) -> bool { id.get(76).map_or(false, |w| w&(1<<8)!=0) } +pub fn ncq_queue_depth(id: &[u16;256]) -> u32 { id.get(75).map_or(1, |w| { let d = (w&0x1F) as u32; if d>0 {(d+1).min(NCQ_MAX_DEPTH as u32)} else {1} }) }