diff --git a/local/patches/base/P1-acpid-acpi-core.patch b/local/patches/base/P1-acpid-acpi-core.patch new file mode 100644 index 00000000..ae12c6bb --- /dev/null +++ b/local/patches/base/P1-acpid-acpi-core.patch @@ -0,0 +1,1682 @@ +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-ec-runtime.patch b/local/patches/base/P1-acpid-ec-runtime.patch new file mode 100644 index 00000000..067ef0d5 --- /dev/null +++ b/local/patches/base/P1-acpid-ec-runtime.patch @@ -0,0 +1,279 @@ +diff --git a/drivers/acpid/src/ec.rs b/drivers/acpid/src/ec.rs +index c322790a..99842586 100644 +--- a/drivers/acpid/src/ec.rs ++++ b/drivers/acpid/src/ec.rs +@@ -1,3 +1,4 @@ ++use std::convert::TryFrom; + use std::time::Duration; + + use acpi::aml::{ +@@ -30,6 +31,28 @@ const BURST_ACK: u8 = 0x90; + + pub const DEFAULT_EC_TIMEOUT: Duration = Duration::from_millis(10); + ++#[derive(Debug, Clone, Copy)] ++enum EcError { ++ Timeout, ++ OffsetOutOfRange, ++} ++ ++impl EcError { ++ fn as_aml_error(self) -> AmlError { ++ match self { ++ EcError::Timeout | EcError::OffsetOutOfRange => { ++ AmlError::NoHandlerForRegionAccess(RegionSpace::EmbeddedControl) ++ } ++ } ++ } ++} ++ ++impl From for AmlError { ++ fn from(value: EcError) -> Self { ++ value.as_aml_error() ++ } ++} ++ + #[repr(transparent)] + pub struct ScBits(u8); + #[allow(dead_code)] +@@ -90,28 +113,33 @@ impl Ec { + Pio::::new(self.data).write(value); + } + #[inline] +- fn wait_for_write_ready(&self) -> Option<()> { ++ fn wait_for_write_ready(&self) -> Result<(), EcError> { + let timeout = Timeout::new(self.timeout); + loop { + if !self.read_reg_sc().ibf() { +- return Some(()); ++ return Ok(()); + } +- timeout.run().ok()?; ++ timeout.run().map_err(|_| EcError::Timeout)?; + } + } + #[inline] +- fn wait_for_read_ready(&self) -> Option<()> { ++ fn wait_for_read_ready(&self) -> Result<(), EcError> { + let timeout = Timeout::new(self.timeout); + loop { + if self.read_reg_sc().obf() { +- return Some(()); ++ return Ok(()); + } +- timeout.run().ok()?; ++ timeout.run().map_err(|_| EcError::Timeout)?; + } + } + ++ #[inline] ++ fn checked_address(offset: usize, byte_index: usize) -> Result { ++ u8::try_from(offset + byte_index).map_err(|_| EcError::OffsetOutOfRange) ++ } ++ + //https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/12_ACPI_Embedded_Controller_Interface_Specification/embedded-controller-command-set.html +- pub fn read(&self, address: u8) -> Option { ++ fn read(&self, address: u8) -> Result { + trace!("ec read addr: {:x}", address); + self.wait_for_write_ready()?; + +@@ -125,9 +153,9 @@ impl Ec { + + let val = self.read_reg_data(); + trace!("got: {:x}", val); +- Some(val) ++ Ok(val) + } +- pub fn write(&self, address: u8, value: u8) -> Option<()> { ++ fn write(&self, address: u8, value: u8) -> Result<(), EcError> { + trace!("ec write addr: {:x}, with: {:x}", address, value); + self.wait_for_write_ready()?; + +@@ -141,7 +169,22 @@ impl Ec { + + self.write_reg_data(value); + trace!("done"); +- Some(()) ++ Ok(()) ++ } ++ ++ fn read_bytes(&self, offset: usize) -> Result<[u8; N], EcError> { ++ let mut bytes = [0u8; N]; ++ for (index, byte) in bytes.iter_mut().enumerate() { ++ *byte = self.read(Self::checked_address(offset, index)?)?; ++ } ++ Ok(bytes) ++ } ++ ++ fn write_bytes(&self, offset: usize, bytes: [u8; N]) -> Result<(), EcError> { ++ for (index, byte) in IntoIterator::into_iter(bytes).enumerate() { ++ self.write(Self::checked_address(offset, index)?, byte)?; ++ } ++ Ok(()) + } + // disabled if not met + // First Access - 400 microseconds +@@ -151,11 +194,11 @@ impl Ec { + #[allow(dead_code)] + fn enable_burst(&self) -> bool { + trace!("ec burst enable"); +- self.wait_for_write_ready(); ++ let _ = self.wait_for_write_ready(); + + self.write_reg_sc(BE_EC); + +- self.wait_for_read_ready(); ++ let _ = self.wait_for_read_ready(); + + let res = self.read_reg_data() == BURST_ACK; + trace!("success: {}", res); +@@ -164,7 +207,7 @@ impl Ec { + #[allow(dead_code)] + fn disable_burst(&self) { + trace!("ec burst disable"); +- self.wait_for_write_ready(); ++ let _ = self.wait_for_write_ready(); + self.write_reg_sc(BD_EC); + trace!("done"); + } +@@ -172,11 +215,11 @@ impl Ec { + #[allow(dead_code)] + fn queue_query(&mut self) -> u8 { + trace!("ec query"); +- self.wait_for_write_ready(); ++ let _ = self.wait_for_write_ready(); + + self.write_reg_sc(QR_EC); + +- self.wait_for_read_ready(); ++ let _ = self.wait_for_read_ready(); + + let val = self.read_reg_data(); + trace!("got: {}", val); +@@ -190,7 +233,10 @@ impl RegionHandler for Ec { + offset: usize, + ) -> Result { + assert_eq!(region.space, RegionSpace::EmbeddedControl); +- self.read(offset as u8).ok_or(AmlError::MutexAcquireTimeout) // TODO proper error type ++ self.read(Self::checked_address(offset, 0)?).map_err(|error| { ++ warn!("EC read_u8 failed at offset {offset:#x}: {error:?}"); ++ error.as_aml_error() ++ }) + } + fn write_u8( + &self, +@@ -199,58 +245,73 @@ impl RegionHandler for Ec { + value: u8, + ) -> Result<(), acpi::aml::AmlError> { + assert_eq!(region.space, RegionSpace::EmbeddedControl); +- self.write(offset as u8, value) +- .ok_or(AmlError::MutexAcquireTimeout) // TODO proper error type +- } +- fn read_u16(&self, _region: &OpRegion, _offset: usize) -> Result { +- warn!("Got u16 EC read from AML!"); +- Err(acpi::aml::AmlError::NoHandlerForRegionAccess( +- RegionSpace::EmbeddedControl, +- )) // TODO proper error type +- } +- fn read_u32(&self, _region: &OpRegion, _offset: usize) -> Result { +- warn!("Got u32 EC read from AML!"); +- Err(acpi::aml::AmlError::NoHandlerForRegionAccess( +- RegionSpace::EmbeddedControl, +- )) // TODO proper error type +- } +- fn read_u64(&self, _region: &OpRegion, _offset: usize) -> Result { +- warn!("Got u64 EC read from AML!"); +- Err(acpi::aml::AmlError::NoHandlerForRegionAccess( +- RegionSpace::EmbeddedControl, +- )) // TODO proper error type ++ self.write(Self::checked_address(offset, 0)?, value) ++ .map_err(|error| { ++ warn!("EC write_u8 failed at offset {offset:#x}: {error:?}"); ++ error.as_aml_error() ++ }) ++ } ++ fn read_u16(&self, region: &OpRegion, offset: usize) -> Result { ++ assert_eq!(region.space, RegionSpace::EmbeddedControl); ++ self.read_bytes::<2>(offset) ++ .map(u16::from_le_bytes) ++ .map_err(|error| { ++ warn!("EC read_u16 failed at offset {offset:#x}: {error:?}"); ++ error.as_aml_error() ++ }) ++ } ++ fn read_u32(&self, region: &OpRegion, offset: usize) -> Result { ++ assert_eq!(region.space, RegionSpace::EmbeddedControl); ++ self.read_bytes::<4>(offset) ++ .map(u32::from_le_bytes) ++ .map_err(|error| { ++ warn!("EC read_u32 failed at offset {offset:#x}: {error:?}"); ++ error.as_aml_error() ++ }) ++ } ++ fn read_u64(&self, region: &OpRegion, offset: usize) -> Result { ++ assert_eq!(region.space, RegionSpace::EmbeddedControl); ++ self.read_bytes::<8>(offset) ++ .map(u64::from_le_bytes) ++ .map_err(|error| { ++ warn!("EC read_u64 failed at offset {offset:#x}: {error:?}"); ++ error.as_aml_error() ++ }) + } + fn write_u16( + &self, +- _region: &OpRegion, +- _offset: usize, +- _value: u16, ++ region: &OpRegion, ++ offset: usize, ++ value: u16, + ) -> Result<(), acpi::aml::AmlError> { +- warn!("Got u16 EC write from AML!"); +- Err(acpi::aml::AmlError::NoHandlerForRegionAccess( +- RegionSpace::EmbeddedControl, +- )) // TODO proper error type ++ assert_eq!(region.space, RegionSpace::EmbeddedControl); ++ self.write_bytes(offset, value.to_le_bytes()).map_err(|error| { ++ warn!("EC write_u16 failed at offset {offset:#x}: {error:?}"); ++ error.as_aml_error() ++ }) + } + fn write_u32( + &self, +- _region: &OpRegion, +- _offset: usize, +- _value: u32, ++ region: &OpRegion, ++ offset: usize, ++ value: u32, + ) -> Result<(), acpi::aml::AmlError> { +- warn!("Got u32 EC write from AML!"); +- Err(acpi::aml::AmlError::NoHandlerForRegionAccess( +- RegionSpace::EmbeddedControl, +- )) // TODO proper error type ++ assert_eq!(region.space, RegionSpace::EmbeddedControl); ++ self.write_bytes(offset, value.to_le_bytes()).map_err(|error| { ++ warn!("EC write_u32 failed at offset {offset:#x}: {error:?}"); ++ error.as_aml_error() ++ }) + } + fn write_u64( + &self, +- _region: &OpRegion, +- _offset: usize, +- _value: u64, ++ region: &OpRegion, ++ offset: usize, ++ value: u64, + ) -> Result<(), acpi::aml::AmlError> { +- warn!("Got u64 EC write from AML!"); +- Err(acpi::aml::AmlError::NoHandlerForRegionAccess( +- RegionSpace::EmbeddedControl, +- )) // TODO proper error type ++ assert_eq!(region.space, RegionSpace::EmbeddedControl); ++ self.write_bytes(offset, value.to_le_bytes()).map_err(|error| { ++ warn!("EC write_u64 failed at offset {offset:#x}: {error:?}"); ++ error.as_aml_error() ++ }) + } + } diff --git a/local/patches/base/P1-acpid-runtime-hardening.patch b/local/patches/base/P1-acpid-runtime-hardening.patch new file mode 100644 index 00000000..866615a7 --- /dev/null +++ b/local/patches/base/P1-acpid-runtime-hardening.patch @@ -0,0 +1,224 @@ +diff --git a/drivers/acpid/src/aml_physmem.rs b/drivers/acpid/src/aml_physmem.rs +index 2bdd667b..69b8c48b 100644 +--- a/drivers/acpid/src/aml_physmem.rs ++++ b/drivers/acpid/src/aml_physmem.rs +@@ -6,7 +6,10 @@ use rustc_hash::FxHashMap; + use std::fmt::LowerHex; + use std::mem::size_of; + use std::ptr::NonNull; +-use std::sync::{Arc, Mutex}; ++use std::sync::atomic::{AtomicU32, Ordering}; ++use std::sync::{Arc, Condvar, Mutex}; ++use std::thread::ThreadId; ++use std::time::{Duration, Instant}; + use syscall::PAGE_SIZE; + + const PAGE_MASK: usize = !(PAGE_SIZE - 1); +@@ -141,6 +144,20 @@ impl AmlPageCache { + pub struct AmlPhysMemHandler { + page_cache: Arc>, + pci_fd: Arc>, ++ aml_mutexes: Arc>>>, ++ next_mutex_handle: Arc, ++} ++ ++#[derive(Debug, Default)] ++struct AmlMutexState { ++ owner: Option, ++ depth: u32, ++} ++ ++#[derive(Debug, Default)] ++struct AmlMutex { ++ state: Mutex, ++ condvar: Condvar, + } + + /// Read from a physical address. +@@ -156,6 +173,30 @@ impl AmlPhysMemHandler { + Self { + page_cache, + pci_fd: Arc::new(pci_fd), ++ aml_mutexes: Arc::new(Mutex::new(FxHashMap::default())), ++ next_mutex_handle: Arc::new(AtomicU32::new(1)), ++ } ++ } ++ ++ fn aml_mutex(&self, handle: Handle) -> Option> { ++ self.aml_mutexes ++ .lock() ++ .unwrap_or_else(|poisoned| poisoned.into_inner()) ++ .get(&handle.0) ++ .cloned() ++ } ++ ++ fn read_phys_or_fault(&self, address: usize) -> T ++ where ++ T: PrimInt + LowerHex, ++ { ++ let mut page_cache = self ++ .page_cache ++ .lock() ++ .unwrap_or_else(|poisoned| poisoned.into_inner()); ++ match page_cache.read_from_phys::(address) { ++ Ok(value) => value, ++ Err(error) => panic!("AML physmem read failed at {:#x}: {}", address, error), + } + } + +@@ -240,43 +281,19 @@ impl acpi::Handler for AmlPhysMemHandler { + + fn read_u8(&self, address: usize) -> u8 { + log::trace!("read u8 {:X}", address); +- if let Ok(mut page_cache) = self.page_cache.lock() { +- if let Ok(value) = page_cache.read_from_phys::(address) { +- return value; +- } +- } +- log::error!("failed to read u8 {:#x}", address); +- 0 ++ self.read_phys_or_fault::(address) + } + fn read_u16(&self, address: usize) -> u16 { + log::trace!("read u16 {:X}", address); +- if let Ok(mut page_cache) = self.page_cache.lock() { +- if let Ok(value) = page_cache.read_from_phys::(address) { +- return value; +- } +- } +- log::error!("failed to read u16 {:#x}", address); +- 0 ++ self.read_phys_or_fault::(address) + } + fn read_u32(&self, address: usize) -> u32 { + log::trace!("read u32 {:X}", address); +- if let Ok(mut page_cache) = self.page_cache.lock() { +- if let Ok(value) = page_cache.read_from_phys::(address) { +- return value; +- } +- } +- log::error!("failed to read u32 {:#x}", address); +- 0 ++ self.read_phys_or_fault::(address) + } + fn read_u64(&self, address: usize) -> u64 { + log::trace!("read u64 {:X}", address); +- if let Ok(mut page_cache) = self.page_cache.lock() { +- if let Ok(value) = page_cache.read_from_phys::(address) { +- return value; +- } +- } +- log::error!("failed to read u64 {:#x}", address); +- 0 ++ self.read_phys_or_fault::(address) + } + + fn write_u8(&self, address: usize, value: u8) { +@@ -415,16 +432,102 @@ impl acpi::Handler for AmlPhysMemHandler { + } + + fn create_mutex(&self) -> Handle { +- log::debug!("TODO: Handler::create_mutex"); +- Handle(0) ++ let handle = self.next_mutex_handle.fetch_add(1, Ordering::Relaxed); ++ self.aml_mutexes ++ .lock() ++ .unwrap_or_else(|poisoned| poisoned.into_inner()) ++ .insert(handle, Arc::new(AmlMutex::default())); ++ log::trace!("created AML mutex handle {handle}"); ++ Handle(handle) + } + + fn acquire(&self, mutex: Handle, timeout: u16) -> Result<(), AmlError> { +- log::debug!("TODO: Handler::acquire"); +- Ok(()) ++ let Some(aml_mutex) = self.aml_mutex(mutex) else { ++ log::error!("attempted to acquire unknown AML mutex handle {}", mutex.0); ++ return Err(AmlError::MutexAcquireTimeout); ++ }; ++ ++ let current_thread = std::thread::current().id(); ++ let deadline = (timeout != 0xffff).then(|| Instant::now() + Duration::from_millis(timeout.into())); ++ ++ let mut state = aml_mutex ++ .state ++ .lock() ++ .unwrap_or_else(|poisoned| poisoned.into_inner()); ++ ++ loop { ++ match state.owner { ++ None => { ++ state.owner = Some(current_thread); ++ state.depth = 1; ++ return Ok(()); ++ } ++ Some(owner) if owner == current_thread => { ++ state.depth = state.depth.saturating_add(1); ++ return Ok(()); ++ } ++ Some(_) if timeout == 0 => return Err(AmlError::MutexAcquireTimeout), ++ Some(_) if timeout == 0xffff => { ++ state = aml_mutex ++ .condvar ++ .wait(state) ++ .unwrap_or_else(|poisoned| poisoned.into_inner()); ++ } ++ Some(_) => { ++ let Some(deadline) = deadline else { ++ return Err(AmlError::MutexAcquireTimeout); ++ }; ++ let now = Instant::now(); ++ if now >= deadline { ++ return Err(AmlError::MutexAcquireTimeout); ++ } ++ ++ let remaining = deadline.saturating_duration_since(now); ++ let (next_state, wait_result) = aml_mutex ++ .condvar ++ .wait_timeout(state, remaining) ++ .unwrap_or_else(|poisoned| poisoned.into_inner()); ++ state = next_state; ++ ++ if wait_result.timed_out() && state.owner != Some(current_thread) { ++ return Err(AmlError::MutexAcquireTimeout); ++ } ++ } ++ } ++ } + } + + fn release(&self, mutex: Handle) { +- log::debug!("TODO: Handler::release"); ++ let Some(aml_mutex) = self.aml_mutex(mutex) else { ++ log::error!("attempted to release unknown AML mutex handle {}", mutex.0); ++ return; ++ }; ++ ++ let current_thread = std::thread::current().id(); ++ let mut state = aml_mutex ++ .state ++ .lock() ++ .unwrap_or_else(|poisoned| poisoned.into_inner()); ++ ++ match state.owner { ++ Some(owner) if owner == current_thread => { ++ if state.depth > 1 { ++ state.depth -= 1; ++ } else { ++ state.owner = None; ++ state.depth = 0; ++ aml_mutex.condvar.notify_one(); ++ } ++ } ++ Some(_) => { ++ log::warn!( ++ "ignoring AML mutex release for handle {} from non-owner thread", ++ mutex.0 ++ ); ++ } ++ None => { ++ log::warn!("ignoring AML mutex release for unlocked handle {}", mutex.0); ++ } ++ } + } + } diff --git a/local/patches/base/P1-acpid-scheme-surface.patch b/local/patches/base/P1-acpid-scheme-surface.patch new file mode 100644 index 00000000..0e7b0cd5 --- /dev/null +++ b/local/patches/base/P1-acpid-scheme-surface.patch @@ -0,0 +1,719 @@ +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 new file mode 100644 index 00000000..770df8eb --- /dev/null +++ b/local/patches/base/P1-pci-irq-wave1-3.patch @@ -0,0 +1,1184 @@ +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/redox.patch b/local/patches/base/redox.patch index a58a28c8..834dde9b 100644 --- a/local/patches/base/redox.patch +++ b/local/patches/base/redox.patch @@ -1,225 +1,60 @@ diff --git a/Cargo.lock b/Cargo.lock -index 3986e775..87c1a277 100644 +index 9934cd8f..b3923c52 100644 --- a/Cargo.lock +++ b/Cargo.lock -@@ -21,7 +21,6 @@ dependencies = [ - [[package]] - name = "acpi" - version = "6.1.1" --source = "git+https://github.com/jackpot51/acpi.git#3dc8a2d98a7a164cbf87e7a86855c4d3bed4de75" - dependencies = [ - "bit_field", - "bitflags 2.11.0", -@@ -54,6 +53,7 @@ dependencies = [ +@@ -54,6 +54,7 @@ dependencies = [ "scheme-utils", "serde", "thiserror 2.0.18", -+ "toml 1.0.6+spec-1.1.0", ++ "toml", ] [[package]] -@@ -86,7 +86,7 @@ version = "0.0.1" - dependencies = [ - "acpi", - "serde", -- "toml", -+ "toml 1.0.6+spec-1.1.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(); - [[package]] -@@ -1109,7 +1109,7 @@ dependencies = [ - "redox_syscall 0.7.4", - "serde", - "serde_json", -- "toml", -+ "toml 1.0.6+spec-1.1.0", - ] + unsafe { pass_fd(&mut cmd, "INIT_NOTIFY", write_pipe.into()) }; - [[package]] -@@ -1505,9 +1505,10 @@ dependencies = [ - "log", - "pcid", - "pico-args", -+ "redox-driver-sys", - "redox_syscall 0.7.4", - "serde", -- "toml", -+ "toml 1.0.6+spec-1.1.0", - ] +- 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}")) ++ })?; - [[package]] -@@ -1779,6 +1780,20 @@ dependencies = [ - "rand_core 0.3.1", - ] - -+[[package]] -+name = "redox-driver-sys" -+version = "0.1.0" -+dependencies = [ -+ "bincode", -+ "bitflags 2.11.0", -+ "libredox", -+ "log", -+ "redox_syscall 0.7.4", -+ "serde", -+ "thiserror 2.0.18", -+ "toml 0.8.23", -+] -+ - [[package]] - name = "redox-initfs" - version = "0.2.0" -@@ -2130,6 +2145,15 @@ dependencies = [ - "zmij", - ] - -+[[package]] -+name = "serde_spanned" -+version = "0.6.9" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -+dependencies = [ -+ "serde", -+] -+ - [[package]] - name = "serde_spanned" - version = "1.0.4" -@@ -2331,6 +2355,18 @@ dependencies = [ - "syn 2.0.117", - ] - -+[[package]] -+name = "toml" -+version = "0.8.23" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" -+dependencies = [ -+ "serde", -+ "serde_spanned 0.6.9", -+ "toml_datetime 0.6.11", -+ "toml_edit", -+] -+ - [[package]] - name = "toml" - version = "1.0.6+spec-1.1.0" -@@ -2339,13 +2375,22 @@ checksum = "399b1124a3c9e16766831c6bba21e50192572cdd98706ea114f9502509686ffc" - dependencies = [ - "indexmap", - "serde_core", -- "serde_spanned", -- "toml_datetime", -+ "serde_spanned 1.0.4", -+ "toml_datetime 1.0.0+spec-1.1.0", - "toml_parser", - "toml_writer", - "winnow", - ] - -+[[package]] -+name = "toml_datetime" -+version = "0.6.11" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" -+dependencies = [ -+ "serde", -+] -+ - [[package]] - name = "toml_datetime" - version = "1.0.0+spec-1.1.0" -@@ -2355,6 +2400,20 @@ dependencies = [ - "serde_core", - ] - -+[[package]] -+name = "toml_edit" -+version = "0.22.27" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" -+dependencies = [ -+ "indexmap", -+ "serde", -+ "serde_spanned 0.6.9", -+ "toml_datetime 0.6.11", -+ "toml_write", -+ "winnow", -+] -+ - [[package]] - name = "toml_parser" - version = "1.0.9+spec-1.1.0" -@@ -2364,6 +2423,12 @@ dependencies = [ - "winnow", - ] - -+[[package]] -+name = "toml_write" -+version = "0.1.2" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" -+ - [[package]] - name = "toml_writer" - version = "1.0.6+spec-1.1.0" -@@ -2438,6 +2503,7 @@ name = "usbscsid" - version = "0.1.0" - dependencies = [ - "base64 0.11.0", -+ "bitflags 2.11.0", - "daemon", - "driver-block", - "libredox", -@@ -2445,6 +2511,7 @@ dependencies = [ - "redox_event", - "redox_syscall 0.7.4", - "thiserror 2.0.18", -+ "toml 1.0.6+spec-1.1.0", - "xhcid", - ] - -@@ -2801,6 +2868,9 @@ name = "winnow" - version = "0.7.15" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" -+dependencies = [ -+ "memchr 2.8.0", -+] - - [[package]] - name = "wit-bindgen" -@@ -2913,7 +2983,7 @@ dependencies = [ - "serde_json", - "smallvec 1.15.1", - "thiserror 2.0.18", -- "toml", -+ "toml 1.0.6+spec-1.1.0", - ] - - [[package]] -diff --git a/bootstrap/Cargo.lock b/bootstrap/Cargo.lock -index e738c973..50057616 100644 ---- a/bootstrap/Cargo.lock -+++ b/bootstrap/Cargo.lock -@@ -41,6 +41,7 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - [[package]] - name = "generic-rt" - version = "0.1.0" -+source = "git+https://gitlab.redox-os.org/redox-os/relibc.git#c35c291beabda0818fd3369d5de5d38553a1759e" - - [[package]] - name = "goblin" -@@ -150,6 +151,7 @@ checksum = "436d45c2b6a5b159d43da708e62b25be3a4a3d5550d654b72216ade4c4bfd717" - [[package]] - name = "redox-rt" - version = "0.1.0" -+source = "git+https://gitlab.redox-os.org/redox-os/relibc.git#c35c291beabda0818fd3369d5de5d38553a1759e" - dependencies = [ - "bitflags", - "generic-rt", + 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/acpid/Cargo.toml b/drivers/acpid/Cargo.toml -index 2d22a8f9..fea105c8 100644 +index 2d22a8f9..f03a4ccb 100644 --- a/drivers/acpid/Cargo.toml +++ b/drivers/acpid/Cargo.toml @@ -21,6 +21,7 @@ rustc-hash = "1.1.0" @@ -231,10 +66,19 @@ index 2d22a8f9..fea105c8 100644 amlserde = { path = "../amlserde" } common = { path = "../common" } diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs -index 94a1eb17..58bcc22d 100644 +index 94a1eb17..dad44d1d 100644 --- a/drivers/acpid/src/acpi.rs +++ b/drivers/acpid/src/acpi.rs -@@ -8,6 +8,7 @@ use std::str::FromStr; +@@ -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; @@ -242,16 +86,28 @@ index 94a1eb17..58bcc22d 100644 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] use common::io::{Io, Pio}; -@@ -25,6 +26,8 @@ use amlserde::{AmlSerde, AmlSerdeValue}; +@@ -16,16 +18,17 @@ use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; + use thiserror::Error; - #[cfg(target_arch = "x86_64")] - pub mod dmar; -+#[cfg(target_arch = "x86_64")] -+use self::dmar::Dmar; + 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. -@@ -206,6 +209,464 @@ impl Sdt { + #[derive(Copy, Clone, Debug)] +@@ -206,6 +209,615 @@ impl Sdt { } } @@ -648,38 +504,13 @@ index 94a1eb17..58bcc22d 100644 +#[cfg(test)] +mod tests { + use super::{ -+ apply_acpi_table_quirks, parse_acpi_table_quirks, smbios_string, DmiInfo, Sdt, SdtHeader, ++ 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 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() -+ } ++ use crate::sleep::SleepTarget; ++ use std::iter::FromIterator; ++ use toml::Value; + + #[test] + fn dmi_info_formats_key_value_lines() { @@ -707,6 +538,182 @@ index 94a1eb17..58bcc22d 100644 + 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()` @@ -716,7 +723,7 @@ index 94a1eb17..58bcc22d 100644 impl Deref for Sdt { type Target = SdtHeader; -@@ -244,16 +705,14 @@ pub struct AmlSymbols { +@@ -244,16 +856,14 @@ pub struct AmlSymbols { // k = name, v = description symbol_cache: FxHashMap, page_cache: Arc>, @@ -734,7 +741,7 @@ index 94a1eb17..58bcc22d 100644 } } -@@ -261,6 +720,9 @@ impl AmlSymbols { +@@ -261,6 +871,9 @@ impl AmlSymbols { if self.aml_context.is_some() { return Err("AML interpreter already initialized".into()); } @@ -744,6 +751,1002 @@ index 94a1eb17..58bcc22d 100644 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/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 @@ -765,20 +1768,663 @@ index c42b379a..f4dff276 100644 if len > remainder.len() { log::warn!("DMAR remapping structure length was smaller than the remaining length of the table."); +diff --git a/drivers/acpid/src/aml_physmem.rs b/drivers/acpid/src/aml_physmem.rs +index 2bdd667b..69b8c48b 100644 +--- a/drivers/acpid/src/aml_physmem.rs ++++ b/drivers/acpid/src/aml_physmem.rs +@@ -6,7 +6,10 @@ use rustc_hash::FxHashMap; + use std::fmt::LowerHex; + use std::mem::size_of; + use std::ptr::NonNull; +-use std::sync::{Arc, Mutex}; ++use std::sync::atomic::{AtomicU32, Ordering}; ++use std::sync::{Arc, Condvar, Mutex}; ++use std::thread::ThreadId; ++use std::time::{Duration, Instant}; + use syscall::PAGE_SIZE; + + const PAGE_MASK: usize = !(PAGE_SIZE - 1); +@@ -141,6 +144,20 @@ impl AmlPageCache { + pub struct AmlPhysMemHandler { + page_cache: Arc>, + pci_fd: Arc>, ++ aml_mutexes: Arc>>>, ++ next_mutex_handle: Arc, ++} ++ ++#[derive(Debug, Default)] ++struct AmlMutexState { ++ owner: Option, ++ depth: u32, ++} ++ ++#[derive(Debug, Default)] ++struct AmlMutex { ++ state: Mutex, ++ condvar: Condvar, + } + + /// Read from a physical address. +@@ -156,6 +173,30 @@ impl AmlPhysMemHandler { + Self { + page_cache, + pci_fd: Arc::new(pci_fd), ++ aml_mutexes: Arc::new(Mutex::new(FxHashMap::default())), ++ next_mutex_handle: Arc::new(AtomicU32::new(1)), ++ } ++ } ++ ++ fn aml_mutex(&self, handle: Handle) -> Option> { ++ self.aml_mutexes ++ .lock() ++ .unwrap_or_else(|poisoned| poisoned.into_inner()) ++ .get(&handle.0) ++ .cloned() ++ } ++ ++ fn read_phys_or_fault(&self, address: usize) -> T ++ where ++ T: PrimInt + LowerHex, ++ { ++ let mut page_cache = self ++ .page_cache ++ .lock() ++ .unwrap_or_else(|poisoned| poisoned.into_inner()); ++ match page_cache.read_from_phys::(address) { ++ Ok(value) => value, ++ Err(error) => panic!("AML physmem read failed at {:#x}: {}", address, error), + } + } + +@@ -240,43 +281,19 @@ impl acpi::Handler for AmlPhysMemHandler { + + fn read_u8(&self, address: usize) -> u8 { + log::trace!("read u8 {:X}", address); +- if let Ok(mut page_cache) = self.page_cache.lock() { +- if let Ok(value) = page_cache.read_from_phys::(address) { +- return value; +- } +- } +- log::error!("failed to read u8 {:#x}", address); +- 0 ++ self.read_phys_or_fault::(address) + } + fn read_u16(&self, address: usize) -> u16 { + log::trace!("read u16 {:X}", address); +- if let Ok(mut page_cache) = self.page_cache.lock() { +- if let Ok(value) = page_cache.read_from_phys::(address) { +- return value; +- } +- } +- log::error!("failed to read u16 {:#x}", address); +- 0 ++ self.read_phys_or_fault::(address) + } + fn read_u32(&self, address: usize) -> u32 { + log::trace!("read u32 {:X}", address); +- if let Ok(mut page_cache) = self.page_cache.lock() { +- if let Ok(value) = page_cache.read_from_phys::(address) { +- return value; +- } +- } +- log::error!("failed to read u32 {:#x}", address); +- 0 ++ self.read_phys_or_fault::(address) + } + fn read_u64(&self, address: usize) -> u64 { + log::trace!("read u64 {:X}", address); +- if let Ok(mut page_cache) = self.page_cache.lock() { +- if let Ok(value) = page_cache.read_from_phys::(address) { +- return value; +- } +- } +- log::error!("failed to read u64 {:#x}", address); +- 0 ++ self.read_phys_or_fault::(address) + } + + fn write_u8(&self, address: usize, value: u8) { +@@ -415,16 +432,102 @@ impl acpi::Handler for AmlPhysMemHandler { + } + + fn create_mutex(&self) -> Handle { +- log::debug!("TODO: Handler::create_mutex"); +- Handle(0) ++ let handle = self.next_mutex_handle.fetch_add(1, Ordering::Relaxed); ++ self.aml_mutexes ++ .lock() ++ .unwrap_or_else(|poisoned| poisoned.into_inner()) ++ .insert(handle, Arc::new(AmlMutex::default())); ++ log::trace!("created AML mutex handle {handle}"); ++ Handle(handle) + } + + fn acquire(&self, mutex: Handle, timeout: u16) -> Result<(), AmlError> { +- log::debug!("TODO: Handler::acquire"); +- Ok(()) ++ let Some(aml_mutex) = self.aml_mutex(mutex) else { ++ log::error!("attempted to acquire unknown AML mutex handle {}", mutex.0); ++ return Err(AmlError::MutexAcquireTimeout); ++ }; ++ ++ let current_thread = std::thread::current().id(); ++ let deadline = (timeout != 0xffff).then(|| Instant::now() + Duration::from_millis(timeout.into())); ++ ++ let mut state = aml_mutex ++ .state ++ .lock() ++ .unwrap_or_else(|poisoned| poisoned.into_inner()); ++ ++ loop { ++ match state.owner { ++ None => { ++ state.owner = Some(current_thread); ++ state.depth = 1; ++ return Ok(()); ++ } ++ Some(owner) if owner == current_thread => { ++ state.depth = state.depth.saturating_add(1); ++ return Ok(()); ++ } ++ Some(_) if timeout == 0 => return Err(AmlError::MutexAcquireTimeout), ++ Some(_) if timeout == 0xffff => { ++ state = aml_mutex ++ .condvar ++ .wait(state) ++ .unwrap_or_else(|poisoned| poisoned.into_inner()); ++ } ++ Some(_) => { ++ let Some(deadline) = deadline else { ++ return Err(AmlError::MutexAcquireTimeout); ++ }; ++ let now = Instant::now(); ++ if now >= deadline { ++ return Err(AmlError::MutexAcquireTimeout); ++ } ++ ++ let remaining = deadline.saturating_duration_since(now); ++ let (next_state, wait_result) = aml_mutex ++ .condvar ++ .wait_timeout(state, remaining) ++ .unwrap_or_else(|poisoned| poisoned.into_inner()); ++ state = next_state; ++ ++ if wait_result.timed_out() && state.owner != Some(current_thread) { ++ return Err(AmlError::MutexAcquireTimeout); ++ } ++ } ++ } ++ } + } + + fn release(&self, mutex: Handle) { +- log::debug!("TODO: Handler::release"); ++ let Some(aml_mutex) = self.aml_mutex(mutex) else { ++ log::error!("attempted to release unknown AML mutex handle {}", mutex.0); ++ return; ++ }; ++ ++ let current_thread = std::thread::current().id(); ++ let mut state = aml_mutex ++ .state ++ .lock() ++ .unwrap_or_else(|poisoned| poisoned.into_inner()); ++ ++ match state.owner { ++ Some(owner) if owner == current_thread => { ++ if state.depth > 1 { ++ state.depth -= 1; ++ } else { ++ state.owner = None; ++ state.depth = 0; ++ aml_mutex.condvar.notify_one(); ++ } ++ } ++ Some(_) => { ++ log::warn!( ++ "ignoring AML mutex release for handle {} from non-owner thread", ++ mutex.0 ++ ); ++ } ++ None => { ++ log::warn!("ignoring AML mutex release for unlocked handle {}", mutex.0); ++ } ++ } + } + } +diff --git a/drivers/acpid/src/ec.rs b/drivers/acpid/src/ec.rs +index c322790a..99842586 100644 +--- a/drivers/acpid/src/ec.rs ++++ b/drivers/acpid/src/ec.rs +@@ -1,3 +1,4 @@ ++use std::convert::TryFrom; + use std::time::Duration; + + use acpi::aml::{ +@@ -30,6 +31,28 @@ const BURST_ACK: u8 = 0x90; + + pub const DEFAULT_EC_TIMEOUT: Duration = Duration::from_millis(10); + ++#[derive(Debug, Clone, Copy)] ++enum EcError { ++ Timeout, ++ OffsetOutOfRange, ++} ++ ++impl EcError { ++ fn as_aml_error(self) -> AmlError { ++ match self { ++ EcError::Timeout | EcError::OffsetOutOfRange => { ++ AmlError::NoHandlerForRegionAccess(RegionSpace::EmbeddedControl) ++ } ++ } ++ } ++} ++ ++impl From for AmlError { ++ fn from(value: EcError) -> Self { ++ value.as_aml_error() ++ } ++} ++ + #[repr(transparent)] + pub struct ScBits(u8); + #[allow(dead_code)] +@@ -90,28 +113,33 @@ impl Ec { + Pio::::new(self.data).write(value); + } + #[inline] +- fn wait_for_write_ready(&self) -> Option<()> { ++ fn wait_for_write_ready(&self) -> Result<(), EcError> { + let timeout = Timeout::new(self.timeout); + loop { + if !self.read_reg_sc().ibf() { +- return Some(()); ++ return Ok(()); + } +- timeout.run().ok()?; ++ timeout.run().map_err(|_| EcError::Timeout)?; + } + } + #[inline] +- fn wait_for_read_ready(&self) -> Option<()> { ++ fn wait_for_read_ready(&self) -> Result<(), EcError> { + let timeout = Timeout::new(self.timeout); + loop { + if self.read_reg_sc().obf() { +- return Some(()); ++ return Ok(()); + } +- timeout.run().ok()?; ++ timeout.run().map_err(|_| EcError::Timeout)?; + } + } + ++ #[inline] ++ fn checked_address(offset: usize, byte_index: usize) -> Result { ++ u8::try_from(offset + byte_index).map_err(|_| EcError::OffsetOutOfRange) ++ } ++ + //https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/12_ACPI_Embedded_Controller_Interface_Specification/embedded-controller-command-set.html +- pub fn read(&self, address: u8) -> Option { ++ fn read(&self, address: u8) -> Result { + trace!("ec read addr: {:x}", address); + self.wait_for_write_ready()?; + +@@ -125,9 +153,9 @@ impl Ec { + + let val = self.read_reg_data(); + trace!("got: {:x}", val); +- Some(val) ++ Ok(val) + } +- pub fn write(&self, address: u8, value: u8) -> Option<()> { ++ fn write(&self, address: u8, value: u8) -> Result<(), EcError> { + trace!("ec write addr: {:x}, with: {:x}", address, value); + self.wait_for_write_ready()?; + +@@ -141,7 +169,22 @@ impl Ec { + + self.write_reg_data(value); + trace!("done"); +- Some(()) ++ Ok(()) ++ } ++ ++ fn read_bytes(&self, offset: usize) -> Result<[u8; N], EcError> { ++ let mut bytes = [0u8; N]; ++ for (index, byte) in bytes.iter_mut().enumerate() { ++ *byte = self.read(Self::checked_address(offset, index)?)?; ++ } ++ Ok(bytes) ++ } ++ ++ fn write_bytes(&self, offset: usize, bytes: [u8; N]) -> Result<(), EcError> { ++ for (index, byte) in IntoIterator::into_iter(bytes).enumerate() { ++ self.write(Self::checked_address(offset, index)?, byte)?; ++ } ++ Ok(()) + } + // disabled if not met + // First Access - 400 microseconds +@@ -151,11 +194,11 @@ impl Ec { + #[allow(dead_code)] + fn enable_burst(&self) -> bool { + trace!("ec burst enable"); +- self.wait_for_write_ready(); ++ let _ = self.wait_for_write_ready(); + + self.write_reg_sc(BE_EC); + +- self.wait_for_read_ready(); ++ let _ = self.wait_for_read_ready(); + + let res = self.read_reg_data() == BURST_ACK; + trace!("success: {}", res); +@@ -164,7 +207,7 @@ impl Ec { + #[allow(dead_code)] + fn disable_burst(&self) { + trace!("ec burst disable"); +- self.wait_for_write_ready(); ++ let _ = self.wait_for_write_ready(); + self.write_reg_sc(BD_EC); + trace!("done"); + } +@@ -172,11 +215,11 @@ impl Ec { + #[allow(dead_code)] + fn queue_query(&mut self) -> u8 { + trace!("ec query"); +- self.wait_for_write_ready(); ++ let _ = self.wait_for_write_ready(); + + self.write_reg_sc(QR_EC); + +- self.wait_for_read_ready(); ++ let _ = self.wait_for_read_ready(); + + let val = self.read_reg_data(); + trace!("got: {}", val); +@@ -190,7 +233,10 @@ impl RegionHandler for Ec { + offset: usize, + ) -> Result { + assert_eq!(region.space, RegionSpace::EmbeddedControl); +- self.read(offset as u8).ok_or(AmlError::MutexAcquireTimeout) // TODO proper error type ++ self.read(Self::checked_address(offset, 0)?).map_err(|error| { ++ warn!("EC read_u8 failed at offset {offset:#x}: {error:?}"); ++ error.as_aml_error() ++ }) + } + fn write_u8( + &self, +@@ -199,58 +245,73 @@ impl RegionHandler for Ec { + value: u8, + ) -> Result<(), acpi::aml::AmlError> { + assert_eq!(region.space, RegionSpace::EmbeddedControl); +- self.write(offset as u8, value) +- .ok_or(AmlError::MutexAcquireTimeout) // TODO proper error type +- } +- fn read_u16(&self, _region: &OpRegion, _offset: usize) -> Result { +- warn!("Got u16 EC read from AML!"); +- Err(acpi::aml::AmlError::NoHandlerForRegionAccess( +- RegionSpace::EmbeddedControl, +- )) // TODO proper error type +- } +- fn read_u32(&self, _region: &OpRegion, _offset: usize) -> Result { +- warn!("Got u32 EC read from AML!"); +- Err(acpi::aml::AmlError::NoHandlerForRegionAccess( +- RegionSpace::EmbeddedControl, +- )) // TODO proper error type +- } +- fn read_u64(&self, _region: &OpRegion, _offset: usize) -> Result { +- warn!("Got u64 EC read from AML!"); +- Err(acpi::aml::AmlError::NoHandlerForRegionAccess( +- RegionSpace::EmbeddedControl, +- )) // TODO proper error type ++ self.write(Self::checked_address(offset, 0)?, value) ++ .map_err(|error| { ++ warn!("EC write_u8 failed at offset {offset:#x}: {error:?}"); ++ error.as_aml_error() ++ }) ++ } ++ fn read_u16(&self, region: &OpRegion, offset: usize) -> Result { ++ assert_eq!(region.space, RegionSpace::EmbeddedControl); ++ self.read_bytes::<2>(offset) ++ .map(u16::from_le_bytes) ++ .map_err(|error| { ++ warn!("EC read_u16 failed at offset {offset:#x}: {error:?}"); ++ error.as_aml_error() ++ }) ++ } ++ fn read_u32(&self, region: &OpRegion, offset: usize) -> Result { ++ assert_eq!(region.space, RegionSpace::EmbeddedControl); ++ self.read_bytes::<4>(offset) ++ .map(u32::from_le_bytes) ++ .map_err(|error| { ++ warn!("EC read_u32 failed at offset {offset:#x}: {error:?}"); ++ error.as_aml_error() ++ }) ++ } ++ fn read_u64(&self, region: &OpRegion, offset: usize) -> Result { ++ assert_eq!(region.space, RegionSpace::EmbeddedControl); ++ self.read_bytes::<8>(offset) ++ .map(u64::from_le_bytes) ++ .map_err(|error| { ++ warn!("EC read_u64 failed at offset {offset:#x}: {error:?}"); ++ error.as_aml_error() ++ }) + } + fn write_u16( + &self, +- _region: &OpRegion, +- _offset: usize, +- _value: u16, ++ region: &OpRegion, ++ offset: usize, ++ value: u16, + ) -> Result<(), acpi::aml::AmlError> { +- warn!("Got u16 EC write from AML!"); +- Err(acpi::aml::AmlError::NoHandlerForRegionAccess( +- RegionSpace::EmbeddedControl, +- )) // TODO proper error type ++ assert_eq!(region.space, RegionSpace::EmbeddedControl); ++ self.write_bytes(offset, value.to_le_bytes()).map_err(|error| { ++ warn!("EC write_u16 failed at offset {offset:#x}: {error:?}"); ++ error.as_aml_error() ++ }) + } + fn write_u32( + &self, +- _region: &OpRegion, +- _offset: usize, +- _value: u32, ++ region: &OpRegion, ++ offset: usize, ++ value: u32, + ) -> Result<(), acpi::aml::AmlError> { +- warn!("Got u32 EC write from AML!"); +- Err(acpi::aml::AmlError::NoHandlerForRegionAccess( +- RegionSpace::EmbeddedControl, +- )) // TODO proper error type ++ assert_eq!(region.space, RegionSpace::EmbeddedControl); ++ self.write_bytes(offset, value.to_le_bytes()).map_err(|error| { ++ warn!("EC write_u32 failed at offset {offset:#x}: {error:?}"); ++ error.as_aml_error() ++ }) + } + fn write_u64( + &self, +- _region: &OpRegion, +- _offset: usize, +- _value: u64, ++ region: &OpRegion, ++ offset: usize, ++ value: u64, + ) -> Result<(), acpi::aml::AmlError> { +- warn!("Got u64 EC write from AML!"); +- Err(acpi::aml::AmlError::NoHandlerForRegionAccess( +- RegionSpace::EmbeddedControl, +- )) // TODO proper error type ++ assert_eq!(region.space, RegionSpace::EmbeddedControl); ++ self.write_bytes(offset, value.to_le_bytes()).map_err(|error| { ++ warn!("EC write_u64 failed at offset {offset:#x}: {error:?}"); ++ error.as_aml_error() ++ }) + } + } diff --git a/drivers/acpid/src/main.rs b/drivers/acpid/src/main.rs -index 0933f638..916e1864 100644 +index 059254b3..e388cd46 100644 --- a/drivers/acpid/src/main.rs +++ b/drivers/acpid/src/main.rs -@@ -4,7 +4,6 @@ use std::mem; +@@ -5,117 +5,189 @@ use std::ops::ControlFlow; use std::os::unix::io::AsRawFd; use std::sync::Arc; -use ::acpi::aml::op_region::{RegionHandler, RegionSpace}; use event::{EventFlags, RawEventQueue}; - use redox_scheme::{ - scheme::{register_sync_scheme, SchemeState, SchemeSync}, -@@ -69,11 +68,7 @@ fn daemon(daemon: daemon::Daemon) -> ! { - _ => panic!("acpid: expected [RX]SDT from kernel to be either of those"), + use redox_scheme::{scheme::register_sync_scheme, Socket}; + use scheme_utils::Blocking; ++use thiserror::Error; + + mod acpi; + mod aml_physmem; + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + mod ec; + ++mod sleep; + mod scheme; + +-fn daemon(daemon: daemon::Daemon) -> ! { +- common::setup_logging( +- "misc", +- "acpi", +- "acpid", +- common::output_level(), +- common::file_level(), +- ); ++#[derive(Debug, Error)] ++enum StartupError { ++ #[error("failed to read `/scheme/kernel.acpi/rxsdt`: {0}")] ++ ReadRootTable(std::io::Error), + +- log::info!("acpid start"); ++ #[error("failed to parse [R/X]SDT from kernel: {0}")] ++ ParseRootTable(self::acpi::InvalidSdtError), + +- let rxsdt_raw_data: Arc<[u8]> = std::fs::read("/scheme/kernel.acpi/rxsdt") +- .expect("acpid: failed to read `/scheme/kernel.acpi/rxsdt`") +- .into(); ++ #[error("kernel returned unsupported root table signature `{signature}`")] ++ UnsupportedRootSignature { signature: String }, + +- if rxsdt_raw_data.is_empty() { +- log::info!("System doesn't use ACPI"); +- daemon.ready(); +- std::process::exit(0); ++ #[error( ++ "root table `{signature}` payload length {payload_len} is not divisible by entry size {entry_size}" ++ )] ++ MisalignedRootEntries { ++ signature: String, ++ payload_len: usize, ++ entry_size: usize, ++ }, ++ ++ #[error("{context}: {message}")] ++ Runtime { ++ context: &'static str, ++ message: String, ++ }, ++} ++ ++impl StartupError { ++ fn runtime(context: &'static str, message: impl Into) -> Self { ++ Self::Runtime { ++ context, ++ message: message.into(), ++ } + } ++} + +- let sdt = self::acpi::Sdt::new(rxsdt_raw_data).expect("acpid: failed to parse [RX]SDT"); ++fn parse_root_sdt(raw_data: Arc<[u8]>) -> Result, StartupError> { ++ if raw_data.is_empty() { ++ return Ok(None); ++ } + +- let mut thirty_two_bit; +- let mut sixty_four_bit; ++ self::acpi::Sdt::new(raw_data) ++ .map(Some) ++ .map_err(StartupError::ParseRootTable) ++} ++ ++fn root_table_physaddrs(sdt: &self::acpi::Sdt) -> Result, StartupError> { ++ let signature = String::from_utf8_lossy(&sdt.signature).into_owned(); ++ let data = sdt.data(); + +- let physaddrs_iter = match &sdt.signature { ++ match &sdt.signature { + b"RSDT" => { +- thirty_two_bit = sdt +- .data() +- .chunks(mem::size_of::()) +- // TODO: With const generics, the compiler has some way of doing this for static sizes. +- .map(|chunk| <[u8; mem::size_of::()]>::try_from(chunk).unwrap()) +- .map(|chunk| u32::from_le_bytes(chunk)) +- .map(u64::from); ++ let entry_size = mem::size_of::(); ++ if data.len() % entry_size != 0 { ++ return Err(StartupError::MisalignedRootEntries { ++ signature, ++ payload_len: data.len(), ++ entry_size, ++ }); ++ } + +- &mut thirty_two_bit as &mut dyn Iterator ++ Ok(data ++ .chunks_exact(entry_size) ++ .map(|chunk| <[u8; mem::size_of::()]>::try_from(chunk).unwrap()) ++ .map(u32::from_le_bytes) ++ .map(u64::from) ++ .collect()) + } + b"XSDT" => { +- sixty_four_bit = sdt +- .data() +- .chunks(mem::size_of::()) +- .map(|chunk| <[u8; mem::size_of::()]>::try_from(chunk).unwrap()) +- .map(|chunk| u64::from_le_bytes(chunk)); ++ let entry_size = mem::size_of::(); ++ if data.len() % entry_size != 0 { ++ return Err(StartupError::MisalignedRootEntries { ++ signature, ++ payload_len: data.len(), ++ entry_size, ++ }); ++ } + +- &mut sixty_four_bit as &mut dyn Iterator ++ Ok(data ++ .chunks_exact(entry_size) ++ .map(|chunk| <[u8; mem::size_of::()]>::try_from(chunk).unwrap()) ++ .map(u64::from_le_bytes) ++ .collect()) + } +- _ => panic!("acpid: expected [RX]SDT from kernel to be either of those"), ++ _ => Err(StartupError::UnsupportedRootSignature { signature }), ++ } ++} ++ ++fn run_acpid(daemon: daemon::Daemon) -> Result<(), StartupError> { ++ let rxsdt_raw_data: Arc<[u8]> = std::fs::read("/scheme/kernel.acpi/rxsdt") ++ .map(Arc::<[u8]>::from) ++ .map_err(StartupError::ReadRootTable)?; ++ ++ let Some(sdt) = parse_root_sdt(rxsdt_raw_data)? else { ++ log::info!("System doesn't use ACPI"); ++ daemon.ready(); ++ std::process::exit(0); }; - let region_handlers: Vec<(RegionSpace, Box)> = vec![ @@ -786,12 +2432,243 @@ index 0933f638..916e1864 100644 - (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_iter); ++ let physaddrs = root_table_physaddrs(&sdt)?; ++ let acpi_context = self::acpi::AcpiContext::init(physaddrs.into_iter()); - // TODO: I/O permission bitmap? +- // TODO: I/O permission bitmap? #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +- common::acquire_port_io_rights().expect("acpid: failed to set I/O privilege level to Ring 3"); ++ common::acquire_port_io_rights().map_err(|error| { ++ StartupError::runtime( ++ "failed to set I/O privilege level to Ring 3", ++ format!("{error}"), ++ ) ++ })?; + +- let shutdown_pipe = File::open("/scheme/kernel.acpi/kstop") +- .expect("acpid: failed to open `/scheme/kernel.acpi/kstop`"); ++ let shutdown_pipe = File::open("/scheme/kernel.acpi/kstop").map_err(|error| { ++ StartupError::runtime( ++ "failed to open `/scheme/kernel.acpi/kstop`", ++ error.to_string(), ++ ) ++ })?; + +- let mut event_queue = RawEventQueue::new().expect("acpid: failed to create event queue"); +- let socket = Socket::nonblock().expect("acpid: failed to create disk scheme"); ++ let mut event_queue = RawEventQueue::new().map_err(|error| { ++ StartupError::runtime("failed to create event queue", error.to_string()) ++ })?; ++ let socket = Socket::nonblock().map_err(|error| { ++ StartupError::runtime("failed to create acpi scheme socket", error.to_string()) ++ })?; + + let mut scheme = self::scheme::AcpiScheme::new(&acpi_context, &socket); + let mut handler = Blocking::new(&socket, 16); + + event_queue + .subscribe(shutdown_pipe.as_raw_fd() as usize, 0, EventFlags::READ) +- .expect("acpid: failed to register shutdown pipe for event queue"); ++ .map_err(|error| { ++ StartupError::runtime( ++ "failed to register shutdown pipe for event queue", ++ error.to_string(), ++ ) ++ })?; + event_queue + .subscribe(socket.inner().raw(), 1, EventFlags::READ) +- .expect("acpid: failed to register scheme socket for event queue"); ++ .map_err(|error| { ++ StartupError::runtime( ++ "failed to register scheme socket for event queue", ++ error.to_string(), ++ ) ++ })?; + +- register_sync_scheme(&socket, "acpi", &mut scheme) +- .expect("acpid: failed to register acpi scheme to namespace"); ++ register_sync_scheme(&socket, "acpi", &mut scheme).map_err(|error| { ++ StartupError::runtime( ++ "failed to register acpi scheme to namespace", ++ error.to_string(), ++ ) ++ })?; + +- daemon.ready(); ++ libredox::call::setrens(0, 0).map_err(|error| { ++ StartupError::runtime("failed to enter null namespace", error.to_string()) ++ })?; + +- libredox::call::setrens(0, 0).expect("acpid: failed to enter null namespace"); ++ daemon.ready(); + + let mut mounted = true; + while mounted { +- let Some(event) = event_queue +- .next() +- .transpose() +- .expect("acpid: failed to read event file") +- else { ++ let Some(event) = event_queue.next().transpose().map_err(|error| { ++ StartupError::runtime("failed to read event file", error.to_string()) ++ })? 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).map_err(|error| { ++ StartupError::runtime("failed to process requests", error.to_string()) ++ })? { + ControlFlow::Continue(()) => {} + ControlFlow::Break(()) => break, + } +@@ -125,19 +197,136 @@ fn daemon(daemon: daemon::Daemon) -> ! { + mounted = false; + } else { + log::debug!("Received request to unknown fd: {}", event.fd); +- continue; + } + } + + drop(shutdown_pipe); + drop(event_queue); + +- acpi_context.set_global_s_state(5); ++ acpi_context.set_global_s_state(5).map_err(|error| { ++ StartupError::runtime("failed to shut down after kernel request", error.to_string()) ++ }) ++} ++ ++fn daemon(daemon: daemon::Daemon) -> ! { ++ common::setup_logging( ++ "misc", ++ "acpi", ++ "acpid", ++ common::output_level(), ++ common::file_level(), ++ ); + +- unreachable!("System should have shut down before this is entered"); ++ log::info!("acpid start"); ++ ++ if let Err(error) = run_acpid(daemon) { ++ log::error!("acpid startup/runtime failure: {error}"); ++ std::process::exit(1); ++ } ++ ++ unreachable!("acpid returned from run_acpid without exiting or shutting down"); + } + + fn main() { + common::init(); + daemon::Daemon::new(daemon); + } ++ ++#[cfg(test)] ++mod tests { ++ use super::{parse_root_sdt, root_table_physaddrs, StartupError}; ++ use crate::acpi::SdtHeader; ++ use std::sync::Arc; ++ ++ fn make_sdt(signature: [u8; 4], payload: &[u8]) -> Arc<[u8]> { ++ let length = (std::mem::size_of::() + payload.len()) as u32; ++ let header = SdtHeader { ++ signature, ++ length, ++ revision: 1, ++ checksum: 0, ++ oem_id: *b"REDBAR", ++ oem_table_id: *b"ACPITEST", ++ oem_revision: 1, ++ creator_id: 1, ++ creator_revision: 1, ++ }; ++ ++ let mut bytes = unsafe { plain::as_bytes(&header) }.to_vec(); ++ bytes.extend_from_slice(payload); ++ ++ let checksum = bytes ++ .iter() ++ .copied() ++ .fold(0u8, |sum, byte| sum.wrapping_add(byte)); ++ bytes[9] = 0u8.wrapping_sub(checksum); ++ ++ bytes.into() ++ } ++ ++ #[test] ++ fn empty_root_table_means_no_acpi() { ++ let parsed = parse_root_sdt(Arc::<[u8]>::from(Vec::::new())).unwrap(); ++ assert!(parsed.is_none()); ++ } ++ ++ #[test] ++ fn rsdt_physaddrs_parse_without_panic() { ++ let payload = [ ++ 0x78, 0x56, 0x34, 0x12, // 0x12345678 ++ 0xF0, 0xDE, 0xBC, 0x9A, // 0x9ABCDEF0 ++ ]; ++ let sdt = parse_root_sdt(make_sdt(*b"RSDT", &payload)) ++ .unwrap() ++ .unwrap(); ++ ++ assert_eq!( ++ root_table_physaddrs(&sdt).unwrap(), ++ vec![0x1234_5678, 0x9ABC_DEF0] ++ ); ++ } ++ ++ #[test] ++ fn xsdt_physaddrs_parse_without_panic() { ++ let payload = [ ++ 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, ++ 0x00, 0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0x99, ++ ]; ++ let sdt = parse_root_sdt(make_sdt(*b"XSDT", &payload)) ++ .unwrap() ++ .unwrap(); ++ ++ assert_eq!( ++ root_table_physaddrs(&sdt).unwrap(), ++ vec![0x1122_3344_5566_7788, 0x99AA_BBCC_DDEE_FF00] ++ ); ++ } ++ ++ #[test] ++ fn invalid_root_signature_is_explicit() { ++ let sdt = parse_root_sdt(make_sdt(*b"FADT", &[])).unwrap().unwrap(); ++ ++ let error = root_table_physaddrs(&sdt).unwrap_err(); ++ assert!(matches!( ++ error, ++ StartupError::UnsupportedRootSignature { .. } ++ )); ++ } ++ ++ #[test] ++ fn misaligned_rsdt_entries_are_rejected() { ++ let sdt = parse_root_sdt(make_sdt(*b"RSDT", &[1, 2, 3])) ++ .unwrap() ++ .unwrap(); ++ ++ let error = root_table_physaddrs(&sdt).unwrap_err(); ++ assert!(matches!( ++ error, ++ StartupError::MisalignedRootEntries { ++ entry_size: 4, ++ payload_len: 3, ++ .. ++ } ++ )); ++ } ++} diff --git a/drivers/acpid/src/scheme.rs b/drivers/acpid/src/scheme.rs -index 5a5040c3..5f1232bd 100644 +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; @@ -807,7 +2684,7 @@ index 5a5040c3..5f1232bd 100644 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::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}; @@ -825,10 +2702,11 @@ index 5a5040c3..5f1232bd 100644 socket: &'sock Socket, } -@@ -41,10 +42,156 @@ enum HandleKind<'a> { +@@ -41,10 +42,170 @@ enum HandleKind<'a> { Table(SdtSignature), Symbols(RwLockReadGuard<'a, AmlSymbols>), Symbol { name: String, description: String }, ++ Reboot, + DmiDir, + Dmi(String), + PowerDir, @@ -978,14 +2856,28 @@ index 5a5040c3..5f1232bd 100644 + + 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 +200,14 @@ impl HandleKind<'_> { +@@ -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, @@ -997,10 +2889,11 @@ index 5a5040c3..5f1232bd 100644 Self::SchemeRoot => false, Self::RegisterPci => false, } -@@ -65,8 +220,18 @@ impl HandleKind<'_> { +@@ -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 @@ -1017,7 +2910,7 @@ index 5a5040c3..5f1232bd 100644 Self::SchemeRoot | Self::RegisterPci => return Err(Error::new(EBADF)), }) } -@@ -77,10 +242,99 @@ impl<'acpi, 'sock> AcpiScheme<'acpi, 'sock> { +@@ -77,10 +258,111 @@ impl<'acpi, 'sock> AcpiScheme<'acpi, 'sock> { Self { ctx, handles: HandleMap::new(), @@ -1027,14 +2920,26 @@ index 5a5040c3..5f1232bd 100644 } + + fn power_snapshot(&self) -> Result { -+ self.ctx.power_snapshot().map_err(|error| { -+ log::warn!("Failed to build ACPI power snapshot: {:?}", error); -+ Error::new(EIO) ++ 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); @@ -1118,7 +3023,7 @@ index 5a5040c3..5f1232bd 100644 } fn parse_hex_digit(hex: u8) -> Option { -@@ -184,9 +438,9 @@ impl SchemeSync for AcpiScheme<'_, '_> { +@@ -184,9 +466,9 @@ impl SchemeSync for AcpiScheme<'_, '_> { HandleKind::SchemeRoot => { // TODO: arrayvec let components = { @@ -1130,10 +3035,11 @@ index 5a5040c3..5f1232bd 100644 v.push(component); } -@@ -195,6 +449,24 @@ impl SchemeSync for AcpiScheme<'_, '_> { +@@ -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 @@ -1155,7 +3061,7 @@ index 5a5040c3..5f1232bd 100644 ["register_pci"] => HandleKind::RegisterPci, ["tables"] => HandleKind::Tables, -@@ -204,7 +476,11 @@ impl SchemeSync for AcpiScheme<'_, '_> { +@@ -204,7 +505,11 @@ impl SchemeSync for AcpiScheme<'_, '_> { } ["symbols"] => { @@ -1168,7 +3074,7 @@ index 5a5040c3..5f1232bd 100644 HandleKind::Symbols(aml_symbols) } else { return Err(Error::new(EIO)); -@@ -212,6 +488,12 @@ impl SchemeSync for AcpiScheme<'_, '_> { +@@ -212,6 +517,12 @@ impl SchemeSync for AcpiScheme<'_, '_> { } ["symbols", symbol] => { @@ -1181,7 +3087,7 @@ index 5a5040c3..5f1232bd 100644 if let Some(description) = self.ctx.aml_lookup(symbol) { HandleKind::Symbol { name: (*symbol).to_owned(), -@@ -225,6 +507,15 @@ impl SchemeSync for AcpiScheme<'_, '_> { +@@ -225,6 +536,15 @@ impl SchemeSync for AcpiScheme<'_, '_> { _ => return Err(Error::new(ENOENT)), } } @@ -1197,7 +3103,7 @@ index 5a5040c3..5f1232bd 100644 HandleKind::Symbols(ref aml_symbols) => { if let Some(description) = aml_symbols.lookup(path) { HandleKind::Symbol { -@@ -235,6 +526,23 @@ impl SchemeSync for AcpiScheme<'_, '_> { +@@ -235,6 +555,23 @@ impl SchemeSync for AcpiScheme<'_, '_> { return Err(Error::new(ENOENT)); } } @@ -1221,7 +3127,7 @@ index 5a5040c3..5f1232bd 100644 _ => return Err(Error::new(EACCES)), }; -@@ -296,7 +604,7 @@ impl SchemeSync for AcpiScheme<'_, '_> { +@@ -296,7 +633,7 @@ impl SchemeSync for AcpiScheme<'_, '_> { ) -> Result { let offset: usize = offset.try_into().map_err(|_| Error::new(EINVAL))?; @@ -1230,7 +3136,7 @@ index 5a5040c3..5f1232bd 100644 if handle.stat { return Err(Error::new(EBADF)); -@@ -309,6 +617,8 @@ impl SchemeSync for AcpiScheme<'_, '_> { +@@ -309,6 +646,8 @@ impl SchemeSync for AcpiScheme<'_, '_> { .ok_or(Error::new(EBADFD))? .as_slice(), HandleKind::Symbol { description, .. } => description.as_bytes(), @@ -1239,7 +3145,7 @@ index 5a5040c3..5f1232bd 100644 _ => return Err(Error::new(EINVAL)), }; -@@ -328,11 +638,11 @@ impl SchemeSync for AcpiScheme<'_, '_> { +@@ -328,13 +667,82 @@ impl SchemeSync for AcpiScheme<'_, '_> { mut buf: DirentBuf<&'buf mut [u8]>, opaque_offset: u64, ) -> Result> { @@ -1249,14 +3155,19 @@ index 5a5040c3..5f1232bd 100644 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<'_, '_> { - })?; - } - } ++ 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() @@ -1314,16 +3225,16 @@ index 5a5040c3..5f1232bd 100644 + .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) -+ { -+ buf.entry(DirEntry { -+ inode: 0, -+ next_opaque_id: idx as u64 + 1, -+ name, + .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, + })?; + } @@ -1340,10 +3251,10 @@ index 5a5040c3..5f1232bd 100644 + inode: 0, + next_opaque_id: idx as u64 + 1, + name: battery.id.as_str(), -+ kind: DirentKind::Directory, -+ })?; -+ } -+ } + kind: DirentKind::Directory, + })?; + } + } + HandleKind::PowerBatteryDir(battery_id) => { + let snapshot = self.power_snapshot()?; + let battery = snapshot @@ -1365,7 +3276,46 @@ index 5a5040c3..5f1232bd 100644 HandleKind::Symbols(aml_symbols) => { for (idx, (symbol_name, _value)) in aml_symbols .symbols_cache() -@@ -470,10 +885,8 @@ impl SchemeSync for AcpiScheme<'_, '_> { +@@ -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); @@ -1377,15 +3327,16 @@ index 5a5040c3..5f1232bd 100644 } Ok(num_fds) -@@ -483,3 +896,38 @@ impl SchemeSync for AcpiScheme<'_, '_> { +@@ -483,3 +955,58 @@ impl SchemeSync for AcpiScheme<'_, '_> { self.handles.remove(id); } } + +#[cfg(test)] +mod tests { -+ use super::dmi_contents; ++ 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() { @@ -1415,1159 +3366,303 @@ index 5a5040c3..5f1232bd 100644 + assert_eq!(dmi_contents(None, "bios_version").as_deref(), Some("")); + assert_eq!(dmi_contents(Some(&dmi_info), "unknown"), None); + } -+} -diff --git a/drivers/acpid/src/main.rs b/drivers/acpid/src/main.rs -index 916e1864..52d8c8b4 100644 ---- a/drivers/acpid/src/main.rs -+++ b/drivers/acpid/src/main.rs -@@ -16,6 +16,7 @@ mod aml_physmem; - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - mod ec; - -+mod sleep; - mod scheme; - - #[derive(Debug, Error)] -diff --git a/drivers/acpid/src/sleep.rs b/drivers/acpid/src/sleep.rs -new file mode 100644 -index 00000000..f8095663 ---- /dev/null -+++ b/drivers/acpid/src/sleep.rs -@@ -0,0 +1,84 @@ -+use std::convert::TryFrom; + -+#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] -+pub enum SleepTarget { -+ S1, -+ S3, -+ S4, -+ S5, -+} ++ #[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) ++ })); ++ } + -+impl SleepTarget { -+ pub fn aml_method_name(self) -> &'static str { -+ match self { -+ Self::S1 => "_S1", -+ Self::S3 => "_S3", -+ Self::S4 => "_S4", -+ Self::S5 => "_S5", ++ #[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/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); + } -+ } -+ -+ 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 { -+ 1 => Ok(Self::S1), -+ 3 => Ok(Self::S3), -+ 4 => Ok(Self::S4), -+ 5 => Ok(Self::S5), -+ _ => Err(()), ++ }; ++ let bar1 = match pci_config.func.bars[1].try_port() { ++ Ok(port) => port, ++ Err(err) => { ++ error!("ac97d: invalid BAR1: {err}"); ++ std::process::exit(1); + } -+ } -+} -+ -+#[derive(Clone, Copy, Debug, Eq, PartialEq)] -+pub enum SleepPhase { -+ Prepare, -+ Enter, -+ Resume, -+} -+ -+#[cfg(test)] -+mod tests { -+ use std::convert::TryFrom; -+ -+ use super::{SleepPhase, SleepTarget}; -+ -+ #[test] -+ fn sleep_target_maps_to_expected_aml_names() { -+ assert_eq!(SleepTarget::S1.aml_method_name(), "_S1"); -+ assert_eq!(SleepTarget::S3.aml_method_name(), "_S3"); -+ assert_eq!(SleepTarget::S4.aml_method_name(), "_S4"); -+ assert_eq!(SleepTarget::S5.aml_method_name(), "_S5"); -+ } -+ -+ #[test] -+ fn sleep_target_parsing_accepts_expected_states() { -+ assert_eq!(SleepTarget::try_from(1), Ok(SleepTarget::S1)); -+ assert_eq!(SleepTarget::try_from(3), Ok(SleepTarget::S3)); -+ assert_eq!(SleepTarget::try_from(4), Ok(SleepTarget::S4)); -+ assert_eq!(SleepTarget::try_from(5), Ok(SleepTarget::S5)); -+ assert_eq!(SleepTarget::try_from(2), Err(())); -+ } -+ -+ #[test] -+ fn only_s5_is_currently_treated_as_soft_off() { -+ assert!(!SleepTarget::S1.is_soft_off()); -+ assert!(!SleepTarget::S3.is_soft_off()); -+ assert!(!SleepTarget::S4.is_soft_off()); -+ assert!(SleepTarget::S5.is_soft_off()); -+ } -+ -+ #[test] -+ fn sleep_phase_debug_surface_is_stable() { -+ assert_eq!(format!("{:?}", SleepPhase::Prepare), "Prepare"); -+ assert_eq!(format!("{:?}", SleepPhase::Enter), "Enter"); -+ assert_eq!(format!("{:?}", SleepPhase::Resume), "Resume"); -+ } -+} -diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs -index 58bcc22d..4f817811 100644 ---- a/drivers/acpid/src/acpi.rs -+++ b/drivers/acpid/src/acpi.rs -@@ -15,6 +15,7 @@ use common::io::{Io, Pio}; ++ }; - use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; - use thiserror::Error; -+use crate::sleep::{SleepPhase, SleepTarget}; + 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); ++ }); - use acpi::{ - aml::{namespace::AmlName, AmlError, Interpreter}, -@@ -952,7 +953,7 @@ pub struct AcpiContext { - fadt: Option, - pm1a_cnt_blk: u64, - pm1b_cnt_blk: u64, -- s5_values: RwLock>, -+ sleep_values: RwLock>, - reset_reg: Option, - reset_value: u8, + println!(" + ac97 {}", pci_config.func.display()); -@@ -1240,7 +1241,7 @@ impl AcpiContext { - fadt, - pm1a_cnt_blk, - pm1b_cnt_blk, -- s5_values: RwLock::new(None), -+ sleep_values: RwLock::new(std::collections::BTreeMap::new()), - reset_reg, - reset_value, - pci_fd: RwLock::new(None), -@@ -1385,23 +1386,35 @@ impl AcpiContext { - /// - 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 (slp_typa, slp_typb) = if let Some(values) = *self.s5_values.read() { -- values -- } else { -- let Ok(values) = self.evaluate_acpi_method("\\", "_S5", &[]) else { -- log::error!("Cannot set S-state, failed to evaluate \\_S5"); -- return; -- }; -- if values.len() < 2 { -- log::error!("Cannot set S-state, \\_S5 package too small"); -- return; -- } -- let values = (values[0] as u8, values[1] as u8); -- *self.s5_values.write() = Some(values); -- values -- }; -+ let target = match SleepTarget::try_from(state) { -+ Ok(target) => target, -+ Err(_) => { -+ log::error!("Cannot set S-state {state}, unsupported target"); -+ return; +@@ -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); + } -+ }; -+ -+ if !target.is_soft_off() { -+ log::warn!( -+ "ACPI sleep groundwork only: {} is recognized but not implemented yet", -+ target.aml_method_name() -+ ); -+ return; + } -+ -+ log::info!("acpid: {:?} {}", SleepPhase::Prepare, target.aml_method_name()); -+ -+ let (slp_typa, slp_typb) = match self.sleep_type_values(target) { -+ Some(values) => values, -+ None => return, -+ }; ++ }; + let mut readiness_based = ReadinessBased::new(&socket, 16); - let mut val = 1 << 13; - log::trace!("Shutdown SLP_TYPa {:X}, SLP_TYPb {:X}", slp_typa, slp_typb); -@@ -1412,6 +1425,7 @@ impl AcpiContext { - { - if self.pm1a_cnt_blk != 0 { - let port = self.pm1a_cnt_blk as u16; -+ log::info!("acpid: {:?} {} via PM1a", SleepPhase::Enter, target.aml_method_name()); - log::warn!("Shutdown with ACPI outw(0x{:X}, 0x{:X})", port, val); - Pio::::new(port).write(val); - } -@@ -1419,6 +1433,7 @@ impl AcpiContext { - if self.pm1b_cnt_blk != 0 { - let mut val_b = 1 << 13; - val_b |= u16::from(slp_typb); -+ log::info!("acpid: {:?} {} via PM1b", SleepPhase::Enter, target.aml_method_name()); - let port = self.pm1b_cnt_blk as u16; - log::warn!("Shutdown with ACPI outw(0x{:X}, 0x{:X})", port, val_b); - Pio::::new(port).write(val_b); -@@ -1438,6 +1453,23 @@ impl AcpiContext { + user_data! { +@@ -56,49 +94,81 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { } } -+ fn sleep_type_values(&self, target: SleepTarget) -> Option<(u8, u8)> { -+ if let Some(values) = self.sleep_values.read().get(&target).copied() { -+ return Some(values); +- 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); + } -+ -+ let method_name = target.aml_method_name(); -+ let Ok(values) = self.evaluate_acpi_method("\\", method_name, &[]) else { -+ log::error!("Cannot set S-state, failed to evaluate \\{method_name}"); -+ return None; -+ }; -+ if values.len() < 2 { -+ log::error!("Cannot set S-state, \\{method_name} package too small"); -+ return None; -+ } -+ let values = (values[0] as u8, values[1] as u8); -+ self.sleep_values.write().insert(target, values); -+ Some(values) -+ } -+ - pub fn acpi_reboot(&self) { - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - if let Some(reset_reg) = &self.reset_reg { -diff --git a/drivers/graphics/ihdgd/config.toml b/drivers/graphics/ihdgd/config.toml -diff --git a/drivers/storage/usbscsid/src/quirks.rs b/drivers/storage/usbscsid/src/quirks.rs -index 5051f1b0..ae4c2e46 100644 ---- a/drivers/storage/usbscsid/src/quirks.rs -+++ b/drivers/storage/usbscsid/src/quirks.rs -@@ -128,7 +128,7 @@ fn quirk_files() -> Option> { - } - - fn parse_runtime_quirks_from_toml(text: &str) -> Vec { -- let Ok(value) = text.parse::() else { -+ let Ok(value) = text.trim().parse::() else { - return Vec::new(); - }; -@@ -201,13 +201,7 @@ mod tests { - #[test] - fn runtime_toml_parser_keeps_supported_flags_and_skips_unknown_ones() { - let entries = parse_runtime_quirks_from_toml( -- r#" -- [[usb_storage_quirk]] -- vendor = 4660 -- product = 22136 -- flags = ["ignore_residue", "unknown_flag", "fix_capacity"] -- "#, -+ "[[usb_storage_quirk]]\nvendor = 4660\nproduct = 22136\nflags = [\"ignore_residue\", \"unknown_flag\", \"fix_capacity\"]\n", - ); - - assert_eq!(entries.len(), 1); -diff --git a/drivers/storage/usbscsid/src/scsi/cmds.rs b/drivers/storage/usbscsid/src/scsi/cmds.rs -index ddc12336..df5d0a0c 100644 ---- a/drivers/storage/usbscsid/src/scsi/cmds.rs -+++ b/drivers/storage/usbscsid/src/scsi/cmds.rs -@@ -265,6 +265,57 @@ impl Write10 { - } - } - -+#[repr(C, packed)] -+#[derive(Clone, Copy, Debug)] -+pub struct SynchronizeCache10 { -+ pub opcode: u8, -+ pub a: u8, -+ pub lba: u32, -+ pub group_num: u8, -+ pub blocks: u16, -+ pub control: u8, -+} -+unsafe impl plain::Plain for SynchronizeCache10 {} -+ -+impl SynchronizeCache10 { -+ pub const fn new(lba: u64, blocks: u16, control: u8) -> Self { -+ Self { -+ opcode: Opcode::SyncCache10 as u8, -+ a: 0, -+ lba: u32::to_be(lba as u32), -+ group_num: 0, -+ blocks: u16::to_be(blocks), -+ control, -+ } -+ } -+} -+ -+#[repr(C, packed)] -+#[derive(Clone, Copy, Debug)] -+pub struct SynchronizeCache16 { -+ pub opcode: u8, -+ pub a: u8, -+ pub lba: u64, -+ pub blocks: u32, -+ pub group_num: u8, -+ pub control: u8, -+} -+unsafe impl plain::Plain for SynchronizeCache16 {} -+ -+impl SynchronizeCache16 { -+ pub const fn new(lba: u64, blocks: u32, control: u8) -> Self { -+ Self { -+ opcode: Opcode::SyncCache16 as u8, -+ a: 0, -+ lba: u64::to_be(lba), -+ blocks: u32::to_be(blocks), -+ group_num: 0, -+ control, -+ } -+ } -+} -+ - #[repr(C, packed)] - #[derive(Clone, Copy, Debug)] - pub struct ModeSense6 { -diff --git a/drivers/storage/usbscsid/src/scsi/mod.rs b/drivers/storage/usbscsid/src/scsi/mod.rs -index b6d379d0..cf4a9707 100644 ---- a/drivers/storage/usbscsid/src/scsi/mod.rs -+++ b/drivers/storage/usbscsid/src/scsi/mod.rs -@@ -25,6 +25,8 @@ const REQUEST_SENSE_CMD_LEN: u8 = 6; - const MIN_INQUIRY_ALLOC_LEN: u16 = 5; - const MIN_REPORT_SUPP_OPCODES_ALLOC_LEN: u32 = 4; - const MAX_SECTORS_64_LIMIT: u64 = 64; -+const SYNC_CACHE10_CMD_LEN: usize = 10; -+const SYNC_CACHE16_CMD_LEN: usize = 16; -@@ -286,6 +288,12 @@ impl Scsi { - pub fn cmd_write10(&mut self) -> Result<&mut cmds::Write10> { - parse_mut_bytes("WRITE(10) command", &mut self.command_buffer) - } -+ pub fn cmd_sync_cache10(&mut self) -> Result<&mut cmds::SynchronizeCache10> { -+ parse_mut_bytes("SYNCHRONIZE CACHE(10) command", &mut self.command_buffer) -+ } -+ pub fn cmd_sync_cache16(&mut self) -> Result<&mut cmds::SynchronizeCache16> { -+ parse_mut_bytes("SYNCHRONIZE CACHE(16) command", &mut self.command_buffer) -+ } - pub fn res_standard_inquiry_data(&self) -> Result<&StandardInquiryData> { - parse_bytes("standard inquiry data", &self.inquiry_buffer) - } -@@ -467,6 +475,10 @@ impl Scsi { - let status = protocol.send_command( - &self.command_buffer[..10], - DeviceReqData::Out(&buffer[..bytes_to_write]), - )?; -+ if self.quirks.contains(UsbStorageQuirkFlags::NEEDS_SYNC_CACHE) -+ && status.kind == SendCommandStatusKind::Success -+ { -+ self.sync_cache(protocol, lba, blocks_to_write)?; -+ } - Ok(status.bytes_transferred(bytes_to_write as u32)) - } else { -@@ -482,8 +494,83 @@ impl Scsi { - let status = protocol.send_command( - &self.command_buffer[..16], - DeviceReqData::Out(&buffer[..bytes_to_write]), - )?; -+ if self.quirks.contains(UsbStorageQuirkFlags::NEEDS_SYNC_CACHE) -+ && status.kind == SendCommandStatusKind::Success -+ { -+ self.sync_cache(protocol, lba, blocks_to_write)?; -+ } - Ok(status.bytes_transferred(bytes_to_write as u32)) - } - } -+ -+ fn sync_cache(&mut self, protocol: &mut dyn Protocol, lba: u64, blocks: u64) -> Result<()> { -+ let use_sync_cache10 = self.quirks.contains(UsbStorageQuirkFlags::INITIAL_READ10) -+ && u32::try_from(lba).is_ok() -+ && u16::try_from(blocks).is_ok(); -+ -+ let status = if use_sync_cache10 { -+ let sync = self.cmd_sync_cache10()?; -+ *sync = cmds::SynchronizeCache10::new( -+ lba, -+ u16::try_from(blocks) -+ .map_err(|_| ScsiError::Overflow("sync cache(10) block count overflow"))?, -+ 0, -+ ); -+ protocol.send_command(&self.command_buffer[..SYNC_CACHE10_CMD_LEN], DeviceReqData::NoData)? -+ } else { -+ let sync = self.cmd_sync_cache16()?; -+ *sync = cmds::SynchronizeCache16::new( -+ lba, -+ u32::try_from(blocks) -+ .map_err(|_| ScsiError::Overflow("sync cache(16) block count overflow"))?, -+ 0, -+ ); -+ protocol.send_command(&self.command_buffer[..SYNC_CACHE16_CMD_LEN], DeviceReqData::NoData)? -+ }; -+ -+ if status.kind == SendCommandStatusKind::Success { -+ return Ok(()); -+ } -+ -+ if let Ok(()) = self.get_ff_sense(protocol, cmds::RequestSense::MINIMAL_ALLOC_LEN) { -+ if let Ok(sense) = self.res_ff_sense_data() { -+ if sense.add_sense_code == 0x3A -+ || sense.add_sense_code == 0x20 -+ || (sense.add_sense_code == 0x04 && sense.add_sense_code_qual == 0x04) -+ || sense.sense_key() == cmds::SenseKey::IllegalRequest -+ { -+ return Ok(()); -+ } -+ } -+ } -+ -+ Err(ScsiError::ProtocolError(ProtocolError::ProtocolError( -+ "SYNCHRONIZE CACHE command failed", -+ ))) -+ } - } -@@ -527,3 +614,53 @@ impl<'a> BlkDescSlice<'a> { - } - } - } -+ -+#[cfg(test)] -+mod tests { -+ use super::*; -+ use crate::protocol::SendCommandStatus; -+ use crate::scsi::opcodes::Opcode; -+ -+ struct MockProtocol { -+ commands: Vec>, -+ } -+ -+ impl MockProtocol { -+ fn new() -> Self { -+ Self { commands: Vec::new() } -+ } -+ } -+ -+ impl Protocol for MockProtocol { -+ fn send_command(&mut self, command: &[u8], _data: DeviceReqData) -> std::result::Result { -+ self.commands.push(command.to_vec()); -+ Ok(SendCommandStatus { residue: None, kind: SendCommandStatusKind::Success }) -+ } -+ } -+ -+ fn scsi_for_tests(quirks: UsbStorageQuirkFlags) -> Scsi { -+ Scsi { -+ command_buffer: [0u8; 16], -+ inquiry_buffer: [0u8; 259], -+ data_buffer: Vec::new(), -+ block_size: 512, -+ block_count: 1024, -+ quirks, -+ } -+ } -+ -+ #[test] -+ fn sync_cache_uses_10_byte_command_for_initial_read10_quirk() { -+ let mut scsi = scsi_for_tests(UsbStorageQuirkFlags::INITIAL_READ10); -+ let mut protocol = MockProtocol::new(); -+ scsi.sync_cache(&mut protocol, 7, 4).unwrap(); -+ assert_eq!(protocol.commands.len(), 1); -+ assert_eq!(protocol.commands[0].len(), 10); -+ assert_eq!(protocol.commands[0][0], Opcode::SyncCache10 as u8); -+ } -+ -+ #[test] -+ fn sync_cache_uses_16_byte_command_without_initial_read10_quirk() { -+ let mut scsi = scsi_for_tests(UsbStorageQuirkFlags::empty()); -+ let mut protocol = MockProtocol::new(); -+ scsi.sync_cache(&mut protocol, 7, 4).unwrap(); -+ assert_eq!(protocol.commands.len(), 1); -+ assert_eq!(protocol.commands[0].len(), 16); -+ assert_eq!(protocol.commands[0][0], Opcode::SyncCache16 as u8); -+ } -+} - -diff --git a/drivers/storage/usbscsid/src/quirks.rs b/drivers/storage/usbscsid/src/quirks.rs -index ae4c2e46..30380aea 100644 ---- a/drivers/storage/usbscsid/src/quirks.rs -+++ b/drivers/storage/usbscsid/src/quirks.rs -@@ -128,7 +128,7 @@ fn quirk_files() -> Option> { - } - - fn parse_runtime_quirks_from_toml(text: &str) -> Vec { -- let Ok(value) = text.parse::() else { -+ let Ok(value) = text.trim().parse::
() else { - return Vec::new(); - }; -@@ -201,13 +201,7 @@ mod tests { - #[test] - fn runtime_toml_parser_keeps_supported_flags_and_skips_unknown_ones() { - let entries = parse_runtime_quirks_from_toml( -- r#" -- [[usb_storage_quirk]] -- vendor = 4660 -- product = 22136 -- flags = ["ignore_residue", "unknown_flag", "fix_capacity"] -- "#, -+ "[[usb_storage_quirk]]\nvendor = 4660\nproduct = 22136\nflags = [\"ignore_residue\", \"unknown_flag\", \"fix_capacity\"]\n", - ); - - assert_eq!(entries.len(), 1); -diff --git a/drivers/storage/usbscsid/src/scsi/cmds.rs b/drivers/storage/usbscsid/src/scsi/cmds.rs -index df5d0a0c..0674c6b5 100644 ---- a/drivers/storage/usbscsid/src/scsi/cmds.rs -+++ b/drivers/storage/usbscsid/src/scsi/cmds.rs -@@ -265,6 +265,57 @@ impl Write10 { - } - } - -+#[repr(C, packed)] -+#[derive(Clone, Copy, Debug)] -+pub struct SynchronizeCache10 { -+ pub opcode: u8, -+ pub a: u8, -+ pub lba: u32, -+ pub group_num: u8, -+ pub blocks: u16, -+ pub control: u8, -+} -+unsafe impl plain::Plain for SynchronizeCache10 {} -+ -+impl SynchronizeCache10 { -+ pub const fn new(lba: u64, blocks: u16, control: u8) -> Self { -+ Self { -+ opcode: Opcode::SyncCache10 as u8, -+ a: 0, -+ lba: u32::to_be(lba as u32), -+ group_num: 0, -+ blocks: u16::to_be(blocks), -+ control, -+ } -+ } -+} -+ -+#[repr(C, packed)] -+#[derive(Clone, Copy, Debug)] -+pub struct SynchronizeCache16 { -+ pub opcode: u8, -+ pub a: u8, -+ pub lba: u64, -+ pub blocks: u32, -+ pub group_num: u8, -+ pub control: u8, -+} -+unsafe impl plain::Plain for SynchronizeCache16 {} -+ -+impl SynchronizeCache16 { -+ pub const fn new(lba: u64, blocks: u32, control: u8) -> Self { -+ Self { -+ opcode: Opcode::SyncCache16 as u8, -+ a: 0, -+ lba: u64::to_be(lba), -+ blocks: u32::to_be(blocks), -+ group_num: 0, -+ control, -+ } -+ } -+} -+ - #[repr(C, packed)] - #[derive(Clone, Copy, Debug)] - pub struct ModeSense6 { -diff --git a/drivers/storage/usbscsid/src/scsi/mod.rs b/drivers/storage/usbscsid/src/scsi/mod.rs -index cf4a9707..ad0565ca 100644 ---- a/drivers/storage/usbscsid/src/scsi/mod.rs -+++ b/drivers/storage/usbscsid/src/scsi/mod.rs -@@ -25,6 +25,8 @@ const REQUEST_SENSE_CMD_LEN: u8 = 6; - const MIN_INQUIRY_ALLOC_LEN: u16 = 5; - const MIN_REPORT_SUPP_OPCODES_ALLOC_LEN: u32 = 4; - const MAX_SECTORS_64_LIMIT: u64 = 64; -+const SYNC_CACHE10_CMD_LEN: usize = 10; -+const SYNC_CACHE16_CMD_LEN: usize = 16; -@@ -286,6 +288,12 @@ impl Scsi { - pub fn cmd_write10(&mut self) -> Result<&mut cmds::Write10> { - parse_mut_bytes("WRITE(10) command", &mut self.command_buffer) - } -+ pub fn cmd_sync_cache10(&mut self) -> Result<&mut cmds::SynchronizeCache10> { -+ parse_mut_bytes("SYNCHRONIZE CACHE(10) command", &mut self.command_buffer) -+ } -+ pub fn cmd_sync_cache16(&mut self) -> Result<&mut cmds::SynchronizeCache16> { -+ parse_mut_bytes("SYNCHRONIZE CACHE(16) command", &mut self.command_buffer) -+ } - pub fn res_standard_inquiry_data(&self) -> Result<&StandardInquiryData> { - parse_bytes("standard inquiry data", &self.inquiry_buffer) - } -@@ -467,6 +475,10 @@ impl Scsi { - let status = protocol.send_command( - &self.command_buffer[..10], - DeviceReqData::Out(&buffer[..bytes_to_write]), - )?; -+ if self.quirks.contains(UsbStorageQuirkFlags::NEEDS_SYNC_CACHE) -+ && status.kind == SendCommandStatusKind::Success -+ { -+ self.sync_cache(protocol, lba, blocks_to_write)?; -+ } - Ok(status.bytes_transferred(bytes_to_write as u32)) - } else { -@@ -482,8 +494,83 @@ impl Scsi { - let status = protocol.send_command( - &self.command_buffer[..16], - DeviceReqData::Out(&buffer[..bytes_to_write]), - )?; -+ if self.quirks.contains(UsbStorageQuirkFlags::NEEDS_SYNC_CACHE) -+ && status.kind == SendCommandStatusKind::Success -+ { -+ self.sync_cache(protocol, lba, blocks_to_write)?; -+ } - Ok(status.bytes_transferred(bytes_to_write as u32)) - } - } -+ -+ fn sync_cache(&mut self, protocol: &mut dyn Protocol, lba: u64, blocks: u64) -> Result<()> { -+ let use_sync_cache10 = self.quirks.contains(UsbStorageQuirkFlags::INITIAL_READ10) -+ && u32::try_from(lba).is_ok() -+ && u16::try_from(blocks).is_ok(); -+ -+ let status = if use_sync_cache10 { -+ let sync = self.cmd_sync_cache10()?; -+ *sync = cmds::SynchronizeCache10::new( -+ lba, -+ u16::try_from(blocks) -+ .map_err(|_| ScsiError::Overflow("sync cache(10) block count overflow"))?, -+ 0, -+ ); -+ protocol.send_command(&self.command_buffer[..SYNC_CACHE10_CMD_LEN], DeviceReqData::NoData)? -+ } else { -+ let sync = self.cmd_sync_cache16()?; -+ *sync = cmds::SynchronizeCache16::new( -+ lba, -+ u32::try_from(blocks) -+ .map_err(|_| ScsiError::Overflow("sync cache(16) block count overflow"))?, -+ 0, -+ ); -+ protocol.send_command(&self.command_buffer[..SYNC_CACHE16_CMD_LEN], DeviceReqData::NoData)? -+ }; -+ -+ if status.kind == SendCommandStatusKind::Success { -+ return Ok(()); -+ } -+ -+ if let Ok(()) = self.get_ff_sense(protocol, cmds::RequestSense::MINIMAL_ALLOC_LEN) { -+ if let Ok(sense) = self.res_ff_sense_data() { -+ if sense.add_sense_code == 0x3A -+ || sense.add_sense_code == 0x20 -+ || (sense.add_sense_code == 0x04 && sense.add_sense_code_qual == 0x04) -+ || sense.sense_key() == cmds::SenseKey::IllegalRequest -+ { -+ return Ok(()); -+ } -+ } -+ } -+ -+ Err(ScsiError::ProtocolError(ProtocolError::ProtocolError( -+ "SYNCHRONIZE CACHE command failed", -+ ))) -+ } - } -@@ -527,3 +614,53 @@ impl<'a> BlkDescSlice<'a> { - } - } - } -+ -+#[cfg(test)] -+mod tests { -+ use super::*; -+ use crate::protocol::SendCommandStatus; -+ use crate::scsi::opcodes::Opcode; -+ -+ struct MockProtocol { -+ commands: Vec>, -+ } -+ -+ impl MockProtocol { -+ fn new() -> Self { -+ Self { commands: Vec::new() } -+ } -+ } -+ -+ impl Protocol for MockProtocol { -+ fn send_command(&mut self, command: &[u8], _data: DeviceReqData) -> std::result::Result { -+ self.commands.push(command.to_vec()); -+ Ok(SendCommandStatus { residue: None, kind: SendCommandStatusKind::Success }) -+ } -+ } -+ -+ fn scsi_for_tests(quirks: UsbStorageQuirkFlags) -> Scsi { -+ Scsi { -+ command_buffer: [0u8; 16], -+ inquiry_buffer: [0u8; 259], -+ data_buffer: Vec::new(), -+ block_size: 512, -+ block_count: 1024, -+ quirks, -+ } -+ } -+ -+ #[test] -+ fn sync_cache_uses_10_byte_command_for_initial_read10_quirk() { -+ let mut scsi = scsi_for_tests(UsbStorageQuirkFlags::INITIAL_READ10); -+ let mut protocol = MockProtocol::new(); -+ scsi.sync_cache(&mut protocol, 7, 4).unwrap(); -+ assert_eq!(protocol.commands.len(), 1); -+ assert_eq!(protocol.commands[0].len(), 10); -+ assert_eq!(protocol.commands[0][0], Opcode::SyncCache10 as u8); -+ } -+ -+ #[test] -+ fn sync_cache_uses_16_byte_command_without_initial_read10_quirk() { -+ let mut scsi = scsi_for_tests(UsbStorageQuirkFlags::empty()); -+ let mut protocol = MockProtocol::new(); -+ scsi.sync_cache(&mut protocol, 7, 4).unwrap(); -+ assert_eq!(protocol.commands.len(), 1); -+ assert_eq!(protocol.commands[0].len(), 16); -+ assert_eq!(protocol.commands[0][0], Opcode::SyncCache16 as u8); -+ } -+} - -diff --git a/drivers/usb/xhcid/src/xhci/device_enumerator.rs b/drivers/usb/xhcid/src/xhci/device_enumerator.rs -index 1f144ac9..00000000 100644 ---- a/drivers/usb/xhcid/src/xhci/device_enumerator.rs -+++ b/drivers/usb/xhcid/src/xhci/device_enumerator.rs -@@ -4,8 +4,12 @@ use crate::xhci::{PortId, Xhci}; - use common::io::Io; - use crossbeam_channel; - use log::{debug, info, warn}; - use std::sync::Arc; --use std::time::Duration; -+use std::time::{Duration, Instant}; - use syscall::EAGAIN; -+ -+const DEFAULT_PORT_RESET_SETTLE_MS: u64 = 16; -+const RESET_DELAY_PORT_RESET_SETTLE_MS: u64 = 100; -+const HUB_SLOW_RESET_PORT_RESET_SETTLE_MS: u64 = 200; - - pub struct DeviceEnumerationRequest { - pub port_id: PortId, -@@ -25,10 +29,14 @@ impl DeviceEnumerator { - loop { - debug!("Start Device Enumerator Loop"); - let request = match self.request_queue.recv() { - Ok(req) => req, - Err(err) => { -- panic!("Failed to received an enumeration request! error: {}", err) -+ warn!( -+ "device enumerator stopping after request queue closed: {}", -+ err -+ ); -+ break; - } - }; - -@@ -64,13 +72,12 @@ impl DeviceEnumerator { - //If the port isn't enabled (i.e. it's a USB2 port), we need to reset it if it isn't resetting already - //A USB3 port won't generate a Connect Status Change until it's already enabled, so this check - //will always be skipped for USB3 ports - if !flags.contains(PortFlags::PED) { -- let disabled_state = flags.contains(PortFlags::PP) -- && flags.contains(PortFlags::CCS) -- && !flags.contains(PortFlags::PED) -- && !flags.contains(PortFlags::PR); -+ let disabled_state = Self::port_is_disabled(&flags); - - if !disabled_state { -- panic!( -- "Port {} isn't in the disabled state! Current flags: {:?}", -+ warn!( -+ "Port {} never reached the disabled state before reset-driven enumeration; current flags: {:?}", - port_id, flags - ); -+ continue; - } else { - debug!("Port {} has entered the disabled state.", port_id); - } -@@ -89,17 +96,15 @@ impl DeviceEnumerator { - - port.clear_prc(); - -- let delay_ms = if early_quirks -- .contains(crate::usb_quirks::UsbQuirkFlags::HUB_SLOW_RESET) -- { -- 200 -- } else if early_quirks.contains(crate::usb_quirks::UsbQuirkFlags::RESET_DELAY) { -- 100 -- } else { -- 16 -- }; ++ }; + 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(); - -- std::thread::sleep(Duration::from_millis(delay_ms)); // Some devices need extra time to settle after reset. +- 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); + } -+ -+ let flags = self.wait_for_port_enabled_state( -+ port_array_index, -+ Duration::from_millis(Self::port_reset_settle_delay_ms(early_quirks)), -+ ); -- let flags = port.flags(); -- -- let enabled_state = flags.contains(PortFlags::PP) -- && flags.contains(PortFlags::CCS) -- && flags.contains(PortFlags::PED) -- && !flags.contains(PortFlags::PR); -+ let enabled_state = Self::port_is_enabled(&flags); - - if !enabled_state { - warn!( -- "Port {} isn't in the enabled state! Current flags: {:?}", -+ "Port {} isn't in the enabled state after bounded reset settle; current flags: {:?}", - port_id, flags - ); -+ continue; - } else { -@@ -140,4 +145,47 @@ impl DeviceEnumerator { - } - } - } -+ -+ fn port_reset_settle_delay_ms(quirks: crate::usb_quirks::UsbQuirkFlags) -> u64 { -+ if quirks.contains(crate::usb_quirks::UsbQuirkFlags::HUB_SLOW_RESET) { -+ HUB_SLOW_RESET_PORT_RESET_SETTLE_MS -+ } else if quirks.contains(crate::usb_quirks::UsbQuirkFlags::RESET_DELAY) { -+ RESET_DELAY_PORT_RESET_SETTLE_MS -+ } else { -+ DEFAULT_PORT_RESET_SETTLE_MS -+ } -+ } -+ -+ fn port_is_disabled(flags: &PortFlags) -> bool { -+ flags.contains(PortFlags::PP) -+ && flags.contains(PortFlags::CCS) -+ && !flags.contains(PortFlags::PED) -+ && !flags.contains(PortFlags::PR) -+ } -+ -+ fn port_is_enabled(flags: &PortFlags) -> bool { -+ flags.contains(PortFlags::PP) -+ && flags.contains(PortFlags::CCS) -+ && flags.contains(PortFlags::PED) -+ && !flags.contains(PortFlags::PR) -+ } -+ -+ fn wait_for_port_enabled_state( -+ &self, -+ port_array_index: usize, -+ settle_timeout: Duration, -+ ) -> PortFlags { -+ let start = Instant::now(); -+ -+ loop { -+ let flags = { -+ let ports = self -+ .hci -+ .ports -+ .lock() -+ .unwrap_or_else(|poisoned| poisoned.into_inner()); -+ ports[port_array_index].flags() -+ }; -+ -+ if Self::port_is_enabled(&flags) -+ || !flags.contains(PortFlags::PR) -+ || start.elapsed() >= settle_timeout -+ { -+ return flags; -+ } -+ -+ std::thread::sleep(Duration::from_millis(1)); -+ } -+ } -diff --git a/drivers/usb/xhcid/src/xhci/device_enumerator.rs b/drivers/usb/xhcid/src/xhci/device_enumerator.rs -index 00000000..00000000 100644 ---- a/drivers/usb/xhcid/src/xhci/device_enumerator.rs -+++ b/drivers/usb/xhcid/src/xhci/device_enumerator.rs -@@ -46,7 +46,11 @@ impl DeviceEnumerator { - debug!("Device Enumerator request for port {}", port_id); - - let (len, flags) = { -- let ports = self.hci.ports.lock().unwrap(); -+ let ports = self -+ .hci -+ .ports -+ .lock() -+ .unwrap_or_else(|poisoned| poisoned.into_inner()); - - let len = ports.len(); - -@@ -86,7 +90,11 @@ impl DeviceEnumerator { - debug!("Received a device connect on port {}, but it's not enabled. Resetting the port.", port_id); - if let Err(err) = self.hci.reset_port(port_id) { - warn!( - "failed to reset port {} before enumeration; skipping attach: {}", - port_id, err - ); + 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); ++ } -- let mut ports = self.hci.ports.lock().unwrap(); -+ let mut ports = self -+ .hci -+ .ports -+ .lock() -+ .unwrap_or_else(|poisoned| poisoned.into_inner()); - let port = &mut ports[port_array_index]; + 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); ++ }); - port.clear_prc(); -@@ -144,10 +152,16 @@ impl DeviceEnumerator { - let result = futures::executor::block_on(self.hci.detach_device(port_id)); - match result { - Ok(was_connected) => { - if was_connected { - info!("Device on port {} was detached", port_id); -+ } else { -+ debug!( -+ "Ignoring duplicate or out-of-order detach event for unattached port {}", -+ port_id -+ ); - } - } - Err(err) => { -- warn!("processing of device attach request failed! Error: {}", err); -+ warn!("processing of device detach request failed! Error: {}", err); - } - } -diff --git a/drivers/usb/xhcid/src/xhci/mod.rs b/drivers/usb/xhcid/src/xhci/mod.rs -index c53cb59f..814fdb4f 100644 ---- a/drivers/usb/xhcid/src/xhci/mod.rs -+++ b/drivers/usb/xhcid/src/xhci/mod.rs -@@ -307,6 +307,7 @@ struct PortState { - slot: u8, - protocol_speed: &'static ProtocolSpeed, - cfg_idx: Option, -+ active_alternates: BTreeMap, - input_context: Mutex>>, - dev_desc: Option, - endpoint_states: BTreeMap, -@@ -324,29 +325,37 @@ pub(crate) enum PortPmState { - impl PortState { - //TODO: fetch using endpoint number instead - fn get_endp_desc(&self, endp_idx: u8) -> Option<&EndpDesc> { -- let cfg_idx = self.cfg_idx?; -- let config_desc = self -- .dev_desc -- .as_ref()? -- .config_descs -- .iter() -- .find(|desc| desc.configuration_value == cfg_idx)?; -- let mut endp_count = 0; -- for if_desc in config_desc.interface_descs.iter() { -- let active_alternate = self -- .active_alternates -- .get(&if_desc.number) -- .copied() -- .unwrap_or(0); -- if if_desc.alternate_setting != active_alternate { -- continue; -- } -- for endp_desc in if_desc.endpoints.iter() { -- if endp_idx == endp_count { -- return Some(endp_desc); -- } -- endp_count += 1; -- } -- } -- None -+ active_endpoint_desc( -+ self.dev_desc.as_ref()?, -+ self.cfg_idx?, -+ &self.active_alternates, -+ endp_idx, -+ ) - } - } -+ -+fn active_configuration<'a>(dev_desc: &'a DevDesc, cfg_idx: u8) -> Option<&'a ConfDesc> { -+ dev_desc -+ .config_descs -+ .iter() -+ .find(|desc| desc.configuration_value == cfg_idx) -+} -+ -+fn active_endpoint_desc<'a>(dev_desc: &'a DevDesc, cfg_idx: u8, active_alternates: &BTreeMap, endp_idx: u8) -> Option<&'a EndpDesc> { -+ let config_desc = active_configuration(dev_desc, cfg_idx)?; -+ let mut endp_count = 0; -+ for if_desc in config_desc.interface_descs.iter() { -+ let active_alternate = active_alternates.get(&if_desc.number).copied().unwrap_or(0); -+ if if_desc.alternate_setting != active_alternate { -+ continue; -+ } -+ for endp_desc in if_desc.endpoints.iter() { -+ if endp_idx == endp_count { -+ return Some(endp_desc); -+ } -+ endp_count += 1; -+ } -+ } -+ None -+} -@@ -872,6 +881,7 @@ impl Xhci { - protocol_speed, - input_context: Mutex::new(input), - dev_desc: None, -+ active_alternates: BTreeMap::new(), - cfg_idx: None, - endpoint_states: std::iter::once(( - 0, -@@ -1516,6 +1526,67 @@ struct DriversConfig { - drivers: Vec, - } -+ -+#[cfg(test)] -+mod tests { -+ use super::{active_endpoint_desc, BTreeMap, ConfDesc, DevDesc, EndpDesc, IfDesc}; -+ use crate::driver_interface::EndpointTy; -+ use smallvec::smallvec; -+ -+ fn endp(address: u8, attributes: u8) -> EndpDesc { -+ EndpDesc { kind: 5, address, attributes, max_packet_size: 64, interval: 1, ssc: None, sspc: None } -+ } -+ -+ fn if_desc(number: u8, alternate_setting: u8, endpoints: Vec) -> IfDesc { -+ IfDesc { -+ kind: 4, -+ number, -+ alternate_setting, -+ class: 3, -+ sub_class: 1, -+ protocol: 1, -+ interface_str: None, -+ endpoints: endpoints.into_iter().collect(), -+ hid_descs: smallvec![], -+ } -+ } -+ -+ fn sample_dev_desc() -> DevDesc { -+ DevDesc { -+ kind: 1, -+ usb: 0x0200, -+ class: 0, -+ sub_class: 0, -+ protocol: 0, -+ packet_size: 64, -+ vendor: 0x1234, -+ product: 0x5678, -+ release: 0x0100, -+ manufacturer_str: None, -+ product_str: None, -+ serial_str: None, -+ config_descs: smallvec![ConfDesc { kind: 2, configuration_value: 1, configuration: None, attributes: 0x80, max_power: 50, interface_descs: smallvec![ if_desc(0, 0, vec![endp(0x81, 0x03)]), if_desc(0, 1, vec![endp(0x82, 0x03), endp(0x02, 0x03)]), if_desc(1, 0, vec![endp(0x83, 0x02)]), ], }], -+ } -+ } -+ -+ #[test] -+ fn active_endpoint_desc_uses_default_alternates_initially() { -+ let dev_desc = sample_dev_desc(); -+ let active = BTreeMap::new(); -+ let first = active_endpoint_desc(&dev_desc, 1, &active, 0).expect("endpoint 0"); -+ let second = active_endpoint_desc(&dev_desc, 1, &active, 1).expect("endpoint 1"); -+ assert_eq!(first.address, 0x81); -+ assert_eq!(first.ty(), EndpointTy::Interrupt); -+ assert_eq!(second.address, 0x83); -+ assert_eq!(second.ty(), EndpointTy::Bulk); -+ assert!(active_endpoint_desc(&dev_desc, 1, &active, 2).is_none()); -+ } -+ -+ #[test] -+ fn active_endpoint_desc_switches_to_selected_alternate() { -+ let dev_desc = sample_dev_desc(); -+ let mut active = BTreeMap::new(); -+ active.insert(0, 1); -+ let first = active_endpoint_desc(&dev_desc, 1, &active, 0).expect("endpoint 0"); -+ let second = active_endpoint_desc(&dev_desc, 1, &active, 1).expect("endpoint 1"); -+ let third = active_endpoint_desc(&dev_desc, 1, &active, 2).expect("endpoint 2"); -+ assert_eq!(first.address, 0x82); -+ assert_eq!(second.address, 0x02); -+ assert_eq!(third.address, 0x83); -+ } -+} - -diff --git a/drivers/usb/xhcid/src/xhci/scheme.rs b/drivers/usb/xhcid/src/xhci/scheme.rs ---- a/drivers/usb/xhcid/src/xhci/scheme.rs -+++ b/drivers/usb/xhcid/src/xhci/scheme.rs -@@ -3487,6 +3487,14 @@ impl EndpointContextSnapshot { - fn capture_values(a: u32, b: u32, trl: u32, trh: u32, c: u32) -> Self { - Self { a, b, trl, trh, c } - } -+ -+ fn restore(&self, ctx: &mut EndpointContext) { -+ ctx.a.write(self.a); -+ ctx.b.write(self.b); -+ ctx.trl.write(self.trl); -+ ctx.trh.write(self.trh); -+ ctx.c.write(self.c); -+ } - } -@@ -1171,7 +1171,9 @@ impl XhciScheme - input_context.device.slot.c.write(snapshot.slot_c); + /* + 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); ++ }); - for (endp_i, endp_snapshot) in endpoint_snapshots { -- endp_snapshot.restore(&mut input_context.device.endpoints[*endp_i]); -+ let endpoint_ptr = core::ptr::addr_of_mut!(input_context.device.endpoints[*endp_i]); -+ let mut endpoint = unsafe { core::ptr::read_unaligned(endpoint_ptr) }; -+ endp_snapshot.restore(&mut endpoint); -+ unsafe { core::ptr::write_unaligned(endpoint_ptr, endpoint) }; + /* + let next_read = device.borrow().next_read(); +@@ -125,7 +201,7 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { } + } - Ok(input_context.physical()) -index b0fb9b85..bba6f232 100644 ---- a/drivers/usb/xhcid/src/xhci/scheme.rs -+++ b/drivers/usb/xhcid/src/xhci/scheme.rs -@@ -1105,11 +1105,28 @@ impl Xhci { - .find(|desc| desc.configuration_value == req.config_desc) - .ok_or(Error::new(EBADFD))?; -+ let configuration_value = config_desc.configuration_value; -+ -+ let interface_layout = config_desc -+ .interface_descs -+ .iter() -+ .map(|if_desc| { -+ ( -+ if_desc.number, -+ if_desc.alternate_setting, -+ if_desc.endpoints.iter().map(|endp| *endp).collect::>(), -+ ) -+ }) -+ .collect::>(); +- std::process::exit(0); ++ std::process::exit(1); + } -- //TODO: USE ENDPOINTS FROM ALL INTERFACES -+ port_state.active_alternates.clear(); -+ for (if_num, _, _) in &interface_layout { -+ port_state.active_alternates.entry(*if_num).or_insert(0); -+ } -+ - 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() { -+ for (if_num, alternate_setting, endpoints) in &interface_layout { -+ let active_alternate = port_state.active_alternates.get(if_num).copied().unwrap_or(0); -+ if *alternate_setting != active_alternate { -+ continue; -+ } -+ for endpoint in endpoints.iter() { - endp_desc_count += 1; - let entry = Self::endp_num_to_dci(endp_desc_count, endpoint); - if entry > new_context_entries { -@@ -1128,7 +1145,7 @@ impl Xhci { - ( - endp_desc_count, - new_context_entries, -- config_desc.configuration_value, -+ configuration_value, - ) - }; -@@ -1397,6 +1414,10 @@ impl Xhci { - if !skip_set_interface { - self.set_interface(port, interface_num, alternate_setting) - .await?; -+ } -+ -+ if let Some(mut port_state) = self.port_states.get_mut(&port) { -+ port_state.active_alternates.insert(interface_num, alternate_setting); - } + #[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/config.toml b/drivers/graphics/ihdgd/config.toml index acbb4e78..210731ae 100644 --- a/drivers/graphics/ihdgd/config.toml @@ -2599,532 +3694,1800 @@ index acbb4e78..210731ae 100644 + 0x7D51, 0x7DD1, ] } command = ["ihdgd"] +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..ec8828ee 100644 +index 3da41d63..c24dfc4b 100644 --- a/drivers/hwd/src/backend/acpi.rs +++ b/drivers/hwd/src/backend/acpi.rs -@@ -1,5 +1,6 @@ - use amlserde::{AmlSerde, AmlSerdeValue}; - use std::{error::Error, fs, process::Command}; -+use std::{thread, time::Duration}; +@@ -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")); - use super::Backend; - -@@ -20,14 +21,57 @@ impl Backend for AcpiBackend { + 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")); - fn probe(&mut self) -> Result<(), Box> { -- // Read symbols from acpi scheme -- let entries = fs::read_dir("/scheme/acpi/symbols")?; -- // TODO: Reimplement with getdents? -- let symbols_fd = libredox::Fd::open( -- "/scheme/acpi/symbols", -- libredox::flag::O_DIRECTORY | libredox::flag::O_RDONLY, -- 0, -- )?; -+ const SYMBOL_RETRY_COUNT: usize = 20; -+ const SYMBOL_RETRY_DELAY: Duration = Duration::from_millis(100); -+ -+ let (entries, symbols_fd) = { -+ let mut last_error = None; -+ -+ let mut ready = None; -+ for attempt in 1..=SYMBOL_RETRY_COUNT { -+ match fs::read_dir("/scheme/acpi/symbols") { -+ Ok(entries) => match libredox::Fd::open( -+ "/scheme/acpi/symbols", -+ libredox::flag::O_DIRECTORY | libredox::flag::O_RDONLY, -+ 0, -+ ) { -+ Ok(symbols_fd) => { -+ ready = Some((entries, symbols_fd)); -+ break; -+ } -+ Err(err) => { -+ let message = format!("open failed: {err}"); -+ if attempt == 1 || attempt == SYMBOL_RETRY_COUNT { -+ log::warn!( -+ "ACPI symbols not ready yet (attempt {attempt}/{SYMBOL_RETRY_COUNT}): {message}" -+ ); -+ } -+ last_error = Some(message); -+ } -+ }, -+ Err(err) => { -+ let message = format!("read_dir failed: {err}"); -+ if attempt == 1 || attempt == SYMBOL_RETRY_COUNT { -+ log::warn!( -+ "ACPI symbols not ready yet (attempt {attempt}/{SYMBOL_RETRY_COUNT}): {message}" -+ ); -+ } -+ last_error = Some(message); -+ } -+ } -+ -+ if attempt != SYMBOL_RETRY_COUNT { -+ thread::sleep(SYMBOL_RETRY_DELAY); + 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); + } + } -+ -+ ready.ok_or_else(|| { -+ std::io::Error::other( -+ last_error.unwrap_or_else(|| "timed out waiting for ACPI symbols".to_string()), -+ ) -+ })? -+ }; -+ - for entry_res in entries { - let entry = entry_res?; - if let Some(file_name) = entry.file_name().to_str() { -diff --git a/drivers/input/ps2d/src/controller.rs b/drivers/input/ps2d/src/controller.rs -index d7af4cba..561aa527 100644 ---- a/drivers/input/ps2d/src/controller.rs -+++ b/drivers/input/ps2d/src/controller.rs -@@ -13,7 +13,7 @@ use common::io::Pio; - #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] - use common::io::Mmio; - --use log::{debug, error, info, trace, warn}; -+use log::{debug, error, trace, warn}; - - use std::fmt; - -@@ -271,6 +271,20 @@ impl Ps2 { + }, + daemon, + format!("network.{name}"), +@@ -51,7 +72,13 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { } } -+ pub fn probe(&mut self) -> bool { -+ let status = self.status(); -+ let status_bits = status.bits(); -+ -+ if status_bits == 0x00 || status_bits == 0xFF { -+ debug!( -+ "ps/2 controller probe returned suspicious status {:02X}", -+ status_bits -+ ); -+ } -+ -+ self.config().is_ok() -+ } -+ - pub fn init_keyboard(&mut self) -> Result<(), Error> { - let mut b; - -diff --git a/drivers/input/ps2d/src/main.rs b/drivers/input/ps2d/src/main.rs -index db17de2a..faa02e99 100644 ---- a/drivers/input/ps2d/src/main.rs -+++ b/drivers/input/ps2d/src/main.rs -@@ -20,7 +20,7 @@ mod mouse; - mod state; - mod vm; - --fn daemon(daemon: daemon::Daemon) -> ! { -+fn run() -> ! { - common::setup_logging( - "input", - "ps2", -@@ -29,9 +29,18 @@ fn daemon(daemon: daemon::Daemon) -> ! { - common::file_level(), - ); - -- acquire_port_io_rights().expect("ps2d: failed to get I/O permission"); -+ if let Err(err) = acquire_port_io_rights() { -+ log::error!("ps2d: failed to get I/O permission: {}", err); -+ process::exit(1); -+ } - -- let input = ProducerHandle::new().expect("ps2d: failed to open input producer"); -+ let input = match ProducerHandle::new() { -+ Ok(input) => input, +- 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!("ps2d: failed to open input producer: {}", err); -+ process::exit(1); -+ } -+ }; - - user_data! { - enum Source { -@@ -44,12 +53,19 @@ fn daemon(daemon: daemon::Daemon) -> ! { - let event_queue: EventQueue = - EventQueue::new().expect("ps2d: failed to create event queue"); - -- let mut key_file = OpenOptions::new() -+ let key_file = OpenOptions::new() - .read(true) - .write(true) - .custom_flags(syscall::O_NONBLOCK as i32) -- .open("/scheme/serio/0") -- .expect("ps2d: failed to open /scheme/serio/0"); -+ .open("/scheme/serio/0"); -+ -+ let mut key_file = match key_file { -+ Ok(key_file) => key_file, -+ Err(err) => { -+ log::error!("ps2d: failed to open /scheme/serio/0: {}", err); -+ process::exit(1); ++ log::error!("e1000d: failed to create event queue: {err}"); ++ std::process::exit(1); + } + }; event_queue .subscribe( -@@ -59,12 +75,19 @@ fn daemon(daemon: daemon::Daemon) -> ! { +@@ -59,32 +86,65 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { + Source::Irq, + event::EventFlags::READ, ) - .unwrap(); - -- let mut mouse_file = OpenOptions::new() -+ let mouse_file = OpenOptions::new() - .read(true) - .write(true) - .custom_flags(syscall::O_NONBLOCK as i32) -- .open("/scheme/serio/1") -- .expect("ps2d: failed to open /scheme/serio/1"); -+ .open("/scheme/serio/1"); -+ -+ let mut mouse_file = match mouse_file { -+ Ok(mouse_file) => mouse_file, -+ Err(err) => { -+ log::error!("ps2d: failed to open /scheme/serio/1: {}", err); -+ process::exit(1); -+ } -+ }; - +- .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( -@@ -78,8 +101,15 @@ fn daemon(daemon: daemon::Daemon) -> ! { - .read(true) - .write(true) - .custom_flags(syscall::O_NONBLOCK as i32) -- .open(format!("/scheme/time/{}", syscall::CLOCK_MONOTONIC)) -- .expect("ps2d: failed to open /scheme/time"); -+ .open(format!("/scheme/time/{}", syscall::CLOCK_MONOTONIC)); -+ -+ let time_file = match time_file { -+ Ok(time_file) => time_file, -+ Err(err) => { -+ log::error!("ps2d: failed to open /scheme/time: {}", err); -+ process::exit(1); -+ } -+ }; - - event_queue - .subscribe( -@@ -89,11 +119,15 @@ fn daemon(daemon: daemon::Daemon) -> ! { + scheme.event_handle().raw(), + Source::Scheme, + event::EventFlags::READ, ) - .unwrap(); - -- libredox::call::setrens(0, 0).expect("ps2d: failed to enter null namespace"); +- .expect("e1000d: failed to subscribe to scheme fd"); - -- daemon.ready(); +- 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!("ps2d: failed to enter null namespace: {}", err); -+ process::exit(1); ++ log::error!("e1000d: failed to enter null namespace: {err}"); ++ std::process::exit(1); + } -- let mut ps2d = Ps2d::new(input, time_file); -+ let Some(mut ps2d) = Ps2d::new(input, time_file) else { -+ log::warn!("ps2d: no PS/2 hardware available, exiting"); -+ process::exit(0); +- 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 mut data = [0; 256]; - for event in event_queue.map(|e| e.expect("ps2d: failed to get next event").user_data) { -@@ -131,5 +165,5 @@ fn daemon(daemon: daemon::Daemon) -> ! { +- 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() { -- daemon::Daemon::new(daemon); -+ run(); - } -diff --git a/drivers/input/ps2d/src/state.rs b/drivers/input/ps2d/src/state.rs -index 9018dc6b..2721c4fd 100644 ---- a/drivers/input/ps2d/src/state.rs -+++ b/drivers/input/ps2d/src/state.rs -@@ -59,9 +59,18 @@ pub struct Ps2d { - } +@@ -55,13 +55,31 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - impl Ps2d { -- pub fn new(input: ProducerHandle, time_file: File) -> Self { -+ pub fn new(input: ProducerHandle, time_file: File) -> Option { - let mut ps2 = Ps2::new(); -- ps2.init().expect("failed to initialize"); -+ -+ if !ps2.probe() { -+ warn!("ps2d: no PS/2 controller detected, skipping initialization"); -+ return None; + 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); + } -+ -+ if let Err(err) = ps2.init() { -+ error!("ps2d: failed to initialize PS/2 controller: {:?}", err); -+ return None; ++ }; + +- 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); + } ++ }; - // FIXME add an option for orbital to disable this when an app captures the mouse. - let vmmouse_relative = false; -@@ -70,7 +79,7 @@ impl Ps2d { - // TODO: QEMU hack, maybe do this when Init timed out? - if vmmouse { - // 3 = MouseId::Intellimouse1 -- MouseState::Bat.handle(3, &mut ps2); -+ let _ = MouseState::Bat.handle(3, &mut ps2); - } - - let mut this = Ps2d { -@@ -96,7 +105,7 @@ impl Ps2d { - this.handle_mouse(None); - } - -- this -+ Some(this) - } - - pub fn irq(&mut self) { -diff --git a/drivers/input/usbhidd/src/main.rs b/drivers/input/usbhidd/src/main.rs -diff --git a/drivers/input/ps2d/src/controller.rs b/drivers/input/ps2d/src/controller.rs -index 561aa527..0310a367 100644 ---- a/drivers/input/ps2d/src/controller.rs -+++ b/drivers/input/ps2d/src/controller.rs -@@ -283,8 +283,27 @@ impl Ps2 { - status_bits - ); - } - -+ let flushed = self.flush_output(); -+ if flushed != 0 { -+ debug!("ps/2 controller probe drained {} stale byte(s)", flushed); -+ } - - self.config().is_ok() - } -+ -+ pub fn flush_output(&mut self) -> usize { -+ let mut flushed = 0; -+ while let Some((keyboard, data)) = self.next() { -+ flushed += 1; -+ trace!( -+ "ps/2 flush discarded {:02X} from {} channel", -+ data, -+ if keyboard { "keyboard" } else { "mouse" } -+ ); -+ } -+ flushed -+ } - - pub fn init_keyboard(&mut self) -> Result<(), Error> { - let mut b; -@@ -325,6 +344,11 @@ impl Ps2 { - self.command(Command::DisableSecond)?; - } - -+ let flushed = self.flush_output(); -+ if flushed != 0 { -+ debug!("ps/2 init discarded {} stale byte(s) before config", flushed); -+ } -+ - // Disable clocks, disable interrupts, and disable translate - { - // Since the default config may have interrupts enabled, and the kernel may eat up -@@ -358,6 +382,11 @@ impl Ps2 { - warn!("self test unexpected value: {:02X}", r); - } - } -+ -+ let flushed = self.flush_output(); -+ if flushed != 0 { -+ debug!("ps/2 init discarded {} byte(s) after controller self-test", flushed); -+ } - - // Initialize keyboard - if let Err(err) = self.init_keyboard() { - -diff --git a/drivers/input/usbhidd/src/main.rs b/drivers/input/usbhidd/src/main.rs -index 15c5b778..c67fb8bc 100644 ---- a/drivers/input/usbhidd/src/main.rs -+++ b/drivers/input/usbhidd/src/main.rs -@@ -159,17 +159,17 @@ fn main() -> Result<()> { - - const USAGE: &'static str = "usbhidd "; - -- let scheme = args.next().expect(USAGE); -+ let scheme = args.next().ok_or_else(|| anyhow::anyhow!(USAGE))?; - let port = args - .next() -- .expect(USAGE) -+ .ok_or_else(|| anyhow::anyhow!(USAGE))? - .parse::() -- .expect("Expected port ID"); -+ .map_err(|err| anyhow::anyhow!("Expected port ID: {err}"))?; - let interface_num = args - .next() -- .expect(USAGE) -+ .ok_or_else(|| anyhow::anyhow!(USAGE))? - .parse::() -- .expect("Expected integer as input of interface"); -+ .context("Expected integer as input of interface")?; - - let name = format!("{}_{}_{}_hid", scheme, port, interface_num); - common::setup_logging( -@@ -247,7 +247,13 @@ fn main() -> Result<()> { - reqs::set_idle(&handle, 1, 0, interface_num as u16).context("Failed to set idle")?; - - let report_desc_len = hid_desc.desc_len; -- assert_eq!(hid_desc.desc_ty, REPORT_DESC_TY); -+ if hid_desc.desc_ty != REPORT_DESC_TY { -+ anyhow::bail!( -+ "unexpected HID descriptor type {:X}, expected {:X}", -+ hid_desc.desc_ty, -+ REPORT_DESC_TY -+ ); -+ } - - let mut report_desc_bytes = vec![0u8; report_desc_len as usize]; - handle -@@ -261,8 +267,8 @@ fn main() -> Result<()> { - ) - .context("Failed to retrieve report descriptor")?; - -- let mut handler = -- ReportHandler::new(&report_desc_bytes).expect("failed to parse report descriptor"); -+ let mut handler = ReportHandler::new(&report_desc_bytes) -+ .map_err(|e| anyhow::anyhow!("failed to parse report descriptor: {}", e))?; - - let report_len = match endp_desc_opt { - Some((_endp_num, endp_desc)) => endp_desc.max_packet_size as usize, -@@ -318,10 +324,14 @@ fn main() -> Result<()> { - let mut mouse_dy = 0i32; - let mut scroll_y = 0i32; - let mut buttons = last_buttons; -- for event in handler -- .handle(&report_buffer) -- .expect("failed to parse report") -- { -+ let events = match handler.handle(&report_buffer) { -+ Ok(events) => events, -+ Err(err) => { -+ log::warn!("failed to parse report: {}", err); -+ continue; + 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); ++ } + } -+ }; -+ for event in events { - log::debug!("{}", event); - if event.usage_page == UsagePage::GenericDesktop as u16 { - if event.usage == GenericDesktopUsage::X as u16 { -diff --git a/drivers/pcid-spawner/Cargo.toml b/drivers/pcid-spawner/Cargo.toml -index 8c03f8d3..8d3b3899 100644 ---- a/drivers/pcid-spawner/Cargo.toml -+++ b/drivers/pcid-spawner/Cargo.toml -@@ -13,6 +13,7 @@ pico-args.workspace = true - redox_syscall.workspace = true - serde.workspace = true - toml.workspace = true -+redox-driver-sys = { path = "../../../../../../../local/recipes/drivers/redox-driver-sys/source" } + }, + 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()?; - config = { path = "../../config" } - common = { path = "../common" } diff --git a/drivers/pcid-spawner/src/main.rs b/drivers/pcid-spawner/src/main.rs -index a968f4d4..c7082b0b 100644 +index a968f4d4..e41caee0 100644 --- a/drivers/pcid-spawner/src/main.rs +++ b/drivers/pcid-spawner/src/main.rs -@@ -1,10 +1,56 @@ - use std::fs; -+use std::path::Path; - use std::process::Command; +@@ -55,10 +55,11 @@ fn main() -> Result<()> { + }; - use anyhow::{anyhow, Context, Result}; + let full_device_id = handle.config().func.full_device_id; ++ let device_addr = handle.config().func.addr; - use pcid_interface::config::Config; - use pcid_interface::PciFunctionHandle; -+use redox_driver_sys::pci::{PciDeviceInfo, PciLocation}; -+ -+const PCI_SUBSYSTEM_IDS_OFFSET: u16 = 0x2C; -+ -+fn parse_location_from_device_path(path: &Path) -> Option { -+ let name = path.file_name()?.to_str()?; -+ let (segment, rest) = name.split_once("--")?; -+ let (bus, rest) = rest.split_once("--")?; -+ let (device, function) = rest.split_once('.')?; -+ -+ Some(PciLocation { -+ segment: u16::from_str_radix(segment, 16).ok()?, -+ bus: u8::from_str_radix(bus, 16).ok()?, -+ device: u8::from_str_radix(device, 16).ok()?, -+ function: function.parse().ok()?, -+ }) -+} -+ -+fn read_subsystem_ids(handle: &mut PciFunctionHandle) -> (u16, u16) { -+ let value = unsafe { handle.read_config(PCI_SUBSYSTEM_IDS_OFFSET) }; -+ (value as u16, (value >> 16) as u16) -+} -+ -+fn build_quirk_info(handle: &mut PciFunctionHandle, device_path: &Path) -> Option { -+ let config = handle.config(); -+ let full_device_id = config.func.full_device_id; -+ let location = parse_location_from_device_path(device_path)?; -+ let (subsystem_vendor_id, subsystem_device_id) = read_subsystem_ids(handle); -+ -+ Some(PciDeviceInfo { -+ location, -+ vendor_id: full_device_id.vendor_id, -+ device_id: full_device_id.device_id, -+ subsystem_vendor_id, -+ subsystem_device_id, -+ revision: full_device_id.revision, -+ class_code: full_device_id.class, -+ subclass: full_device_id.subclass, -+ prog_if: full_device_id.interface, -+ header_type: 0, -+ irq: None, -+ bars: Vec::new(), -+ capabilities: Vec::new(), -+ }) -+} + log::debug!( + "pcid-spawner enumerated: PCI {} {}", +- handle.config().func.addr, ++ device_addr, + full_device_id.display() + ); - fn main() -> Result<()> { - let mut args = pico_args::Arguments::from_env(); -@@ -85,6 +131,20 @@ fn main() -> Result<()> { +@@ -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); -+ if let Some(info) = build_quirk_info(&mut handle, &device_path) { -+ let quirk_flags = info.quirks(); -+ if !quirk_flags.is_empty() { -+ log::info!( -+ "pcid-spawner: quirks for {} {:04x}:{:04x} = {:?}", -+ info.location.scheme_path(), -+ info.vendor_id, -+ info.device_id, -+ quirk_flags -+ ); -+ } -+ command.env("PCI_QUIRK_FLAGS", format!("{:#x}", quirk_flags.bits())); -+ } +- 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); + - log::info!("pcid-spawner: spawn {:?}", command); ++ if let Err(err) = handle.try_enable_device() { ++ log::error!( ++ "pcid-spawner: failed to enable {} before spawn: {}", ++ device_addr, ++ err ++ ); ++ continue; ++ } - handle.enable_device(); -@@ -99,3 +159,20 @@ fn main() -> Result<()> { + 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::parse_location_from_device_path; -+ use std::path::Path; ++ use super::{PciBar, PciBarError}; + + #[test] -+ fn parses_scheme_pci_path_name() { -+ let location = parse_location_from_device_path(Path::new("/scheme/pci/0000--2a--1f.3")) -+ .expect("parse location"); ++ fn try_port_accepts_port_bar() { ++ assert_eq!(PciBar::Port(0x1234).try_port(), Ok(0x1234)); ++ } + -+ assert_eq!(location.segment, 0); -+ assert_eq!(location.bus, 0x2a); -+ assert_eq!(location.device, 0x1f); -+ assert_eq!(location.function, 3); ++ #[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 c2caf804..95acdb57 100644 +index bb9f39a3..df026ab4 100644 --- a/drivers/pcid/src/scheme.rs +++ b/drivers/pcid/src/scheme.rs @@ -21,6 +21,7 @@ enum Handle { @@ -3217,7 +5580,7 @@ index c2caf804..95acdb57 100644 Handle::Channel { addr, ref mut st } => { Self::write_channel(&self.pcie, &mut self.tree, addr, st, buf) } -@@ -318,6 +354,10 @@ impl PciScheme { +@@ -316,6 +352,10 @@ impl SchemeSync for PciScheme { func.enabled = false; } } @@ -3228,7 +5591,7 @@ index c2caf804..95acdb57 100644 _ => {} } } -@@ -343,6 +383,7 @@ impl PciScheme { +@@ -341,6 +381,7 @@ impl PciScheme { let path = &after[1..]; match path { @@ -3236,762 +5599,377 @@ index c2caf804..95acdb57 100644 "channel" => { if func.enabled { return Err(Error::new(ENOLCK)); -diff --git a/drivers/storage/usbscsid/Cargo.toml b/drivers/storage/usbscsid/Cargo.toml -index 4a36934e..a9c6447c 100644 ---- a/drivers/storage/usbscsid/Cargo.toml -+++ b/drivers/storage/usbscsid/Cargo.toml -@@ -10,6 +10,7 @@ license = "MIT" +@@ -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))?; - [dependencies] - base64 = "0.11" # Only for debugging -+bitflags.workspace = true - libredox.workspace = true - plain.workspace = true - driver-block = { path = "../driver-block" } -@@ -17,6 +18,7 @@ daemon = { path = "../../../daemon" } - redox_event.workspace = true - redox_syscall = { workspace = true, features = ["std"] } - thiserror.workspace = true -+toml.workspace = true - xhcid = { path = "../../usb/xhcid" } + 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); ++ }); - [lints] -diff --git a/drivers/storage/usbscsid/src/quirks.rs b/drivers/storage/usbscsid/src/quirks.rs -new file mode 100644 -index 00000000..5051f1b0 ---- /dev/null -+++ b/drivers/storage/usbscsid/src/quirks.rs -@@ -0,0 +1,212 @@ -+use std::fs; -+use std::path::{Path, PathBuf}; -+use std::sync::OnceLock; -+ -+use bitflags::bitflags; -+use toml::Value; -+ -+bitflags! { -+ #[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] -+ pub struct UsbStorageQuirkFlags: u32 { -+ const IGNORE_RESIDUE = 1 << 0; -+ const FIX_CAPACITY = 1 << 1; -+ const SINGLE_LUN = 1 << 2; -+ const MAX_SECTORS_64 = 1 << 3; -+ const INITIAL_READ10 = 1 << 4; -+ -+ const FIX_INQUIRY = 1 << 5; -+ const NOT_LOCKABLE = 1 << 6; -+ const SCM_MULT_TARG = 1 << 7; -+ const SANE_SENSE = 1 << 8; -+ const BULK_IGNORE_TAG = 1 << 9; -+ const NEEDS_SYNC_CACHE = 1 << 10; -+ const NO_WP_DETECT = 1 << 11; -+ const NO_READ_CAP16 = 1 << 12; -+ const IGNORE_DEVICE = 1 << 13; + 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); + } -+} -+ -+#[derive(Clone, Copy)] -+struct CompiledQuirkEntry { -+ vendor: u16, -+ product: u16, -+ flags: UsbStorageQuirkFlags, -+} -+ -+#[derive(Clone, Copy, Debug, Eq, PartialEq)] -+struct RuntimeQuirkEntry { -+ vendor: u16, -+ product: u16, -+ flags: UsbStorageQuirkFlags, -+} -+ -+const COMPILED_QUIRKS: &[CompiledQuirkEntry] = &[ -+ CompiledQuirkEntry { vendor: 0x03EB, product: 0x2002, flags: UsbStorageQuirkFlags::IGNORE_RESIDUE }, -+ CompiledQuirkEntry { vendor: 0x03F0, product: 0x4002, flags: UsbStorageQuirkFlags::FIX_CAPACITY }, -+ CompiledQuirkEntry { vendor: 0x0409, product: 0x0040, flags: UsbStorageQuirkFlags::SINGLE_LUN }, -+ CompiledQuirkEntry { vendor: 0x0421, product: 0x0019, flags: UsbStorageQuirkFlags::MAX_SECTORS_64 }, -+ CompiledQuirkEntry { vendor: 0x090C, product: 0x6000, flags: UsbStorageQuirkFlags::INITIAL_READ10 }, -+ CompiledQuirkEntry { vendor: 0x1B1C, product: 0x1AB5, flags: UsbStorageQuirkFlags::INITIAL_READ10 }, -+]; -+ -+static RUNTIME_QUIRKS: OnceLock> = OnceLock::new(); -+ -+pub fn lookup_usb_storage_quirks(vendor: u16, product: u16) -> UsbStorageQuirkFlags { -+ let mut flags = UsbStorageQuirkFlags::empty(); -+ -+ for entry in COMPILED_QUIRKS { -+ if entry.vendor == vendor && entry.product == product { -+ flags |= entry.flags; -+ } -+ } -+ -+ for entry in runtime_quirks() { -+ if entry.vendor == vendor && entry.product == product { -+ flags |= entry.flags; -+ } -+ } -+ -+ flags -+} -+ -+fn runtime_quirks() -> &'static [RuntimeQuirkEntry] { -+ RUNTIME_QUIRKS.get_or_init(load_runtime_quirks).as_slice() -+} -+ -+fn load_runtime_quirks() -> Vec { -+ let mut entries = Vec::new(); -+ let Some(dir_entries) = quirk_files() else { -+ return entries; -+ }; -+ -+ for path in dir_entries { -+ let Ok(text) = fs::read_to_string(&path) else { -+ continue; + 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); ++ } + }; -+ entries.extend(parse_runtime_quirks_from_toml(&text)); -+ } -+ -+ entries -+} -+ -+fn quirk_files() -> Option> { -+ let quirks_dir = Path::new("/etc/quirks.d"); -+ let read_dir = fs::read_dir(quirks_dir).ok()?; -+ -+ let mut files = read_dir -+ .filter_map(|entry| entry.ok()) -+ .map(|entry| entry.path()) -+ .filter(|path| path.extension().and_then(|ext| ext.to_str()) == Some("toml")) -+ .collect::>(); -+ files.sort(); -+ Some(files) -+} -+ -+fn parse_runtime_quirks_from_toml(text: &str) -> Vec { -+ let Ok(value) = text.parse::() else { -+ return Vec::new(); -+ }; -+ -+ let Some(entries) = value.get("usb_storage_quirk").and_then(Value::as_array) else { -+ return Vec::new(); -+ }; -+ -+ entries.iter().filter_map(parse_runtime_quirk_entry).collect() -+} -+ -+fn parse_runtime_quirk_entry(value: &Value) -> Option { -+ let table = value.as_table()?; -+ let vendor = u16::try_from(table.get("vendor")?.as_integer()?).ok()?; -+ let product = u16::try_from(table.get("product")?.as_integer()?).ok()?; -+ let flags = parse_flag_list(table.get("flags")?.as_array()?); -+ -+ (!flags.is_empty()).then_some(RuntimeQuirkEntry { vendor, product, flags }) -+} -+ -+fn parse_flag_list(values: &[Value]) -> UsbStorageQuirkFlags { -+ let mut flags = UsbStorageQuirkFlags::empty(); -+ -+ for value in values { -+ if let Some(name) = value.as_str().and_then(parse_flag_name) { -+ flags |= name; + 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); + } -+ } -+ -+ flags -+} -+ -+fn parse_flag_name(name: &str) -> Option { -+ Some(match name { -+ "ignore_residue" => UsbStorageQuirkFlags::IGNORE_RESIDUE, -+ "fix_capacity" => UsbStorageQuirkFlags::FIX_CAPACITY, -+ "single_lun" => UsbStorageQuirkFlags::SINGLE_LUN, -+ "max_sectors_64" => UsbStorageQuirkFlags::MAX_SECTORS_64, -+ "initial_read10" => UsbStorageQuirkFlags::INITIAL_READ10, -+ "fix_inquiry" => UsbStorageQuirkFlags::FIX_INQUIRY, -+ "not_lockable" => UsbStorageQuirkFlags::NOT_LOCKABLE, -+ "scm_mult_targ" => UsbStorageQuirkFlags::SCM_MULT_TARG, -+ "sane_sense" => UsbStorageQuirkFlags::SANE_SENSE, -+ "bulk_ignore_tag" => UsbStorageQuirkFlags::BULK_IGNORE_TAG, -+ "needs_sync_cache" => UsbStorageQuirkFlags::NEEDS_SYNC_CACHE, -+ "no_wp_detect" => UsbStorageQuirkFlags::NO_WP_DETECT, -+ "no_read_cap16" => UsbStorageQuirkFlags::NO_READ_CAP16, -+ "ignore_device" => UsbStorageQuirkFlags::IGNORE_DEVICE, -+ _ => return None, -+ }) -+} -+ -+#[cfg(test)] -+mod tests { -+ use super::*; -+ -+ #[test] -+ fn compiled_fallback_lookup_returns_expected_flags() { -+ let flags = lookup_usb_storage_quirks(0x090C, 0x6000); -+ assert!(flags.contains(UsbStorageQuirkFlags::INITIAL_READ10)); -+ } -+ -+ #[test] -+ fn runtime_toml_parser_keeps_supported_flags_and_skips_unknown_ones() { -+ let entries = parse_runtime_quirks_from_toml( -+ r#" -+ [[usb_storage_quirk]] -+ vendor = 0x1234 -+ product = 0x5678 -+ flags = ["ignore_residue", "unknown_flag", "fix_capacity"] -+ "#, -+ ); -+ -+ assert_eq!(entries.len(), 1); -+ assert!(entries[0].flags.contains(UsbStorageQuirkFlags::IGNORE_RESIDUE)); -+ assert!(entries[0].flags.contains(UsbStorageQuirkFlags::FIX_CAPACITY)); -+ assert!(!entries[0].flags.contains(UsbStorageQuirkFlags::SINGLE_LUN)); -+ } -+} -diff --git a/drivers/storage/usbscsid/src/main.rs b/drivers/storage/usbscsid/src/main.rs -index 5382d118..dca7762c 100644 ---- a/drivers/storage/usbscsid/src/main.rs -+++ b/drivers/storage/usbscsid/src/main.rs -@@ -1,53 +1,67 @@ - use std::collections::BTreeMap; - use std::env; -+use std::error::Error as StdError; -+use std::io; - use driver_block::{Disk, DiskScheme, ExecutorTrait}; - use syscall::{Error, EIO}; - use xhcid_interface::{ConfigureEndpointsReq, PortId, XhciClientHandle}; + 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); ++ }); - pub mod protocol; -+pub mod quirks; - pub mod scsi; + 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); - use crate::protocol::Protocol; - use crate::scsi::Scsi; +- 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; ++ } -+type Result = std::result::Result>; -+ -+const USAGE: &str = "usbscsid "; -+ - fn main() { - daemon::Daemon::new(daemon); - } - fn daemon(daemon: daemon::Daemon) -> ! { -- let mut args = env::args().skip(1); -+ let exit_code = match run(daemon) { -+ Ok(()) => 0, -+ Err(err) => { -+ eprintln!("usbscsid: {err}"); -+ 1 -+ } -+ }; - -- const USAGE: &'static str = "usbscsid "; -+ std::process::exit(exit_code); -+} -+ -+fn run(daemon: daemon::Daemon) -> Result<()> { -+ let mut args = env::args().skip(1); - -- let scheme = args.next().expect(USAGE); -- let port = args -+ let scheme = next_arg(&mut args, "scheme")?; -+ let port: PortId = args - .next() -- .expect(USAGE) -- .parse::() -- .expect("Expected port ID"); -- let protocol = args -+ .ok_or_else(|| usage_error("missing port argument"))? -+ .parse() -+ .map_err(|e| usage_error(format!("invalid port ID: {e}")))?; -+ let protocol_num: u8 = args - .next() -- .expect(USAGE) -- .parse::() -- .expect("protocol has to be a number 0-255"); -+ .ok_or_else(|| usage_error("missing protocol argument"))? -+ .parse() -+ .map_err(|e| usage_error(format!("protocol must be a number 0-255: {e}")))?; - - println!( - "USB SCSI driver spawned with scheme `{}`, port {}, protocol {}", -- scheme, port, protocol -+ scheme, port, protocol_num - ); - - let disk_scheme_name = format!("disk.usb-{scheme}+{port}-scsi"); - -- // TODO: Use eventfds. -- let handle = -- XhciClientHandle::new(scheme.to_owned(), port).expect("Failed to open XhciClientHandle"); -+ let handle = XhciClientHandle::new(scheme.to_owned(), port) -+ .map_err(|e| runtime_error(format!("failed to open XhciClientHandle: {e}")))?; - - let desc = handle - .get_standard_descs() -- .expect("Failed to get standard descriptors"); -+ .map_err(|e| runtime_error(format!("failed to get standard descriptors: {e}")))?; - -- // TODO: Perhaps the drivers should just be given the config, interface, and alternate setting -- // from xhcid. - let (conf_desc, configuration_value, (if_desc, interface_num, alternate_setting)) = desc - .config_descs - .iter() -@@ -65,7 +79,7 @@ fn daemon(daemon: daemon::Daemon) -> ! { - interface_desc, - )) - }) -- .expect("Failed to find suitable configuration"); -+ .ok_or_else(|| runtime_error("failed to find suitable SCSI BOT configuration"))?; - - handle - .configure_endpoints(&ConfigureEndpointsReq { -@@ -74,20 +88,37 @@ fn daemon(daemon: daemon::Daemon) -> ! { - alternate_setting: Some(alternate_setting), - hub_ports: None, - }) -- .expect("Failed to configure endpoints"); -- -- let mut protocol = protocol::setup(&handle, protocol, &desc, &conf_desc, &if_desc) -- .expect("Failed to setup protocol"); -- -- // TODO: Let all of the USB drivers fork or be managed externally, and xhcid won't have to keep -- // track of all the drivers. -- let mut scsi = Scsi::new(&mut *protocol).expect("usbscsid: failed to setup SCSI"); -+ .map_err(|e| runtime_error(format!("failed to configure endpoints: {e}")))?; -+ -+ let vendor = desc.vendor; -+ let product = desc.product; -+ let storage_quirks = quirks::lookup_usb_storage_quirks(vendor, product); -+ -+ let mut protocol = protocol::setup( -+ &handle, -+ protocol_num, -+ &desc, -+ &conf_desc, -+ &if_desc, -+ storage_quirks, -+ ) -+ .ok_or_else(|| { -+ runtime_error(format!( -+ "failed to setup protocol (protocol 0x{protocol_num:02x})" -+ )) -+ })?; -+ -+ let mut scsi = Scsi::new(&mut *protocol, storage_quirks) -+ .map_err(|e| runtime_error(format!("failed to setup SCSI: {e}")))?; - println!("SCSI initialized"); - let mut buffer = [0u8; 512]; -- scsi.read(&mut *protocol, 0, &mut buffer).unwrap(); -- println!("DISK CONTENT: {}", base64::encode(&buffer[..])); -+ match scsi.read(&mut *protocol, 0, &mut buffer) { -+ Ok(_) => println!("DISK CONTENT: {}", base64::encode(&buffer[..])), -+ Err(e) => eprintln!("usbscsid: initial sector read failed: {e}"), -+ } - -- let event_queue = event::EventQueue::new().unwrap(); -+ let event_queue = event::EventQueue::new() -+ .map_err(|e| runtime_error(format!("failed to create event queue: {e}")))?; - - event::user_data! { - enum Event { -@@ -119,17 +150,41 @@ fn daemon(daemon: daemon::Daemon) -> ! { - Event::Scheme, - event::EventFlags::READ, - ) -- .unwrap(); -+ .map_err(|e| runtime_error(format!("failed to subscribe to scheme events: {e}")))?; - - for event in event_queue { -- match event.unwrap().user_data { -- Event::Scheme => driver_block::FuturesExecutor -- .block_on(scheme.tick()) -- .unwrap(), -+ match event { -+ Ok(ev) => match ev.user_data { -+ Event::Scheme => { -+ if let Err(e) = driver_block::FuturesExecutor.block_on(scheme.tick()) { -+ eprintln!("usbscsid: scheme tick error: {e}"); +- FuturesExecutor.block_on(scheme.tick()).unwrap(); ++ if let Err(err) = FuturesExecutor.block_on(scheme.tick()) { ++ error!("ahcid: failed to handle IRQ: {err}"); ++ break; ++ } + } + } -+ }, -+ Err(e) => { -+ eprintln!("usbscsid: event queue error: {e}"); -+ } ++ 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); -+ Err(runtime_error("event queue terminated").into()) -+} -+ -+fn next_arg(args: &mut impl Iterator, name: &str) -> io::Result { -+ args.next() -+ .ok_or_else(|| usage_error(format!("missing {name} argument"))) -+} -+ -+fn usage_error(message: impl Into) -> io::Error { -+ let message = message.into(); -+ io::Error::new( -+ io::ErrorKind::InvalidInput, -+ format!("{message} (usage: {USAGE})"), -+ ) -+} -+ -+fn runtime_error(message: impl Into) -> io::Error { -+ io::Error::other(message.into()) ++ 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; - struct UsbDisk<'a> { +- 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/usbscsid/src/main.rs b/drivers/storage/usbscsid/src/main.rs +index 5382d118..3a403bd3 100644 +--- a/drivers/storage/usbscsid/src/main.rs ++++ b/drivers/storage/usbscsid/src/main.rs +@@ -3,7 +3,7 @@ use std::env; + + use driver_block::{Disk, DiskScheme, ExecutorTrait}; + use syscall::{Error, EIO}; +-use xhcid_interface::{ConfigureEndpointsReq, PortId, XhciClientHandle}; ++use xhcid_interface::{PortId, XhciClientHandle}; + + pub mod protocol; + pub mod scsi; +@@ -12,9 +12,9 @@ use crate::protocol::Protocol; + use crate::scsi::Scsi; + + fn main() { +- daemon::Daemon::new(daemon); ++ run(); + } +-fn daemon(daemon: daemon::Daemon) -> ! { ++fn run() -> ! { + let mut args = env::args().skip(1); + + const USAGE: &'static str = "usbscsid "; +@@ -67,15 +67,6 @@ fn daemon(daemon: daemon::Daemon) -> ! { + }) + .expect("Failed to find suitable configuration"); + +- handle +- .configure_endpoints(&ConfigureEndpointsReq { +- config_desc: configuration_value, +- interface_desc: Some(interface_num), +- alternate_setting: Some(alternate_setting), +- hub_ports: None, +- }) +- .expect("Failed to configure endpoints"); +- + let mut protocol = protocol::setup(&handle, protocol, &desc, &conf_desc, &if_desc) + .expect("Failed to setup protocol"); + +@@ -108,9 +99,6 @@ fn daemon(daemon: daemon::Daemon) -> ! { + &driver_block::FuturesExecutor, + ); + +- // FIXME should this wait notifying readiness until the disk scheme is created? +- daemon.ready(); +- + //libredox::call::setrens(0, 0).expect("nvmed: failed to enter null namespace"); + + event_queue diff --git a/drivers/storage/usbscsid/src/protocol/bot.rs b/drivers/storage/usbscsid/src/protocol/bot.rs -index b751d51a..848ae0e9 100644 +index b751d51a..b5d43cba 100644 --- a/drivers/storage/usbscsid/src/protocol/bot.rs +++ b/drivers/storage/usbscsid/src/protocol/bot.rs -@@ -8,6 +8,7 @@ use xhcid_interface::{ - }; - - use super::{Protocol, ProtocolError, SendCommandStatus, SendCommandStatusKind}; -+use crate::quirks::UsbStorageQuirkFlags; - - pub const CBW_SIGNATURE: u32 = 0x43425355; - -@@ -88,9 +89,12 @@ pub struct BulkOnlyTransport<'a> { - bulk_out: XhciEndpHandle, - bulk_in_num: u8, - bulk_out_num: u8, -+ bulk_in_addr: u8, -+ bulk_out_addr: u8, - max_lun: u8, - current_tag: u32, - interface_num: u8, -+ quirks: UsbStorageQuirkFlags, - } - - pub const FEATURE_ENDPOINT_HALT: u16 = 0; -@@ -98,23 +102,29 @@ pub const FEATURE_ENDPOINT_HALT: u16 = 0; - impl<'a> BulkOnlyTransport<'a> { - pub fn init( - handle: &'a XhciClientHandle, -- config_desc: &ConfDesc, -+ _config_desc: &ConfDesc, - if_desc: &IfDesc, -+ quirks: UsbStorageQuirkFlags, +@@ -103,16 +103,22 @@ impl<'a> BulkOnlyTransport<'a> { ) -> Result { let endpoints = &if_desc.endpoints; - let bulk_in_num = (endpoints -+ let (bulk_in_idx, bulk_in_desc) = endpoints ++ let bulk_in_num = endpoints .iter() - .position(|endpoint| endpoint.direction() == EndpDirection::In) - .unwrap() - + 1) as u8; - let bulk_out_num = (endpoints -+ .enumerate() -+ .find(|(_, endpoint)| endpoint.direction() == EndpDirection::In) -+ .ok_or(ProtocolError::ProtocolError("no bulk IN endpoint found"))?; -+ let (bulk_out_idx, bulk_out_desc) = endpoints ++ .find(|endpoint| endpoint.direction() == EndpDirection::In) ++ .map(|endpoint| endpoint.address & 0x0F) ++ .filter(|num| *num != 0) ++ .ok_or(ProtocolError::ProtocolError( ++ "missing bulk-in endpoint descriptor", ++ ))?; ++ let bulk_out_num = endpoints .iter() - .position(|endpoint| endpoint.direction() == EndpDirection::Out) - .unwrap() - + 1) as u8; -+ .enumerate() -+ .find(|(_, endpoint)| endpoint.direction() == EndpDirection::Out) -+ .ok_or(ProtocolError::ProtocolError("no bulk OUT endpoint found"))?; ++ .find(|endpoint| endpoint.direction() == EndpDirection::Out) ++ .map(|endpoint| endpoint.address & 0x0F) ++ .filter(|num| *num != 0) ++ .ok_or(ProtocolError::ProtocolError( ++ "missing bulk-out endpoint descriptor", ++ ))?; -- let max_lun = get_max_lun(handle, 0)?; -+ let bulk_in_num = (bulk_in_idx + 1) as u8; -+ let bulk_out_num = (bulk_out_idx + 1) as u8; -+ let bulk_in_addr = bulk_in_desc.address; -+ let bulk_out_addr = bulk_out_desc.address; -+ -+ let max_lun = get_max_lun(handle, if_desc.number.into())?; + let max_lun = get_max_lun(handle, 0)?; println!("BOT_MAX_LUN {}", max_lun); - - Ok(Self { -@@ -122,10 +132,13 @@ impl<'a> BulkOnlyTransport<'a> { - bulk_out: handle.open_endpoint(bulk_out_num)?, - bulk_in_num, - bulk_out_num, -+ bulk_in_addr, -+ bulk_out_addr, - handle, - max_lun, - current_tag: 0, - interface_num: if_desc.number, -+ quirks, - }) - } - fn clear_stall_in(&mut self) -> Result<(), XhciClientHandleError> { -@@ -133,7 +146,7 @@ impl<'a> BulkOnlyTransport<'a> { - self.bulk_in.reset(false)?; - self.handle.clear_feature( - PortReqRecipient::Endpoint, -- u16::from(self.bulk_in_num), -+ u16::from(self.bulk_in_addr), - FEATURE_ENDPOINT_HALT, - )?; - } -@@ -144,7 +157,7 @@ impl<'a> BulkOnlyTransport<'a> { - self.bulk_out.reset(false)?; - self.handle.clear_feature( - PortReqRecipient::Endpoint, -- u16::from(self.bulk_out_num), -+ u16::from(self.bulk_out_addr), - FEATURE_ENDPOINT_HALT, - )?; - } -@@ -162,38 +175,59 @@ impl<'a> BulkOnlyTransport<'a> { - } - Ok(()) - } -- fn read_csw_raw( -- &mut self, -- csw_buffer: &mut [u8; 13], -- already: bool, -- ) -> Result<(), ProtocolError> { -- match self.bulk_in.transfer_read(&mut csw_buffer[..])? { -- PortTransferStatus { -- kind: PortTransferStatusKind::Stalled, -- .. -- } => { -- if already { -+ fn read_csw(&mut self, csw_buffer: &mut [u8; 13]) -> Result<(), ProtocolError> { -+ let mut attempts = 0u8; -+ loop { -+ let status = self.bulk_in.transfer_read(&mut csw_buffer[..])?; -+ match status { -+ PortTransferStatus { -+ kind: PortTransferStatusKind::Stalled, -+ .. -+ } => { -+ attempts += 1; -+ if attempts >= 2 { -+ self.reset_recovery()?; -+ return Err(ProtocolError::ProtocolError( -+ "bulk IN stalled repeatedly when reading CSW", -+ )); -+ } -+ eprintln!("usbscsid: bulk IN stalled when reading CSW, clearing stall"); -+ self.clear_stall_in()?; -+ continue; -+ } -+ PortTransferStatus { -+ kind: PortTransferStatusKind::ShortPacket, -+ bytes_transferred, -+ } if bytes_transferred != 13 => { -+ eprintln!( -+ "usbscsid: short packet when reading CSW ({} != 13)", -+ bytes_transferred -+ ); - self.reset_recovery()?; -+ return Err(ProtocolError::ProtocolError( -+ "short packet when reading CSW", -+ )); -+ } -+ PortTransferStatus { -+ kind: PortTransferStatusKind::Success, -+ .. -+ } -+ | PortTransferStatus { -+ kind: PortTransferStatusKind::ShortPacket, -+ bytes_transferred: 13, -+ } => return Ok(()), -+ _ => { -+ eprintln!( -+ "usbscsid: unexpected transfer status when reading CSW: {:?}", -+ status -+ ); -+ self.reset_recovery()?; -+ return Err(ProtocolError::ProtocolError( -+ "unexpected transfer status when reading CSW", -+ )); - } -- println!("bulk in endpoint stalled when reading CSW"); -- self.clear_stall_in()?; -- self.read_csw_raw(csw_buffer, true)?; -- } -- PortTransferStatus { -- kind: PortTransferStatusKind::ShortPacket, -- bytes_transferred, -- } if bytes_transferred != 13 => { -- panic!( -- "received a short packet when reading CSW ({} != 13)", -- bytes_transferred -- ) - } -- _ => (), - } -- Ok(()) -- } -- fn read_csw(&mut self, csw_buffer: &mut [u8; 13]) -> Result<(), ProtocolError> { -- self.read_csw_raw(csw_buffer, false) - } - } - -@@ -207,8 +241,14 @@ impl<'a> Protocol for BulkOnlyTransport<'a> { - let tag = self.current_tag; - - let mut cbw_bytes = [0u8; 31]; -- let cbw = plain::from_mut_bytes::(&mut cbw_bytes).unwrap(); -- *cbw = CommandBlockWrapper::new(tag, data.len() as u32, data.direction().into(), 0, cb)?; -+ let cbw = plain::from_mut_bytes::(&mut cbw_bytes) -+ .map_err(|_| ProtocolError::ProtocolError("CBW buffer size mismatch"))?; -+ let lun: u8 = if self.quirks.contains(UsbStorageQuirkFlags::SINGLE_LUN) { -+ 0 -+ } else { -+ 0 -+ }; -+ *cbw = CommandBlockWrapper::new(tag, data.len() as u32, data.direction().into(), lun, cb)?; - let cbw = *cbw; - - match self.bulk_out.transfer_write(&cbw_bytes)? { -@@ -216,22 +256,48 @@ impl<'a> Protocol for BulkOnlyTransport<'a> { - kind: PortTransferStatusKind::Stalled, - .. - } => { -- // TODO: Error handling -- panic!("bulk out endpoint stalled when sending CBW {:?}", cbw); -- //self.clear_stall_out()?; -- //dbg!(self.bulk_in.status()?, self.bulk_out.status()?); -+ eprintln!( -+ "usbscsid: bulk OUT endpoint stalled when sending CBW {:?}", -+ cbw -+ ); -+ self.clear_stall_out()?; -+ return Err(ProtocolError::ProtocolError( -+ "bulk OUT endpoint stalled when sending CBW", -+ )); - } - PortTransferStatus { - bytes_transferred, .. - } if bytes_transferred != 31 => { -- panic!( -- "received short packet when sending CBW ({} != 31)", -+ eprintln!( -+ "usbscsid: short packet when sending CBW ({} != 31)", - bytes_transferred - ); -+ self.reset_recovery()?; -+ return Err(ProtocolError::ProtocolError( -+ "short packet when sending CBW", -+ )); -+ } -+ PortTransferStatus { -+ kind: PortTransferStatusKind::Success, -+ .. -+ } => (), -+ PortTransferStatus { -+ kind: PortTransferStatusKind::ShortPacket, -+ bytes_transferred: 31, -+ } => (), -+ status => { -+ eprintln!( -+ "usbscsid: unexpected transfer status {:?} when sending CBW", -+ status -+ ); -+ self.reset_recovery()?; -+ return Err(ProtocolError::ProtocolError( -+ "unexpected transfer status when sending CBW", -+ )); - } -- _ => (), - } - -+ let data_len = data.len() as u32; - let early_residue: Option = match data { - DeviceReqData::In(buffer) => match self.bulk_in.transfer_read(buffer)? { - PortTransferStatus { -@@ -240,15 +306,19 @@ impl<'a> Protocol for BulkOnlyTransport<'a> { - } => match kind { - PortTransferStatusKind::Success => None, - PortTransferStatusKind::ShortPacket => { -- println!( -- "received short packet (len {}) when transferring data", -- bytes_transferred -+ let residue = data_len.saturating_sub(bytes_transferred); -+ eprintln!( -+ "usbscsid: short packet ({} of {} bytes) during data read", -+ bytes_transferred, data_len - ); -- NonZeroU32::new(bytes_transferred) -+ NonZeroU32::new(residue) - } - PortTransferStatusKind::Stalled => { -- panic!("bulk in endpoint stalled when reading data"); -- //self.clear_stall_in()?; -+ eprintln!("usbscsid: bulk IN endpoint stalled when reading data"); -+ self.clear_stall_in()?; -+ return Err(ProtocolError::ProtocolError( -+ "bulk IN endpoint stalled during data read", -+ )); - } - PortTransferStatusKind::Unknown => { - return Err(ProtocolError::XhciError( -@@ -266,15 +336,19 @@ impl<'a> Protocol for BulkOnlyTransport<'a> { - } => match kind { - PortTransferStatusKind::Success => None, - PortTransferStatusKind::ShortPacket => { -- println!( -- "received short packet (len {}) when transferring data", -- bytes_transferred -+ let residue = data_len.saturating_sub(bytes_transferred); -+ eprintln!( -+ "usbscsid: short packet ({} of {} bytes) during data write", -+ bytes_transferred, data_len - ); -- NonZeroU32::new(bytes_transferred) -+ NonZeroU32::new(residue) - } - PortTransferStatusKind::Stalled => { -- panic!("bulk out endpoint stalled when reading data"); -- //self.clear_stall_out()?; -+ eprintln!("usbscsid: bulk OUT endpoint stalled when writing data"); -+ self.clear_stall_out()?; -+ return Err(ProtocolError::ProtocolError( -+ "bulk OUT endpoint stalled during data write", -+ )); - } - PortTransferStatusKind::Unknown => { - return Err(ProtocolError::XhciError( -@@ -290,9 +364,14 @@ impl<'a> Protocol for BulkOnlyTransport<'a> { - - let mut csw_buffer = [0u8; 13]; - self.read_csw(&mut csw_buffer)?; -- let csw = plain::from_bytes::(&csw_buffer).unwrap(); -+ let csw = plain::from_bytes::(&csw_buffer) -+ .map_err(|_| ProtocolError::ProtocolError("CSW buffer size mismatch"))?; - -- let residue = early_residue.or(NonZeroU32::new(csw.data_residue)); -+ let residue = if self.quirks.contains(UsbStorageQuirkFlags::IGNORE_RESIDUE) { -+ None -+ } else { -+ early_residue.or(NonZeroU32::new(csw.data_residue)) -+ }; - - if csw.status == CswStatus::Failed as u8 { - println!("CSW indicated failure (CSW {:?}, CBW {:?})", csw, cbw); diff --git a/drivers/storage/usbscsid/src/protocol/mod.rs b/drivers/storage/usbscsid/src/protocol/mod.rs -index a580765f..bde9affc 100644 +index a580765f..62edac60 100644 --- a/drivers/storage/usbscsid/src/protocol/mod.rs +++ b/drivers/storage/usbscsid/src/protocol/mod.rs -@@ -6,6 +6,8 @@ use xhcid_interface::{ - ConfDesc, DevDesc, DeviceReqData, IfDesc, XhciClientHandle, XhciClientHandleError, - }; - -+use crate::quirks::UsbStorageQuirkFlags; -+ - #[derive(Debug, Error)] - pub enum ProtocolError { - #[error("Too large command block ({0} > 16)")] -@@ -59,22 +61,19 @@ pub trait Protocol { - /// Bulk-only transport - pub mod bot; - --mod uas { -- // TODO --} -- - use bot::BulkOnlyTransport; - +@@ -68,14 +68,14 @@ use bot::BulkOnlyTransport; pub fn setup<'a>( handle: &'a XhciClientHandle, protocol: u8, @@ -3999,1601 +5977,136 @@ index a580765f..bde9affc 100644 + _dev_desc: &DevDesc, conf_desc: &ConfDesc, if_desc: &IfDesc, -+ quirks: UsbStorageQuirkFlags, - ) -> Option> { +-) -> Option> { ++) -> Result, ProtocolError> { match protocol { - 0x50 => Some(Box::new( +- 0x50 => Some(Box::new( - BulkOnlyTransport::init(handle, conf_desc, if_desc).unwrap(), -+ BulkOnlyTransport::init(handle, conf_desc, if_desc, quirks).ok()?, ++ 0x50 => Ok(Box::new(BulkOnlyTransport::init(handle, conf_desc, if_desc)?)), ++ _ => Err(ProtocolError::ProtocolError( ++ "unsupported USB mass-storage transport protocol", )), - _ => None, - } -diff --git a/drivers/storage/usbscsid/src/scsi/cmds.rs b/drivers/storage/usbscsid/src/scsi/cmds.rs -index ab02525e..ddc12336 100644 ---- a/drivers/storage/usbscsid/src/scsi/cmds.rs -+++ b/drivers/storage/usbscsid/src/scsi/cmds.rs -@@ -179,9 +179,6 @@ unsafe impl plain::Plain for Read16 {} - - impl Read16 { - pub const fn new(lba: u64, transfer_len: u32, control: u8) -> Self { -- // TODO: RDPROTECT, DPO, FUA, RARC -- // TODO: DLD -- // TODO: Group number - Self { - opcode: Opcode::Read16 as u8, - a: 0, -@@ -193,6 +190,31 @@ impl Read16 { +- _ => None, } } - -+#[repr(C, packed)] -+#[derive(Clone, Copy, Debug)] -+pub struct Read10 { -+ pub opcode: u8, -+ pub a: u8, -+ pub lba: u32, -+ pub group_num: u8, -+ pub transfer_len: u16, -+ pub control: u8, -+} -+unsafe impl plain::Plain for Read10 {} -+ -+impl Read10 { -+ pub const fn new(lba: u64, transfer_len: u16, control: u8) -> Self { -+ Self { -+ opcode: Opcode::Read10 as u8, -+ a: 0, -+ lba: u32::to_be(lba as u32), -+ group_num: 0, -+ transfer_len: u16::to_be(transfer_len), -+ control, -+ } -+ } -+} -+ - #[repr(C, packed)] - #[derive(Clone, Copy, Debug)] - pub struct Write16 { -@@ -219,6 +241,31 @@ impl Write16 { - } +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() { } -+#[repr(C, packed)] -+#[derive(Clone, Copy, Debug)] -+pub struct Write10 { -+ pub opcode: u8, -+ pub a: u8, -+ pub lba: u32, -+ pub group_num: u8, -+ pub transfer_len: u16, -+ pub control: u8, -+} -+unsafe impl plain::Plain for Write10 {} -+ -+impl Write10 { -+ pub const fn new(lba: u64, transfer_len: u16, control: u8) -> Self { -+ Self { -+ opcode: Opcode::Write10 as u8, -+ a: 0, -+ lba: u32::to_be(lba as u32), -+ group_num: 0, -+ transfer_len: u16::to_be(transfer_len), -+ control, -+ } + 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); + } -+} -+ - #[repr(C, packed)] - #[derive(Clone, Copy, Debug)] - pub struct ModeSense6 { -@@ -438,7 +485,35 @@ impl ReadCapacity10 { - } - } - } --// TODO: ReadCapacity16 -+ -+/// SERVICE ACTION IN(16) with service action 0x10 (READ CAPACITY(16)). -+/// Required for devices larger than 2 TB where ReadCapacity10 cannot -+/// represent the full block count. -+#[repr(C, packed)] -+#[derive(Clone, Copy, Debug)] -+pub struct ReadCapacity16 { -+ pub opcode: u8, -+ pub service_action: u8, -+ pub lba: u64, -+ pub alloc_len: u32, -+ pub _rsvd: u8, -+ pub control: u8, -+} -+ -+impl ReadCapacity16 { -+ pub const fn new(control: u8) -> Self { -+ Self { -+ opcode: Opcode::ServiceAction9E as u8, -+ service_action: 0x10, -+ lba: 0, -+ alloc_len: u32::to_be(32), -+ _rsvd: 0, -+ control, -+ } -+ } -+} -+ -+unsafe impl plain::Plain for ReadCapacity16 {} - - #[repr(C, packed)] - #[derive(Clone, Copy, Debug)] -@@ -457,6 +532,27 @@ impl ReadCapacity10ParamData { - } + unreachable!(); } -+/// Response data for READ CAPACITY(16). The minimum useful response is -+/// 12 bytes (max LBA + block length), but the device may return up to -+/// 32 bytes with additional protection and mapping information. -+#[repr(C, packed)] -+#[derive(Clone, Copy, Debug)] -+pub struct ReadCapacity16ParamData { -+ pub max_lba: u64, -+ pub block_len: u32, -+ pub _rest: [u8; 20], -+} -+unsafe impl plain::Plain for ReadCapacity16ParamData {} -+ -+impl ReadCapacity16ParamData { -+ pub const fn block_count(&self) -> u64 { -+ u64::from_be(self.max_lba) +@@ -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 ++ )); + } -+ pub const fn logical_block_len(&self) -> u32 { -+ u32::from_be(self.block_len) -+ } -+} -+ - #[repr(C, packed)] - #[derive(Clone, Copy, Debug)] - pub struct RwErrorRecoveryPage { -diff --git a/drivers/storage/usbscsid/src/scsi/mod.rs b/drivers/storage/usbscsid/src/scsi/mod.rs -index 790abea6..b6d379d0 100644 ---- a/drivers/storage/usbscsid/src/scsi/mod.rs -+++ b/drivers/storage/usbscsid/src/scsi/mod.rs -@@ -8,6 +8,7 @@ use thiserror::Error; - use xhcid_interface::DeviceReqData; + log::info!("virtio-blk: initiating startup sequence :^)"); - use crate::protocol::{Protocol, ProtocolError, SendCommandStatus, SendCommandStatusKind}; -+use crate::quirks::UsbStorageQuirkFlags; - use cmds::StandardInquiryData; + let device = virtio_core::probe_device(&mut pcid_handle)?; +diff --git a/drivers/usb/usbctl/src/main.rs b/drivers/usb/usbctl/src/main.rs +index 9b5773d9..232f7cfc 100644 +--- a/drivers/usb/usbctl/src/main.rs ++++ b/drivers/usb/usbctl/src/main.rs +@@ -15,6 +15,9 @@ fn main() { + Command::new("port") + .arg(Arg::new("PORT").num_args(1).required(true)) + .subcommand(Command::new("status")) ++ .subcommand(Command::new("pm-state")) ++ .subcommand(Command::new("suspend")) ++ .subcommand(Command::new("resume")) + .subcommand( + Command::new("endpoint") + .arg(Arg::new("ENDPOINT_NUM").num_args(1).required(true)) +@@ -38,6 +41,15 @@ fn main() { + if let Some(_status_scmd_matches) = port_scmd_matches.subcommand_matches("status") { + let state = handle.port_state().expect("Failed to get port state"); + println!("{}", state.as_str()); ++ } else if let Some(_pm_state_scmd_matches) = port_scmd_matches.subcommand_matches("pm-state") { ++ let state = handle ++ .port_pm_state() ++ .expect("Failed to get port power-management state"); ++ println!("{}", state.as_str()); ++ } else if let Some(_suspend_scmd_matches) = port_scmd_matches.subcommand_matches("suspend") { ++ handle.suspend_device().expect("Failed to suspend device"); ++ } else if let Some(_resume_scmd_matches) = port_scmd_matches.subcommand_matches("resume") { ++ handle.resume_device().expect("Failed to resume device"); + } else if let Some(endp_scmd_matches) = port_scmd_matches.subcommand_matches("endpoint") { + let endp_num = endp_scmd_matches + .get_one::("ENDPOINT_NUM") +diff --git a/drivers/usb/xhcid/drivers.toml b/drivers/usb/xhcid/drivers.toml +index 83c90e23..470ec063 100644 +--- a/drivers/usb/xhcid/drivers.toml ++++ b/drivers/usb/xhcid/drivers.toml +@@ -1,9 +1,8 @@ +-#TODO: causes XHCI errors +-#[[drivers]] +-#name = "SCSI over USB" +-#class = 8 # Mass Storage class +-#subclass = 6 # SCSI transparent command set +-#command = ["usbscsid", "$SCHEME", "$PORT", "$IF_PROTO"] ++[[drivers]] ++name = "SCSI over USB" ++class = 8 # Mass Storage class ++subclass = 6 # SCSI transparent command set ++command = ["usbscsid", "$SCHEME", "$PORT", "$IF_PROTO"] - pub struct Scsi { -@@ -16,6 +17,7 @@ pub struct Scsi { - data_buffer: Vec, - pub block_size: u32, - pub block_count: u64, -+ pub quirks: UsbStorageQuirkFlags, - } - - const INQUIRY_CMD_LEN: u8 = 6; -@@ -23,6 +25,7 @@ const REPORT_SUPP_OPCODES_CMD_LEN: u8 = 12; - const REQUEST_SENSE_CMD_LEN: u8 = 6; - const MIN_INQUIRY_ALLOC_LEN: u16 = 5; - const MIN_REPORT_SUPP_OPCODES_ALLOC_LEN: u32 = 4; -+const MAX_SECTORS_64_LIMIT: u64 = 64; - - type Result = std::result::Result; - -@@ -35,11 +38,74 @@ pub enum ScsiError { - - #[error("overflow")] - Overflow(&'static str), -+ -+ #[error("invalid size for {context}: expected {expected}, got {actual}")] -+ InvalidSize { -+ context: &'static str, -+ expected: usize, -+ actual: usize, -+ }, -+ -+ #[error("insufficient bytes for {context}: need {needed}, have {actual}")] -+ InsufficientBytes { -+ context: &'static str, -+ needed: usize, -+ actual: usize, -+ }, -+ -+ #[error("plain parse error for {context}: {message}")] -+ PlainParse { -+ context: &'static str, -+ message: String, -+ }, -+ -+ #[error("invalid block size reported by device: {0}")] -+ InvalidBlockSize(u32), -+} -+ -+fn parse_bytes<'a, T: plain::Plain>(context: &'static str, bytes: &'a [u8]) -> Result<&'a T> { -+ let needed = mem::size_of::(); -+ if bytes.len() < needed { -+ return Err(ScsiError::InsufficientBytes { -+ context, -+ needed, -+ actual: bytes.len(), -+ }); -+ } -+ plain::from_bytes(bytes).map_err(|e| ScsiError::PlainParse { -+ context, -+ message: format!("{e:?}"), -+ }) -+} -+ -+fn parse_mut_bytes<'a, T: plain::Plain>( -+ context: &'static str, -+ bytes: &'a mut [u8], -+) -> Result<&'a mut T> { -+ let needed = mem::size_of::(); -+ if bytes.len() < needed { -+ return Err(ScsiError::InsufficientBytes { -+ context, -+ needed, -+ actual: bytes.len(), -+ }); -+ } -+ plain::from_mut_bytes(bytes).map_err(|e| ScsiError::PlainParse { -+ context, -+ message: format!("{e:?}"), -+ }) - } - - impl Scsi { -- pub fn new(protocol: &mut dyn Protocol) -> Result { -- assert_eq!(std::mem::size_of::(), 96); -+ pub fn new(protocol: &mut dyn Protocol, quirks: UsbStorageQuirkFlags) -> Result { -+ let inquiry_size = std::mem::size_of::(); -+ if inquiry_size != 96 { -+ return Err(ScsiError::InvalidSize { -+ context: "StandardInquiryData", -+ expected: 96, -+ actual: inquiry_size, -+ }); -+ } - - let mut this = Self { - command_buffer: [0u8; 16], -@@ -49,6 +115,7 @@ impl Scsi { - data_buffer: Vec::new(), - block_size: 0, - block_count: 0, -+ quirks, - }; - - // Get the max length that the device supports, of the Standard Inquiry Data. -@@ -56,9 +123,11 @@ impl Scsi { - // Get the Standard Inquiry Data. - this.get_standard_inquiry_data(protocol, max_inquiry_len)?; - -- let version = this.res_standard_inquiry_data().version(); -+ let version = this.res_standard_inquiry_data()?.version(); - println!("Inquiry version: {}", version); - -+ let fix_capacity = this.quirks.contains(UsbStorageQuirkFlags::FIX_CAPACITY); -+ - let (block_size, block_count) = { - let (_, blkdescs, mode_page_iter) = this.get_mode_sense10(protocol)?; - -@@ -74,10 +143,25 @@ impl Scsi { - println!("read_capacity10"); - let r = this.read_capacity(protocol)?; - println!("read_capacity10 result: {:?}", r); -- (r.logical_block_len(), r.block_count().into()) -+ let mut count = r.block_count(); -+ if fix_capacity { -+ count = count.saturating_sub(1); -+ } -+ if count == u32::MAX { -+ println!("read_capacity10 returned max LBA, trying read_capacity16"); -+ let r16 = this.read_capacity16(protocol)?; -+ println!("read_capacity16 result: {:?}", r16); -+ (r16.logical_block_len(), r16.block_count()) -+ } else { -+ (r.logical_block_len(), u64::from(count)) -+ } - } - }; - -+ if block_size == 0 { -+ return Err(ScsiError::InvalidBlockSize(block_size)); -+ } -+ - this.block_size = block_size; - this.block_count = block_count; - -@@ -85,7 +169,7 @@ impl Scsi { - } - pub fn get_inquiry_alloc_len(&mut self, protocol: &mut dyn Protocol) -> Result { - self.get_standard_inquiry_data(protocol, MIN_INQUIRY_ALLOC_LEN)?; -- let standard_inquiry_data = self.res_standard_inquiry_data(); -+ let standard_inquiry_data = self.res_standard_inquiry_data()?; - Ok(4 + u16::from(standard_inquiry_data.additional_len)) - } - pub fn get_standard_inquiry_data( -@@ -93,7 +177,7 @@ impl Scsi { - protocol: &mut dyn Protocol, - max_inquiry_len: u16, - ) -> Result<()> { -- let inquiry = self.cmd_inquiry(); -+ let inquiry = self.cmd_inquiry()?; - *inquiry = cmds::Inquiry::new(false, 0, max_inquiry_len, 0); - - protocol.send_command( -@@ -103,7 +187,7 @@ impl Scsi { - Ok(()) - } - pub fn get_ff_sense(&mut self, protocol: &mut dyn Protocol, alloc_len: u8) -> Result<()> { -- let request_sense = self.cmd_request_sense(); -+ let request_sense = self.cmd_request_sense()?; - *request_sense = cmds::RequestSense::new(false, alloc_len, 0); - self.data_buffer.resize(alloc_len.into(), 0); - protocol.send_command( -@@ -117,14 +201,14 @@ impl Scsi { - protocol: &mut dyn Protocol, - ) -> Result<&cmds::ReadCapacity10ParamData> { - // The spec explicitly states that the allocation length is 8 bytes. -- let read_capacity10 = self.cmd_read_capacity10(); -+ let read_capacity10 = self.cmd_read_capacity10()?; - *read_capacity10 = cmds::ReadCapacity10::new(0); - self.data_buffer.resize(10usize, 0u8); - protocol.send_command( - &self.command_buffer[..10], - DeviceReqData::In(&mut self.data_buffer[..8]), - )?; -- Ok(self.res_read_capacity10()) -+ self.res_read_capacity10() - } - pub fn get_mode_sense10( - &mut self, -@@ -135,7 +219,7 @@ impl Scsi { - impl Iterator>, - )> { - let initial_alloc_len = mem::size_of::() as u16; // covers both mode_data_len and blk_desc_len. -- let mode_sense10 = self.cmd_mode_sense10(); -+ let mode_sense10 = self.cmd_mode_sense10()?; - *mode_sense10 = cmds::ModeSense10::get_block_desc(initial_alloc_len, 0); - self.data_buffer - .resize(mem::size_of::(), 0); -@@ -146,108 +230,166 @@ impl Scsi { - &self.command_buffer[..10], - DeviceReqData::In(&mut self.data_buffer[..initial_alloc_len as usize]), - )? { -- self.get_ff_sense(protocol, 252)?; -- panic!("{:?}", self.res_ff_sense_data()); -+ if let Ok(()) = self.get_ff_sense(protocol, 252) { -+ match self.res_ff_sense_data() { -+ Ok(sense_data) => { -+ eprintln!("usbscsid: MODE SENSE(10) failed: {:?}", sense_data); -+ } -+ Err(err) => { -+ eprintln!( -+ "usbscsid: MODE SENSE(10) failed and sense parsing failed: {err}" -+ ); -+ } -+ } -+ } -+ return Err(ScsiError::ProtocolError(ProtocolError::ProtocolError( -+ "MODE SENSE(10) command failed", -+ ))); - } - -- let optimal_alloc_len = self.res_mode_param_header10().mode_data_len() + 2; // the length of the mode data field itself -+ let optimal_alloc_len = self.res_mode_param_header10()?.mode_data_len() + 2; // the length of the mode data field itself - -- let mode_sense10 = self.cmd_mode_sense10(); -+ let mode_sense10 = self.cmd_mode_sense10()?; - *mode_sense10 = cmds::ModeSense10::get_block_desc(optimal_alloc_len, 0); - self.data_buffer.resize(optimal_alloc_len as usize, 0); - protocol.send_command( - &self.command_buffer[..10], - DeviceReqData::In(&mut self.data_buffer[..optimal_alloc_len as usize]), - )?; -- Ok(( -- self.res_mode_param_header10(), -- self.res_blkdesc_mode10(), -- self.res_mode_pages10(), -- )) -+ let header = self.res_mode_param_header10()?; -+ let blkdescs = self.res_blkdesc_mode10()?; -+ let mode_pages = self.res_mode_pages10()?; -+ Ok((header, blkdescs, mode_pages)) - } - -- pub fn cmd_inquiry(&mut self) -> &mut cmds::Inquiry { -- plain::from_mut_bytes(&mut self.command_buffer).unwrap() -+ pub fn cmd_inquiry(&mut self) -> Result<&mut cmds::Inquiry> { -+ parse_mut_bytes("INQUIRY command", &mut self.command_buffer) -+ } -+ pub fn cmd_mode_sense6(&mut self) -> Result<&mut cmds::ModeSense6> { -+ parse_mut_bytes("MODE SENSE(6) command", &mut self.command_buffer) - } -- pub fn cmd_mode_sense6(&mut self) -> &mut cmds::ModeSense6 { -- plain::from_mut_bytes(&mut self.command_buffer).unwrap() -+ pub fn cmd_mode_sense10(&mut self) -> Result<&mut cmds::ModeSense10> { -+ parse_mut_bytes("MODE SENSE(10) command", &mut self.command_buffer) - } -- pub fn cmd_mode_sense10(&mut self) -> &mut cmds::ModeSense10 { -- plain::from_mut_bytes(&mut self.command_buffer).unwrap() -+ pub fn cmd_request_sense(&mut self) -> Result<&mut cmds::RequestSense> { -+ parse_mut_bytes("REQUEST SENSE command", &mut self.command_buffer) - } -- pub fn cmd_request_sense(&mut self) -> &mut cmds::RequestSense { -- plain::from_mut_bytes(&mut self.command_buffer).unwrap() -+ pub fn cmd_read_capacity10(&mut self) -> Result<&mut cmds::ReadCapacity10> { -+ parse_mut_bytes("READ CAPACITY(10) command", &mut self.command_buffer) - } -- pub fn cmd_read_capacity10(&mut self) -> &mut cmds::ReadCapacity10 { -- plain::from_mut_bytes(&mut self.command_buffer).unwrap() -+ pub fn cmd_read16(&mut self) -> Result<&mut cmds::Read16> { -+ parse_mut_bytes("READ(16) command", &mut self.command_buffer) - } -- pub fn cmd_read16(&mut self) -> &mut cmds::Read16 { -- plain::from_mut_bytes(&mut self.command_buffer).unwrap() -+ pub fn cmd_read10(&mut self) -> Result<&mut cmds::Read10> { -+ parse_mut_bytes("READ(10) command", &mut self.command_buffer) - } -- pub fn cmd_write16(&mut self) -> &mut cmds::Write16 { -- plain::from_mut_bytes(&mut self.command_buffer).unwrap() -+ pub fn cmd_write16(&mut self) -> Result<&mut cmds::Write16> { -+ parse_mut_bytes("WRITE(16) command", &mut self.command_buffer) - } -- pub fn res_standard_inquiry_data(&self) -> &StandardInquiryData { -- plain::from_bytes(&self.inquiry_buffer).unwrap() -+ pub fn cmd_write10(&mut self) -> Result<&mut cmds::Write10> { -+ parse_mut_bytes("WRITE(10) command", &mut self.command_buffer) - } -- pub fn res_ff_sense_data(&self) -> &cmds::FixedFormatSenseData { -- plain::from_bytes(&self.data_buffer).unwrap() -+ pub fn res_standard_inquiry_data(&self) -> Result<&StandardInquiryData> { -+ parse_bytes("standard inquiry data", &self.inquiry_buffer) - } -- pub fn res_mode_param_header6(&self) -> &cmds::ModeParamHeader6 { -- plain::from_bytes(&self.data_buffer).unwrap() -+ pub fn res_ff_sense_data(&self) -> Result<&cmds::FixedFormatSenseData> { -+ parse_bytes("fixed format sense data", &self.data_buffer) - } -- pub fn res_mode_param_header10(&self) -> &cmds::ModeParamHeader10 { -- plain::from_bytes(&self.data_buffer).unwrap() -+ pub fn res_mode_param_header6(&self) -> Result<&cmds::ModeParamHeader6> { -+ parse_bytes("MODE SENSE(6) parameter header", &self.data_buffer) - } -- pub fn res_blkdesc_mode6(&self) -> &[cmds::ShortLbaModeParamBlkDesc] { -- let header = self.res_mode_param_header6(); -+ pub fn res_mode_param_header10(&self) -> Result<&cmds::ModeParamHeader10> { -+ parse_bytes("MODE SENSE(10) parameter header", &self.data_buffer) -+ } -+ pub fn res_blkdesc_mode6(&self) -> Result<&[cmds::ShortLbaModeParamBlkDesc]> { -+ let header = self.res_mode_param_header6()?; - let descs_start = mem::size_of::(); -- plain::slice_from_bytes( -- &self.data_buffer[descs_start..descs_start + usize::from(header.block_desc_len)], -+ let desc_len = usize::from(header.block_desc_len); -+ let descs_end = descs_start -+ .checked_add(desc_len) -+ .ok_or(ScsiError::Overflow("block descriptor length overflowed"))?; -+ if descs_end > self.data_buffer.len() { -+ return Err(ScsiError::Overflow( -+ "block descriptor length exceeds data buffer", -+ )); -+ } -+ Ok( -+ plain::slice_from_bytes(&self.data_buffer[descs_start..descs_end]) -+ .map_err(|_| ScsiError::Overflow("block descriptor alignment mismatch"))?, - ) -- .unwrap() - } -- pub fn res_blkdesc_mode10(&self) -> BlkDescSlice<'_> { -- let header = self.res_mode_param_header10(); -+ pub fn res_blkdesc_mode10(&self) -> Result> { -+ let header = self.res_mode_param_header10()?; - let descs_start = mem::size_of::(); -+ let descs_end = descs_start -+ .checked_add(usize::from(header.block_desc_len())) -+ .ok_or(ScsiError::Overflow("block descriptor length overflowed"))?; -+ let desc_range = descs_start..descs_end; -+ if desc_range.end > self.data_buffer.len() { -+ return Err(ScsiError::Overflow( -+ "block descriptor length exceeds data buffer", -+ )); -+ } - if header.longlba() { -- BlkDescSlice::Long( -- plain::slice_from_bytes( -- &self.data_buffer -- [descs_start..descs_start + usize::from(header.block_desc_len())], -- ) -- .unwrap(), -- ) -- } else if self.res_standard_inquiry_data().periph_dev_ty() -- != cmds::PeriphDeviceType::DirectAccess as u8 -- && self.res_standard_inquiry_data().version() == cmds::InquiryVersion::Spc3 as u8 -- { -- BlkDescSlice::General( -- plain::slice_from_bytes( -- &self.data_buffer -- [descs_start..descs_start + usize::from(header.block_desc_len())], -- ) -- .unwrap(), -- ) -+ Ok(BlkDescSlice::Long( -+ plain::slice_from_bytes(&self.data_buffer[desc_range]).map_err(|_| { -+ ScsiError::Overflow("long LBA block descriptor alignment mismatch") -+ })?, -+ )) - } else { -- BlkDescSlice::Short( -- plain::slice_from_bytes( -- &self.data_buffer -- [descs_start..descs_start + usize::from(header.block_desc_len())], -- ) -- .unwrap(), -- ) -+ let inquiry = self.res_standard_inquiry_data()?; -+ if inquiry.periph_dev_ty() != cmds::PeriphDeviceType::DirectAccess as u8 -+ && inquiry.version() == cmds::InquiryVersion::Spc3 as u8 -+ { -+ Ok(BlkDescSlice::General( -+ plain::slice_from_bytes(&self.data_buffer[desc_range]).map_err(|_| { -+ ScsiError::Overflow("general block descriptor alignment mismatch") -+ })?, -+ )) -+ } else { -+ Ok(BlkDescSlice::Short( -+ plain::slice_from_bytes(&self.data_buffer[desc_range]).map_err(|_| { -+ ScsiError::Overflow("short LBA block descriptor alignment mismatch") -+ })?, -+ )) -+ } - } - } - -- pub fn res_mode_pages10(&self) -> impl Iterator> { -- let header = self.res_mode_param_header10(); -+ pub fn res_mode_pages10(&self) -> Result> + '_> { -+ let header = self.res_mode_param_header10()?; - let descs_start = mem::size_of::(); -- let buffer = &self.data_buffer[descs_start + header.block_desc_len() as usize..]; -- cmds::mode_page_iter(buffer) -+ let pages_start = descs_start -+ .checked_add(header.block_desc_len() as usize) -+ .ok_or(ScsiError::Overflow("mode page offset overflowed"))?; -+ if pages_start > self.data_buffer.len() { -+ return Err(ScsiError::Overflow("mode page offset exceeds data buffer")); -+ } -+ let buffer = &self.data_buffer[pages_start..]; -+ Ok(cmds::mode_page_iter(buffer)) -+ } -+ pub fn res_read_capacity10(&self) -> Result<&cmds::ReadCapacity10ParamData> { -+ parse_bytes("READ CAPACITY(10) parameter data", &self.data_buffer) -+ } -+ pub fn read_capacity16( -+ &mut self, -+ protocol: &mut dyn Protocol, -+ ) -> Result<&cmds::ReadCapacity16ParamData> { -+ let cmd = self.cmd_read_capacity16()?; -+ *cmd = cmds::ReadCapacity16::new(0); -+ self.data_buffer -+ .resize(mem::size_of::(), 0); -+ protocol.send_command( -+ &self.command_buffer[..16], -+ DeviceReqData::In(&mut self.data_buffer[..32]), -+ )?; -+ self.res_read_capacity16() - } -- pub fn res_read_capacity10(&self) -> &cmds::ReadCapacity10ParamData { -- plain::from_bytes(&self.data_buffer).unwrap() -+ pub fn cmd_read_capacity16(&mut self) -> Result<&mut cmds::ReadCapacity16> { -+ parse_mut_bytes("READ CAPACITY(16) command", &mut self.command_buffer) -+ } -+ pub fn res_read_capacity16(&self) -> Result<&cmds::ReadCapacity16ParamData> { -+ parse_bytes("READ CAPACITY(16) parameter data", &self.data_buffer) - } - pub fn get_disk_size(&self) -> u64 { - self.block_count * u64::from(self.block_size) -@@ -258,44 +400,90 @@ impl Scsi { - lba: u64, - buffer: &mut [u8], - ) -> Result { -- let blocks_to_read = buffer.len() as u64 / u64::from(self.block_size); -- let bytes_to_read = blocks_to_read as usize * self.block_size as usize; -- let transfer_len = u32::try_from(blocks_to_read).or(Err(ScsiError::Overflow( -- "number of blocks to read couldn't fit inside a u32", -- )))?; -+ let mut blocks_to_read = buffer.len() as u64 / u64::from(self.block_size); -+ -+ if self.quirks.contains(UsbStorageQuirkFlags::MAX_SECTORS_64) -+ && blocks_to_read > MAX_SECTORS_64_LIMIT - { -- let read = self.cmd_read16(); -- *read = cmds::Read16::new(lba, transfer_len, 0); -+ blocks_to_read = MAX_SECTORS_64_LIMIT; -+ } -+ -+ let bytes_to_read = blocks_to_read as usize * self.block_size as usize; -+ -+ if self.quirks.contains(UsbStorageQuirkFlags::INITIAL_READ10) { -+ let transfer_len = u16::try_from(blocks_to_read).or(Err(ScsiError::Overflow( -+ "number of blocks to read couldn't fit inside a u16 for READ(10)", -+ )))?; -+ { -+ let read = self.cmd_read10()?; -+ *read = cmds::Read10::new(lba, transfer_len, 0); -+ } -+ self.data_buffer.resize(bytes_to_read, 0u8); -+ let status = protocol.send_command( -+ &self.command_buffer[..10], -+ DeviceReqData::In(&mut self.data_buffer[..bytes_to_read]), -+ )?; -+ buffer[..bytes_to_read].copy_from_slice(&self.data_buffer[..bytes_to_read]); -+ Ok(status.bytes_transferred(bytes_to_read as u32)) -+ } else { -+ let transfer_len = u32::try_from(blocks_to_read).or(Err(ScsiError::Overflow( -+ "number of blocks to read couldn't fit inside a u32", -+ )))?; -+ { -+ let read = self.cmd_read16()?; -+ *read = cmds::Read16::new(lba, transfer_len, 0); -+ } -+ self.data_buffer.resize(bytes_to_read, 0u8); -+ let status = protocol.send_command( -+ &self.command_buffer[..16], -+ DeviceReqData::In(&mut self.data_buffer[..bytes_to_read]), -+ )?; -+ buffer[..bytes_to_read].copy_from_slice(&self.data_buffer[..bytes_to_read]); -+ Ok(status.bytes_transferred(bytes_to_read as u32)) - } -- // TODO: Use the to-be-written TransferReadStream instead of relying on everything being -- // able to fit within a single buffer. -- self.data_buffer.resize(bytes_to_read, 0u8); -- let status = protocol.send_command( -- &self.command_buffer[..16], -- DeviceReqData::In(&mut self.data_buffer[..bytes_to_read]), -- )?; -- buffer[..bytes_to_read].copy_from_slice(&self.data_buffer[..bytes_to_read]); -- Ok(status.bytes_transferred(bytes_to_read as u32)) - } - pub fn write(&mut self, protocol: &mut dyn Protocol, lba: u64, buffer: &[u8]) -> Result { -- let blocks_to_write = buffer.len() as u64 / u64::from(self.block_size); -- let bytes_to_write = blocks_to_write as usize * self.block_size as usize; -- let transfer_len = u32::try_from(blocks_to_write).or(Err(ScsiError::Overflow( -- "number of blocks to write couldn't fit inside a u32", -- )))?; -+ let mut blocks_to_write = buffer.len() as u64 / u64::from(self.block_size); -+ -+ if self.quirks.contains(UsbStorageQuirkFlags::MAX_SECTORS_64) -+ && blocks_to_write > MAX_SECTORS_64_LIMIT - { -- let read = self.cmd_write16(); -- *read = cmds::Write16::new(lba, transfer_len, 0); -+ blocks_to_write = MAX_SECTORS_64_LIMIT; -+ } -+ -+ let bytes_to_write = blocks_to_write as usize * self.block_size as usize; -+ -+ if self.quirks.contains(UsbStorageQuirkFlags::INITIAL_READ10) { -+ let transfer_len = u16::try_from(blocks_to_write).or(Err(ScsiError::Overflow( -+ "number of blocks to write couldn't fit inside a u16 for WRITE(10)", -+ )))?; -+ { -+ let write = self.cmd_write10()?; -+ *write = cmds::Write10::new(lba, transfer_len, 0); -+ } -+ self.data_buffer.resize(bytes_to_write, 0u8); -+ self.data_buffer[..bytes_to_write].copy_from_slice(&buffer[..bytes_to_write]); -+ let status = protocol.send_command( -+ &self.command_buffer[..10], -+ DeviceReqData::Out(&buffer[..bytes_to_write]), -+ )?; -+ Ok(status.bytes_transferred(bytes_to_write as u32)) -+ } else { -+ let transfer_len = u32::try_from(blocks_to_write).or(Err(ScsiError::Overflow( -+ "number of blocks to write couldn't fit inside a u32", -+ )))?; -+ { -+ let write = self.cmd_write16()?; -+ *write = cmds::Write16::new(lba, transfer_len, 0); -+ } -+ self.data_buffer.resize(bytes_to_write, 0u8); -+ self.data_buffer[..bytes_to_write].copy_from_slice(&buffer[..bytes_to_write]); -+ let status = protocol.send_command( -+ &self.command_buffer[..16], -+ DeviceReqData::Out(&buffer[..bytes_to_write]), -+ )?; -+ Ok(status.bytes_transferred(bytes_to_write as u32)) - } -- // TODO: Use the to-be-written TransferReadStream instead of relying on everything being -- // able to fit within a single buffer. -- self.data_buffer.resize(bytes_to_write, 0u8); -- self.data_buffer[..bytes_to_write].copy_from_slice(&buffer[..bytes_to_write]); -- let status = protocol.send_command( -- &self.command_buffer[..16], -- DeviceReqData::Out(&buffer[..bytes_to_write]), -- )?; -- Ok(status.bytes_transferred(bytes_to_write as u32)) - } - } - #[derive(Debug)] -diff --git a/drivers/usb/usbhubd/src/main.rs b/drivers/usb/usbhubd/src/main.rs -index 0e58542d..b13bb58a 100644 ---- a/drivers/usb/usbhubd/src/main.rs -+++ b/drivers/usb/usbhubd/src/main.rs -@@ -84,7 +84,7 @@ fn main() -> Result<(), Box> { - })?; - - // Read hub descriptor -- let (ports, usb_3) = if desc.major_version() >= 3 { -+ let (ports, usb_3, hub_think_time) = if desc.major_version() >= 3 { - // USB 3.0 hubs - let mut hub_desc = usb::HubDescriptorV3::default(); - handle -@@ -101,7 +101,7 @@ fn main() -> Result<(), Box> { - "Failed to read USB 3 hub descriptor for port {port_id}: {err}" - )) - })?; -- (hub_desc.ports, true) -+ (hub_desc.ports, true, None) - } else { - // USB 2.0 and earlier hubs - let mut hub_desc = usb::HubDescriptorV2::default(); -@@ -119,7 +119,7 @@ fn main() -> Result<(), Box> { - "Failed to read USB 2 hub descriptor for port {port_id}: {err}" - )) - })?; -- (hub_desc.ports, false) -+ (hub_desc.ports, false, hub_desc.tt_think_time(desc.protocol)) - }; -@@ -128,6 +128,7 @@ fn main() -> Result<(), Box> { - interface_desc: None, //TODO: stalls on USB 3 hub: Some(interface_num), - alternate_setting: None, //TODO: stalls on USB 3 hub: Some(if_desc.alternate_setting), - hub_ports: Some(ports), -+ hub_think_time, - }) - .map_err(|err| { - other_error(format!( - -diff --git a/drivers/usb/xhcid/src/usb/hub.rs b/drivers/usb/xhcid/src/usb/hub.rs -index 2d278320..fb02b17b 100644 ---- a/drivers/usb/xhcid/src/usb/hub.rs -+++ b/drivers/usb/xhcid/src/usb/hub.rs -@@ -17,6 +17,23 @@ unsafe impl plain::Plain for HubDescriptorV2 {} - - impl HubDescriptorV2 { - pub const DESCRIPTOR_KIND: u8 = 0x29; -+ -+ pub fn tt_think_time(self, device_protocol: u8) -> Option { -+ const HUB_CHAR_TTTT: u16 = 0x0060; -+ const HUB_TTTT_8_BITS: u16 = 0x0000; -+ const HUB_TTTT_16_BITS: u16 = 0x0020; -+ const HUB_TTTT_24_BITS: u16 = 0x0040; -+ const HUB_TTTT_32_BITS: u16 = 0x0060; -+ -+ match self.characteristics & HUB_CHAR_TTTT { -+ HUB_TTTT_8_BITS if device_protocol != 0 => Some(0), -+ HUB_TTTT_16_BITS => Some(1), -+ HUB_TTTT_24_BITS => Some(2), -+ HUB_TTTT_32_BITS => Some(3), -+ _ => None, -+ } -+ } - } -@@ -196,3 +213,23 @@ impl HubPortStatus { - } - } - } -+ -+#[cfg(test)] -+mod tests { -+ use super::HubDescriptorV2; -+ -+ #[test] -+ fn usb2_hub_tt_think_time_decodes_linux_compatible_values() { -+ let mut hub = HubDescriptorV2::default(); -+ -+ hub.characteristics = 0x0000; -+ assert_eq!(hub.tt_think_time(0), None); -+ assert_eq!(hub.tt_think_time(1), Some(0)); -+ -+ hub.characteristics = 0x0020; -+ assert_eq!(hub.tt_think_time(0), Some(1)); -+ -+ hub.characteristics = 0x0040; -+ assert_eq!(hub.tt_think_time(0), Some(2)); -+ -+ hub.characteristics = 0x0060; -+ assert_eq!(hub.tt_think_time(0), Some(3)); -+ } -+} - -diff --git a/drivers/usb/xhcid/src/xhci/scheme.rs b/drivers/usb/xhcid/src/xhci/scheme.rs -index 627d33a7..7eb553ae 100644 ---- a/drivers/usb/xhcid/src/xhci/scheme.rs -+++ b/drivers/usb/xhcid/src/xhci/scheme.rs -@@ -1196,11 +1196,8 @@ impl Xhci { - // Set hub data - current_slot_a &= !(1 << 26); - current_slot_b &= !HUB_PORTS_MASK; -- current_slot_c &= !TT_THINK_TIME_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; -- if let Some(hub_think_time) = req.hub_think_time { -- current_slot_c |= (u32::from(hub_think_time) << TT_THINK_TIME_SHIFT) & TT_THINK_TIME_MASK; -- } - } - current_slot_c = apply_hub_tt_info(current_slot_c, req); - -@@ -3250,6 +3247,21 @@ fn resolve_active_alternates( - active - } - -+fn apply_hub_tt_info(current_slot_c: u32, req: &ConfigureEndpointsReq) -> u32 { -+ const TT_THINK_TIME_MASK: u32 = 0x0003_0000; -+ const TT_THINK_TIME_SHIFT: u8 = 16; -+ -+ let mut slot_c = current_slot_c & !TT_THINK_TIME_MASK; -+ if req.hub_ports.is_some() { -+ if let Some(hub_think_time) = req.hub_think_time { -+ slot_c |= (u32::from(hub_think_time) << TT_THINK_TIME_SHIFT) & TT_THINK_TIME_MASK; -+ } -+ } -+ slot_c -+} -+ - use lazy_static::lazy_static; - use std::ops::{Add, Div, Rem}; -@@ -3283,4 +3295,18 @@ mod tests { - assert_eq!(resolved.get(&0), Some(&1)); - assert_eq!(resolved.get(&1), Some(&2)); - } -+ -+ #[test] -+ fn apply_hub_tt_info_only_sets_bits_for_hub_requests() { -+ let req = ConfigureEndpointsReq { -+ config_desc: 1, -+ interface_desc: None, -+ alternate_setting: None, -+ hub_ports: Some(4), -+ hub_think_time: Some(3), -+ }; -+ assert_eq!(apply_hub_tt_info(0, &req), 0x0003_0000); -+ -+ let no_hub = ConfigureEndpointsReq { hub_ports: None, ..req.clone() }; -+ assert_eq!(apply_hub_tt_info(0x0003_0000, &no_hub), 0); -+ } - } - -diff --git a/drivers/usb/usbhubd/src/main.rs b/drivers/usb/usbhubd/src/main.rs -index 0e58542d..b13bb58a 100644 ---- a/drivers/usb/usbhubd/src/main.rs -+++ b/drivers/usb/usbhubd/src/main.rs -@@ -84,7 +84,7 @@ fn main() -> Result<(), Box> { - })?; - - // Read hub descriptor -- let (ports, usb_3) = if desc.major_version() >= 3 { -+ let (ports, usb_3, hub_think_time) = if desc.major_version() >= 3 { - // USB 3.0 hubs - let mut hub_desc = usb::HubDescriptorV3::default(); - handle -@@ -101,7 +101,7 @@ fn main() -> Result<(), Box> { - "Failed to read USB 3 hub descriptor for port {port_id}: {err}" - )) - })?; -- (hub_desc.ports, true) -+ (hub_desc.ports, true, None) - } else { - // USB 2.0 and earlier hubs - let mut hub_desc = usb::HubDescriptorV2::default(); -@@ -119,7 +119,7 @@ fn main() -> Result<(), Box> { - "Failed to read USB 2 hub descriptor for port {port_id}: {err}" - )) - })?; -- (hub_desc.ports, false) -+ (hub_desc.ports, false, hub_desc.tt_think_time(desc.protocol)) - }; -@@ -128,6 +128,7 @@ fn main() -> Result<(), Box> { - interface_desc: None, //TODO: stalls on USB 3 hub: Some(interface_num), - alternate_setting: None, //TODO: stalls on USB 3 hub: Some(if_desc.alternate_setting), - hub_ports: Some(ports), -+ hub_think_time, - }) - .map_err(|err| { - other_error(format!( - -diff --git a/drivers/usb/xhcid/src/usb/hub.rs b/drivers/usb/xhcid/src/usb/hub.rs -index b7dc4d54..2d278320 100644 ---- a/drivers/usb/xhcid/src/usb/hub.rs -+++ b/drivers/usb/xhcid/src/usb/hub.rs -@@ -17,6 +17,23 @@ unsafe impl plain::Plain for HubDescriptorV2 {} - - impl HubDescriptorV2 { - pub const DESCRIPTOR_KIND: u8 = 0x29; -+ -+ pub fn tt_think_time(self, device_protocol: u8) -> Option { -+ const HUB_CHAR_TTTT: u16 = 0x0060; -+ const HUB_TTTT_8_BITS: u16 = 0x0000; -+ const HUB_TTTT_16_BITS: u16 = 0x0020; -+ const HUB_TTTT_24_BITS: u16 = 0x0040; -+ const HUB_TTTT_32_BITS: u16 = 0x0060; -+ -+ match self.characteristics & HUB_CHAR_TTTT { -+ HUB_TTTT_8_BITS if device_protocol != 0 => Some(0), -+ HUB_TTTT_16_BITS => Some(1), -+ HUB_TTTT_24_BITS => Some(2), -+ HUB_TTTT_32_BITS => Some(3), -+ _ => None, -+ } -+ } - } -@@ -196,3 +213,23 @@ impl HubPortStatus { - } - } - } -+ -+#[cfg(test)] -+mod tests { -+ use super::HubDescriptorV2; -+ -+ #[test] -+ fn usb2_hub_tt_think_time_decodes_linux_compatible_values() { -+ let mut hub = HubDescriptorV2::default(); -+ -+ hub.characteristics = 0x0000; -+ assert_eq!(hub.tt_think_time(0), None); -+ assert_eq!(hub.tt_think_time(1), Some(0)); -+ -+ hub.characteristics = 0x0020; -+ assert_eq!(hub.tt_think_time(0), Some(1)); -+ -+ hub.characteristics = 0x0040; -+ assert_eq!(hub.tt_think_time(0), Some(2)); -+ -+ hub.characteristics = 0x0060; -+ assert_eq!(hub.tt_think_time(0), Some(3)); -+ } -+} - -diff --git a/drivers/usb/xhcid/src/xhci/scheme.rs b/drivers/usb/xhcid/src/xhci/scheme.rs -index d5266ca0..627d33a7 100644 ---- a/drivers/usb/xhcid/src/xhci/scheme.rs -+++ b/drivers/usb/xhcid/src/xhci/scheme.rs -@@ -1196,11 +1196,8 @@ impl Xhci { - // Set hub data - current_slot_a &= !(1 << 26); - current_slot_b &= !HUB_PORTS_MASK; -- current_slot_c &= !TT_THINK_TIME_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; -- if let Some(hub_think_time) = req.hub_think_time { -- current_slot_c |= (u32::from(hub_think_time) << TT_THINK_TIME_SHIFT) & TT_THINK_TIME_MASK; -- } - } -+ current_slot_c = apply_hub_tt_info(current_slot_c, req); - - input_context.device.slot.a.write(current_slot_a); - input_context.device.slot.b.write(current_slot_b); -@@ -3250,6 +3247,21 @@ fn resolve_active_alternates( - active - } - -+fn apply_hub_tt_info(current_slot_c: u32, req: &ConfigureEndpointsReq) -> u32 { -+ const TT_THINK_TIME_MASK: u32 = 0x0003_0000; -+ const TT_THINK_TIME_SHIFT: u8 = 16; -+ -+ let mut slot_c = current_slot_c & !TT_THINK_TIME_MASK; -+ if req.hub_ports.is_some() { -+ if let Some(hub_think_time) = req.hub_think_time { -+ slot_c |= (u32::from(hub_think_time) << TT_THINK_TIME_SHIFT) & TT_THINK_TIME_MASK; -+ } -+ } -+ slot_c -+} -+ - use lazy_static::lazy_static; - use std::ops::{Add, Div, Rem}; -@@ -3283,4 +3295,18 @@ mod tests { - assert_eq!(resolved.get(&0), Some(&1)); - assert_eq!(resolved.get(&1), Some(&2)); - } -+ -+ #[test] -+ fn apply_hub_tt_info_only_sets_bits_for_hub_requests() { -+ let req = ConfigureEndpointsReq { -+ config_desc: 1, -+ interface_desc: None, -+ alternate_setting: None, -+ hub_ports: Some(4), -+ hub_think_time: Some(3), -+ }; -+ assert_eq!(apply_hub_tt_info(0, &req), 0x0003_0000); -+ -+ let no_hub = ConfigureEndpointsReq { hub_ports: None, ..req.clone() }; -+ assert_eq!(apply_hub_tt_info(0x0003_0000, &no_hub), 0); -+ } - } - -diff --git a/drivers/usb/usbhubd/src/main.rs b/drivers/usb/usbhubd/src/main.rs -index 2c8b9876..68538b77 100644 ---- a/drivers/usb/usbhubd/src/main.rs -+++ b/drivers/usb/usbhubd/src/main.rs -@@ -1,27 +1,41 @@ --use std::{env, thread, time}; -+use std::{env, error::Error, io, thread, time}; - - use xhcid_interface::{ -- plain, usb, ConfigureEndpointsReq, DevDesc, DeviceReqData, PortId, PortReqRecipient, PortReqTy, -- XhciClientHandle, -+ plain, usb, ConfigureEndpointsReq, DevDesc, DeviceReqData, EndpDirection, PortId, -+ PortReqRecipient, PortReqTy, XhciClientHandle, XhciEndpHandle, - }; - --fn main() { -+fn invalid_input_error(message: impl Into) -> Box { -+ let message = message.into(); -+ log::error!("{message}"); -+ Box::new(io::Error::new(io::ErrorKind::InvalidInput, message)) -+} -+ -+fn other_error(message: impl Into) -> Box { -+ let message = message.into(); -+ log::error!("{message}"); -+ Box::new(io::Error::other(message)) -+} -+ -+fn main() -> Result<(), Box> { - common::init(); - let mut args = env::args().skip(1); - - const USAGE: &'static str = "usbhubd "; - -- let scheme = args.next().expect(USAGE); -+ let scheme = args.next().ok_or_else(|| invalid_input_error(USAGE))?; - let port_id = args - .next() -- .expect(USAGE) -+ .ok_or_else(|| invalid_input_error(USAGE))? - .parse::() -- .expect("Expected port ID"); -+ .map_err(|err| invalid_input_error(format!("Expected port ID: {err}")))?; - let interface_num = args - .next() -- .expect(USAGE) -+ .ok_or_else(|| invalid_input_error(USAGE))? - .parse::() -- .expect("Expected integer as input of interface"); -+ .map_err(|err| { -+ invalid_input_error(format!("Expected integer as input of interface: {err}")) -+ })?; - - log::info!( - "USB HUB driver spawned with scheme `{}`, port {}, interface {}", -@@ -39,11 +53,16 @@ fn main() { - common::file_level(), - ); - -- let handle = -- XhciClientHandle::new(scheme.clone(), port_id).expect("Failed to open XhciClientHandle"); -- let desc: DevDesc = handle -- .get_standard_descs() -- .expect("Failed to get standard descriptors"); -+ let handle = XhciClientHandle::new(scheme.clone(), port_id).map_err(|err| { -+ other_error(format!( -+ "Failed to open XhciClientHandle for scheme `{scheme}` port {port_id}: {err}" -+ )) -+ })?; -+ let desc: DevDesc = handle.get_standard_descs().map_err(|err| { -+ other_error(format!( -+ "Failed to get standard descriptors for hub on port {port_id}: {err}" -+ )) -+ })?; - - let (conf_desc, if_desc) = desc - .config_descs -@@ -58,7 +77,11 @@ fn main() { - })?; - Some((conf_desc.clone(), if_desc)) - }) -- .expect("Failed to find suitable configuration"); -+ .ok_or_else(|| { -+ other_error(format!( -+ "Failed to find suitable configuration for hub interface {interface_num}" -+ )) -+ })?; - - // Read hub descriptor - let (ports, usb_3) = if desc.major_version() >= 3 { -@@ -73,7 +96,11 @@ fn main() { - 0, - DeviceReqData::In(unsafe { plain::as_mut_bytes(&mut hub_desc) }), - ) -- .expect("Failed to read hub descriptor"); -+ .map_err(|err| { -+ other_error(format!( -+ "Failed to read USB 3 hub descriptor for port {port_id}: {err}" -+ )) -+ })?; - (hub_desc.ports, true) - } else { - // USB 2.0 and earlier hubs -@@ -87,7 +114,11 @@ fn main() { - 0, - DeviceReqData::In(unsafe { plain::as_mut_bytes(&mut hub_desc) }), - ) -- .expect("Failed to read hub descriptor"); -+ .map_err(|err| { -+ other_error(format!( -+ "Failed to read USB 2 hub descriptor for port {port_id}: {err}" -+ )) -+ })?; - (hub_desc.ports, false) - }; - -@@ -99,7 +130,11 @@ fn main() { - alternate_setting: None, //TODO: stalls on USB 3 hub: Some(if_desc.alternate_setting), - hub_ports: Some(ports), - }) -- .expect("Failed to configure endpoints after reading hub descriptor"); -+ .map_err(|err| { -+ other_error(format!( -+ "Failed to configure endpoints after reading hub descriptor on port {port_id}: {err}" -+ )) -+ })?; - - if usb_3 { - handle -@@ -111,139 +146,353 @@ fn main() { - 0, - DeviceReqData::NoData, - ) -- .expect("Failed to set hub depth"); -+ .map_err(|err| { -+ other_error(format!("Failed to set hub depth for port {port_id}: {err}")) -+ })?; - } - -+ let interrupt_endpoint_desc = if_desc -+ .endpoints -+ .iter() -+ .find(|endp_desc| endp_desc.is_interrupt() && endp_desc.direction() == EndpDirection::In) -+ .copied(); -+ let status_change_bitmap_size = (usize::from(ports) + 8) / 8; -+ - // Initialize states - struct PortState { - port_id: PortId, - port_sts: usb::HubPortStatus, - handle: XhciClientHandle, - attached: bool, -+ suspended: bool, - } - - impl PortState { -- pub fn ensure_attached(&mut self, attached: bool) { -+ pub fn ensure_attached(&mut self, attached: bool) -> io::Result<()> { - if attached == self.attached { -- return; -+ return Ok(()); - } - - if attached { -- self.handle.attach().expect("Failed to attach"); -+ self.handle.attach().map_err(|err| { -+ io::Error::other(format!( -+ "Failed to attach child device on port {}: {err}", -+ self.port_id -+ )) -+ })?; - } else { -- self.handle.detach().expect("Failed to detach"); -+ self.handle.detach().map_err(|err| { -+ io::Error::other(format!( -+ "Failed to detach child device on port {}: {err}", -+ self.port_id -+ )) -+ })?; - } - - self.attached = attached; -+ Ok(()) -+ } -+ -+ pub fn ensure_suspended(&mut self, suspended: bool) -> io::Result<()> { -+ if suspended == self.suspended { -+ return Ok(()); -+ } -+ -+ if suspended { -+ self.handle.suspend_device().map_err(|err| { -+ io::Error::other(format!( -+ "Failed to suspend child device on port {}: {err}", -+ self.port_id -+ )) -+ })?; -+ } else { -+ self.handle.resume_device().map_err(|err| { -+ io::Error::other(format!( -+ "Failed to resume child device on port {}: {err}", -+ self.port_id -+ )) -+ })?; -+ } -+ -+ self.suspended = suspended; -+ Ok(()) - } - } - - let mut states = Vec::new(); - for port in 1..=ports { -- let child_port_id = port_id.child(port).expect("Cannot get child port ID"); -- states.push(PortState { -+ let child_port_id = match port_id.child(port) { -+ Ok(child_port_id) => child_port_id, -+ Err(err) => { -+ log::warn!( -+ "Skipping hub port {port}: cannot derive child port ID from parent port {port_id}: {err}" -+ ); -+ states.push(None); -+ continue; -+ } -+ }; -+ -+ let child_handle = match XhciClientHandle::new(scheme.clone(), child_port_id) { -+ Ok(child_handle) => child_handle, -+ Err(err) => { -+ log::warn!( -+ "Skipping hub port {port} (child port {child_port_id}): failed to open XhciClientHandle: {err}" -+ ); -+ states.push(None); -+ continue; -+ } -+ }; -+ -+ states.push(Some(PortState { - port_id: child_port_id, - port_sts: if usb_3 { - usb::HubPortStatus::V3(usb::HubPortStatusV3::default()) - } else { - usb::HubPortStatus::V2(usb::HubPortStatusV2::default()) - }, -- handle: XhciClientHandle::new(scheme.clone(), child_port_id) -- .expect("Failed to open XhciClientHandle"), -+ handle: child_handle, - attached: false, -- }); -+ suspended: false, -+ })); - } - -- //TODO: use change flags? -- loop { -- for port in 1..=ports { -- let port_idx: usize = port.checked_sub(1).unwrap().into(); -- let state = states.get_mut(port_idx).unwrap(); -- -- let port_sts = if usb_3 { -- let mut port_sts = usb::HubPortStatusV3::default(); -- handle -- .device_request( -- PortReqTy::Class, -- PortReqRecipient::Other, -- usb::SetupReq::GetStatus as u8, -- 0, -- port as u16, -- DeviceReqData::In(unsafe { plain::as_mut_bytes(&mut port_sts) }), -- ) -- .expect("Failed to retrieve port status"); -- usb::HubPortStatus::V3(port_sts) -- } else { -- let mut port_sts = usb::HubPortStatusV2::default(); -- handle -- .device_request( -- PortReqTy::Class, -- PortReqRecipient::Other, -- usb::SetupReq::GetStatus as u8, -- 0, -- port as u16, -- DeviceReqData::In(unsafe { plain::as_mut_bytes(&mut port_sts) }), -- ) -- .expect("Failed to retrieve port status"); -- usb::HubPortStatus::V2(port_sts) -- }; -- if state.port_sts != port_sts { -- state.port_sts = port_sts; -- log::info!("port {} status {:X?}", port, port_sts); -- } -- -- // Ensure port is powered on -- if !port_sts.is_powered() { -- log::info!("power on port {port}"); -- handle -- .device_request( -- PortReqTy::Class, -- PortReqRecipient::Other, -- usb::SetupReq::SetFeature as u8, -- usb::HubPortFeature::PortPower as u16, -- port as u16, -- DeviceReqData::NoData, -- ) -- .expect("Failed to set port power"); -- state.ensure_attached(false); -- continue; -+ let mut process_port = |port: u8| -> Result<(), Box> { -+ let port_idx: usize = match port.checked_sub(1) { -+ Some(port_idx) => port_idx.into(), -+ None => { -+ return Err(other_error(format!( -+ "Failed to derive zero-based index for hub port {port}" -+ ))); - } -+ }; -+ let Some(state_entry) = states.get_mut(port_idx) else { -+ return Err(other_error(format!( -+ "Missing state entry for hub port {port} at index {port_idx}" -+ ))); -+ }; -+ let Some(state) = state_entry.as_mut() else { -+ return Ok(()); -+ }; - -- // Ignore disconnected port -- if !port_sts.is_connected() { -- state.ensure_attached(false); -- continue; -+ let port_sts = if usb_3 { -+ let mut port_sts = usb::HubPortStatusV3::default(); -+ if let Err(err) = handle.device_request( -+ PortReqTy::Class, -+ PortReqRecipient::Other, -+ usb::SetupReq::GetStatus as u8, -+ 0, -+ port as u16, -+ DeviceReqData::In(unsafe { plain::as_mut_bytes(&mut port_sts) }), -+ ) { -+ log::warn!("Failed to retrieve USB 3 status for hub port {port}: {err}"); -+ if let Err(err) = state.ensure_attached(false) { -+ log::warn!( -+ "Failed to detach child device after status error on hub port {port}: {err}" -+ ); -+ } -+ return Ok(()); -+ } -+ usb::HubPortStatus::V3(port_sts) -+ } else { -+ let mut port_sts = usb::HubPortStatusV2::default(); -+ if let Err(err) = handle.device_request( -+ PortReqTy::Class, -+ PortReqRecipient::Other, -+ usb::SetupReq::GetStatus as u8, -+ 0, -+ port as u16, -+ DeviceReqData::In(unsafe { plain::as_mut_bytes(&mut port_sts) }), -+ ) { -+ log::warn!("Failed to retrieve USB 2 status for hub port {port}: {err}"); -+ if let Err(err) = state.ensure_attached(false) { -+ log::warn!( -+ "Failed to detach child device after status error on hub port {port}: {err}" -+ ); -+ } -+ return Ok(()); - } -+ usb::HubPortStatus::V2(port_sts) -+ }; -+ if state.port_sts != port_sts { -+ state.port_sts = port_sts; -+ log::info!("port {} status {:X?}", port, port_sts); -+ } - -- // Ignore port in reset -- if port_sts.is_resetting() { -- state.ensure_attached(false); -- continue; -+ // Ensure port is powered on -+ if !port_sts.is_powered() { -+ if let Err(err) = state.ensure_suspended(false) { -+ log::warn!("Failed to resume child device for unpowered hub port {port}: {err}"); - } -+ log::info!("power on port {port}"); -+ if let Err(err) = handle.device_request( -+ PortReqTy::Class, -+ PortReqRecipient::Other, -+ usb::SetupReq::SetFeature as u8, -+ usb::HubPortFeature::PortPower as u16, -+ port as u16, -+ DeviceReqData::NoData, -+ ) { -+ log::warn!("Failed to set port power for hub port {port}: {err}"); -+ if let Err(err) = state.ensure_attached(false) { -+ log::warn!( -+ "Failed to detach child device after power error on hub port {port}: {err}" -+ ); -+ } -+ return Ok(()); -+ } -+ if let Err(err) = state.ensure_attached(false) { -+ log::warn!("Failed to detach child device for unpowered hub port {port}: {err}"); -+ } -+ return Ok(()); -+ } - -- // Ensure port is enabled -- if !port_sts.is_enabled() { -- log::info!("reset port {port}"); -- handle -- .device_request( -- PortReqTy::Class, -- PortReqRecipient::Other, -- usb::SetupReq::SetFeature as u8, -- usb::HubPortFeature::PortReset as u16, -- port as u16, -- DeviceReqData::NoData, -- ) -- .expect("Failed to set port enable"); -- state.ensure_attached(false); -- continue; -+ // Ignore disconnected port -+ if !port_sts.is_connected() { -+ if let Err(err) = state.ensure_suspended(false) { -+ log::warn!("Failed to resume child device for disconnected hub port {port}: {err}"); -+ } -+ if let Err(err) = state.ensure_attached(false) { -+ log::warn!("Failed to detach child device for disconnected hub port {port}: {err}"); - } -+ return Ok(()); -+ } - -- state.ensure_attached(true); -+ // Ignore port in reset -+ if port_sts.is_resetting() { -+ if let Err(err) = state.ensure_suspended(false) { -+ log::warn!("Failed to resume child device for resetting hub port {port}: {err}"); -+ } -+ if let Err(err) = state.ensure_attached(false) { -+ log::warn!("Failed to detach child device for resetting hub port {port}: {err}"); -+ } -+ return Ok(()); - } - -- //TODO: use interrupts or poll faster? -- thread::sleep(time::Duration::new(1, 0)); -+ // Ensure port is enabled -+ if !port_sts.is_enabled() { -+ log::info!("reset port {port}"); -+ if let Err(err) = handle.device_request( -+ PortReqTy::Class, -+ PortReqRecipient::Other, -+ usb::SetupReq::SetFeature as u8, -+ usb::HubPortFeature::PortReset as u16, -+ port as u16, -+ DeviceReqData::NoData, -+ ) { -+ log::warn!("Failed to reset hub port {port}: {err}"); -+ if let Err(err) = state.ensure_attached(false) { -+ log::warn!( -+ "Failed to detach child device after reset error on hub port {port}: {err}" -+ ); -+ } -+ return Ok(()); -+ } -+ if let Err(err) = state.ensure_attached(false) { -+ log::warn!("Failed to detach child device while resetting hub port {port}: {err}"); -+ } -+ return Ok(()); -+ } -+ -+ if let Err(err) = state.ensure_suspended(port_sts.is_suspended()) { -+ log::warn!("Failed to synchronize child suspend state for hub port {port}: {err}"); -+ } -+ -+ if let Err(err) = state.ensure_attached(true) { -+ log::warn!("Failed to attach child device for hub port {port}: {err}"); -+ } -+ -+ Ok(()) -+ }; -+ -+ let try_open_interrupt_endpoint = || -> Option { -+ let Some(interrupt_endpoint_desc) = interrupt_endpoint_desc else { -+ return None; -+ }; -+ -+ let interrupt_endpoint_num = interrupt_endpoint_desc.address & 0x0F; -+ match handle.open_endpoint(interrupt_endpoint_num) { -+ Ok(interrupt_endpoint) => { -+ log::info!( -+ "Using hub interrupt endpoint {} IN (max packet size {})", -+ interrupt_endpoint_num, -+ interrupt_endpoint_desc.max_packet_size -+ ); -+ Some(interrupt_endpoint) -+ } -+ Err(err) => { -+ log::warn!( -+ "Failed to open hub interrupt endpoint {} on port {}: {}; falling back to polling", -+ interrupt_endpoint_num, -+ port_id, -+ err -+ ); -+ None -+ } -+ } -+ }; -+ -+ for port in 1..=ports { -+ process_port(port)?; -+ } -+ -+ if interrupt_endpoint_desc.is_none() { -+ log::warn!( -+ "No interrupt IN endpoint found for hub on port {}; falling back to polling", -+ port_id -+ ); - } - -- //TODO: read interrupt port for changes -+ let mut interrupt_endpoint = try_open_interrupt_endpoint(); -+ let mut poll_iterations: u32 = 0; -+ -+ loop { -+ if let Some(endp) = interrupt_endpoint.as_mut() { -+ let mut change_bitmap = vec![0_u8; status_change_bitmap_size]; -+ match endp.transfer_read(&mut change_bitmap) { -+ Ok(_) => { -+ for port in 1..=ports { -+ let bit = usize::from(port); -+ let byte_idx = bit / 8; -+ let bit_idx = bit % 8; -+ -+ if change_bitmap -+ .get(byte_idx) -+ .is_some_and(|byte| ((byte >> bit_idx) & 1) != 0) -+ { -+ process_port(port)?; -+ } -+ } -+ poll_iterations = 0; -+ continue; -+ } -+ Err(err) => { -+ log::warn!( -+ "Failed to read hub interrupt endpoint on port {}: {}; falling back to polling", -+ port_id, -+ err -+ ); -+ interrupt_endpoint = None; -+ poll_iterations = 0; -+ } -+ } -+ } -+ -+ for port in 1..=ports { -+ process_port(port)?; -+ } -+ -+ poll_iterations = poll_iterations.saturating_add(1); -+ if interrupt_endpoint.is_none() -+ && interrupt_endpoint_desc.is_some() -+ && poll_iterations % 10 == 0 -+ { -+ interrupt_endpoint = try_open_interrupt_endpoint(); -+ if interrupt_endpoint.is_some() { -+ poll_iterations = 0; -+ continue; -+ } -+ } -+ -+ thread::sleep(time::Duration::new(1, 0)); -+ } - } + [[drivers]] + name = "USB HUB" diff --git a/drivers/usb/xhcid/src/driver_interface.rs b/drivers/usb/xhcid/src/driver_interface.rs -index 727f8d7e..557e6bce 100644 +index 727f8d7e..82f839ae 100644 --- a/drivers/usb/xhcid/src/driver_interface.rs +++ b/drivers/usb/xhcid/src/driver_interface.rs -@@ -560,6 +560,16 @@ impl XhciClientHandle { +@@ -444,6 +444,33 @@ impl str::FromStr for PortState { + } + } + ++#[repr(u8)] ++#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] ++pub enum PortPmState { ++ Active, ++ Suspended, ++} ++impl PortPmState { ++ pub fn as_str(&self) -> &'static str { ++ match self { ++ Self::Active => "active", ++ Self::Suspended => "suspended", ++ } ++ } ++} ++ ++impl str::FromStr for PortPmState { ++ type Err = Invalid; ++ ++ fn from_str(s: &str) -> result::Result { ++ Ok(match s { ++ "active" => Self::Active, ++ "suspended" => Self::Suspended, ++ _ => return Err(Invalid("read reserved port PM state")), ++ }) ++ } ++} ++ + #[repr(u8)] + #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] + pub enum EndpointStatus { +@@ -560,6 +587,16 @@ impl XhciClientHandle { let _bytes_written = file.write(&[])?; Ok(()) } @@ -5610,128 +6123,420 @@ index 727f8d7e..557e6bce 100644 pub fn get_standard_descs(&self) -> result::Result { let json = self.read("descriptors")?; Ok(serde_json::from_slice(&json)?) -diff --git a/drivers/usb/xhcid/Cargo.toml b/drivers/usb/xhcid/Cargo.toml -index 778376b0..1651bcf5 100644 ---- a/drivers/usb/xhcid/Cargo.toml -+++ b/drivers/usb/xhcid/Cargo.toml -@@ -32,6 +32,7 @@ common = { path = "../../common" } - daemon = { path = "../../../daemon" } - pcid = { path = "../../pcid" } -+redox-driver-sys = { path = "../../../../../../../local/recipes/drivers/redox-driver-sys/source" } - libredox.workspace = true - regex = "1.10.6" - -diff --git a/drivers/usb/xhcid/src/usb_quirks.rs b/drivers/usb/xhcid/src/usb_quirks.rs -new file mode 100644 -index 00000000..83ca324d ---- /dev/null -+++ b/drivers/usb/xhcid/src/usb_quirks.rs -@@ -0,0 +1,10 @@ -+pub use redox_driver_sys::quirks::UsbQuirkFlags; -+ -+use crate::driver_interface::PortId; -+ -+pub fn lookup_usb_quirks(vendor: u16, product: u16) -> UsbQuirkFlags { -+ redox_driver_sys::quirks::lookup_usb_quirks(vendor, product) -+} -+ -+pub fn lookup_usb_quirks_early(_port_id: PortId) -> UsbQuirkFlags { -+ UsbQuirkFlags::empty() -+} +@@ -582,6 +619,10 @@ impl XhciClientHandle { + let string = self.read_to_string("state")?; + Ok(string.parse()?) + } ++ pub fn port_pm_state(&self) -> result::Result { ++ let string = self.read_to_string("pm_state")?; ++ Ok(string.parse()?) ++ } + pub fn open_endpoint_ctl(&self, num: u8) -> result::Result { + let path = format!("endpoints/{}/ctl", num); + let fd = self.fd.openat(&path, libredox::flag::O_RDWR, 0)?; diff --git a/drivers/usb/xhcid/src/main.rs b/drivers/usb/xhcid/src/main.rs -index 25b2fdd6..d5dea9b2 100644 +index d345a52f..562c580a 100644 --- a/drivers/usb/xhcid/src/main.rs +++ b/drivers/usb/xhcid/src/main.rs -@@ -49,6 +49,7 @@ use crate::xhci::{InterruptMethod, Xhci}; - // mean anything. - pub mod driver_interface; +@@ -33,7 +33,7 @@ use std::sync::Arc; + use pcid_interface::irq_helpers::read_bsp_apic_id; + #[cfg(target_arch = "x86_64")] + use pcid_interface::irq_helpers::{ +- allocate_first_msi_interrupt_on_bsp, allocate_single_interrupt_vector_for_msi, ++ try_allocate_first_msi_interrupt_on_bsp, try_allocate_single_interrupt_vector_for_msi, + }; + use pcid_interface::{PciFeature, PciFeatureInfo, PciFunctionHandle}; -+mod usb_quirks; - mod usb; - mod xhci; - -@@ -141,8 +142,19 @@ fn daemon_with_context_size( +@@ -61,11 +61,24 @@ fn get_int_method(pcid_handle: &mut PciFunctionHandle) -> (Option, Interru + let has_msix = all_pci_features.iter().any(|feature| feature.is_msix()); - let address = unsafe { pcid_handle.map_bar(0) }.ptr.as_ptr() as usize; + if has_msix { +- let msix_info = match pcid_handle.feature_info(PciFeature::MsiX) { +- PciFeatureInfo::Msi(_) => panic!(), +- PciFeatureInfo::MsiX(s) => s, ++ let msix_info = match pcid_handle.try_feature_info(PciFeature::MsiX) { ++ Ok(PciFeatureInfo::MsiX(s)) => s, ++ Ok(PciFeatureInfo::Msi(_)) => { ++ log::error!("xhcid: invalid MSI-X feature response payload"); ++ return (None, InterruptMethod::Polling); ++ } ++ Err(err) => { ++ log::error!("xhcid: failed to fetch MSI-X feature info: {err}"); ++ return (None, InterruptMethod::Polling); ++ } ++ }; ++ let mut info = match unsafe { msix_info.try_map_and_mask_all(pcid_handle) } { ++ Ok(info) => info, ++ Err(err) => { ++ log::error!("xhcid: failed to map MSI-X registers: {err}"); ++ return (None, InterruptMethod::Polling); ++ } + }; +- let mut info = unsafe { msix_info.map_and_mask_all(pcid_handle) }; + + // Allocate one msi vector. + +@@ -75,27 +88,53 @@ fn get_int_method(pcid_handle: &mut PciFunctionHandle) -> (Option, Interru + + let table_entry_pointer = info.table_entry_pointer(k); + +- let destination_id = read_bsp_apic_id().expect("xhcid: failed to read BSP apic id"); ++ let destination_id = match read_bsp_apic_id() { ++ Ok(id) => id, ++ Err(err) => { ++ log::error!("xhcid: failed to read BSP APIC ID: {err}"); ++ return (None, InterruptMethod::Polling); ++ } ++ }; + let (msg_addr_and_data, interrupt_handle) = +- allocate_single_interrupt_vector_for_msi(destination_id); ++ match try_allocate_single_interrupt_vector_for_msi(destination_id) { ++ Ok(result) => result, ++ Err(err) => { ++ log::error!("xhcid: failed to allocate MSI-X vector: {err}"); ++ return (None, InterruptMethod::Polling); ++ } ++ }; + table_entry_pointer.write_addr_and_data(msg_addr_and_data); + table_entry_pointer.unmask(); + + (Some(interrupt_handle), InterruptMethod::Msi) + }; + +- pcid_handle.enable_feature(PciFeature::MsiX); ++ if let Err(err) = pcid_handle.try_enable_feature(PciFeature::MsiX) { ++ log::error!("xhcid: failed to enable MSI-X: {err}"); ++ return (None, InterruptMethod::Polling); ++ } + log::debug!("Enabled MSI-X"); + + method + } else if has_msi { +- let interrupt_handle = allocate_first_msi_interrupt_on_bsp(pcid_handle); +- (Some(interrupt_handle), InterruptMethod::Msi) ++ match try_allocate_first_msi_interrupt_on_bsp(pcid_handle) { ++ Ok(interrupt_handle) => (Some(interrupt_handle), InterruptMethod::Msi), ++ Err(err) => { ++ log::error!("xhcid: failed to allocate MSI interrupt: {err}"); ++ (None, InterruptMethod::Polling) ++ } ++ } + } else if let Some(irq) = pci_config.func.legacy_interrupt_line { + log::debug!("Legacy IRQ {}", irq); + + // legacy INTx# interrupt pins. +- (Some(irq.irq_handle("xhcid")), InterruptMethod::Intx) ++ match irq.try_irq_handle("xhcid") { ++ Ok(file) => (Some(file), InterruptMethod::Intx), ++ Err(err) => { ++ log::error!("xhcid: failed to open legacy IRQ handle: {err}"); ++ (None, InterruptMethod::Polling) ++ } ++ } + } else { + // no interrupts at all + (None, InterruptMethod::Polling) +@@ -109,7 +148,13 @@ fn get_int_method(pcid_handle: &mut PciFunctionHandle) -> (Option, Interru + + if let Some(irq) = pci_config.func.legacy_interrupt_line { + // legacy INTx# interrupt pins. +- (Some(irq.irq_handle("xhcid")), InterruptMethod::Intx) ++ match irq.try_irq_handle("xhcid") { ++ Ok(file) => (Some(file), InterruptMethod::Intx), ++ Err(err) => { ++ log::error!("xhcid: failed to open legacy IRQ handle: {err}"); ++ (None, InterruptMethod::Polling) ++ } ++ } + } else { + // no interrupts at all + (None, InterruptMethod::Polling) +@@ -136,23 +181,48 @@ fn daemon_with_context_size( + + log::debug!("XHCI PCI CONFIG: {:?}", pci_config); + +- let address = unsafe { pcid_handle.map_bar(0) }.ptr.as_ptr() as usize; ++ let address = match unsafe { pcid_handle.try_map_bar(0) } { ++ Ok(bar) => bar.ptr.as_ptr() as usize, ++ Err(err) => { ++ log::error!("xhcid: failed to map BAR0: {err}"); ++ std::process::exit(1); ++ } ++ }; ++ ++ let (irq_file, interrupt_method) = get_int_method(&mut pcid_handle); - let (irq_file, interrupt_method) = (None, InterruptMethod::Polling); //get_int_method(&mut pcid_handle); - //TODO: Fix interrupts. -+ let (irq_file, interrupt_method) = get_int_method(&mut pcid_handle); -+ + match interrupt_method { -+ InterruptMethod::Msi => { -+ log::info!("xhcid: using MSI/MSI-X interrupt delivery"); -+ } -+ InterruptMethod::Intx => { -+ log::info!("xhcid: using legacy INTx interrupt delivery"); -+ } -+ InterruptMethod::Polling => { -+ log::warn!("xhcid: using polling event delivery"); -+ } ++ InterruptMethod::Msi => log::info!("xhcid: using MSI/MSI-X interrupt delivery"), ++ InterruptMethod::Intx => log::info!("xhcid: using legacy INTx interrupt delivery"), ++ InterruptMethod::Polling => log::warn!("xhcid: falling back to polling mode"), + } log::info!("XHCI {}", pci_config.func.display()); - diff --git a/drivers/usb/xhcid/src/usb/hub.rs b/drivers/usb/xhcid/src/usb/hub.rs -index 9dab55e8..fe6efdd2 100644 ---- a/drivers/usb/xhcid/src/usb/hub.rs -+++ b/drivers/usb/xhcid/src/usb/hub.rs -@@ -86,8 +86,10 @@ pub enum HubPortFeature { - PortOverCurrent = 3, - PortReset = 4, - PortLinkState = 5, -+ PortSuspend = 7, - PortPower = 8, - CPortConnection = 16, -+ CPortSuspend = 18, - CPortOverCurrent = 19, - CPortReset = 20, - } -@@ -184,4 +186,11 @@ impl HubPortStatus { - Self::V3(x) => x.contains(HubPortStatusV3::ENABLE), - } - } -+ -+ pub fn is_suspended(&self) -> bool { -+ match self { -+ Self::V2(x) => x.contains(HubPortStatusV2::SUSPEND), -+ Self::V3(_) => false, + let scheme_name = format!("usb.{}", name); +- let socket = Socket::create().expect("xhcid: failed to create usb scheme"); ++ let socket = match Socket::create() { ++ Ok(socket) => socket, ++ Err(err) => { ++ log::error!("xhcid: failed to create usb scheme: {err}"); ++ std::process::exit(1); + } -+ } ++ }; + let handler = Blocking::new(&socket, 16); + + let hci = Arc::new( +- Xhci::::new(scheme_name.clone(), address, interrupt_method, pcid_handle) +- .expect("xhcid: failed to allocate device"), ++ match Xhci::::new(scheme_name.clone(), address, interrupt_method, pcid_handle) { ++ Ok(hci) => hci, ++ Err(err) => { ++ log::error!("xhcid: failed to allocate device: {err}"); ++ std::process::exit(1); ++ } ++ }, + ); + register_sync_scheme(&socket, &scheme_name, &mut &*hci) +- .expect("xhcid: failed to regsiter scheme to namespace"); ++ .unwrap_or_else(|err| { ++ log::error!("xhcid: failed to register scheme to namespace: {err}"); ++ std::process::exit(1); ++ }); + + daemon.ready(); + +@@ -163,7 +233,10 @@ fn daemon_with_context_size( + + handler + .process_requests_blocking(&*hci) +- .expect("xhcid: failed to process requests"); ++ .unwrap_or_else(|err| { ++ log::error!("xhcid: failed to process requests: {err}"); ++ std::process::exit(1); ++ }); } + + fn main() { +@@ -171,7 +244,13 @@ fn main() { + } + + fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { +- let address = unsafe { pcid_handle.map_bar(0) }.ptr.as_ptr() as usize; ++ let address = match unsafe { pcid_handle.try_map_bar(0) } { ++ Ok(bar) => bar.ptr.as_ptr() as usize, ++ Err(err) => { ++ log::error!("xhcid: failed to map BAR0: {err}"); ++ std::process::exit(1); ++ } ++ }; + let cap = unsafe { &mut *(address as *mut xhci::CapabilityRegs) }; + if cap.csz() { + daemon_with_context_size::<{ xhci::CONTEXT_64 }>(daemon, pcid_handle) diff --git a/drivers/usb/xhcid/src/xhci/device_enumerator.rs b/drivers/usb/xhcid/src/xhci/device_enumerator.rs -index 74b9f732..1f144ac9 100644 +index 74b9f732..493e79df 100644 --- a/drivers/usb/xhcid/src/xhci/device_enumerator.rs +++ b/drivers/usb/xhcid/src/xhci/device_enumerator.rs -@@ -54,6 +54,7 @@ impl DeviceEnumerator { +@@ -4,9 +4,11 @@ use common::io::Io; + use crossbeam_channel; + use log::{debug, info, warn}; + use std::sync::Arc; +-use std::time::Duration; ++use std::time::{Duration, Instant}; + use syscall::EAGAIN; + ++const DEFAULT_PORT_RESET_SETTLE_MS: u64 = 16; ++ + pub struct DeviceEnumerationRequest { + pub port_id: PortId, + } +@@ -28,7 +30,11 @@ impl DeviceEnumerator { + let request = match self.request_queue.recv() { + Ok(req) => req, + Err(err) => { +- panic!("Failed to received an enumeration request! error: {}", err) ++ warn!( ++ "device enumerator stopping after request queue closed: {}", ++ err ++ ); ++ break; + } }; - if flags.contains(PortFlags::CCS) { -+ let early_quirks = crate::usb_quirks::lookup_usb_quirks_early(port_id); - debug!( - "Received Device Connect Port Status Change Event with port flags {:?}", - flags -@@ -85,7 +86,17 @@ impl DeviceEnumerator { +@@ -38,7 +44,11 @@ impl DeviceEnumerator { + debug!("Device Enumerator request for port {}", port_id); + + let (len, flags) = { +- let ports = self.hci.ports.lock().unwrap(); ++ let ports = self ++ .hci ++ .ports ++ .lock() ++ .unwrap_or_else(|poisoned| poisoned.into_inner()); + + let len = ports.len(); + +@@ -62,43 +72,52 @@ impl DeviceEnumerator { + //A USB3 port won't generate a Connect Status Change until it's already enabled, so this check + //will always be skipped for USB3 ports + if !flags.contains(PortFlags::PED) { +- let disabled_state = flags.contains(PortFlags::PP) +- && flags.contains(PortFlags::CCS) +- && !flags.contains(PortFlags::PED) +- && !flags.contains(PortFlags::PR); ++ let disabled_state = Self::port_is_disabled(&flags); + + if !disabled_state { +- panic!( +- "Port {} isn't in the disabled state! Current flags: {:?}", ++ warn!( ++ "Port {} never reached the disabled state before reset-driven enumeration; current flags: {:?}", + port_id, flags + ); ++ continue; + } else { + debug!("Port {} has entered the disabled state.", port_id); + } + + //THIS LOCKS THE PORTS. DO NOT LOCK PORTS BEFORE THIS POINT + debug!("Received a device connect on port {}, but it's not enabled. Resetting the port.", port_id); +- let _ = self.hci.reset_port(port_id); ++ if let Err(err) = self.hci.reset_port(port_id) { ++ warn!( ++ "failed to reset port {} before enumeration; skipping attach: {}", ++ port_id, err ++ ); ++ continue; ++ } + +- let mut ports = self.hci.ports.lock().unwrap(); ++ let mut ports = self ++ .hci ++ .ports ++ .lock() ++ .unwrap_or_else(|poisoned| poisoned.into_inner()); + let port = &mut ports[port_array_index]; port.clear_prc(); - std::thread::sleep(Duration::from_millis(16)); //Some controllers need some extra time to make the transition. -+ let delay_ms = if early_quirks -+ .contains(crate::usb_quirks::UsbQuirkFlags::HUB_SLOW_RESET) -+ { -+ 200 -+ } else if early_quirks.contains(crate::usb_quirks::UsbQuirkFlags::RESET_DELAY) { -+ 100 -+ } else { -+ 16 -+ }; ++ drop(ports); + +- let flags = port.flags(); ++ let flags = self.wait_for_port_enabled_state( ++ port_array_index, ++ Duration::from_millis(DEFAULT_PORT_RESET_SETTLE_MS), ++ ); + +- let enabled_state = flags.contains(PortFlags::PP) +- && flags.contains(PortFlags::CCS) +- && flags.contains(PortFlags::PED) +- && !flags.contains(PortFlags::PR); ++ let enabled_state = Self::port_is_enabled(&flags); + + if !enabled_state { + warn!( +- "Port {} isn't in the enabled state! Current flags: {:?}", ++ "Port {} isn't in the enabled state after bounded reset settle; current flags: {:?}", + port_id, flags + ); ++ continue; + } else { + debug!( + "Port {} is in the enabled state. Proceeding with enumeration", +@@ -131,13 +150,60 @@ impl DeviceEnumerator { + Ok(was_connected) => { + if was_connected { + info!("Device on port {} was detached", port_id); ++ } else { ++ debug!( ++ "Ignoring duplicate or out-of-order detach event for unattached port {}", ++ port_id ++ ); + } + } + Err(err) => { +- warn!("processing of device attach request failed! Error: {}", err); ++ warn!("processing of device detach request failed! Error: {}", err); + } + } + } + } + } + -+ std::thread::sleep(Duration::from_millis(delay_ms)); // Some devices need extra time to settle after reset. ++ fn port_is_disabled(flags: &PortFlags) -> bool { ++ flags.contains(PortFlags::PP) ++ && flags.contains(PortFlags::CCS) ++ && !flags.contains(PortFlags::PED) ++ && !flags.contains(PortFlags::PR) ++ } ++ ++ fn port_is_enabled(flags: &PortFlags) -> bool { ++ flags.contains(PortFlags::PP) ++ && flags.contains(PortFlags::CCS) ++ && flags.contains(PortFlags::PED) ++ && !flags.contains(PortFlags::PR) ++ } ++ ++ fn wait_for_port_enabled_state( ++ &self, ++ port_array_index: usize, ++ settle_timeout: Duration, ++ ) -> PortFlags { ++ let start = Instant::now(); ++ ++ loop { ++ let flags = { ++ let ports = self ++ .hci ++ .ports ++ .lock() ++ .unwrap_or_else(|poisoned| poisoned.into_inner()); ++ ports[port_array_index].flags() ++ }; ++ ++ if Self::port_is_enabled(&flags) ++ || !flags.contains(PortFlags::PR) ++ || start.elapsed() >= settle_timeout ++ { ++ return flags; ++ } ++ ++ std::thread::sleep(Duration::from_millis(1)); ++ } ++ } + } +diff --git a/drivers/usb/xhcid/src/xhci/irq_reactor.rs b/drivers/usb/xhcid/src/xhci/irq_reactor.rs +index ac492d5b..310fe51f 100644 +--- a/drivers/usb/xhcid/src/xhci/irq_reactor.rs ++++ b/drivers/usb/xhcid/src/xhci/irq_reactor.rs +@@ -633,7 +633,10 @@ impl Xhci { + pub fn with_ring T>(&self, id: RingId, function: F) -> Option { + use super::RingOrStreams; - let flags = port.flags(); +- let slot_state = self.port_states.get(&id.port)?; ++ let slot_state = self ++ .port_states ++ .get(&id.port) ++ .or_else(|| self.staged_port_states.get(&id.port))?; + let endpoint_state = slot_state.endpoint_states.get(&id.endpoint_num)?; + let ring_ref = match endpoint_state.transfer { +@@ -650,7 +653,10 @@ impl Xhci { + ) -> Option { + use super::RingOrStreams; + +- let mut slot_state = self.port_states.get_mut(&id.port)?; ++ let mut slot_state = self ++ .port_states ++ .get_mut(&id.port) ++ .or_else(|| self.staged_port_states.get_mut(&id.port))?; + let mut endpoint_state = slot_state.endpoint_states.get_mut(&id.endpoint_num)?; + + let ring_ref = match endpoint_state.transfer { diff --git a/drivers/usb/xhcid/src/xhci/mod.rs b/drivers/usb/xhcid/src/xhci/mod.rs -index f2143676..d81648bf 100644 +index f2143676..9ce15161 100644 --- a/drivers/usb/xhcid/src/xhci/mod.rs +++ b/drivers/usb/xhcid/src/xhci/mod.rs @@ -11,12 +11,13 @@ @@ -5740,10 +6545,10 @@ index f2143676..d81648bf 100644 use std::convert::TryFrom; -use std::fs::File; +use std::fs::{self, File}; -+use std::time::Duration; 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}; @@ -5751,7 +6556,7 @@ index f2143676..d81648bf 100644 use syscall::{EAGAIN, PAGE_SIZE}; use chashmap::CHashMap; -@@ -77,7 +78,55 @@ pub enum InterruptMethod { +@@ -77,7 +78,52 @@ pub enum InterruptMethod { Msi, } @@ -5763,18 +6568,15 @@ index f2143676..d81648bf 100644 + let contents = fs::read_to_string(path).ok()?; + contents + .lines() -+ .map(str::trim) ++ .map(|line| line.trim()) + .find(|line| !line.is_empty() && !line.starts_with('#')) -+ .map(ToOwned::to_owned) ++ .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 -+ ); ++ warn!("failed to remove xhcid test hook file {}: {}", path, err); + } + } + } @@ -5807,7 +6609,7 @@ index f2143676..d81648bf 100644 /// Gets descriptors, before the port state is initiated. async fn get_desc_raw( &self, -@@ -104,7 +153,18 @@ impl Xhci { +@@ -104,7 +150,17 @@ impl Xhci { ); let future = { @@ -5823,20 +6625,10 @@ index f2143676..d81648bf 100644 + .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) -@@ -150,7 +210,7 @@ impl Xhci { - trace!("Handling the transfer event TRB!"); - self::scheme::handle_transfer_event_trb("GET_DESC", &event_trb, &status_trb)?; - -- //self.event_handler_finished(); -+ self.event_handler_finished(); - Ok(()) - } - -@@ -283,6 +343,7 @@ pub struct Xhci { +@@ -283,6 +339,7 @@ pub struct Xhci { handles: CHashMap, next_handle: AtomicUsize, port_states: CHashMap>, @@ -5844,13 +6636,12 @@ index f2143676..d81648bf 100644 drivers: CHashMap>, scheme_name: String, -@@ -311,6 +372,144 @@ struct PortState { +@@ -311,6 +368,93 @@ struct PortState { input_context: Mutex>>, dev_desc: Option, endpoint_states: BTreeMap, -+ quirks: crate::usb_quirks::UsbQuirkFlags, -+ pm_state: PortPmState, + lifecycle: Arc, ++ pm_state: PortPmState, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -5885,40 +6676,6 @@ index f2143676..d81648bf 100644 + self.inner.lock().unwrap_or_else(|err| err.into_inner()) + } + -+ pub(crate) fn state(&self) -> PortLifecycleState { -+ self.lock_inner().state -+ } -+ -+ pub(crate) fn begin_operation(&self, allow_attaching: bool) -> Result<()> { -+ let mut inner = self.lock_inner(); -+ -+ let allowed = match inner.state { -+ PortLifecycleState::Attached => true, -+ PortLifecycleState::Attaching => allow_attaching, -+ PortLifecycleState::Detaching => false, -+ }; -+ -+ if !allowed { -+ return Err(Error::new(EBUSY)); -+ } -+ -+ inner.active_operations += 1; -+ Ok(()) -+ } -+ -+ pub(crate) fn finish_operation(&self) { -+ let mut inner = self.lock_inner(); -+ -+ if inner.active_operations == 0 { -+ return; -+ } -+ -+ inner.active_operations -= 1; -+ if inner.active_operations == 0 { -+ self.idle.notify_all(); -+ } -+ } -+ + pub(crate) fn finish_attach_success(&self) -> PortLifecycleState { + let mut inner = self.lock_inner(); + @@ -5958,22 +6715,6 @@ index f2143676..d81648bf 100644 + } +} + -+pub(crate) struct PortOperationGuard { -+ lifecycle: Arc, -+} -+ -+impl PortOperationGuard { -+ pub(crate) fn new(lifecycle: Arc) -> Self { -+ Self { lifecycle } -+ } -+} -+ -+impl Drop for PortOperationGuard { -+ fn drop(&mut self) { -+ self.lifecycle.finish_operation(); -+ } -+} -+ +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub(crate) enum PortPmState { + Active, @@ -5989,7 +6730,7 @@ index f2143676..d81648bf 100644 } impl PortState { -@@ -463,6 +662,7 @@ impl Xhci { +@@ -463,6 +607,7 @@ impl Xhci { handles: CHashMap::new(), next_handle: AtomicUsize::new(0), port_states: CHashMap::new(), @@ -5997,69 +6738,13 @@ index f2143676..d81648bf 100644 drivers: CHashMap::new(), scheme_name, -@@ -615,29 +815,24 @@ impl Xhci { - route_string: 0, - }; - -- //Get the CCS and CSC flags -- let (ccs, csc, flags) = { -+ // Only queue ports that are actually connected at startup. A stale CSC bit on an -+ // otherwise disconnected port should not trigger a full attach attempt. -+ let (ccs, flags) = { - let mut ports = self.ports.lock().unwrap(); - let port = &mut ports[port_id.root_hub_port_index()]; - let flags = port.flags(); - let ccs = flags.contains(PortFlags::CCS); -- let csc = flags.contains(PortFlags::CSC); - -- (ccs, csc, flags) -+ (ccs, flags) - }; - - debug!("Port {} has flags {:?}", port_id, flags); - -- match (ccs, csc) { -- (false, false) => { // Nothing is connected, and there was no port status change -- //Do nothing -- } -- _ => { -- //Either something is connected, or nothing is connected and a port status change was asserted. -- self.device_enumerator_sender -- .send(DeviceEnumerationRequest { port_id }) -- .expect("Failed to generate the port enumeration request!"); -- } -+ if ccs { -+ info!("xhcid: queueing initial enumeration for port {} with flags {:?}", port_id, flags); -+ self.device_enumerator_sender -+ .send(DeviceEnumerationRequest { port_id }) -+ .expect("Failed to generate the port enumeration request!"); - } - } - } -@@ -757,7 +952,7 @@ impl Xhci { - - trace!("Slot is enabled!"); - self::scheme::handle_event_trb("ENABLE_SLOT", &event_trb, &command_trb)?; -- //self.event_handler_finished(); -+ self.event_handler_finished(); - - Ok(event_trb.event_slot()) - } -@@ -768,7 +963,7 @@ impl Xhci { - .await; - - self::scheme::handle_event_trb("DISABLE_SLOT", &event_trb, &command_trb)?; -- //self.event_handler_finished(); -+ self.event_handler_finished(); - - Ok(()) - } -@@ -793,11 +988,13 @@ impl Xhci { +@@ -793,11 +938,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) { ++ 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)); } @@ -6069,7 +6754,7 @@ index f2143676..d81648bf 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 +1005,114 @@ impl Xhci { +@@ -808,74 +956,101 @@ impl Xhci { port_id, data, state, speed, flags ); @@ -6097,11 +6782,10 @@ index f2143676..d81648bf 100644 + } - 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(), + None => { -+ warn!("Failed to find supported protocol information for port {}", port_id); ++ warn!("Failed to find supported protocol information for port"); + 0 + } + }; @@ -6122,7 +6806,6 @@ index f2143676..d81648bf 100644 - 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 @@ -6145,41 +6828,17 @@ index f2143676..d81648bf 100644 + ); } - }; -- -- debug!("Addressed device"); + return Err(err); + } + }; -- // TODO: Should the descriptors be cached in PortState, or refetched? +- debug!("Addressed device"); + let mut input = unsafe { self.alloc_dma_zeroed::>()? }; -- let mut port_state = PortState { +- // 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, -- 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!"); -+ speed, -+ early_quirks, -+ ) ++ .address_device(&mut input, port_id, slot_ty, slot, protocol_speed, speed) + .await + { + Ok(device_ring) => device_ring, @@ -6194,10 +6853,27 @@ index f2143676..d81648bf 100644 + 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"); -+ info!("xhcid: addressed device on port {} slot {}", port_id, slot); -+ + +- // Ensure correct packet size is used + let lifecycle = Arc::new(PortLifecycle::new_attaching()); + let port_state = PortState { + slot, @@ -6213,17 +6889,14 @@ index f2143676..d81648bf 100644 + }, + )) + .collect::>(), -+ quirks: early_quirks, -+ pm_state: PortPmState::Active, + lifecycle: Arc::clone(&lifecycle), ++ pm_state: PortPmState::Active, + }; + self.staged_port_states.insert(port_id, port_state); + debug!("Got staged port state!"); - -- // 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 @@ -6239,28 +6912,15 @@ index f2143676..d81648bf 100644 self.update_max_packet_size(&mut *input, slot, dev_desc_8_byte) .await?; -@@ -884,38 +1121,131 @@ impl Xhci { - debug!("Got the 8 byte dev descriptor: {:X?}", dev_desc_8_byte); +@@ -885,97 +1060,175 @@ impl Xhci { let dev_desc = self.get_desc(port_id, slot).await?; -+ info!( -+ "xhcid: got descriptors for port {} vendor {:04x} product {:04x}", -+ port_id, -+ dev_desc.vendor, -+ dev_desc.product -+ ); -+ 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 -+ .staged_port_states -+ .get_mut(&port_id) -+ .ok_or(Error::new(ENOENT))?; -+ port_state.quirks = quirks; -+ port_state.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!"); { @@ -6325,6 +6985,7 @@ index f2143676..d81648bf 100644 + error!("Failed to spawn driver for port {}: `{}`", port_id, err) + } + } ++ + info!("xhcid: finished attach for port {}", port_id); + Ok(()) + } @@ -6347,6 +7008,23 @@ index f2143676..d81648bf 100644 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) @@ -6367,29 +7045,76 @@ index f2143676..d81648bf 100644 + return Ok(false); + } + }; ++ drop(published_state); ++ drop(staged_state); + -+ info!("xhcid: begin detach quiesce for port {}", port_id); + lifecycle.begin_detaching(); -+ info!("xhcid: detach quiesce complete for port {}", port_id); -+ -+ if let Some(delay_ms) = self.consume_test_hook_delay_ms("delay_before_detach_disable_ms=") { -+ info!( -+ "xhcid: test hook delaying detach disable for port {} by {} ms", -+ port_id, delay_ms -+ ); -+ thread::sleep(Duration::from_millis(delay_ms)); -+ } + + 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() { -@@ -961,21 +1291,26 @@ impl Xhci { ++ 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); @@ -6426,92 +7151,18 @@ index f2143676..d81648bf 100644 } } -@@ -1004,7 +1339,7 @@ impl Xhci { - .await; - - self::scheme::handle_event_trb("EVALUATE_CONTEXT", &event_trb, &command_trb)?; -- //self.event_handler_finished(); -+ self.event_handler_finished(); - - Ok(()) - } -@@ -1039,7 +1374,7 @@ impl Xhci { - debug!("Completed the command to update the default control pipe"); - - self::scheme::handle_event_trb("EVALUATE_CONTEXT", &event_trb, &command_trb)?; -- //self.event_handler_finished(); -+ self.event_handler_finished(); - - Ok(()) - } -@@ -1052,6 +1387,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 +1498,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!( -@@ -1175,10 +1516,10 @@ impl Xhci { - port, - event_trb.completion_code() - ); -- //self.event_handler_finished(); -+ self.event_handler_finished(); - return Err(Error::new(EIO)); - } -- //self.event_handler_finished(); -+ self.event_handler_finished(); - - Ok(ring) - } -@@ -1281,6 +1622,12 @@ impl Xhci { - ifdesc.sub_class, - ifdesc.protocol, - ); -+ match driver.name.as_str() { -+ "USB HID" => info!("USB HID driver spawned"), -+ "SCSI over USB" => info!("USB SCSI driver spawned"), -+ "USB HUB" => info!("USB HUB driver spawned"), -+ _ => {} -+ } - let (command, args) = driver.command.split_first().ok_or(Error::new(EBADMSG))?; - - let command = if command.starts_with('/') { -@@ -1487,3 +1834,52 @@ lazy_static! { - toml::from_slice::(TOML).expect("Failed to parse internally embedded config file") - }; +@@ -1458,6 +1711,53 @@ pub fn start_device_enumerator(hci: &Arc>) { + })); } -+ + +#[cfg(test)] +mod tests { -+ use super::{Xhci, XHCID_TEST_HOOK_MAX_DELAY_MS}; + 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) @@ -6544,36 +7195,29 @@ index f2143676..d81648bf 100644 + 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=" -+ ), ++ 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 f2d439a4..bc6d7fca 100644 +index b524f5d8..aac4fa82 100644 --- a/drivers/usb/xhcid/src/xhci/scheme.rs +++ b/drivers/usb/xhcid/src/xhci/scheme.rs -@@ -18,12 +18,15 @@ - //! port/endpoints//data - use std::convert::TryFrom; +@@ -20,6 +20,7 @@ use std::convert::TryFrom; use std::io::prelude::*; -+use std::io::Write; use std::ops::Deref; -+use std::sync::Arc; use std::sync::atomic; ++use std::collections::BTreeMap; 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,16 +35,16 @@ use common::io::Io; +@@ -32,9 +33,9 @@ use common::io::Io; use redox_scheme::{CallerCtx, OpenResult}; use syscall::schemev2::NewFdFlags; use syscall::{ @@ -6586,15 +7230,7 @@ index f2d439a4..bc6d7fca 100644 }; use super::{port, usb}; - use super::{EndpointState, PortId, Xhci}; - - use super::context::{ -- SlotState, StreamContextArray, StreamContextType, CONTEXT_32, CONTEXT_64, -+ EndpointContext, SlotState, StreamContextArray, StreamContextType, CONTEXT_32, CONTEXT_64, - SLOT_CONTEXT_STATE_MASK, SLOT_CONTEXT_STATE_SHIFT, - }; - use super::extended::ProtocolSpeed; -@@ -60,10 +63,16 @@ lazy_static! { +@@ -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."); @@ -6611,7 +7247,7 @@ index f2d439a4..bc6d7fca 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 { +@@ -137,12 +144,15 @@ pub enum Handle { Port(PortId, Vec), // port, contents PortDesc(PortId, Vec), // port, contents PortState(PortId), // port @@ -6627,7 +7263,7 @@ index f2d439a4..bc6d7fca 100644 SchemeRoot, } -@@ -172,6 +184,8 @@ enum SchemeParameters { +@@ -172,6 +182,8 @@ enum SchemeParameters { PortDesc(PortId), // port number /// /port/state PortState(PortId), // port number @@ -6636,7 +7272,7 @@ index f2d439a4..bc6d7fca 100644 /// /port/request PortReq(PortId), // port number /// /port/endpoints -@@ -187,6 +201,10 @@ enum SchemeParameters { +@@ -187,6 +199,10 @@ enum SchemeParameters { AttachDevice(PortId), // port number /// /port/detach DetachDevice(PortId), // port number @@ -6647,7 +7283,7 @@ index f2d439a4..bc6d7fca 100644 } impl Handle { -@@ -209,6 +227,9 @@ impl Handle { +@@ -209,6 +225,9 @@ impl Handle { Handle::PortState(port_num) => { format!("port{}/state", port_num) } @@ -6657,7 +7293,7 @@ index f2d439a4..bc6d7fca 100644 Handle::PortReq(port_num, _) => { format!("port{}/request", port_num) } -@@ -235,6 +256,12 @@ impl Handle { +@@ -235,6 +254,12 @@ impl Handle { Handle::DetachDevice(port_num) => { format!("port{}/detach", port_num) } @@ -6670,7 +7306,7 @@ index f2d439a4..bc6d7fca 100644 Handle::SchemeRoot => String::from(""), } } -@@ -258,10 +285,13 @@ impl Handle { +@@ -258,10 +283,13 @@ impl Handle { &Handle::PortReq(_, PortReqState::Tmp) => unreachable!(), &Handle::PortReq(_, PortReqState::TmpSetup(_)) => unreachable!(), &Handle::PortState(_) => HandleType::Character, @@ -6684,7 +7320,7 @@ index f2d439a4..bc6d7fca 100644 &Handle::Endpoint(_, _, ref st) => match st { EndpointHandleTy::Data => HandleType::Character, EndpointHandleTy::Ctl => HandleType::Character, -@@ -289,10 +319,13 @@ impl Handle { +@@ -289,10 +317,13 @@ impl Handle { &Handle::PortReq(_, PortReqState::Tmp) => None, &Handle::PortReq(_, PortReqState::TmpSetup(_)) => None, &Handle::PortState(_) => None, @@ -6698,7 +7334,7 @@ index f2d439a4..bc6d7fca 100644 &Handle::Endpoint(_, _, ref st) => match st { EndpointHandleTy::Data => None, EndpointHandleTy::Ctl => None, -@@ -383,6 +416,14 @@ impl SchemeParameters { +@@ -383,6 +414,14 @@ impl SchemeParameters { let port_num = get_port_id_from_regex(®EX_PORT_DETACH, scheme, 0)?; Ok(Self::DetachDevice(port_num)) @@ -6713,7 +7349,7 @@ index f2d439a4..bc6d7fca 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 { +@@ -391,6 +430,10 @@ impl SchemeParameters { let port_num = get_port_id_from_regex(®EX_PORT_STATE, scheme, 0)?; Ok(Self::PortState(port_num)) @@ -6724,262 +7360,65 @@ index f2d439a4..bc6d7fca 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 { +@@ -523,6 +566,39 @@ pub enum AnyDescriptor { + SuperSpeedPlusCompanion(usb::SuperSpeedPlusIsochCmpDescriptor), } - impl Xhci { -+ fn begin_port_operation( -+ &self, -+ port: PortId, -+ allow_attaching: bool, -+ require_active_pm: bool, -+ ) -> Result { -+ let lifecycle = { -+ let port_state = self.port_states.get(&port).ok_or(Error::new(EBADFD))?; -+ Arc::clone(&port_state.lifecycle) -+ }; ++#[derive(Clone, Copy)] ++struct ConfigureContextSnapshot { ++ add_context: u32, ++ drop_context: u32, ++ control: u32, ++ slot_a: u32, ++ slot_b: u32, ++} + -+ lifecycle.begin_operation(allow_attaching)?; -+ let guard = super::PortOperationGuard::new(lifecycle); ++#[derive(Clone, Copy)] ++struct EndpointContextSnapshot { ++ a: u32, ++ b: u32, ++ trl: u32, ++ trh: u32, ++ c: u32, ++} + -+ if require_active_pm { -+ let pm_state = self -+ .port_states -+ .get(&port) -+ .ok_or(Error::new(EBADFD))? -+ .pm_state; -+ if pm_state != super::PortPmState::Active { -+ drop(guard); -+ return Err(Error::new(EBUSY)); -+ } -+ } -+ -+ Ok(guard) ++impl EndpointContextSnapshot { ++ fn capture_values(a: u32, b: u32, trl: u32, trh: u32, c: u32) -> Self { ++ Self { a, b, trl, trh, c } + } ++} + -+ fn begin_transfer_operation(&self, port: PortId) -> Result { -+ self.begin_port_operation(port, true, true) -+ } ++struct EndpointProgram { ++ endp_num_xhc: u8, ++ a: u32, ++ b: u32, ++ trl: u32, ++ trh: u32, ++ c: u32, ++} + -+ fn begin_routable_operation(&self, port: PortId) -> Result { -+ self.begin_port_operation(port, false, true) -+ } -+ -+ fn begin_attached_operation(&self, port: PortId) -> Result { -+ self.begin_port_operation(port, false, false) -+ } -+ - async fn new_if_desc( - &self, - port_id: PortId, -@@ -564,15 +650,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 - }, -@@ -590,10 +683,9 @@ impl Xhci { - /// # Locking - /// This function will lock `Xhci::cmd` and `Xhci::dbs`. - pub async fn execute_command(&self, f: F) -> (Trb, Trb) { -- //TODO: find out why this bit is set earlier! - if self.interrupt_is_pending(0) { - debug!("The EHB bit is already set!"); -- //self.force_clear_interrupt(0); -+ self.force_clear_interrupt(0); - } - - let next_event = { -@@ -628,6 +720,54 @@ 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!"); -+ self.force_clear_interrupt(0); -+ } -+ -+ 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 +779,9 @@ impl Xhci { + impl AnyDescriptor { + fn parse(bytes: &[u8]) -> Option<(Self, usize)> { + if bytes.len() < 2 { +@@ -639,6 +715,8 @@ impl Xhci { where D: FnMut(&mut Trb, bool) -> ControlFlow, { -+ let _op = self.begin_transfer_operation(port_num)?; + self.ensure_port_active(port_num)?; + let future = { let mut port_state = self.port_state_mut(port_num)?; let slot = port_state.slot; -@@ -690,7 +833,21 @@ impl Xhci { - - handle_transfer_event_trb("CONTROL_TRANSFER", &event_trb, &status_trb)?; - -- //self.event_handler_finished(); -+ 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 +866,9 @@ impl Xhci { +@@ -709,6 +787,8 @@ impl Xhci { where D: FnMut(&mut Trb, bool) -> ControlFlow, { -+ let _op = self.begin_transfer_operation(port_num)?; + 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 +945,31 @@ 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 -+ ); -+ } -+ } -+ -+ self.event_handler_finished(); -+ -+ return Err(err); -+ } - - // FIXME: EDTLA if event data was set - if event_trb.completion_code() != TrbCompletionCode::ShortPacket as u8 -@@ -798,6 +982,8 @@ impl Xhci { - // TODO: Handle event data - trace!("EVENT DATA: {:?}", event_trb.event_data()); - -+ 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 { - trb.reset_endpoint(slot, endp_num_xhc, tsp, cycle); - }) - .await; -- //self.event_handler_finished(); -+ 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 -+ .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; -+ -+ 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,106 @@ impl Xhci { +@@ -949,35 +1029,102 @@ impl Xhci { self.port_states.get_mut(&port).ok_or(Error::new(EBADF)) } @@ -6990,17 +7429,13 @@ index f2d439a4..bc6d7fca 100644 + 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_or_else(|err| err.into_inner()); ++ 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); -+ input_context.device.slot.c.write(snapshot.slot_c); + + for (endp_i, endp_snapshot) in endpoint_snapshots { + input_context.device.endpoints[*endp_i].a.write(endp_snapshot.a); @@ -7042,11 +7477,11 @@ index f2d439a4..bc6d7fca 100644 + }) + .await; + -+ self.event_handler_finished(); -+ -+ if let Err(rollback_err) = -+ handle_event_trb("CONFIGURE_ENDPOINT_ROLLBACK", &rollback_event_trb, &rollback_command_trb) -+ { ++ 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 @@ -7063,9 +7498,10 @@ index f2d439a4..bc6d7fca 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 (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 @@ -7076,7 +7512,6 @@ index f2d439a4..bc6d7fca 100644 .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; @@ -7088,6 +7523,7 @@ index f2d439a4..bc6d7fca 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() @@ -7097,14 +7533,13 @@ index f2d439a4..bc6d7fca 100644 + let endp_desc_count = endpoint_descs.len(); + let mut new_context_entries = 1u8; + for (endp_idx, endpoint) in endpoint_descs.iter().enumerate() { -+ let endp_num = endp_idx as u8 + 1; -+ let entry = Self::endp_num_to_dci(endp_num, endpoint); ++ 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; -@@ -988,11 +1262,13 @@ impl Xhci { +@@ -988,74 +1135,22 @@ impl Xhci { } ( @@ -7114,145 +7549,201 @@ index f2d439a4..bc6d7fca 100644 new_context_entries, - config_desc.configuration_value, + configuration_value, ++ speed_id, ) }; -+ let endp_desc_count = endpoint_descs.len(); let lec = self.cap.lec(); let log_max_psa_size = self.cap.max_psa_size(); -@@ -1002,9 +1278,160 @@ impl Xhci { - Error::new(EIO) - })?; +- 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); +- } +- +- 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 mut staged_endpoint_states = BTreeMap::new(); ++ let mut endpoint_programs = Vec::new(); -+ let mut endpoint_programs = Vec::with_capacity(endp_desc_count as usize); -+ let mut staged_endpoint_states = Vec::with_capacity(endp_desc_count as usize); -+ - { -+ for (endp_idx, endp_desc) in endpoint_descs.iter().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(); -+ -+ let primary_streams = if let Some(log_max_streams) = usb_log_max_streams { -+ if log_max_psa_size != 0 { -+ cmp::min(u8::from(log_max_streams), log_max_psa_size + 1) - 1 -+ } else { -+ 0 -+ } -+ } else { -+ 0 -+ }; -+ let linear_stream_array = primary_streams != 0; -+ -+ 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, -+ 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 max_error_count = 3; -+ let ep_ty = endp_desc.xhci_ep_type()?; -+ let host_initiate_disable = false; -+ -+ let avg_trb_len: u16 = match endp_desc.ty() { -+ EndpointTy::Ctrl => { -+ warn!("trying to use control endpoint"); -+ return Err(Error::new(EIO)); -+ } -+ EndpointTy::Bulk | EndpointTy::Isoch => 3072, -+ EndpointTy::Interrupt => 1024, -+ }; -+ -+ assert_eq!(ep_ty & 0x7, ep_ty); -+ assert_eq!(mult & 0x3, mult); -+ assert_eq!(max_error_count & 0x3, max_error_count); -+ assert_ne!(ep_ty, 0); -+ -+ let ring_ptr = if usb_log_max_streams.is_some() { -+ let mut array = -+ StreamContextArray::new::(self.cap.ac64(), 1 << (primary_streams + 1))?; -+ -+ array.add_ring::(self.cap.ac64(), 1, true)?; -+ let array_ptr = array.register(); -+ -+ assert_eq!( -+ array_ptr & 0xFFFF_FFFF_FFFF_FF81, -+ array_ptr, -+ "stream ctx ptr not aligned to 16 bytes" -+ ); -+ -+ staged_endpoint_states.push(( -+ endp_num, -+ EndpointState { -+ transfer: super::RingOrStreams::Streams(array), -+ driver_if_state: EndpIfState::Init, -+ }, -+ )); -+ -+ array_ptr -+ } else { -+ let ring = Ring::new::(self.cap.ac64(), 16, true)?; -+ let ring_ptr = ring.register(); -+ -+ assert_eq!( -+ ring_ptr & 0xFFFF_FFFF_FFFF_FF81, -+ ring_ptr, -+ "ring pointer not aligned to 16 bytes" -+ ); -+ -+ staged_endpoint_states.push(( -+ endp_num, -+ EndpointState { -+ transfer: super::RingOrStreams::Ring(ring), -+ driver_if_state: EndpIfState::Init, -+ }, -+ )); -+ -+ ring_ptr -+ }; -+ assert_eq!(primary_streams & 0x1F, primary_streams); -+ -+ endpoint_programs.push(EndpointProgram { -+ endp_num, -+ 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, -+ 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, -+ 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!("staged endpoint {}", endp_num); -+ } -+ } -+ +- 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(); + +@@ -1077,20 +1172,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()?; +@@ -1113,7 +1208,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))?; + +@@ -1126,15 +1221,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(); +@@ -1144,68 +1237,185 @@ 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 mut input_context = port_state.input_context.lock().unwrap(); -+ let mut input_context = port_state -+ .input_context -+ .lock() -+ .unwrap_or_else(|err| err.into_inner()); +- 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(), @@ -7260,9 +7751,11 @@ index f2d439a4..bc6d7fca 100644 + control: input_context.control.read(), + slot_a: input_context.device.slot.a.read(), + slot_b: input_context.device.slot.b.read(), -+ slot_c: input_context.device.slot.c.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| { @@ -7277,72 +7770,48 @@ index f2d439a4..bc6d7fca 100644 + 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); -@@ -1015,25 +1442,26 @@ impl Xhci { +- //self.event_handler_finished(); ++ // 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 HUB_PORTS_MASK: u32 = 0xFF00_0000; - const HUB_PORTS_SHIFT: u8 = 24; -+ let mut current_slot_c = input_context.device.slot.c.read(); - - 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; - } -+ current_slot_c = apply_hub_tt_info(current_slot_c, req); - - input_context.device.slot.a.write(current_slot_a); - input_context.device.slot.b.write(current_slot_b); -+ input_context.device.slot.c.write(current_slot_c); - - let control = if self.op.lock().unwrap().cie() { - (u32::from(req.alternate_setting.unwrap_or(0)) << 16) -@@ -1043,174 +1471,138 @@ impl Xhci { - 0 - }; - input_context.control.write(control); -- } - -- 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); -- -- let usb_log_max_streams = endp_desc.log_max_streams(); -- -- // TODO: Secondary streams. -- let primary_streams = if let Some(log_max_streams) = usb_log_max_streams { -- // TODO: Can streams-capable be configured to not use streams? -- if log_max_psa_size != 0 { -- 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 }; +- handle_event_trb("CONFIGURE_ENDPOINT", &event_trb, &command_trb)?; ++ 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); @@ -7352,163 +7821,35 @@ index f2d439a4..bc6d7fca 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 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; - -- let max_esit_payload = Self::endp_ctx_max_esit_payload( -- speed_id, -- 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 max_error_count = 3; -- let ep_ty = endp_desc.xhci_ep_type()?; -- let host_initiate_disable = false; -- -- // TODO: Maybe this value is out of scope for xhcid, because the actual usb device -- // driver probably knows better. The spec says that the initial value should be 8 bytes -- // for control, 1KiB for interrupt and 3KiB for bulk and isoch. -- let avg_trb_len: u16 = match endp_desc.ty() { -- 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( ++ self.rollback_configure_attempt( + port, ++ slot, + 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); -- assert_ne!(ep_ty, 0); // 0 means invalid. -- -- 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, -- "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, -- }, -+ 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)?; -- let ring_ptr = ring.register(); ++ "CONFIGURE_ENDPOINT failure", ++ ) ++ .await; + return Err(err); -+ } + } -- assert_eq!( -- ring_ptr & 0xFFFF_FFFF_FFFF_FF81, -- 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 -- }; -- 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 -- | u32::from(primary_streams) << 10 -- | u32::from(linear_stream_array) << 15 -- | u32::from(interval) << 16 -- | u32::from(max_esit_payload_hi) << 24, +- // Tell the device about this configuration. +- self.set_configuration(port, configuration_value).await?; + if self.consume_test_hook("fail_after_configure_endpoint") { + info!( + "xhcid: test hook injecting failure after CONFIGURE_ENDPOINT for port {}", + port - ); -- input_context.device.endpoints[endp_i].b.write( -- 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)); -- -- log::debug!("initialized endpoint {}", endp_num); ++ ); + self.rollback_configure_attempt( + port, + slot, @@ -7518,62 +7859,36 @@ index f2d439a4..bc6d7fca 100644 + ) + .await; + return Err(Error::new(EIO)); - } - -- { -- 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(); -+ // Tell the device about this configuration. -+ 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); - -- let (event_trb, command_trb) = self -- .execute_command(|trb, cycle| { -- trb.configure_endpoint(slot, input_context_physical, cycle) -- }) -+ if !skip_set_configuration { -+ 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; - -- //self.event_handler_finished(); -+ 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?; ++ } ++ ++ 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); ++ } ++ ++ 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)); ++ } ++ + { + let mut port_state = self.port_states.get_mut(&port).ok_or(Error::new(EBADFD))?; + port_state.cfg_idx = Some(configuration_value); @@ -7585,301 +7900,7 @@ index f2d439a4..bc6d7fca 100644 Ok(()) } - - async fn configure_endpoints(&self, port: PortId, json_buf: &[u8]) -> Result<()> { -+ let _op = self.begin_routable_operation(port)?; - let mut req: ConfigureEndpointsReq = - serde_json::from_slice(json_buf).or(Err(Error::new(EBADMSG)))?; - -@@ -1234,8 +1626,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?; -+ } - } - } - -@@ -1432,7 +1836,7 @@ impl Xhci { - }, - ) - .await?; -- //self.event_handler_finished(); -+ self.event_handler_finished(); - - let bytes_transferred = dma_buf - .as_ref() -@@ -1453,52 +1857,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 +1969,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 +2027,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 +2046,9 @@ impl Xhci { - } - Some(unexpected) => { - log::warn!("expected endpoint, got {:X?}", unexpected); -+ if bad_descriptor { -+ continue; -+ } - break; - } - None => break, -@@ -1578,8 +2073,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 +2093,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 +2368,7 @@ impl Xhci { +@@ -1856,7 +2066,7 @@ impl Xhci { if (flags & O_DIRECTORY != 0) || (flags & O_STAT != 0) { let mut contents = Vec::new(); @@ -7888,7 +7909,7 @@ index f2d439a4..bc6d7fca 100644 if self.slot_state( self.port_states -@@ -1893,6 +2405,14 @@ impl Xhci { +@@ -1893,6 +2103,14 @@ impl Xhci { Ok(Handle::PortState(port_num)) } @@ -7903,7 +7924,7 @@ index f2d439a4..bc6d7fca 100644 /// implements open() for /port/endpoints /// /// # Arguments -@@ -2087,6 +2607,30 @@ impl Xhci { +@@ -2087,6 +2305,30 @@ impl Xhci { Ok(Handle::DetachDevice(port_num)) } @@ -7934,7 +7955,7 @@ index f2d439a4..bc6d7fca 100644 /// implements open() for /port/request /// /// # Arguments -@@ -2155,6 +2699,9 @@ impl SchemeSync for &Xhci { +@@ -2155,6 +2397,9 @@ impl SchemeSync for &Xhci { SchemeParameters::PortState(port_number) => { self.open_handle_port_state(port_number, flags)? } @@ -7944,7 +7965,7 @@ index f2d439a4..bc6d7fca 100644 SchemeParameters::PortReq(port_number) => { self.open_handle_port_request(port_number, flags)? } -@@ -2173,6 +2720,12 @@ impl SchemeSync for &Xhci { +@@ -2173,6 +2418,12 @@ impl SchemeSync for &Xhci { SchemeParameters::DetachDevice(port_number) => { self.open_handle_detach_device(port_number, flags)? } @@ -7957,7 +7978,7 @@ index f2d439a4..bc6d7fca 100644 }; let fd = self.next_handle.fetch_add(1, atomic::Ordering::Relaxed); -@@ -2203,7 +2756,11 @@ impl SchemeSync for &Xhci { +@@ -2203,7 +2454,11 @@ impl SchemeSync for &Xhci { //If we have a handle to the configure scheme, we need to mark it as write only. match &*guard { @@ -7970,7 +7991,7 @@ index f2d439a4..bc6d7fca 100644 stat.st_mode = stat.st_mode | 0o200; } _ => {} -@@ -2263,6 +2820,8 @@ impl SchemeSync for &Xhci { +@@ -2263,6 +2518,8 @@ impl SchemeSync for &Xhci { Handle::ConfigureEndpoints(_) => Err(Error::new(EBADF)), Handle::AttachDevice(_) => Err(Error::new(EBADF)), Handle::DetachDevice(_) => Err(Error::new(EBADF)), @@ -7979,7 +8000,7 @@ index f2d439a4..bc6d7fca 100644 Handle::SchemeRoot => Err(Error::new(EBADF)), &mut Handle::Endpoint(port_num, endp_num, ref mut st) => match st { -@@ -2294,6 +2853,10 @@ impl SchemeSync for &Xhci { +@@ -2294,6 +2551,10 @@ impl SchemeSync for &Xhci { Ok(Xhci::::write_dyn_string(string, buf, offset)) } @@ -7990,7 +8011,7 @@ index f2d439a4..bc6d7fca 100644 &mut Handle::PortReq(port_num, ref mut st) => { let state = std::mem::replace(st, PortReqState::Tmp); drop(guard); // release the lock -@@ -2333,6 +2896,14 @@ impl SchemeSync for &Xhci { +@@ -2333,6 +2594,14 @@ impl SchemeSync for &Xhci { block_on(self.detach_device(port_num))?; Ok(buf.len()) } @@ -8005,44 +8026,38 @@ index f2d439a4..bc6d7fca 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 +2927,59 @@ impl Xhci { - self.handles.remove(&fd); - } +@@ -2357,6 +2626,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))?; -+ if port_state.lifecycle.state() == super::PortLifecycleState::Detaching { -+ return Err(Error::new(EBUSY)); -+ } + -+ let pm_state = port_state.pm_state; -+ match pm_state { ++ match port_state.pm_state { + super::PortPmState::Active => Ok(()), -+ super::PortPmState::Suspended => Err(Error::new(EBUSY)), ++ 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 _op = self.begin_attached_operation(port_num)?; + 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)); -+ } -+ + 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 _op = self.begin_attached_operation(port_num)?; + let mut port_state = self.port_states.get_mut(&port_num).ok_or(Error::new(EBADFD))?; + + if port_state.pm_state == super::PortPmState::Active { @@ -8059,13 +8074,14 @@ index f2d439a4..bc6d7fca 100644 + } + + 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))?; -@@ -2406,6 +3030,8 @@ impl Xhci { +@@ -2407,6 +2724,8 @@ impl Xhci { endp_num: u8, clear_feature: bool, ) -> Result<()> { @@ -8074,225 +8090,430 @@ index f2d439a4..bc6d7fca 100644 if self.get_endp_status(port_num, endp_num)? != EndpointStatus::Halted { return Err(Error::new(EPROTO)); } -@@ -2531,7 +3157,7 @@ impl Xhci { - ) - }) - .await; -- //self.event_handler_finished(); -+ self.event_handler_finished(); +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"); - handle_event_trb("SET_TR_DEQUEUE_PTR", &event_trb, &command_trb) - } -@@ -2541,10 +3167,14 @@ impl Xhci { - endp_num: u8, - buf: &[u8], - ) -> Result { -+ let _op = self.begin_routable_operation(port_num)?; - let mut port_state = self - .port_states - .get_mut(&port_num) - .ok_or(Error::new(EBADF))?; -+ if port_state.pm_state != super::PortPmState::Active { -+ return Err(Error::new(EBUSY)); +- 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 ep_if_state = &mut port_state - .endpoint_states -@@ -2562,6 +3192,7 @@ impl Xhci { - }, - XhciEndpCtlReq::Reset { no_clear_feature } => match ep_if_state { - EndpIfState::Init => { -+ drop(port_state); - self.on_req_reset_device(port_num, endp_num, !no_clear_feature) - .await? - } -@@ -2631,6 +3262,9 @@ impl Xhci { - endp_num: u8, - buf: &[u8], - ) -> Result { -+ let _op = self.begin_routable_operation(port_num)?; -+ self.ensure_port_active(port_num)?; -+ - let mut port_state = self - .port_states - .get_mut(&port_num) -@@ -2732,6 +3366,9 @@ impl Xhci { - endp_num: u8, - buf: &mut [u8], - ) -> Result { -+ let _op = self.begin_routable_operation(port_num)?; -+ self.ensure_port_active(port_num)?; -+ - let mut port_state = self - .port_states - .get_mut(&port_num) -@@ -2832,6 +3469,64 @@ pub fn handle_transfer_event_trb(name: &str, event_trb: &Trb, transfer_trb: &Trb - Err(Error::new(EIO)) - } - } -+ -+fn apply_hub_tt_info(current_slot_c: u32, req: &ConfigureEndpointsReq) -> u32 { -+ const TT_THINK_TIME_MASK: u32 = 0x0003_0000; -+ const TT_THINK_TIME_SHIFT: u8 = 16; -+ -+ let mut slot_c = current_slot_c & !TT_THINK_TIME_MASK; -+ if req.hub_ports.is_some() { -+ if let Some(hub_think_time) = req.hub_think_time { -+ slot_c |= (u32::from(hub_think_time) << TT_THINK_TIME_SHIFT) & TT_THINK_TIME_MASK; -+ } + 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); + } -+ slot_c -+} -+ -+#[derive(Clone, Copy)] -+struct ConfigureContextSnapshot { -+ add_context: u32, -+ drop_context: u32, -+ control: u32, -+ slot_a: u32, -+ slot_b: u32, -+ slot_c: 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 } -+ } -+ -+ fn restore(&self, ctx: &mut EndpointContext) { -+ ctx.a.write(self.a); -+ ctx.b.write(self.b); -+ ctx.trl.write(self.trl); -+ ctx.trh.write(self.trh); -+ ctx.c.write(self.c); -+ } -+} -+ -+#[derive(Clone, Copy)] -+struct EndpointProgram { -+ endp_num: u8, -+ endp_num_xhc: u8, -+ a: u32, -+ b: u32, -+ trl: u32, -+ trh: u32, -+ c: u32, -+} -+ - use lazy_static::lazy_static; - use std::ops::{Add, Div, Rem}; -@@ -2845,3 +3540,26 @@ where - a / b - } - } -+ -+#[cfg(test)] -+mod tests { -+ use super::{apply_hub_tt_info, ConfigureEndpointsReq}; -+ -+ #[test] -+ fn apply_hub_tt_info_only_sets_bits_for_hub_requests() { -+ let req = ConfigureEndpointsReq { -+ config_desc: 1, -+ interface_desc: None, -+ alternate_setting: None, -+ hub_ports: Some(4), -+ hub_think_time: Some(3), -+ }; -+ assert_eq!(apply_hub_tt_info(0, &req), 0x0003_0000); -+ -+ let no_hub = ConfigureEndpointsReq { -+ hub_ports: None, -+ ..req.clone() -+ }; -+ assert_eq!(apply_hub_tt_info(0x0003_0000, &no_hub), 0); -+ } -+} -diff --git a/init.initfs.d/40_ps2d.service b/init.initfs.d/40_ps2d.service -index 881e75ea..bbee2699 100644 ---- a/init.initfs.d/40_ps2d.service -+++ b/init.initfs.d/40_ps2d.service -@@ -5,4 +5,4 @@ condition_architecture = ["x86", "x86_64"] - - [service] - cmd = "ps2d" --type = "notify" -+type = "oneshot_async" -diff --git a/init/src/scheduler.rs b/init/src/scheduler.rs -index d42a4e57..f8ac5cd3 100644 ---- a/init/src/scheduler.rs -+++ b/init/src/scheduler.rs -@@ -1,7 +1,8 @@ - use std::collections::VecDeque; - --use crate::InitConfig; -+use crate::service::ServiceType; - use crate::unit::{Unit, UnitId, UnitKind, UnitStore}; -+use crate::InitConfig; - - pub struct Scheduler { - pending: VecDeque, -@@ -92,22 +93,31 @@ fn run(unit: &mut Unit, config: &mut InitConfig) { + let mut width = 0; + let mut height = 0; +@@ -233,25 +245,55 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { } - UnitKind::Service { service } => { - if config.skip_cmd.contains(&service.cmd) { -- eprintln!("Skipping '{} {}'", service.cmd, service.args.join(" ")); -+ eprintln!("init: SKIP {} {}", service.cmd, service.args.join(" ")); - return; + } + +- 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) -> ! { } -- if config.log_debug { -- eprintln!( -- "Starting {} ({})", -- unit.info.description.as_ref().unwrap_or(&unit.id.0), -- service.cmd, -- ); -+ -+ // Always log blocking service types (notify, scheme, oneshot) -+ // since these can hang the boot if the child fails to signal. -+ // OneshotAsync services are fire-and-forget, only log at debug. -+ let is_blocking = !matches!(service.type_, ServiceType::OneshotAsync); -+ -+ if is_blocking || config.log_debug { -+ let desc = unit.info.description.as_ref().map(|s| s.as_str()).unwrap_or("-"); -+ eprintln!("init: START {desc} | {} {}", service.cmd, service.args.join(" ")); + } + +- 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) -> ! { + } + } } -+ - service.spawn(&config.envs); -+ -+ if is_blocking || config.log_debug { -+ let desc = unit.info.description.as_ref().map(|s| s.as_str()).unwrap_or("-"); -+ eprintln!("init: DONE {desc} | {} {}", service.cmd, service.args.join(" ")); ++ Ok(_) => {} ++ Err(err) => { ++ eprintln!("vboxd: failed to read IRQ file: {err}"); ++ std::process::exit(1); ++ } + } } - UnitKind::Target {} => { - if config.log_debug { - eprintln!( -- "Reached target {}", -+ "init: TARGET {}", - unit.info.description.as_ref().unwrap_or(&unit.id.0), - ); + } + +- 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, + } + } - pub fn subst_env<'a>(arg: &str) -> String { - if arg.starts_with('$') { +- 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/build-system/005-qtbase-toolchain-elf-header.patch b/local/patches/build-system/005-qtbase-toolchain-elf-header.patch new file mode 100644 index 00000000..0ff037c3 --- /dev/null +++ b/local/patches/build-system/005-qtbase-toolchain-elf-header.patch @@ -0,0 +1,44 @@ +diff --git a/recipes/wip/qt/qtbase/recipe.toml b/recipes/wip/qt/qtbase/recipe.toml +index 0af3b77ba..05eac497a 100644 +--- a/recipes/wip/qt/qtbase/recipe.toml ++++ b/recipes/wip/qt/qtbase/recipe.toml +@@ -39,6 +39,18 @@ choose_relibc_lib_stage() { + return 1 + } + ++choose_toolchain_root() { ++ if [ -n "${COOKBOOK_HOST_SYSROOT:-}" ] && [ -d "${COOKBOOK_HOST_SYSROOT}" ]; then ++ printf '%s\n' "${COOKBOOK_HOST_SYSROOT}" ++ return 0 ++ fi ++ if [ -d "${HOME}/.redoxer/x86_64-unknown-redox/toolchain" ]; then ++ printf '%s\n' "${HOME}/.redoxer/x86_64-unknown-redox/toolchain" ++ return 0 ++ fi ++ printf '%s\n' "${COOKBOOK_ROOT}/prefix/x86_64-unknown-redox/sysroot" ++} ++ + if RELIBC_STAGE_LIB="$(choose_relibc_lib_stage "$RELIBC_STAGE_LIB_STAGE")"; then + : + elif RELIBC_STAGE_LIB="$(choose_relibc_lib_stage "$RELIBC_STAGE_LIB_TMP")"; then +@@ -58,6 +70,20 @@ if [ -d "${RELIBC_STAGE_INCLUDE}" ]; then + export CPPFLAGS="${CPPFLAGS} -I${RELIBC_STAGE_INCLUDE}" + export CFLAGS="${CFLAGS} -I${RELIBC_STAGE_INCLUDE}" + export CXXFLAGS="${CXXFLAGS} -I${RELIBC_STAGE_INCLUDE}" ++ ++ # The Redox GCC toolchain currently prefers its own bundled target elf.h ++ # under .../x86_64-unknown-redox/include/ over the recipe sysroot copy. ++ # Sync the freshly built relibc header into that toolchain include root so ++ # Qt's ELF plugin parser sees the corrected ELF64 typedef layout. ++ TOOLCHAIN_ROOT="$(choose_toolchain_root)" ++ TOOLCHAIN_TARGET_INCLUDE="${TOOLCHAIN_ROOT}/x86_64-unknown-redox/include" ++ TOOLCHAIN_TARGET_USR_INCLUDE="${TOOLCHAIN_ROOT}/x86_64-unknown-redox/usr/include" ++ if [ -f "${RELIBC_STAGE_INCLUDE}/elf.h" ] && [ -d "${TOOLCHAIN_TARGET_INCLUDE}" ]; then ++ cp -f "${RELIBC_STAGE_INCLUDE}/elf.h" "${TOOLCHAIN_TARGET_INCLUDE}/elf.h" ++ fi ++ if [ -f "${RELIBC_STAGE_INCLUDE}/elf.h" ] && [ -d "${TOOLCHAIN_TARGET_USR_INCLUDE}" ]; then ++ cp -f "${RELIBC_STAGE_INCLUDE}/elf.h" "${TOOLCHAIN_TARGET_USR_INCLUDE}/elf.h" ++ fi + fi + if [ -d "${RELIBC_STAGE_LIB}" ]; then + mkdir -p "${COOKBOOK_SYSROOT}/lib" diff --git a/local/scripts/test-iommu-qemu.sh b/local/scripts/test-iommu-qemu.sh index 5270cc2b..6f4be888 100755 --- a/local/scripts/test-iommu-qemu.sh +++ b/local/scripts/test-iommu-qemu.sh @@ -23,7 +23,6 @@ find_uefi_firmware() { return 1 } -# Print usage information usage() { cat << USAGE Usage: $(basename "$0") [--check] [config] [extra qemu args...] @@ -35,7 +34,7 @@ Options: --check Boot and verify the guest reaches a login prompt Arguments: - config Optional config name (default: redbear-desktop) + config Optional config name (default: redbear-mini) extra qemu args Additional arguments appended to the QEMU command Environment: @@ -44,7 +43,7 @@ Environment: Examples: $(basename "$0") $(basename "$0") --check - $(basename "$0") redbear-desktop -m 4G + $(basename "$0") redbear-mini -m 4G USAGE exit 0 @@ -52,7 +51,7 @@ USAGE check_mode=0 filtered_args=() -config="redbear-desktop" +config="redbear-mini" for arg in "$@"; do case "$arg" in --help|-h|help) @@ -70,6 +69,10 @@ for arg in "$@"; do esac done +if [[ "$config" == "redbear-mini" ]]; then + config="redbear-minimal" +fi + firmware="$(find_uefi_firmware)" || { echo "ERROR: no usable x86_64 UEFI firmware found" >&2 exit 1 diff --git a/local/scripts/test-lowlevel-controllers-qemu.sh b/local/scripts/test-lowlevel-controllers-qemu.sh index 9c156938..5483e0ff 100755 --- a/local/scripts/test-lowlevel-controllers-qemu.sh +++ b/local/scripts/test-lowlevel-controllers-qemu.sh @@ -10,15 +10,18 @@ usage() { Usage: test-lowlevel-controllers-qemu.sh [config] Run the bounded low-level controller/runtime proof helpers in sequence. -Defaults to redbear-desktop. +Defaults to redbear-mini (mapped by the individual helpers where needed). + +Note: the IOMMU first-use proof still requires a target that actually ships `/usr/bin/iommu`, so +the wrapper automatically upgrades that single leg to `redbear-full` when invoked with +`redbear-mini`. Checks run: + - MSI-X path - xHCI interrupt path - IOMMU first-use path - PS/2 + serio path - monotonic timer path - -MSI-X remains a separate proof helper because its current default target is redbear-full. USAGE } @@ -31,13 +34,25 @@ for arg in "$@"; do esac done -config="${1:-redbear-desktop}" +config="${1:-redbear-mini}" +iommu_config="$config" +if [[ "$config" == "redbear-mini" ]]; then + iommu_config="redbear-full" +fi + +echo ">>> Running MSI-X proof" +bash "$SCRIPT_DIR/test-msix-qemu.sh" "$config" echo ">>> Running xHCI interrupt proof" bash "$SCRIPT_DIR/test-xhci-irq-qemu.sh" --check "$config" echo ">>> Running IOMMU first-use proof" -bash "$SCRIPT_DIR/test-iommu-qemu.sh" --check "$config" +iommu_image="build/x86_64/${iommu_config}/harddrive.img" +if [[ -f "$iommu_image" ]]; then + bash "$SCRIPT_DIR/test-iommu-qemu.sh" --check "$iommu_config" +else + echo "SKIP: IOMMU first-use proof skipped because $iommu_image is missing" +fi echo ">>> Running PS/2 + serio proof" bash "$SCRIPT_DIR/test-ps2-qemu.sh" --check "$config" @@ -45,4 +60,4 @@ bash "$SCRIPT_DIR/test-ps2-qemu.sh" --check "$config" echo ">>> Running monotonic timer proof" bash "$SCRIPT_DIR/test-timer-qemu.sh" --check "$config" -echo "All bounded low-level controller checks passed for $config" +echo "All bounded low-level controller checks passed for $config (IOMMU leg used $iommu_config)" diff --git a/local/scripts/test-msix-qemu.sh b/local/scripts/test-msix-qemu.sh index 3ee544ef..3a59f3dc 100644 --- a/local/scripts/test-msix-qemu.sh +++ b/local/scripts/test-msix-qemu.sh @@ -28,7 +28,7 @@ usage() { Usage: test-msix-qemu.sh [config] Boot a Red Bear image in QEMU and verify a live MSI-X path via virtio-net. -Defaults to redbear-full. +Defaults to redbear-mini (mapped to the in-tree redbear-minimal image). USAGE } @@ -41,7 +41,10 @@ for arg in "$@"; do esac done -config="${1:-redbear-full}" +config="${1:-redbear-mini}" +if [[ "$config" == "redbear-mini" ]]; then + config="redbear-minimal" +fi arch="${ARCH:-$(uname -m)}" image="build/$arch/$config/harddrive.img" extra="build/$arch/$config/extra.img" @@ -68,7 +71,7 @@ rm -f "$log_file" set +e timeout 90s qemu-system-x86_64 \ -name "Red Bear OS x86_64" \ - -device qemu-xhci \ + -device qemu-xhci,id=xhci \ -smp 4 \ -m 2048 \ -bios "$firmware" \ @@ -81,17 +84,21 @@ timeout 90s qemu-system-x86_64 \ -netdev user,id=net0 \ -object filter-dump,id=f1,netdev=net0,file="build/$arch/$config/network.pcap" \ -nographic -vga none \ - -drive file="$image",format=raw,if=none,id=drv0 \ + -drive file="$image",format=raw,if=none,id=drv0,snapshot=on \ -device nvme,drive=drv0,serial=NVME_SERIAL \ - -drive file="$extra",format=raw,if=none,id=drv1 \ + -drive file="$extra",format=raw,if=none,id=drv1,snapshot=on \ -device nvme,drive=drv1,serial=NVME_EXTRA \ -enable-kvm -cpu host \ > "$log_file" 2>&1 set -e -if ! grep -q "virtio: using MSI-X" "$log_file"; then - echo "ERROR: no live MSI-X evidence found in $log_file" >&2 - exit 1 +if ! grep -q "virtio-net: using MSI-X interrupt delivery" "$log_file"; then + echo "ERROR: no live MSI-X evidence found in $log_file" >&2 + exit 1 fi +echo "IRQ_DRIVER=virtio-net" +echo "IRQ_MODE=msix" +echo "IRQ_REASON=driver_selected_msix" +echo "IRQ_LOG=$log_file" echo "MSI-X runtime path detected in $log_file" diff --git a/local/scripts/test-ps2-qemu.sh b/local/scripts/test-ps2-qemu.sh index 536cb905..23d89c0b 100755 --- a/local/scripts/test-ps2-qemu.sh +++ b/local/scripts/test-ps2-qemu.sh @@ -28,12 +28,13 @@ usage() { Usage: test-ps2-qemu.sh [--check] [config] [extra qemu args...] Launch or validate the PS/2 + serio path on a Red Bear image in QEMU. +Defaults to redbear-mini (mapped to the in-tree redbear-minimal image). USAGE } check_mode=0 filtered_args=() -config="redbear-desktop" +config="redbear-mini" for arg in "$@"; do case "$arg" in --help|-h|help) @@ -52,6 +53,10 @@ for arg in "$@"; do esac done +if [[ "$config" == "redbear-mini" ]]; then + config="redbear-minimal" +fi + firmware="$(find_uefi_firmware)" || { echo "ERROR: no usable x86_64 UEFI firmware found" >&2 exit 1 diff --git a/local/scripts/test-timer-qemu.sh b/local/scripts/test-timer-qemu.sh index cdf7d2d9..c4687b35 100755 --- a/local/scripts/test-timer-qemu.sh +++ b/local/scripts/test-timer-qemu.sh @@ -28,12 +28,13 @@ usage() { Usage: test-timer-qemu.sh [--check] [config] [extra qemu args...] Launch or validate the startup timer path on a Red Bear image in QEMU. +Defaults to redbear-mini (mapped to the in-tree redbear-minimal image). USAGE } check_mode=0 filtered_args=() -config="redbear-desktop" +config="redbear-mini" for arg in "$@"; do case "$arg" in --help|-h|help) @@ -52,6 +53,10 @@ for arg in "$@"; do esac done +if [[ "$config" == "redbear-mini" ]]; then + config="redbear-minimal" +fi + firmware="$(find_uefi_firmware)" || { echo "ERROR: no usable x86_64 UEFI firmware found" >&2 exit 1 @@ -79,18 +84,17 @@ if [[ "$check_mode" -eq 1 ]]; then expect <&2 @@ -96,16 +104,21 @@ if [[ ! -f "$usb_img" ]]; then fi seed_usb_image "$usb_img" >/dev/null -pkill -f "qemu-system-x86_64.*$image" 2>/dev/null || true +ln -sf "$image" "$session_image" +ln -sf "$extra" "$session_extra" +ln -sf "$usb_img" "$session_usb_img" + +pkill -f "qemu-system-x86_64.*$session_tag" 2>/dev/null || true sleep 1 rm -f "$log_file" expect < hid_port -expect -re {USB HID driver spawned} expect -re {xhcid: finished attach for port [0-9\.]+} expect -re {Device on port [0-9\.]+ was attached} -expect -re {USB HID driver spawned with scheme .*, port [0-9\.]+, interface 0} -set hid_spawn \$expect_out(0,string) -regexp {scheme .([^,]+), port} \$hid_spawn -> hid_scheme -set hid_scheme [string range \$hid_scheme 0 end-1] +send "\r" +expect -re {# } +set hid_scheme "usb.pci-0000-00-01.0_xhci" +send "H=/tmp/xhcid-test-hook\r" +expect -re {# } +send "P=/scheme/\$hid_scheme/port\$hid_port\r" +expect -re {# } -send "printf '' > /scheme/\$hid_scheme/port\$hid_port/suspend\r" +send "echo x > \$P/suspend\r" expect -re {xhcid: suspended port [0-9\.]+} -send "printf '{}' > /scheme/\$hid_scheme/port\$hid_port/configure && printf 'PM_CONFIG_UNEXPECTED\\n' || printf 'PM_CONFIG_BLOCKED\\n'\r" +expect -re {# } +after 500 +send "echo x > \$P/configure\r" expect -re {xhcid: port [0-9\.]+ rejected routable operation while suspended} -expect -re {PM_CONFIG_BLOCKED} -send "printf '' > /scheme/\$hid_scheme/port\$hid_port/resume\r" +expect -re {# } +after 500 +send "echo x > \$P/resume\r" expect -re {xhcid: resumed port [0-9\.]+} +expect -re {# } send "\001c" expect "(qemu)" @@ -147,7 +166,7 @@ expect "(qemu)" send "\001c" expect -re {Device on port [0-9\.]+ was detached} -send "printf 'fail_after_configure_endpoint\n' > /tmp/xhcid-test-hook\r" +send "echo fail_after_configure_endpoint > \$H\r" after 500 send "\001c" @@ -156,7 +175,6 @@ send "device_add usb-kbd,bus=xhci.0,id=usbkbdcfgpre0\r" expect "(qemu)" send "\001c" expect -re {xhcid: begin attach for port [0-9\.]+} -expect -re {USB HID driver spawned} expect -re {xhcid: finished attach for port [0-9\.]+} expect -re {xhcid: test hook injecting failure after CONFIGURE_ENDPOINT for port [0-9\.]+} @@ -173,7 +191,6 @@ send "device_add usb-kbd,bus=xhci.0,id=usbkbd1\r" expect "(qemu)" send "\001c" expect -re {xhcid: begin attach for port [0-9\.]+} -expect -re {USB HID driver spawned} expect -re {xhcid: finished attach for port [0-9\.]+} expect -re {Device on port [0-9\.]+ was attached} @@ -184,7 +201,7 @@ expect "(qemu)" send "\001c" expect -re {Device on port [0-9\.]+ was detached} -send "printf 'fail_after_set_configuration\n' > /tmp/xhcid-test-hook\r" +send "echo fail_after_set_configuration > \$H\r" after 500 send "\001c" @@ -203,7 +220,7 @@ expect "(qemu)" send "\001c" expect -re {Device on port [0-9\.]+ was detached} -send "printf 'delay_before_attach_commit_ms=2000\n' > /tmp/xhcid-test-hook\r" +send "echo delay_before_attach_commit_ms=15000 > \$H\r" after 500 send "\001c" @@ -212,7 +229,7 @@ send "device_add usb-storage,bus=xhci.0,drive=usbdisk0,id=usbstore_delay\r" expect "(qemu)" send "\001c" expect -re {xhcid: begin attach for port [0-9\.]+} -expect -re {xhcid: test hook delaying attach commit for port [0-9\.]+ by 2000 ms} +expect -re {xhcid: test hook delaying attach commit for port [0-9\.]+ by 15000 ms} send "\001c" expect "(qemu)" @@ -224,11 +241,10 @@ expect -re {Device on port [0-9\.]+ was detached} send "\001c" expect "(qemu)" -send "device_add usb-storage,bus=xhci.0,drive=usbdisk0,id=usbstore0\r" +send "device_add usb-storage,bus=xhci.0,drive=usbdisk1,id=usbstore0\r" expect "(qemu)" send "\001c" expect -re {xhcid: begin attach for port [0-9\.]+} -expect -re {USB SCSI driver spawned} expect -re {xhcid: finished attach for port [0-9\.]+} expect -re {Device on port [0-9\.]+ was attached} after 3000 @@ -244,7 +260,8 @@ send "shutdown\r" sleep 2 EOF -pkill -f "qemu-system-x86_64.*$image" 2>/dev/null || true +pkill -f "qemu-system-x86_64.*$session_tag" 2>/dev/null || true +rm -f "$session_image" "$session_extra" "$session_usb_img" failures=0 @@ -320,14 +337,14 @@ else failures=$((failures + 1)) fi -if grep -aq 'PM_CONFIG_BLOCKED' "$log_file" && [[ "$(grep -Eac '(^|[^[:alpha:]])suspended([^[:alpha:]]|$)' "$log_file")" -ge 1 ]]; then +if grep -aq 'xhcid: suspended port ' "$log_file" && grep -aq 'xhcid: resumed port ' "$log_file" && grep -aq 'xhcid: port .* rejected routable operation while suspended' "$log_file"; then echo " [PASS] Suspend/resume admission checks blocked configure while suspended" else echo " [FAIL] Missing suspend/resume admission evidence" >&2 failures=$((failures + 1)) fi -if grep -aqi "panic\|failed to disable port slot" "$log_file"; then +if grep -aqi "Failed to setup protocol\|failed to disable port slot" "$log_file"; then echo " [FAIL] Lifecycle path hit crash-class or teardown errors" >&2 failures=$((failures + 1)) else diff --git a/local/scripts/test-xhci-irq-qemu.sh b/local/scripts/test-xhci-irq-qemu.sh index d429ba47..4f6da3a1 100644 --- a/local/scripts/test-xhci-irq-qemu.sh +++ b/local/scripts/test-xhci-irq-qemu.sh @@ -79,7 +79,7 @@ if [[ "$check_mode" -eq 1 ]]; then log_file="build/$arch/$config/xhci-irq-check.log" rm -f "$log_file" set +e - timeout 90s qemu-system-x86_64 \ + timeout 180s qemu-system-x86_64 \ -name "Red Bear OS x86_64" \ -device qemu-xhci,id=xhci \ -device usb-kbd,bus=xhci.0 \ @@ -112,6 +112,23 @@ if [[ "$check_mode" -eq 1 ]]; then echo "ERROR: xhcid interrupt-mode proof never observed attached-device enumeration pressure; see $log_file" >&2 exit 1 fi + mode="unknown" + reason="unknown" + if grep -q "xhcid: using MSI/MSI-X interrupt delivery" "$log_file"; then + mode="msi_or_msix" + reason="driver_selected_interrupt_delivery" + elif grep -q "xhcid: using legacy INTx interrupt delivery" "$log_file"; then + mode="legacy" + reason="driver_fell_back_to_legacy_intx" + elif grep -q "xhcid: falling back to polling mode" "$log_file"; then + mode="polling" + reason="driver_fell_back_to_polling" + fi + + echo "IRQ_DRIVER=xhcid" + echo "IRQ_MODE=$mode" + echo "IRQ_REASON=$reason" + echo "IRQ_LOG=$log_file" echo "xHCI interrupt mode detected in $log_file" exit 0 fi