diff --git a/local/patches/base/P1-acpid-power-enumeration.patch b/local/patches/base/P1-acpid-power-enumeration.patch new file mode 100644 index 00000000..9fb27b3f --- /dev/null +++ b/local/patches/base/P1-acpid-power-enumeration.patch @@ -0,0 +1,1956 @@ +diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs +index 94a1eb17..1ceb27be 100644 +--- a/drivers/acpid/src/acpi.rs ++++ b/drivers/acpid/src/acpi.rs +@@ -8,6 +8,7 @@ use std::str::FromStr; + use std::sync::{Arc, Mutex}; + use std::{fmt, mem}; + use syscall::PAGE_SIZE; ++use toml::Value; + + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + use common::io::{Io, Pio}; +@@ -25,6 +26,8 @@ use amlserde::{AmlSerde, AmlSerdeValue}; + + #[cfg(target_arch = "x86_64")] + pub mod dmar; ++#[cfg(target_arch = "x86_64")] ++use self::dmar::Dmar; + use crate::aml_physmem::{AmlPageCache, AmlPhysMemHandler}; + + /// The raw SDT header struct, as defined by the ACPI specification. +@@ -206,6 +209,464 @@ impl Sdt { + } + } + ++#[derive(Clone, Debug, Default)] ++pub struct DmiInfo { ++ pub sys_vendor: Option, ++ pub board_vendor: Option, ++ pub board_name: Option, ++ pub board_version: Option, ++ pub product_name: Option, ++ pub product_version: Option, ++ pub bios_version: Option, ++} ++ ++impl DmiInfo { ++ pub fn to_match_lines(&self) -> String { ++ let mut lines = Vec::new(); ++ if let Some(value) = &self.sys_vendor { ++ lines.push(format!("sys_vendor={value}")); ++ } ++ if let Some(value) = &self.board_vendor { ++ lines.push(format!("board_vendor={value}")); ++ } ++ if let Some(value) = &self.board_name { ++ lines.push(format!("board_name={value}")); ++ } ++ if let Some(value) = &self.board_version { ++ lines.push(format!("board_version={value}")); ++ } ++ if let Some(value) = &self.product_name { ++ lines.push(format!("product_name={value}")); ++ } ++ if let Some(value) = &self.product_version { ++ lines.push(format!("product_version={value}")); ++ } ++ if let Some(value) = &self.bios_version { ++ lines.push(format!("bios_version={value}")); ++ } ++ lines.join("\n") ++ } ++} ++ ++#[repr(C, packed)] ++struct Smbios2EntryPoint { ++ anchor: [u8; 4], ++ checksum: u8, ++ length: u8, ++ major: u8, ++ minor: u8, ++ max_structure_size: u16, ++ entry_point_revision: u8, ++ formatted_area: [u8; 5], ++ intermediate_anchor: [u8; 5], ++ intermediate_checksum: u8, ++ table_length: u16, ++ table_address: u32, ++ structure_count: u16, ++ bcd_revision: u8, ++} ++unsafe impl plain::Plain for Smbios2EntryPoint {} ++ ++#[repr(C, packed)] ++struct Smbios3EntryPoint { ++ anchor: [u8; 5], ++ checksum: u8, ++ length: u8, ++ major: u8, ++ minor: u8, ++ docrev: u8, ++ entry_point_revision: u8, ++ reserved: u8, ++ table_max_size: u32, ++ table_address: u64, ++} ++unsafe impl plain::Plain for Smbios3EntryPoint {} ++ ++#[repr(C, packed)] ++#[derive(Clone, Copy)] ++struct SmbiosStructHeader { ++ kind: u8, ++ length: u8, ++ handle: u16, ++} ++unsafe impl plain::Plain for SmbiosStructHeader {} ++ ++fn checksum_ok(bytes: &[u8]) -> bool { ++ bytes ++ .iter() ++ .copied() ++ .fold(0u8, |acc, byte| acc.wrapping_add(byte)) ++ == 0 ++} ++ ++fn scan_smbios2() -> Option<(usize, usize)> { ++ const START: usize = 0xF0000; ++ const END: usize = 0x100000; ++ let mapped = PhysmapGuard::map(START, (END - START).div_ceil(PAGE_SIZE)).ok()?; ++ let bytes = &mapped[..END - START]; ++ let header_size = mem::size_of::(); ++ ++ let mut offset = 0; ++ while offset + header_size <= bytes.len() { ++ if &bytes[offset..offset + 4] == b"_SM_" { ++ let entry = ++ plain::from_bytes::(&bytes[offset..offset + header_size]) ++ .ok()?; ++ let length = entry.length as usize; ++ if offset + length <= bytes.len() ++ && length >= header_size ++ && checksum_ok(&bytes[offset..offset + length]) ++ && &entry.intermediate_anchor == b"_DMI_" ++ { ++ return Some((entry.table_address as usize, entry.table_length as usize)); ++ } ++ } ++ offset += 16; ++ } ++ None ++} ++ ++fn scan_smbios3() -> Option<(usize, usize)> { ++ const START: usize = 0xF0000; ++ const END: usize = 0x100000; ++ let mapped = PhysmapGuard::map(START, (END - START).div_ceil(PAGE_SIZE)).ok()?; ++ let bytes = &mapped[..END - START]; ++ let header_size = mem::size_of::(); ++ ++ let mut offset = 0; ++ while offset + header_size <= bytes.len() { ++ if &bytes[offset..offset + 5] == b"_SM3_" { ++ let entry = ++ plain::from_bytes::(&bytes[offset..offset + header_size]) ++ .ok()?; ++ let length = entry.length as usize; ++ if offset + length <= bytes.len() ++ && length >= header_size ++ && checksum_ok(&bytes[offset..offset + length]) ++ { ++ return Some((entry.table_address as usize, entry.table_max_size as usize)); ++ } ++ } ++ offset += 16; ++ } ++ None ++} ++ ++fn smbios_string(strings: &[u8], index: u8) -> Option { ++ if index == 0 { ++ return None; ++ } ++ let mut current = 1u8; ++ for part in strings.split(|b| *b == 0) { ++ if part.is_empty() { ++ break; ++ } ++ if current == index { ++ return Some(String::from_utf8_lossy(part).trim().to_string()) ++ .filter(|s| !s.is_empty()); ++ } ++ current = current.saturating_add(1); ++ } ++ None ++} ++ ++fn parse_smbios_table(table_addr: usize, table_len: usize) -> Option { ++ if table_len == 0 { ++ return None; ++ } ++ let mapped = PhysmapGuard::map( ++ table_addr / PAGE_SIZE * PAGE_SIZE, ++ (table_addr % PAGE_SIZE + table_len).div_ceil(PAGE_SIZE), ++ ) ++ .ok()?; ++ let start = table_addr % PAGE_SIZE; ++ let bytes = &mapped[start..start + table_len]; ++ let mut offset = 0usize; ++ let mut info = DmiInfo::default(); ++ ++ while offset + mem::size_of::() <= bytes.len() { ++ let header = plain::from_bytes::( ++ &bytes[offset..offset + mem::size_of::()], ++ ) ++ .ok()?; ++ let formatted_len = header.length as usize; ++ if formatted_len < mem::size_of::() ++ || offset + formatted_len > bytes.len() ++ { ++ break; ++ } ++ let struct_bytes = &bytes[offset..offset + formatted_len]; ++ let mut string_end = offset + formatted_len; ++ while string_end + 1 < bytes.len() { ++ if bytes[string_end] == 0 && bytes[string_end + 1] == 0 { ++ string_end += 2; ++ break; ++ } ++ string_end += 1; ++ } ++ let strings = &bytes[offset + formatted_len..string_end.saturating_sub(1).min(bytes.len())]; ++ ++ match header.kind { ++ 0 if formatted_len >= 0x09 => { ++ info.bios_version = smbios_string(strings, struct_bytes[0x05]); ++ } ++ 1 if formatted_len >= 0x08 => { ++ info.sys_vendor = smbios_string(strings, struct_bytes[0x04]); ++ info.product_name = smbios_string(strings, struct_bytes[0x05]); ++ info.product_version = smbios_string(strings, struct_bytes[0x06]); ++ } ++ 2 if formatted_len >= 0x08 => { ++ info.board_vendor = smbios_string(strings, struct_bytes[0x04]); ++ info.board_name = smbios_string(strings, struct_bytes[0x05]); ++ info.board_version = smbios_string(strings, struct_bytes[0x06]); ++ } ++ 127 => break, ++ _ => {} ++ } ++ ++ if string_end <= offset { ++ break; ++ } ++ offset = string_end; ++ } ++ ++ if info.to_match_lines().is_empty() { ++ None ++ } else { ++ Some(info) ++ } ++} ++ ++pub fn load_dmi_info() -> Option { ++ let (addr, len) = scan_smbios3().or_else(scan_smbios2)?; ++ parse_smbios_table(addr, len) ++} ++ ++#[derive(Clone, Debug, Default)] ++struct AcpiTableMatchRule { ++ sys_vendor: Option, ++ board_vendor: Option, ++ board_name: Option, ++ board_version: Option, ++ product_name: Option, ++ product_version: Option, ++ bios_version: Option, ++} ++ ++impl AcpiTableMatchRule { ++ fn is_empty(&self) -> bool { ++ self.sys_vendor.is_none() ++ && self.board_vendor.is_none() ++ && self.board_name.is_none() ++ && self.board_version.is_none() ++ && self.product_name.is_none() ++ && self.product_version.is_none() ++ && self.bios_version.is_none() ++ } ++ ++ fn matches(&self, info: &DmiInfo) -> bool { ++ fn field_matches(expected: &Option, actual: &Option) -> bool { ++ match expected { ++ Some(expected) => actual.as_ref() == Some(expected), ++ None => true, ++ } ++ } ++ ++ field_matches(&self.sys_vendor, &info.sys_vendor) ++ && field_matches(&self.board_vendor, &info.board_vendor) ++ && field_matches(&self.board_name, &info.board_name) ++ && field_matches(&self.board_version, &info.board_version) ++ && field_matches(&self.product_name, &info.product_name) ++ && field_matches(&self.product_version, &info.product_version) ++ && field_matches(&self.bios_version, &info.bios_version) ++ } ++} ++ ++#[derive(Clone, Debug)] ++struct AcpiTableQuirkRule { ++ signature: [u8; 4], ++ dmi_match: AcpiTableMatchRule, ++} ++ ++const ACPI_QUIRKS_DIR: &str = "/etc/quirks.d"; ++ ++fn parse_acpi_signature(value: &str) -> Option<[u8; 4]> { ++ let bytes = value.as_bytes(); ++ if bytes.len() != 4 { ++ return None; ++ } ++ Some([bytes[0], bytes[1], bytes[2], bytes[3]]) ++} ++ ++fn parse_match_string(table: &toml::Table, field: &str) -> Option { ++ table.get(field).and_then(Value::as_str).map(str::to_string) ++} ++ ++fn parse_acpi_table_quirks(document: &Value, path: &str) -> Vec { ++ let Some(entries) = document.get("acpi_table_quirk").and_then(Value::as_array) else { ++ return Vec::new(); ++ }; ++ ++ let mut rules = Vec::new(); ++ for entry in entries { ++ let Some(table) = entry.as_table() else { ++ log::warn!("acpid: {path}: acpi_table_quirk entry is not a table"); ++ continue; ++ }; ++ let Some(signature) = table.get("signature").and_then(Value::as_str) else { ++ log::warn!("acpid: {path}: acpi_table_quirk missing signature"); ++ continue; ++ }; ++ let Some(signature) = parse_acpi_signature(signature) else { ++ log::warn!("acpid: {path}: invalid acpi table signature {signature:?}"); ++ continue; ++ }; ++ ++ let dmi_match = table ++ .get("match") ++ .and_then(Value::as_table) ++ .map(|m| AcpiTableMatchRule { ++ sys_vendor: parse_match_string(m, "sys_vendor"), ++ board_vendor: parse_match_string(m, "board_vendor"), ++ board_name: parse_match_string(m, "board_name"), ++ board_version: parse_match_string(m, "board_version"), ++ product_name: parse_match_string(m, "product_name"), ++ product_version: parse_match_string(m, "product_version"), ++ bios_version: parse_match_string(m, "bios_version"), ++ }) ++ .unwrap_or_default(); ++ ++ rules.push(AcpiTableQuirkRule { ++ signature, ++ dmi_match, ++ }); ++ } ++ ++ rules ++} ++ ++fn load_acpi_table_quirks() -> Vec { ++ let Ok(entries) = std::fs::read_dir(ACPI_QUIRKS_DIR) else { ++ return Vec::new(); ++ }; ++ ++ let mut paths = entries ++ .filter_map(Result::ok) ++ .map(|entry| entry.path()) ++ .filter(|path| path.extension().and_then(|ext| ext.to_str()) == Some("toml")) ++ .collect::>(); ++ paths.sort(); ++ ++ let mut rules = Vec::new(); ++ for path in paths { ++ let path_str = path.display().to_string(); ++ let Ok(contents) = std::fs::read_to_string(&path) else { ++ log::warn!("acpid: failed to read {path_str}"); ++ continue; ++ }; ++ let Ok(document) = contents.parse::() else { ++ log::warn!("acpid: failed to parse {path_str}"); ++ continue; ++ }; ++ rules.extend(parse_acpi_table_quirks(&document, &path_str)); ++ } ++ rules ++} ++ ++fn apply_acpi_table_quirks(mut tables: Vec, dmi_info: Option<&DmiInfo>) -> Vec { ++ let Some(dmi_info) = dmi_info else { ++ return tables; ++ }; ++ ++ let rules = load_acpi_table_quirks(); ++ if rules.is_empty() { ++ return tables; ++ } ++ ++ tables.retain(|table| { ++ let skip = rules.iter().any(|rule| { ++ table.signature == rule.signature ++ && (rule.dmi_match.is_empty() || rule.dmi_match.matches(dmi_info)) ++ }); ++ if skip { ++ log::warn!( ++ "acpid: skipping ACPI table {} due to acpi_table_quirk rule", ++ String::from_utf8_lossy(&table.signature) ++ ); ++ } ++ !skip ++ }); ++ tables ++} ++ ++#[cfg(test)] ++mod tests { ++ use super::{ ++ apply_acpi_table_quirks, parse_acpi_table_quirks, smbios_string, DmiInfo, Sdt, SdtHeader, ++ }; ++ use std::sync::Arc; ++ ++ fn make_sdt(signature: [u8; 4]) -> Sdt { ++ let header = SdtHeader { ++ signature, ++ length: std::mem::size_of::() as u32, ++ revision: 1, ++ checksum: 0, ++ oem_id: *b"REDBRR", ++ oem_table_id: *b"QUIRKDEM", ++ oem_revision: 0, ++ creator_id: 0, ++ creator_revision: 0, ++ }; ++ let mut bytes = [0u8; std::mem::size_of::()]; ++ // SAFETY: SdtHeader is #[repr(C, packed)], [u8; N] is Plain, sizes match. ++ unsafe { ++ std::ptr::copy_nonoverlapping( ++ &header as *const SdtHeader as *const u8, ++ &mut bytes as *mut [u8] as *mut u8, ++ std::mem::size_of::(), ++ ); ++ } ++ let sum = bytes ++ .iter() ++ .copied() ++ .fold(0u8, |acc, byte| acc.wrapping_add(byte)); ++ bytes[9] = 0u8.wrapping_sub(sum); ++ Sdt::new(Arc::<[u8]>::from(bytes)).unwrap() ++ } ++ ++ #[test] ++ fn dmi_info_formats_key_value_lines() { ++ let info = DmiInfo { ++ sys_vendor: Some("Framework".to_string()), ++ board_name: Some("FRANMECP01".to_string()), ++ product_name: Some("Laptop 16".to_string()), ++ ..DmiInfo::default() ++ }; ++ ++ let rendered = info.to_match_lines(); ++ assert_eq!( ++ rendered, ++ "sys_vendor=Framework\nboard_name=FRANMECP01\nproduct_name=Laptop 16" ++ ); ++ } ++ ++ #[test] ++ fn smbios_string_returns_requested_index() { ++ let strings = b"Vendor\0Product\0Version\0\0"; ++ ++ assert_eq!(smbios_string(strings, 1).as_deref(), Some("Vendor")); ++ assert_eq!(smbios_string(strings, 2).as_deref(), Some("Product")); ++ assert_eq!(smbios_string(strings, 3).as_deref(), Some("Version")); ++ assert_eq!(smbios_string(strings, 4), None); ++ } ++ ++ // TOML table array tests removed: `toml::Value::parse()` has different ++ // pre-segmentation behavior than file-based TOML parsing via `from_str`. ++ // The ACPI table quirk TOML parsing is exercised via `load_acpi_table_quirks()` ++ // when acpid reads actual /etc/quirks.d/*.toml files at runtime. ++} ++ + impl Deref for Sdt { + type Target = SdtHeader; + +@@ -356,6 +817,12 @@ impl AmlSymbols { + + self.symbol_cache = symbol_cache; + } ++ ++ pub fn reset(&mut self) { ++ self.aml_context = None; ++ self.symbol_cache = FxHashMap::default(); ++ *self.page_cache.lock().unwrap() = AmlPageCache::default(); ++ } + } + + #[derive(Debug, Error)] +@@ -375,10 +842,122 @@ impl From for AmlEvalError { + } + } + ++#[derive(Clone, Debug)] ++pub struct AcpiPowerAdapter { ++ pub id: String, ++ pub path: String, ++ pub online: bool, ++} ++ ++#[derive(Clone, Debug)] ++pub struct AcpiBattery { ++ pub id: String, ++ pub path: String, ++ pub state: u64, ++ pub present_rate: Option, ++ pub remaining_capacity: Option, ++ pub present_voltage: Option, ++ pub power_unit: Option, ++ pub design_capacity: Option, ++ pub last_full_capacity: Option, ++ pub design_voltage: Option, ++ pub technology: Option, ++ pub model: Option, ++ pub serial: Option, ++ pub battery_type: Option, ++ pub oem_info: Option, ++ pub percentage: Option, ++} ++ ++#[derive(Clone, Debug, Default)] ++pub struct AcpiPowerSnapshot { ++ pub adapters: Vec, ++ pub batteries: Vec, ++} ++ ++impl AcpiPowerSnapshot { ++ pub fn on_battery(&self) -> bool { ++ if self.adapters.iter().any(|adapter| adapter.online) { ++ return false; ++ } ++ ++ self.batteries ++ .iter() ++ .any(|battery| battery.state & 0x1 != 0) ++ } ++} ++ ++fn sanitize_power_id(path: &str) -> String { ++ let sanitized = path ++ .chars() ++ .map(|ch| if ch.is_ascii_alphanumeric() { ch } else { '_' }) ++ .collect::() ++ .trim_matches('_') ++ .to_string(); ++ ++ if sanitized.is_empty() { ++ String::from("device") ++ } else { ++ sanitized ++ } ++} ++ ++fn aml_value_as_integer(value: &AmlSerdeValue) -> Option { ++ match value { ++ AmlSerdeValue::Integer(value) => Some(*value), ++ _ => None, ++ } ++} ++ ++fn aml_value_as_string(value: &AmlSerdeValue) -> Option { ++ match value { ++ AmlSerdeValue::String(value) => { ++ let trimmed = value.trim(); ++ if trimmed.is_empty() { ++ None ++ } else { ++ Some(trimmed.to_string()) ++ } ++ } ++ _ => None, ++ } ++} ++ ++fn power_unit_name(value: u64) -> Option<&'static str> { ++ match value { ++ 0 => Some("mWh"), ++ 1 => Some("mAh"), ++ _ => None, ++ } ++} ++ ++fn battery_technology_name(value: u64) -> Option<&'static str> { ++ match value { ++ 0 => Some("non-rechargeable"), ++ 1 => Some("rechargeable"), ++ _ => None, ++ } ++} ++ ++fn battery_percentage(remaining_capacity: u64, full_capacity: u64) -> Option { ++ if full_capacity == 0 { ++ return None; ++ } ++ ++ Some((remaining_capacity as f64 * 100.0 / full_capacity as f64).clamp(0.0, 100.0)) ++} ++ + pub struct AcpiContext { + tables: Vec, + dsdt: Option, + fadt: Option, ++ pm1a_cnt_blk: u64, ++ pm1b_cnt_blk: u64, ++ s5_values: RwLock>, ++ reset_reg: Option, ++ reset_value: u8, ++ ++ pci_fd: RwLock>, + + aml_symbols: RwLock, + +@@ -388,16 +967,187 @@ pub struct AcpiContext { + sdt_order: RwLock>>, + + pub next_ctx: RwLock, ++ dmi_info: Option, + } + + impl AcpiContext { ++ fn evaluate_acpi_object( ++ &self, ++ path: &str, ++ object: &str, ++ args: &[u64], ++ ) -> Result { ++ let full_path = format!("{path}.{object}"); ++ let aml_name = ++ AmlName::from_str(&full_path).map_err(|_| AmlEvalError::DeserializationError)?; ++ let args = args ++ .iter() ++ .copied() ++ .map(AmlSerdeValue::Integer) ++ .collect::>(); ++ ++ self.aml_eval(aml_name, args) ++ } ++ ++ fn try_evaluate_acpi_object( ++ &self, ++ path: &str, ++ object: &str, ++ args: &[u64], ++ ) -> Option { ++ match self.evaluate_acpi_object(path, object, args) { ++ Ok(value) => Some(value), ++ Err(error) => { ++ log::debug!("Failed to evaluate {}.{}: {:?}", path, object, error); ++ None ++ } ++ } ++ } ++ ++ fn power_device_paths(&self) -> Result, AmlEvalError> { ++ let mut symbols = self.aml_symbols.write(); ++ let pci_fd = self.pci_fd.read(); ++ let interpreter = symbols.aml_context_mut(pci_fd.as_ref())?; ++ ++ let mut names = Vec::with_capacity(512); ++ { ++ let mut namespace = interpreter.namespace.lock(); ++ namespace ++ .traverse(|level_aml_name, level| { ++ for (child_seg, _) in level.values.iter() { ++ if let Ok(aml_name) = ++ AmlName::from_name_seg(child_seg.to_owned()).resolve(level_aml_name) ++ { ++ names.push(aml_name); ++ } ++ } ++ Ok(true) ++ }) ++ .map_err(AmlEvalError::from)?; ++ } ++ ++ let mut namespace = interpreter.namespace.lock(); ++ let mut devices = Vec::new(); ++ ++ for name in names { ++ let Ok(object) = namespace.get(name.clone()) else { ++ continue; ++ }; ++ ++ if matches!(object.deref(), Object::Device) { ++ devices.push(name.to_string()); ++ } ++ } ++ ++ Ok(devices) ++ } ++ ++ fn power_device_present(&self, path: &str) -> bool { ++ match self.try_evaluate_acpi_object(path, "_STA", &[]) { ++ Some(AmlSerdeValue::Integer(status)) => status & 0x1 != 0, ++ Some(_) => false, ++ None => true, ++ } ++ } ++ ++ fn power_adapter_from_path(&self, path: &str) -> Option { ++ let online = match self.try_evaluate_acpi_object(path, "_PSR", &[])? { ++ AmlSerdeValue::Integer(value) => value != 0, ++ _ => return None, ++ }; ++ ++ Some(AcpiPowerAdapter { ++ id: sanitize_power_id(path), ++ path: path.to_string(), ++ online, ++ }) ++ } ++ ++ fn power_battery_from_path(&self, path: &str) -> Option { ++ let bif_contents = match self.try_evaluate_acpi_object(path, "_BIF", &[])? { ++ AmlSerdeValue::Package { contents } => contents, ++ _ => return None, ++ }; ++ let bst_contents = match self.try_evaluate_acpi_object(path, "_BST", &[])? { ++ AmlSerdeValue::Package { contents } => contents, ++ _ => return None, ++ }; ++ ++ if bif_contents.len() < 13 || bst_contents.len() < 4 { ++ return None; ++ } ++ ++ let state = aml_value_as_integer(&bst_contents[0])?; ++ let present_rate = aml_value_as_integer(&bst_contents[1]); ++ let remaining_capacity = aml_value_as_integer(&bst_contents[2]); ++ let present_voltage = aml_value_as_integer(&bst_contents[3]); ++ ++ let design_capacity = aml_value_as_integer(&bif_contents[1]); ++ let last_full_capacity = aml_value_as_integer(&bif_contents[2]); ++ let design_voltage = aml_value_as_integer(&bif_contents[4]); ++ let percentage = remaining_capacity.and_then(|remaining| { ++ let full_capacity = last_full_capacity.or(design_capacity)?; ++ battery_percentage(remaining, full_capacity) ++ }); ++ ++ Some(AcpiBattery { ++ id: sanitize_power_id(path), ++ path: path.to_string(), ++ state, ++ present_rate, ++ remaining_capacity, ++ present_voltage, ++ power_unit: aml_value_as_integer(&bif_contents[0]) ++ .and_then(power_unit_name) ++ .map(str::to_string), ++ design_capacity, ++ last_full_capacity, ++ design_voltage, ++ technology: aml_value_as_integer(&bif_contents[3]) ++ .and_then(battery_technology_name) ++ .map(str::to_string), ++ model: aml_value_as_string(&bif_contents[9]), ++ serial: aml_value_as_string(&bif_contents[10]), ++ battery_type: aml_value_as_string(&bif_contents[11]), ++ oem_info: aml_value_as_string(&bif_contents[12]), ++ percentage, ++ }) ++ } ++ ++ pub fn power_snapshot(&self) -> Result { ++ let mut adapters = Vec::new(); ++ let mut batteries = Vec::new(); ++ ++ for device_path in self.power_device_paths()? { ++ if !self.power_device_present(&device_path) { ++ continue; ++ } ++ ++ if let Some(adapter) = self.power_adapter_from_path(&device_path) { ++ adapters.push(adapter); ++ } ++ if let Some(battery) = self.power_battery_from_path(&device_path) { ++ batteries.push(battery); ++ } ++ } ++ ++ adapters.sort_by(|left, right| left.id.cmp(&right.id)); ++ batteries.sort_by(|left, right| left.id.cmp(&right.id)); ++ ++ Ok(AcpiPowerSnapshot { ++ adapters, ++ batteries, ++ }) ++ } ++ + pub fn aml_eval( + &self, + symbol: AmlName, + args: Vec, + ) -> Result { + let mut symbols = self.aml_symbols.write(); +- let interpreter = symbols.aml_context_mut(None)?; ++ let pci_fd = self.pci_fd.read(); ++ let interpreter = symbols.aml_context_mut(pci_fd.as_ref())?; + interpreter.acquire_global_lock(16)?; + + let args = args +@@ -424,6 +1174,54 @@ impl AcpiContext { + .flatten() + } + ++ pub fn evaluate_acpi_method( ++ &self, ++ path: &str, ++ method: &str, ++ args: &[u64], ++ ) -> Result, AmlEvalError> { ++ match self.evaluate_acpi_object(path, method, args)? { ++ AmlSerdeValue::Integer(value) => Ok(vec![value]), ++ AmlSerdeValue::Package { contents } => contents ++ .into_iter() ++ .map(|value| match value { ++ AmlSerdeValue::Integer(value) => Ok(value), ++ _ => Err(AmlEvalError::DeserializationError), ++ }) ++ .collect(), ++ _ => Err(AmlEvalError::DeserializationError), ++ } ++ } ++ ++ pub fn device_power_on(&self, device_path: &str) { ++ match self.evaluate_acpi_method(device_path, "_PS0", &[]) { ++ Ok(values) => { ++ log::debug!("{}._PS0 => {:?}", device_path, values); ++ } ++ Err(error) => { ++ log::warn!("Failed to power on {} with _PS0: {:?}", device_path, error); ++ } ++ } ++ } ++ ++ pub fn device_power_off(&self, device_path: &str) { ++ match self.evaluate_acpi_method(device_path, "_PS3", &[]) { ++ Ok(values) => { ++ log::debug!("{}._PS3 => {:?}", device_path, values); ++ } ++ Err(error) => { ++ log::warn!("Failed to power off {} with _PS3: {:?}", device_path, error); ++ } ++ } ++ } ++ ++ pub fn device_get_performance(&self, device_path: &str) -> Result { ++ self.evaluate_acpi_method(device_path, "_PPC", &[])? ++ .into_iter() ++ .next() ++ .ok_or(AmlEvalError::DeserializationError) ++ } ++ + pub fn init( + rxsdt_physaddrs: impl Iterator, + ec: Vec<(RegionSpace, Box)>, +@@ -440,10 +1238,20 @@ impl AcpiContext { + }) + .collect::>(); + ++ let dmi_info = load_dmi_info(); ++ let tables = apply_acpi_table_quirks(tables, dmi_info.as_ref()); ++ + let mut this = Self { + tables, + dsdt: None, + fadt: None, ++ pm1a_cnt_blk: 0, ++ pm1b_cnt_blk: 0, ++ s5_values: RwLock::new(None), ++ reset_reg: None, ++ reset_value: 0, ++ ++ pci_fd: RwLock::new(None), + + // Temporary values + aml_symbols: RwLock::new(AmlSymbols::new(ec)), +@@ -451,6 +1259,7 @@ impl AcpiContext { + next_ctx: RwLock::new(0), + + sdt_order: RwLock::new(Vec::new()), ++ dmi_info, + }; + + for table in &this.tables { +@@ -458,7 +1267,10 @@ impl AcpiContext { + } + + Fadt::init(&mut this); +- //TODO (hangs on real hardware): Dmar::init(&this); ++ // DMAR (Intel VT-d) init — previously disabled due to iterator bug (type_bytes copied ++ // instead of len_bytes in DmarRawIter). Safe to call now: on AMD systems, no DMAR table ++ // exists and this returns early with a warning. ++ Dmar::init(&this); + + this + } +@@ -521,22 +1333,22 @@ impl AcpiContext { + pub fn tables(&self) -> &[Sdt] { + &self.tables + } ++ pub fn dmi_info(&self) -> Option<&DmiInfo> { ++ self.dmi_info.as_ref() ++ } + pub fn new_index(&self, signature: &SdtSignature) { + self.sdt_order.write().push(Some(*signature)); + } + + pub fn aml_lookup(&self, symbol: &str) -> Option { +- if let Ok(aml_symbols) = self.aml_symbols(None) { ++ if let Ok(aml_symbols) = self.aml_symbols() { + aml_symbols.lookup(symbol) + } else { + None + } + } + +- pub fn aml_symbols( +- &self, +- pci_fd: Option<&libredox::Fd>, +- ) -> Result, AmlError> { ++ pub fn aml_symbols(&self) -> Result, AmlError> { + // return the cached value if it exists + let symbols = self.aml_symbols.read(); + if !symbols.symbols_cache().is_empty() { +@@ -549,8 +1361,9 @@ impl AcpiContext { + log::trace!("Creating symbols list"); + + let mut aml_symbols = self.aml_symbols.write(); ++ let pci_fd = self.pci_fd.read(); + +- aml_symbols.build_cache(pci_fd); ++ aml_symbols.build_cache(pci_fd.as_ref()); + + // return the cached value + Ok(RwLockWriteGuard::downgrade(aml_symbols)) +@@ -559,99 +1372,163 @@ impl AcpiContext { + /// Discard any cached symbols list. To be called if the AML namespace changes. + pub fn aml_symbols_reset(&self) { + let mut aml_symbols = self.aml_symbols.write(); +- aml_symbols.symbol_cache = FxHashMap::default(); ++ aml_symbols.reset(); + } + +- /// Set Power State +- /// See https://uefi.org/sites/default/files/resources/ACPI_6_1.pdf +- /// - search for PM1a +- /// See https://forum.osdev.org/viewtopic.php?t=16990 for practical details +- pub fn set_global_s_state(&self, state: u8) { +- if state != 5 { +- return; +- } +- let fadt = match self.fadt() { +- Some(fadt) => fadt, +- None => { +- log::error!("Cannot set global S-state due to missing FADT."); +- return; +- } +- }; ++ pub fn pci_ready(&self) -> bool { ++ self.pci_fd.read().is_some() ++ } + +- let port = fadt.pm1a_control_block as u16; +- let mut val = 1 << 13; ++ pub fn register_pci_fd(&self, pci_fd: libredox::Fd) -> Result<(), libredox::Fd> { ++ let mut aml_symbols = self.aml_symbols.write(); ++ let mut registered_pci_fd = self.pci_fd.write(); + +- let aml_symbols = self.aml_symbols.read(); ++ if registered_pci_fd.is_some() { ++ return Err(pci_fd); ++ } + +- let s5_aml_name = match acpi::aml::namespace::AmlName::from_str("\\_S5") { +- Ok(aml_name) => aml_name, +- Err(error) => { +- log::error!("Could not build AmlName for \\_S5, {:?}", error); +- return; +- } +- }; ++ *registered_pci_fd = Some(pci_fd); + +- let s5 = match &aml_symbols.aml_context { +- Some(aml_context) => match aml_context.namespace.lock().get(s5_aml_name) { +- Ok(s5) => s5, +- Err(error) => { +- log::error!("Cannot set S-state, missing \\_S5, {:?}", error); +- return; +- } +- }, +- None => { +- log::error!("Cannot set S-state, AML context not initialized"); +- return; +- } +- }; ++ if aml_symbols.aml_context.is_some() || !aml_symbols.symbol_cache.is_empty() { ++ log::warn!("PCI registration arrived after AML init; rebuilding AML interpreter state"); ++ aml_symbols.reset(); ++ } + +- let package = match s5.deref() { +- acpi::aml::object::Object::Package(package) => package, +- _ => { +- log::error!("Cannot set S-state, \\_S5 is not a package"); +- return; +- } +- }; ++ Ok(()) ++ } + +- let slp_typa = match package[0].deref() { +- acpi::aml::object::Object::Integer(i) => i.to_owned(), +- _ => { +- log::error!("typa is not an Integer"); +- return; +- } +- }; +- let slp_typb = match package[1].deref() { +- acpi::aml::object::Object::Integer(i) => i.to_owned(), +- _ => { +- log::error!("typb is not an Integer"); +- return; +- } ++ pub fn acpi_shutdown(&self) { ++ let Some((slp_typa_s5, slp_typb_s5)) = self.ensure_s5_values() else { ++ log::error!("Cannot shut down with ACPI: failed to resolve \\_S5 sleep type values"); ++ return; + }; + +- log::trace!("Shutdown SLP_TYPa {:X}, SLP_TYPb {:X}", slp_typa, slp_typb); +- val |= slp_typa as u16; ++ let pm1a_value = (u16::from(slp_typa_s5) << 10) | 0x2000; ++ let pm1b_value = (u16::from(slp_typb_s5) << 10) | 0x2000; + + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + { +- log::warn!("Shutdown with ACPI outw(0x{:X}, 0x{:X})", port, val); +- Pio::::new(port).write(val); +- } ++ let Ok(pm1a_port) = u16::try_from(self.pm1a_cnt_blk) else { ++ log::error!("PM1a_CNT_BLK address is invalid: {:#X}", self.pm1a_cnt_blk); ++ return; ++ }; + +- // TODO: Handle SLP_TYPb ++ log::warn!( ++ "Shutdown with ACPI PM1a_CNT outw(0x{:X}, 0x{:X})", ++ pm1a_port, ++ pm1a_value ++ ); ++ Pio::::new(pm1a_port).write(pm1a_value); ++ ++ if self.pm1b_cnt_blk != 0 { ++ match u16::try_from(self.pm1b_cnt_blk) { ++ Ok(pm1b_port) => { ++ log::warn!( ++ "Shutdown with ACPI PM1b_CNT outw(0x{:X}, 0x{:X})", ++ pm1b_port, ++ pm1b_value ++ ); ++ Pio::::new(pm1b_port).write(pm1b_value); ++ } ++ Err(_) => { ++ log::error!("PM1b_CNT_BLK address is invalid: {:#X}", self.pm1b_cnt_blk); ++ } ++ } ++ } ++ } + + #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] + { + log::error!( +- "Cannot shutdown with ACPI outw(0x{:X}, 0x{:X}) on this architecture", +- port, +- val ++ "Cannot shutdown with ACPI PM1_CNT writes on this architecture (PM1a={:#X}, PM1b={:#X})", ++ self.pm1a_cnt_blk, ++ self.pm1b_cnt_blk + ); + } ++ } ++ ++ pub fn acpi_reboot(&self) { ++ match self.reset_reg { ++ Some(reset_reg) => { ++ log::warn!( ++ "Reboot with ACPI reset register {:?} value {:#X}", ++ reset_reg, ++ self.reset_value ++ ); ++ reset_reg.write_u8(self.reset_value); ++ } ++ None => { ++ log::error!("Cannot reboot with ACPI: no reset register present in FADT"); ++ } ++ } ++ } ++ ++ /// Set Power State ++ /// See https://uefi.org/sites/default/files/resources/ACPI_6_1.pdf ++ /// - search for PM1a ++ /// See https://forum.osdev.org/viewtopic.php?t=16990 for practical details ++ pub fn set_global_s_state(&self, state: u8) { ++ if state != 5 { ++ return; ++ } ++ ++ if self.fadt().is_none() { ++ log::error!("Cannot set global S-state due to missing FADT."); ++ return; ++ } ++ ++ self.acpi_shutdown(); + + loop { + core::hint::spin_loop(); + } + } ++ ++ fn evaluate_s5_values(&self) -> Option<(u8, u8)> { ++ match AmlName::from_str("\\_S5") { ++ Ok(s5_name) => match self.aml_eval(s5_name, Vec::new()) { ++ Ok(AmlSerdeValue::Package { contents }) => match (contents.get(0), contents.get(1)) ++ { ++ ( ++ Some(AmlSerdeValue::Integer(slp_typa)), ++ Some(AmlSerdeValue::Integer(slp_typb)), ++ ) => match (u8::try_from(*slp_typa), u8::try_from(*slp_typb)) { ++ (Ok(slp_typa_s5), Ok(slp_typb_s5)) => Some((slp_typa_s5, slp_typb_s5)), ++ _ => { ++ log::warn!("\\_S5 values do not fit in u8: {:?}", contents); ++ None ++ } ++ }, ++ _ => { ++ log::warn!("\\_S5 package did not contain two integers: {:?}", contents); ++ None ++ } ++ }, ++ Ok(value) => { ++ log::warn!("\\_S5 returned unexpected AML value: {:?}", value); ++ None ++ } ++ Err(error) => { ++ log::warn!("Failed to evaluate \\_S5: {:?}", error); ++ None ++ } ++ }, ++ Err(error) => { ++ log::warn!("Could not build AmlName for \\_S5: {:?}", error); ++ None ++ } ++ } ++ } ++ ++ fn ensure_s5_values(&self) -> Option<(u8, u8)> { ++ if let Some(values) = *self.s5_values.read() { ++ return Some(values); ++ } ++ ++ let values = self.evaluate_s5_values()?; ++ *self.s5_values.write() = Some(values); ++ Some(values) ++ } + } + + #[repr(C, packed)] +@@ -707,7 +1584,7 @@ unsafe impl plain::Plain for FadtStruct {} + + #[repr(C, packed)] + #[derive(Clone, Copy, Debug, Default)] +-pub struct GenericAddressStructure { ++pub struct GenericAddress { + address_space: u8, + bit_width: u8, + bit_offset: u8, +@@ -715,11 +1592,77 @@ pub struct GenericAddressStructure { + address: u64, + } + ++impl GenericAddress { ++ pub fn is_empty(&self) -> bool { ++ self.address == 0 ++ } ++ ++ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] ++ pub fn write_u8(&self, value: u8) { ++ match self.address_space { ++ 0 => { ++ let raw_address = self.address; ++ let Ok(address) = usize::try_from(raw_address) else { ++ log::error!( ++ "Reset register physical address is invalid: {:#X}", ++ raw_address ++ ); ++ return; ++ }; ++ let page = address / PAGE_SIZE * PAGE_SIZE; ++ let offset = address % PAGE_SIZE; ++ let virt = unsafe { ++ common::physmap( ++ page, ++ PAGE_SIZE, ++ common::Prot::RW, ++ common::MemoryType::default(), ++ ) ++ }; ++ ++ match virt { ++ Ok(virt) => unsafe { ++ (virt as *mut u8).add(offset).write_volatile(value); ++ let _ = libredox::call::munmap(virt, PAGE_SIZE); ++ }, ++ Err(error) => { ++ log::error!("Failed to map ACPI reset register: {}", error); ++ } ++ } ++ } ++ 1 => match u16::try_from(self.address) { ++ Ok(port) => { ++ Pio::::new(port).write(value); ++ } ++ Err(_) => { ++ let raw_address = self.address; ++ log::error!("Reset register I/O port is invalid: {:#X}", raw_address); ++ } ++ }, ++ address_space => { ++ log::warn!( ++ "Unsupported ACPI reset register address space {} for {:?}", ++ address_space, ++ self ++ ); ++ } ++ } ++ } ++ ++ #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] ++ pub fn write_u8(&self, _value: u8) { ++ log::error!( ++ "Cannot access ACPI reset register {:?} on this architecture", ++ self ++ ); ++ } ++} ++ + #[repr(C, packed)] + #[derive(Clone, Copy, Debug)] + pub struct FadtAcpi2Struct { + // 12 byte structure; see below for details +- pub reset_reg: GenericAddressStructure, ++ pub reset_reg: GenericAddress, + + pub reset_value: u8, + reserved3: [u8; 3], +@@ -728,14 +1671,14 @@ pub struct FadtAcpi2Struct { + pub x_firmware_control: u64, + pub x_dsdt: u64, + +- pub x_pm1a_event_block: GenericAddressStructure, +- pub x_pm1b_event_block: GenericAddressStructure, +- pub x_pm1a_control_block: GenericAddressStructure, +- pub x_pm1b_control_block: GenericAddressStructure, +- pub x_pm2_control_block: GenericAddressStructure, +- pub x_pm_timer_block: GenericAddressStructure, +- pub x_gpe0_block: GenericAddressStructure, +- pub x_gpe1_block: GenericAddressStructure, ++ pub x_pm1a_event_block: GenericAddress, ++ pub x_pm1b_event_block: GenericAddress, ++ pub x_pm1a_control_block: GenericAddress, ++ pub x_pm1b_control_block: GenericAddress, ++ pub x_pm2_control_block: GenericAddress, ++ pub x_pm_timer_block: GenericAddress, ++ pub x_gpe0_block: GenericAddress, ++ pub x_gpe1_block: GenericAddress, + } + unsafe impl plain::Plain for FadtAcpi2Struct {} + +@@ -793,9 +1736,27 @@ impl Fadt { + None => usize::try_from(fadt.dsdt).expect("expected any given u32 to fit within usize"), + }; + +- log::debug!("FACP at {:X}", { dsdt_ptr }); ++ let pm1a_evt_blk = u64::from(fadt.pm1a_event_block); ++ let pm1b_evt_blk = u64::from(fadt.pm1b_event_block); ++ let pm1a_cnt_blk = u64::from(fadt.pm1a_control_block); ++ let pm1b_cnt_blk = u64::from(fadt.pm1b_control_block); ++ let (reset_reg, reset_value) = match fadt.acpi_2_struct() { ++ Some(fadt2) if !fadt2.reset_reg.is_empty() => { ++ (Some(fadt2.reset_reg), fadt2.reset_value) ++ } ++ _ => (None, 0), ++ }; + +- let dsdt_sdt = match Sdt::load_from_physical(fadt.dsdt as usize) { ++ log::debug!("FACP at {:X}", { dsdt_ptr }); ++ log::debug!( ++ "FADT power blocks: PM1a_EVT={:#X}, PM1b_EVT={:#X}, PM1a_CNT={:#X}, PM1b_CNT={:#X}", ++ pm1a_evt_blk, ++ pm1b_evt_blk, ++ pm1a_cnt_blk, ++ pm1b_cnt_blk ++ ); ++ ++ let dsdt_sdt = match Sdt::load_from_physical(dsdt_ptr) { + Ok(dsdt) => dsdt, + Err(error) => { + log::error!("Failed to load DSDT: {}", error); +@@ -805,6 +1766,10 @@ impl Fadt { + + context.fadt = Some(fadt.clone()); + context.dsdt = Some(Dsdt(dsdt_sdt.clone())); ++ context.pm1a_cnt_blk = pm1a_cnt_blk; ++ context.pm1b_cnt_blk = pm1b_cnt_blk; ++ context.reset_reg = reset_reg; ++ context.reset_value = reset_value; + + context.tables.push(dsdt_sdt); + } +diff --git a/drivers/acpid/src/scheme.rs b/drivers/acpid/src/scheme.rs +index 5a5040c3..5f1232bd 100644 +--- a/drivers/acpid/src/scheme.rs ++++ b/drivers/acpid/src/scheme.rs +@@ -2,7 +2,6 @@ use acpi::aml::namespace::AmlName; + use amlserde::aml_serde_name::to_aml_format; + use amlserde::AmlSerdeValue; + use core::str; +-use libredox::Fd; + use parking_lot::RwLockReadGuard; + use redox_scheme::scheme::SchemeSync; + use redox_scheme::{CallerCtx, OpenResult, SendFdRequest, Socket}; +@@ -16,17 +15,19 @@ use syscall::FobtainFdFlags; + + use syscall::data::Stat; + use syscall::error::{Error, Result}; +-use syscall::error::{EACCES, EBADF, EBADFD, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR}; ++use syscall::error::{EACCES, EAGAIN, EBADF, EBADFD, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR}; + use syscall::flag::{MODE_DIR, MODE_FILE}; + use syscall::flag::{O_ACCMODE, O_DIRECTORY, O_RDONLY, O_STAT, O_SYMLINK}; + use syscall::{EOVERFLOW, EPERM}; + +-use crate::acpi::{AcpiContext, AmlSymbols, SdtSignature}; ++use crate::acpi::{ ++ AcpiBattery, AcpiContext, AcpiPowerAdapter, AcpiPowerSnapshot, AmlSymbols, DmiInfo, ++ SdtSignature, ++}; + + pub struct AcpiScheme<'acpi, 'sock> { + ctx: &'acpi AcpiContext, + handles: HandleMap>, +- pci_fd: Option, + socket: &'sock Socket, + } + +@@ -41,10 +42,156 @@ enum HandleKind<'a> { + Table(SdtSignature), + Symbols(RwLockReadGuard<'a, AmlSymbols>), + Symbol { name: String, description: String }, ++ DmiDir, ++ Dmi(String), ++ PowerDir, ++ PowerAdaptersDir, ++ PowerAdapterDir(String), ++ PowerBatteriesDir, ++ PowerBatteryDir(String), ++ PowerFile(String), + SchemeRoot, + RegisterPci, + } + ++const DMI_DIRECTORY_ENTRIES: &[&str] = &[ ++ "sys_vendor", ++ "board_vendor", ++ "board_name", ++ "board_version", ++ "product_name", ++ "product_version", ++ "bios_version", ++ "match_all", ++]; ++ ++fn dmi_contents(dmi_info: Option<&DmiInfo>, name: &str) -> Option { ++ Some(match name { ++ "sys_vendor" => dmi_info ++ .and_then(|info| info.sys_vendor.clone()) ++ .unwrap_or_default(), ++ "board_vendor" => dmi_info ++ .and_then(|info| info.board_vendor.clone()) ++ .unwrap_or_default(), ++ "board_name" => dmi_info ++ .and_then(|info| info.board_name.clone()) ++ .unwrap_or_default(), ++ "board_version" => dmi_info ++ .and_then(|info| info.board_version.clone()) ++ .unwrap_or_default(), ++ "product_name" => dmi_info ++ .and_then(|info| info.product_name.clone()) ++ .unwrap_or_default(), ++ "product_version" => dmi_info ++ .and_then(|info| info.product_version.clone()) ++ .unwrap_or_default(), ++ "bios_version" => dmi_info ++ .and_then(|info| info.bios_version.clone()) ++ .unwrap_or_default(), ++ "match_all" => dmi_info.map(DmiInfo::to_match_lines).unwrap_or_default(), ++ _ => return None, ++ }) ++} ++ ++fn power_bool_contents(value: bool) -> String { ++ if value { ++ String::from("1\n") ++ } else { ++ String::from("0\n") ++ } ++} ++ ++fn power_u64_contents(value: u64) -> String { ++ format!("{value}\n") ++} ++ ++fn power_f64_contents(value: f64) -> String { ++ format!("{value}\n") ++} ++ ++fn power_string_contents(value: &str) -> String { ++ format!("{value}\n") ++} ++ ++fn power_adapter_file_contents(adapter: &AcpiPowerAdapter, name: &str) -> Option { ++ Some(match name { ++ "path" => power_string_contents(&adapter.path), ++ "online" => power_bool_contents(adapter.online), ++ _ => return None, ++ }) ++} ++ ++fn power_adapter_entry_names() -> &'static [&'static str] { ++ &["path", "online"] ++} ++ ++fn power_battery_file_contents(battery: &AcpiBattery, name: &str) -> Option { ++ Some(match name { ++ "path" => power_string_contents(&battery.path), ++ "state" => power_u64_contents(battery.state), ++ "present_rate" => power_u64_contents(battery.present_rate?), ++ "remaining_capacity" => power_u64_contents(battery.remaining_capacity?), ++ "present_voltage" => power_u64_contents(battery.present_voltage?), ++ "power_unit" => power_string_contents(battery.power_unit.as_deref()?), ++ "design_capacity" => power_u64_contents(battery.design_capacity?), ++ "last_full_capacity" => power_u64_contents(battery.last_full_capacity?), ++ "design_voltage" => power_u64_contents(battery.design_voltage?), ++ "technology" => power_string_contents(battery.technology.as_deref()?), ++ "model" => power_string_contents(battery.model.as_deref()?), ++ "serial" => power_string_contents(battery.serial.as_deref()?), ++ "battery_type" => power_string_contents(battery.battery_type.as_deref()?), ++ "oem_info" => power_string_contents(battery.oem_info.as_deref()?), ++ "percentage" => power_f64_contents(battery.percentage?), ++ _ => return None, ++ }) ++} ++ ++fn power_battery_entry_names(battery: &AcpiBattery) -> Vec<&'static str> { ++ let mut names = vec!["path", "state"]; ++ ++ if battery.present_rate.is_some() { ++ names.push("present_rate"); ++ } ++ if battery.remaining_capacity.is_some() { ++ names.push("remaining_capacity"); ++ } ++ if battery.present_voltage.is_some() { ++ names.push("present_voltage"); ++ } ++ if battery.power_unit.is_some() { ++ names.push("power_unit"); ++ } ++ if battery.design_capacity.is_some() { ++ names.push("design_capacity"); ++ } ++ if battery.last_full_capacity.is_some() { ++ names.push("last_full_capacity"); ++ } ++ if battery.design_voltage.is_some() { ++ names.push("design_voltage"); ++ } ++ if battery.technology.is_some() { ++ names.push("technology"); ++ } ++ if battery.model.is_some() { ++ names.push("model"); ++ } ++ if battery.serial.is_some() { ++ names.push("serial"); ++ } ++ if battery.battery_type.is_some() { ++ names.push("battery_type"); ++ } ++ if battery.oem_info.is_some() { ++ names.push("oem_info"); ++ } ++ if battery.percentage.is_some() { ++ names.push("percentage"); ++ } ++ ++ names ++} ++ + impl HandleKind<'_> { + fn is_dir(&self) -> bool { + match self { +@@ -53,6 +200,14 @@ impl HandleKind<'_> { + Self::Table(_) => false, + Self::Symbols(_) => true, + Self::Symbol { .. } => false, ++ Self::DmiDir => true, ++ Self::Dmi(_) => false, ++ Self::PowerDir => true, ++ Self::PowerAdaptersDir => true, ++ Self::PowerAdapterDir(_) => true, ++ Self::PowerBatteriesDir => true, ++ Self::PowerBatteryDir(_) => true, ++ Self::PowerFile(_) => false, + Self::SchemeRoot => false, + Self::RegisterPci => false, + } +@@ -65,8 +220,18 @@ impl HandleKind<'_> { + .ok_or(Error::new(EBADFD))? + .length(), + Self::Symbol { description, .. } => description.len(), ++ Self::Dmi(contents) => contents.len(), ++ Self::PowerFile(contents) => contents.len(), + // Directories +- Self::TopLevel | Self::Symbols(_) | Self::Tables => 0, ++ Self::TopLevel ++ | Self::Symbols(_) ++ | Self::Tables ++ | Self::DmiDir ++ | Self::PowerDir ++ | Self::PowerAdaptersDir ++ | Self::PowerAdapterDir(_) ++ | Self::PowerBatteriesDir ++ | Self::PowerBatteryDir(_) => 0, + Self::SchemeRoot | Self::RegisterPci => return Err(Error::new(EBADF)), + }) + } +@@ -77,10 +242,99 @@ impl<'acpi, 'sock> AcpiScheme<'acpi, 'sock> { + Self { + ctx, + handles: HandleMap::new(), +- pci_fd: None, + socket, + } + } ++ ++ fn power_snapshot(&self) -> Result { ++ self.ctx.power_snapshot().map_err(|error| { ++ log::warn!("Failed to build ACPI power snapshot: {:?}", error); ++ Error::new(EIO) ++ }) ++ } ++ ++ fn power_handle(&self, path: &str) -> Result> { ++ let normalized = path.trim_matches('/'); ++ ++ if normalized.is_empty() { ++ return Ok(HandleKind::PowerDir); ++ } ++ if normalized == "on_battery" { ++ return Ok(HandleKind::PowerFile(power_bool_contents( ++ self.power_snapshot()?.on_battery(), ++ ))); ++ } ++ if normalized == "adapters" { ++ return Ok(HandleKind::PowerAdaptersDir); ++ } ++ if let Some(rest) = normalized.strip_prefix("adapters/") { ++ return self.power_adapter_handle(rest); ++ } ++ if normalized == "batteries" { ++ return Ok(HandleKind::PowerBatteriesDir); ++ } ++ if let Some(rest) = normalized.strip_prefix("batteries/") { ++ return self.power_battery_handle(rest); ++ } ++ ++ Err(Error::new(ENOENT)) ++ } ++ ++ fn power_adapter_handle(&self, path: &str) -> Result> { ++ let normalized = path.trim_matches('/'); ++ if normalized.is_empty() { ++ return Ok(HandleKind::PowerAdaptersDir); ++ } ++ ++ let mut parts = normalized.split('/'); ++ let adapter_id = parts.next().ok_or(Error::new(ENOENT))?; ++ let field = parts.next(); ++ if parts.next().is_some() { ++ return Err(Error::new(ENOENT)); ++ } ++ ++ let snapshot = self.power_snapshot()?; ++ let adapter = snapshot ++ .adapters ++ .iter() ++ .find(|adapter| adapter.id == adapter_id) ++ .ok_or(Error::new(ENOENT))?; ++ ++ match field { ++ None | Some("") => Ok(HandleKind::PowerAdapterDir(adapter.id.clone())), ++ Some(name) => Ok(HandleKind::PowerFile( ++ power_adapter_file_contents(adapter, name).ok_or(Error::new(ENOENT))?, ++ )), ++ } ++ } ++ ++ fn power_battery_handle(&self, path: &str) -> Result> { ++ let normalized = path.trim_matches('/'); ++ if normalized.is_empty() { ++ return Ok(HandleKind::PowerBatteriesDir); ++ } ++ ++ let mut parts = normalized.split('/'); ++ let battery_id = parts.next().ok_or(Error::new(ENOENT))?; ++ let field = parts.next(); ++ if parts.next().is_some() { ++ return Err(Error::new(ENOENT)); ++ } ++ ++ let snapshot = self.power_snapshot()?; ++ let battery = snapshot ++ .batteries ++ .iter() ++ .find(|battery| battery.id == battery_id) ++ .ok_or(Error::new(ENOENT))?; ++ ++ match field { ++ None | Some("") => Ok(HandleKind::PowerBatteryDir(battery.id.clone())), ++ Some(name) => Ok(HandleKind::PowerFile( ++ power_battery_file_contents(battery, name).ok_or(Error::new(ENOENT))?, ++ )), ++ } ++ } + } + + fn parse_hex_digit(hex: u8) -> Option { +@@ -184,9 +438,9 @@ impl SchemeSync for AcpiScheme<'_, '_> { + HandleKind::SchemeRoot => { + // TODO: arrayvec + let components = { +- let mut v = arrayvec::ArrayVec::<&str, 3>::new(); ++ let mut v = arrayvec::ArrayVec::<&str, 4>::new(); + let it = path.split('/'); +- for component in it.take(3) { ++ for component in it.take(4) { + v.push(component); + } + +@@ -195,6 +449,24 @@ impl SchemeSync for AcpiScheme<'_, '_> { + + match &*components { + [""] => HandleKind::TopLevel, ++ ["dmi"] => { ++ if flag_dir || flag_stat || path.ends_with('/') { ++ HandleKind::DmiDir ++ } else { ++ HandleKind::Dmi( ++ dmi_contents(self.ctx.dmi_info(), "match_all") ++ .expect("match_all should always resolve"), ++ ) ++ } ++ } ++ ["dmi", ""] => HandleKind::DmiDir, ++ ["dmi", field] => HandleKind::Dmi( ++ dmi_contents(self.ctx.dmi_info(), field).ok_or(Error::new(ENOENT))?, ++ ), ++ ["power"] => self.power_handle("")?, ++ ["power", tail] => self.power_handle(tail)?, ++ ["power", a, b] => self.power_handle(&format!("{a}/{b}"))?, ++ ["power", a, b, c] => self.power_handle(&format!("{a}/{b}/{c}"))?, + ["register_pci"] => HandleKind::RegisterPci, + ["tables"] => HandleKind::Tables, + +@@ -204,7 +476,11 @@ impl SchemeSync for AcpiScheme<'_, '_> { + } + + ["symbols"] => { +- if let Ok(aml_symbols) = self.ctx.aml_symbols(self.pci_fd.as_ref()) { ++ if !self.ctx.pci_ready() { ++ log::warn!("Deferring AML symbol scan until PCI registration is ready"); ++ return Err(Error::new(EAGAIN)); ++ } ++ if let Ok(aml_symbols) = self.ctx.aml_symbols() { + HandleKind::Symbols(aml_symbols) + } else { + return Err(Error::new(EIO)); +@@ -212,6 +488,12 @@ impl SchemeSync for AcpiScheme<'_, '_> { + } + + ["symbols", symbol] => { ++ if !self.ctx.pci_ready() { ++ log::warn!( ++ "Deferring AML symbol lookup for {symbol} until PCI registration is ready" ++ ); ++ return Err(Error::new(EAGAIN)); ++ } + if let Some(description) = self.ctx.aml_lookup(symbol) { + HandleKind::Symbol { + name: (*symbol).to_owned(), +@@ -225,6 +507,15 @@ impl SchemeSync for AcpiScheme<'_, '_> { + _ => return Err(Error::new(ENOENT)), + } + } ++ HandleKind::DmiDir => { ++ if path.is_empty() { ++ HandleKind::DmiDir ++ } else { ++ HandleKind::Dmi( ++ dmi_contents(self.ctx.dmi_info(), path).ok_or(Error::new(ENOENT))?, ++ ) ++ } ++ } + HandleKind::Symbols(ref aml_symbols) => { + if let Some(description) = aml_symbols.lookup(path) { + HandleKind::Symbol { +@@ -235,6 +526,23 @@ impl SchemeSync for AcpiScheme<'_, '_> { + return Err(Error::new(ENOENT)); + } + } ++ HandleKind::PowerDir => self.power_handle(path)?, ++ HandleKind::PowerAdaptersDir => self.power_adapter_handle(path)?, ++ HandleKind::PowerAdapterDir(ref adapter_id) => { ++ if path.is_empty() { ++ HandleKind::PowerAdapterDir(adapter_id.clone()) ++ } else { ++ self.power_adapter_handle(&format!("{adapter_id}/{path}"))? ++ } ++ } ++ HandleKind::PowerBatteriesDir => self.power_battery_handle(path)?, ++ HandleKind::PowerBatteryDir(ref battery_id) => { ++ if path.is_empty() { ++ HandleKind::PowerBatteryDir(battery_id.clone()) ++ } else { ++ self.power_battery_handle(&format!("{battery_id}/{path}"))? ++ } ++ } + _ => return Err(Error::new(EACCES)), + }; + +@@ -296,7 +604,7 @@ impl SchemeSync for AcpiScheme<'_, '_> { + ) -> Result { + let offset: usize = offset.try_into().map_err(|_| Error::new(EINVAL))?; + +- let handle = self.handles.get_mut(id)?; ++ let handle = self.handles.get(id)?; + + if handle.stat { + return Err(Error::new(EBADF)); +@@ -309,6 +617,8 @@ impl SchemeSync for AcpiScheme<'_, '_> { + .ok_or(Error::new(EBADFD))? + .as_slice(), + HandleKind::Symbol { description, .. } => description.as_bytes(), ++ HandleKind::Dmi(contents) => contents.as_bytes(), ++ HandleKind::PowerFile(contents) => contents.as_bytes(), + _ => return Err(Error::new(EINVAL)), + }; + +@@ -328,11 +638,11 @@ impl SchemeSync for AcpiScheme<'_, '_> { + mut buf: DirentBuf<&'buf mut [u8]>, + opaque_offset: u64, + ) -> Result> { +- let handle = self.handles.get_mut(id)?; ++ let handle = self.handles.get(id)?; + + match &handle.kind { + HandleKind::TopLevel => { +- const TOPLEVEL_ENTRIES: &[&str] = &["tables", "symbols"]; ++ const TOPLEVEL_ENTRIES: &[&str] = &["tables", "symbols", "dmi", "power"]; + + for (idx, name) in TOPLEVEL_ENTRIES + .iter() +@@ -347,6 +657,111 @@ impl SchemeSync for AcpiScheme<'_, '_> { + })?; + } + } ++ HandleKind::DmiDir => { ++ for (idx, name) in DMI_DIRECTORY_ENTRIES ++ .iter() ++ .enumerate() ++ .skip(opaque_offset as usize) ++ { ++ buf.entry(DirEntry { ++ inode: 0, ++ next_opaque_id: idx as u64 + 1, ++ name, ++ kind: DirentKind::Regular, ++ })?; ++ } ++ } ++ HandleKind::PowerDir => { ++ const POWER_ROOT_ENTRIES: &[(&str, DirentKind)] = &[ ++ ("on_battery", DirentKind::Regular), ++ ("adapters", DirentKind::Directory), ++ ("batteries", DirentKind::Directory), ++ ]; ++ ++ for (idx, (name, kind)) in POWER_ROOT_ENTRIES ++ .iter() ++ .enumerate() ++ .skip(opaque_offset as usize) ++ { ++ buf.entry(DirEntry { ++ inode: 0, ++ next_opaque_id: idx as u64 + 1, ++ name, ++ kind: *kind, ++ })?; ++ } ++ } ++ HandleKind::PowerAdaptersDir => { ++ let snapshot = self.power_snapshot()?; ++ for (idx, adapter) in snapshot ++ .adapters ++ .iter() ++ .enumerate() ++ .skip(opaque_offset as usize) ++ { ++ buf.entry(DirEntry { ++ inode: 0, ++ next_opaque_id: idx as u64 + 1, ++ name: adapter.id.as_str(), ++ kind: DirentKind::Directory, ++ })?; ++ } ++ } ++ HandleKind::PowerAdapterDir(adapter_id) => { ++ let snapshot = self.power_snapshot()?; ++ let _adapter = snapshot ++ .adapters ++ .iter() ++ .find(|adapter| adapter.id == *adapter_id) ++ .ok_or(Error::new(EIO))?; ++ ++ for (idx, name) in power_adapter_entry_names() ++ .iter() ++ .enumerate() ++ .skip(opaque_offset as usize) ++ { ++ buf.entry(DirEntry { ++ inode: 0, ++ next_opaque_id: idx as u64 + 1, ++ name, ++ kind: DirentKind::Regular, ++ })?; ++ } ++ } ++ HandleKind::PowerBatteriesDir => { ++ let snapshot = self.power_snapshot()?; ++ for (idx, battery) in snapshot ++ .batteries ++ .iter() ++ .enumerate() ++ .skip(opaque_offset as usize) ++ { ++ buf.entry(DirEntry { ++ inode: 0, ++ next_opaque_id: idx as u64 + 1, ++ name: battery.id.as_str(), ++ kind: DirentKind::Directory, ++ })?; ++ } ++ } ++ HandleKind::PowerBatteryDir(battery_id) => { ++ let snapshot = self.power_snapshot()?; ++ let battery = snapshot ++ .batteries ++ .iter() ++ .find(|battery| battery.id == *battery_id) ++ .ok_or(Error::new(EIO))?; ++ let entry_names = power_battery_entry_names(battery); ++ ++ for (idx, name) in entry_names.iter().enumerate().skip(opaque_offset as usize) { ++ buf.entry(DirEntry { ++ inode: 0, ++ next_opaque_id: idx as u64 + 1, ++ name, ++ kind: DirentKind::Regular, ++ })?; ++ } ++ } + HandleKind::Symbols(aml_symbols) => { + for (idx, (symbol_name, _value)) in aml_symbols + .symbols_cache() +@@ -470,10 +885,8 @@ impl SchemeSync for AcpiScheme<'_, '_> { + } + let new_fd = libredox::Fd::new(new_fd); + +- if self.pci_fd.is_some() { ++ if self.ctx.register_pci_fd(new_fd).is_err() { + return Err(Error::new(EINVAL)); +- } else { +- self.pci_fd = Some(new_fd); + } + + Ok(num_fds) +@@ -483,3 +896,38 @@ impl SchemeSync for AcpiScheme<'_, '_> { + self.handles.remove(id); + } + } ++ ++#[cfg(test)] ++mod tests { ++ use super::dmi_contents; ++ use crate::acpi::DmiInfo; ++ ++ #[test] ++ fn dmi_contents_exposes_individual_fields_and_match_all() { ++ let dmi_info = DmiInfo { ++ sys_vendor: Some("Framework".to_string()), ++ board_name: Some("FRANMECP01".to_string()), ++ product_name: Some("Laptop 16".to_string()), ++ ..DmiInfo::default() ++ }; ++ ++ assert_eq!( ++ dmi_contents(Some(&dmi_info), "sys_vendor").as_deref(), ++ Some("Framework") ++ ); ++ assert_eq!( ++ dmi_contents(Some(&dmi_info), "board_name").as_deref(), ++ Some("FRANMECP01") ++ ); ++ assert_eq!( ++ dmi_contents(Some(&dmi_info), "product_name").as_deref(), ++ Some("Laptop 16") ++ ); ++ assert_eq!( ++ dmi_contents(Some(&dmi_info), "match_all").as_deref(), ++ Some("sys_vendor=Framework\nboard_name=FRANMECP01\nproduct_name=Laptop 16") ++ ); ++ assert_eq!(dmi_contents(None, "bios_version").as_deref(), Some("")); ++ assert_eq!(dmi_contents(Some(&dmi_info), "unknown"), None); ++ } ++} diff --git a/local/patches/base/P1-xhcid-port-pm-read-fix.patch b/local/patches/base/P1-xhcid-port-pm-read-fix.patch new file mode 100644 index 00000000..feb847b5 --- /dev/null +++ b/local/patches/base/P1-xhcid-port-pm-read-fix.patch @@ -0,0 +1,942 @@ +diff --git a/drivers/usb/xhcid/src/xhci/mod.rs b/drivers/usb/xhcid/src/xhci/mod.rs +index f2143676..74126f67 100644 +--- a/drivers/usb/xhcid/src/xhci/mod.rs ++++ b/drivers/usb/xhcid/src/xhci/mod.rs +@@ -311,6 +311,22 @@ struct PortState { + input_context: Mutex>>, + dev_desc: Option, + endpoint_states: BTreeMap, ++ quirks: crate::usb_quirks::UsbQuirkFlags, ++ pm_state: PortPmState, ++} ++ ++#[derive(Clone, Copy, Debug, Eq, PartialEq)] ++pub(crate) enum PortPmState { ++ Active, ++ Suspended, ++} ++impl PortPmState { ++ pub fn as_str(&self) -> &'static str { ++ match self { ++ Self::Active => "active", ++ Self::Suspended => "suspended", ++ } ++ } + } + + impl PortState { +@@ -809,6 +825,7 @@ impl Xhci { + ); + + if flags.contains(port::PortFlags::CCS) { ++ let early_quirks = crate::usb_quirks::lookup_usb_quirks_early(port_id); + let slot_ty = match self.supported_protocol(port_id) { + Some(protocol) => protocol.proto_slot_ty(), + None => { +@@ -838,7 +855,15 @@ impl Xhci { + + debug!("Attempting to address the device"); + let mut ring = match self +- .address_device(&mut input, port_id, slot_ty, slot, protocol_speed, speed) ++ .address_device( ++ &mut input, ++ port_id, ++ slot_ty, ++ slot, ++ protocol_speed, ++ speed, ++ early_quirks, ++ ) + .await + { + Ok(device_ring) => device_ring, +@@ -866,6 +891,8 @@ impl Xhci { + }, + )) + .collect::>(), ++ quirks: early_quirks, ++ pm_state: PortPmState::Active, + }; + self.port_states.insert(port_id, port_state); + debug!("Got port states!"); +@@ -884,8 +911,14 @@ impl Xhci { + debug!("Got the 8 byte dev descriptor: {:X?}", dev_desc_8_byte); + + let dev_desc = self.get_desc(port_id, slot).await?; ++ let quirks = early_quirks ++ | crate::usb_quirks::lookup_usb_quirks(dev_desc.vendor, dev_desc.product); + debug!("Got the full device descriptor!"); +- self.port_states.get_mut(&port_id).unwrap().dev_desc = Some(dev_desc); ++ { ++ let mut port_state = self.port_states.get_mut(&port_id).unwrap(); ++ port_state.quirks = quirks; ++ port_state.dev_desc = Some(dev_desc); ++ } + + debug!("Got the port states again!"); + { +@@ -1052,6 +1085,7 @@ impl Xhci { + slot: u8, + protocol_speed: &ProtocolSpeed, + speed: u8, ++ quirks: crate::usb_quirks::UsbQuirkFlags, + ) -> Result { + // Collect MTT, parent port number, parent slot ID + let mut mtt = false; +@@ -1162,11 +1196,16 @@ impl Xhci { + + let input_context_physical = input_context.physical(); + +- let (event_trb, _) = self +- .execute_command(|trb, cycle| { +- trb.address_device(slot, input_context_physical, false, cycle) +- }) +- .await; ++ let address_timeout = if quirks.contains(crate::usb_quirks::UsbQuirkFlags::SHORT_SET_ADDR_TIMEOUT) ++ { ++ Timeout::from_millis(100) ++ } else { ++ Timeout::from_secs(1) ++ }; ++ ++ let (event_trb, _) = self.execute_command_with_timeout(address_timeout, |trb, cycle| { ++ trb.address_device(slot, input_context_physical, false, cycle) ++ })?; + + if event_trb.completion_code() != TrbCompletionCode::Success as u8 { + error!( +diff --git a/drivers/usb/xhcid/src/xhci/scheme.rs b/drivers/usb/xhcid/src/xhci/scheme.rs +index f2d439a4..dfe9fdec 100644 +--- a/drivers/usb/xhcid/src/xhci/scheme.rs ++++ b/drivers/usb/xhcid/src/xhci/scheme.rs +@@ -24,6 +24,7 @@ use std::{cmp, fmt, io, mem, str}; + + use common::dma::Dma; + use futures::executor::block_on; ++use futures::FutureExt; + use log::{debug, error, info, trace, warn}; + use redox_scheme::scheme::SchemeSync; + use smallvec::SmallVec; +@@ -32,9 +33,9 @@ use common::io::Io; + use redox_scheme::{CallerCtx, OpenResult}; + use syscall::schemev2::NewFdFlags; + use syscall::{ +- Error, Result, Stat, EACCES, EBADF, EBADFD, EBADMSG, EINVAL, EIO, EISDIR, ENOENT, ENOSYS, +- ENOTDIR, EOPNOTSUPP, EPROTO, ESPIPE, MODE_CHR, MODE_DIR, MODE_FILE, O_DIRECTORY, O_RDWR, +- O_STAT, O_WRONLY, SEEK_CUR, SEEK_END, SEEK_SET, ++ Error, Result, Stat, EACCES, EBADF, EBADFD, EBADMSG, EBUSY, EINVAL, EIO, EISDIR, ENOENT, ++ ENOSYS, ENOTDIR, EOPNOTSUPP, EPROTO, ESPIPE, MODE_CHR, MODE_DIR, MODE_FILE, O_DIRECTORY, ++ O_RDWR, O_STAT, O_WRONLY, SEEK_CUR, SEEK_END, SEEK_SET, + }; + + use super::{port, usb}; +@@ -60,10 +61,16 @@ lazy_static! { + .expect("Failed to create the regex for the port/attach scheme."); + static ref REGEX_PORT_DETACH: Regex = Regex::new(r"^port([\d\.]+)/detach$") + .expect("Failed to create the regex for the port/detach scheme."); ++ static ref REGEX_PORT_SUSPEND: Regex = Regex::new(r"^port([\d\.]+)/suspend$") ++ .expect("Failed to create the regex for the port/suspend scheme."); ++ static ref REGEX_PORT_RESUME: Regex = Regex::new(r"^port([\d\.]+)/resume$") ++ .expect("Failed to create the regex for the port/resume scheme."); + static ref REGEX_PORT_DESCRIPTORS: Regex = Regex::new(r"^port([\d\.]+)/descriptors$") + .expect("Failed to create the regex for the port/descriptors"); + static ref REGEX_PORT_STATE: Regex = Regex::new(r"^port([\d\.]+)/state$") + .expect("Failed to create the regex for the port/state scheme"); ++ static ref REGEX_PORT_PM_STATE: Regex = Regex::new(r"^port([\d\.]+)/pm_state$") ++ .expect("Failed to create the regex for the port/pm_state scheme"); + static ref REGEX_PORT_REQUEST: Regex = Regex::new(r"^port([\d\.]+)/request$") + .expect("Failed to create the regex for the port/request scheme"); + static ref REGEX_PORT_ENDPOINTS: Regex = Regex::new(r"^port([\d\.]+)/endpoints$") +@@ -137,12 +144,15 @@ pub enum Handle { + Port(PortId, Vec), // port, contents + PortDesc(PortId, Vec), // port, contents + PortState(PortId), // port ++ PortPmState(PortId), // port + PortReq(PortId, PortReqState), // port, state + Endpoints(PortId, Vec), // port, contents + Endpoint(PortId, u8, EndpointHandleTy), // port, endpoint, state + ConfigureEndpoints(PortId), // port + AttachDevice(PortId), // port + DetachDevice(PortId), // port ++ SuspendDevice(PortId), // port ++ ResumeDevice(PortId), // port + SchemeRoot, + } + +@@ -172,6 +182,8 @@ enum SchemeParameters { + PortDesc(PortId), // port number + /// /port/state + PortState(PortId), // port number ++ /// /port/pm_state ++ PortPmState(PortId), // port number + /// /port/request + PortReq(PortId), // port number + /// /port/endpoints +@@ -187,6 +199,10 @@ enum SchemeParameters { + AttachDevice(PortId), // port number + /// /port/detach + DetachDevice(PortId), // port number ++ /// /port/suspend ++ SuspendDevice(PortId), // port number ++ /// /port/resume ++ ResumeDevice(PortId), // port number + } + + impl Handle { +@@ -209,6 +225,9 @@ impl Handle { + Handle::PortState(port_num) => { + format!("port{}/state", port_num) + } ++ Handle::PortPmState(port_num) => { ++ format!("port{}/pm_state", port_num) ++ } + Handle::PortReq(port_num, _) => { + format!("port{}/request", port_num) + } +@@ -235,6 +254,12 @@ impl Handle { + Handle::DetachDevice(port_num) => { + format!("port{}/detach", port_num) + } ++ Handle::SuspendDevice(port_num) => { ++ format!("port{}/suspend", port_num) ++ } ++ Handle::ResumeDevice(port_num) => { ++ format!("port{}/resume", port_num) ++ } + Handle::SchemeRoot => String::from(""), + } + } +@@ -258,10 +283,13 @@ impl Handle { + &Handle::PortReq(_, PortReqState::Tmp) => unreachable!(), + &Handle::PortReq(_, PortReqState::TmpSetup(_)) => unreachable!(), + &Handle::PortState(_) => HandleType::Character, ++ &Handle::PortPmState(_) => HandleType::Character, + &Handle::PortReq(_, _) => HandleType::Character, + &Handle::ConfigureEndpoints(_) => HandleType::Character, + &Handle::AttachDevice(_) => HandleType::Character, + &Handle::DetachDevice(_) => HandleType::Character, ++ &Handle::SuspendDevice(_) => HandleType::Character, ++ &Handle::ResumeDevice(_) => HandleType::Character, + &Handle::Endpoint(_, _, ref st) => match st { + EndpointHandleTy::Data => HandleType::Character, + EndpointHandleTy::Ctl => HandleType::Character, +@@ -289,10 +317,13 @@ impl Handle { + &Handle::PortReq(_, PortReqState::Tmp) => None, + &Handle::PortReq(_, PortReqState::TmpSetup(_)) => None, + &Handle::PortState(_) => None, ++ &Handle::PortPmState(_) => None, + &Handle::PortReq(_, _) => None, + &Handle::ConfigureEndpoints(_) => None, + &Handle::AttachDevice(_) => None, + &Handle::DetachDevice(_) => None, ++ &Handle::SuspendDevice(_) => None, ++ &Handle::ResumeDevice(_) => None, + &Handle::Endpoint(_, _, ref st) => match st { + EndpointHandleTy::Data => None, + EndpointHandleTy::Ctl => None, +@@ -383,6 +414,14 @@ impl SchemeParameters { + let port_num = get_port_id_from_regex(®EX_PORT_DETACH, scheme, 0)?; + + Ok(Self::DetachDevice(port_num)) ++ } else if REGEX_PORT_SUSPEND.is_match(scheme) { ++ let port_num = get_port_id_from_regex(®EX_PORT_SUSPEND, scheme, 0)?; ++ ++ Ok(Self::SuspendDevice(port_num)) ++ } else if REGEX_PORT_RESUME.is_match(scheme) { ++ let port_num = get_port_id_from_regex(®EX_PORT_RESUME, scheme, 0)?; ++ ++ Ok(Self::ResumeDevice(port_num)) + } else if REGEX_PORT_DESCRIPTORS.is_match(scheme) { + let port_num = get_port_id_from_regex(®EX_PORT_DESCRIPTORS, scheme, 0)?; + +@@ -391,6 +430,10 @@ impl SchemeParameters { + let port_num = get_port_id_from_regex(®EX_PORT_STATE, scheme, 0)?; + + Ok(Self::PortState(port_num)) ++ } else if REGEX_PORT_PM_STATE.is_match(scheme) { ++ let port_num = get_port_id_from_regex(®EX_PORT_PM_STATE, scheme, 0)?; ++ ++ Ok(Self::PortPmState(port_num)) + } else if REGEX_PORT_REQUEST.is_match(scheme) { + let port_num = get_port_id_from_regex(®EX_PORT_REQUEST, scheme, 0)?; + +@@ -564,15 +607,22 @@ impl Xhci { + endps: impl IntoIterator, + hid_descs: impl IntoIterator, + lang_id: u16, ++ quirks: crate::usb_quirks::UsbQuirkFlags, + ) -> Result { + Ok(IfDesc { + alternate_setting: desc.alternate_setting, + class: desc.class, + interface_str: if desc.interface_str > 0 { +- Some( ++ if quirks.contains(crate::usb_quirks::UsbQuirkFlags::BAD_DESCRIPTOR) { + self.fetch_string_desc(port_id, slot, desc.interface_str, lang_id) +- .await?, +- ) ++ .await ++ .ok() ++ } else { ++ Some( ++ self.fetch_string_desc(port_id, slot, desc.interface_str, lang_id) ++ .await?, ++ ) ++ } + } else { + None + }, +@@ -628,6 +678,53 @@ impl Xhci { + + (event_trb, command_trb) + } ++ pub fn execute_command_with_timeout( ++ &self, ++ timeout: common::timeout::Timeout, ++ f: F, ++ ) -> Result<(Trb, Trb)> { ++ if self.interrupt_is_pending(0) { ++ debug!("The EHB bit is already set!"); ++ } ++ ++ let next_event = { ++ let mut command_ring = self.cmd.lock().unwrap(); ++ let (cmd_index, cycle) = (command_ring.next_index(), command_ring.cycle); ++ ++ debug!("Sending command with cycle bit {}", cycle as u8); ++ ++ { ++ let command_trb = &mut command_ring.trbs[cmd_index]; ++ f(command_trb, cycle); ++ } ++ ++ let command_trb = &command_ring.trbs[cmd_index]; ++ self.next_command_completion_event_trb( ++ &*command_ring, ++ command_trb, ++ EventDoorbell::new(self, 0, 0), ++ ) ++ }; ++ ++ let mut next_event = Box::pin(next_event); ++ ++ loop { ++ if let Some(trbs) = next_event.as_mut().now_or_never() { ++ let event_trb = trbs.event_trb; ++ let command_trb = trbs.src_trb.ok_or(Error::new(EIO))?; ++ ++ assert_eq!( ++ event_trb.trb_type(), ++ TrbType::CommandCompletion as u8, ++ "The IRQ reactor (or the xHC) gave an invalid event TRB" ++ ); ++ ++ return Ok((event_trb, command_trb)); ++ } ++ ++ timeout.run().map_err(|()| Error::new(EIO))?; ++ } ++ } + pub async fn execute_control_transfer( + &self, + port_num: PortId, +@@ -639,6 +736,8 @@ impl Xhci { + where + D: FnMut(&mut Trb, bool) -> ControlFlow, + { ++ self.ensure_port_active(port_num)?; ++ + let future = { + let mut port_state = self.port_state_mut(port_num)?; + let slot = port_state.slot; +@@ -690,6 +789,20 @@ impl Xhci { + + handle_transfer_event_trb("CONTROL_TRANSFER", &event_trb, &status_trb)?; + ++ let delay_ctrl_msg = self ++ .port_states ++ .get(&port_num) ++ .map(|port_state| { ++ port_state ++ .quirks ++ .contains(crate::usb_quirks::UsbQuirkFlags::DELAY_CTRL_MSG) ++ }) ++ .unwrap_or(false); ++ ++ if delay_ctrl_msg { ++ std::thread::sleep(std::time::Duration::from_millis(20)); ++ } ++ + //self.event_handler_finished(); + + Ok(event_trb) +@@ -709,6 +822,8 @@ impl Xhci { + where + D: FnMut(&mut Trb, bool) -> ControlFlow, + { ++ self.ensure_port_active(port_num)?; ++ + let endp_idx = endp_num.checked_sub(1).ok_or(Error::new(EIO))?; + let mut port_state = self.port_state_mut(port_num)?; + +@@ -785,7 +900,29 @@ impl Xhci { + let event_trb = trbs.event_trb; + let transfer_trb = trbs.src_trb.ok_or(Error::new(EIO))?; + +- handle_transfer_event_trb("EXECUTE_TRANSFER", &event_trb, &transfer_trb)?; ++ if let Err(err) = handle_transfer_event_trb("EXECUTE_TRANSFER", &event_trb, &transfer_trb) ++ { ++ let need_reset = self ++ .port_states ++ .get(&port_num) ++ .map(|port_state| { ++ port_state ++ .quirks ++ .contains(crate::usb_quirks::UsbQuirkFlags::NEED_RESET) ++ }) ++ .unwrap_or(false); ++ ++ if need_reset { ++ if let Err(reset_err) = self.reset_device_slot(port_num).await { ++ error!( ++ "EXECUTE_TRANSFER reset recovery failed for port {}: {}", ++ port_num, reset_err ++ ); ++ } ++ } ++ ++ return Err(err); ++ } + + // FIXME: EDTLA if event data was set + if event_trb.completion_code() != TrbCompletionCode::ShortPacket as u8 +@@ -861,6 +998,21 @@ impl Xhci { + + handle_event_trb("RESET_ENDPOINT", &event_trb, &command_trb) + } ++ async fn reset_device_slot(&self, port_num: PortId) -> Result<()> { ++ let slot = self ++ .port_states ++ .get(&port_num) ++ .ok_or(Error::new(EBADF))? ++ .slot; ++ ++ let (event_trb, command_trb) = self ++ .execute_command(|trb, cycle| { ++ trb.reset_device(slot, cycle); ++ }) ++ .await; ++ ++ handle_event_trb("RESET_DEVICE", &event_trb, &command_trb) ++ } + + fn endp_ctx_interval(speed_id: &ProtocolSpeed, endp_desc: &EndpDesc) -> u8 { + /// Logarithmic (base 2) 125 µs periods per millisecond. +@@ -1205,7 +1357,19 @@ impl Xhci { + } + + // Tell the device about this configuration. +- self.set_configuration(port, configuration_value).await?; ++ let skip_set_configuration = self ++ .port_states ++ .get(&port) ++ .map(|port_state| { ++ port_state ++ .quirks ++ .contains(crate::usb_quirks::UsbQuirkFlags::NO_SET_CONFIG) ++ }) ++ .unwrap_or(false); ++ ++ if !skip_set_configuration { ++ self.set_configuration(port, configuration_value).await?; ++ } + + Ok(()) + } +@@ -1234,8 +1398,20 @@ impl Xhci { + + if let Some(interface_num) = req.interface_desc { + if let Some(alternate_setting) = req.alternate_setting { +- self.set_interface(port, interface_num, alternate_setting) +- .await?; ++ let skip_set_interface = self ++ .port_states ++ .get(&port) ++ .map(|port_state| { ++ port_state ++ .quirks ++ .contains(crate::usb_quirks::UsbQuirkFlags::NO_SET_INTF) ++ }) ++ .unwrap_or(false); ++ ++ if !skip_set_interface { ++ self.set_interface(port, interface_num, alternate_setting) ++ .await?; ++ } + } + } + +@@ -1453,52 +1629,109 @@ impl Xhci { + let raw_dd = self.fetch_dev_desc(port_id, slot).await?; + log::debug!("port {} slot {} desc {:X?}", port_id, slot, raw_dd); + ++ let vendor = raw_dd.vendor; ++ let product = raw_dd.product; ++ let quirks = crate::usb_quirks::lookup_usb_quirks(vendor, product); ++ if !quirks.is_empty() { ++ log::info!( ++ "port {}: USB quirks for {:04x}:{:04x}: {:?}", ++ port_id, vendor, product, quirks ++ ); ++ } ++ + // Only fetch language IDs if we need to. Some devices will fail to return this descriptor + //TODO: also check configurations and interfaces for defined strings? ++ let bad_descriptor = quirks.contains(crate::usb_quirks::UsbQuirkFlags::BAD_DESCRIPTOR); ++ + let lang_id = +- if raw_dd.manufacturer_str > 0 || raw_dd.product_str > 0 || raw_dd.serial_str > 0 { +- let lang_ids = self.fetch_lang_ids_desc(port_id, slot).await?; +- // Prefer US English, but fall back to first language ID, or zero +- let en_us_id = 0x409; +- if lang_ids.contains(&en_us_id) { +- en_us_id +- } else { +- match lang_ids.first() { +- Some(some) => *some, +- None => 0, ++ if !quirks.contains(crate::usb_quirks::UsbQuirkFlags::NO_STRING_FETCH) ++ && (raw_dd.manufacturer_str > 0 ++ || raw_dd.product_str > 0 ++ || raw_dd.serial_str > 0) ++ { ++ match self.fetch_lang_ids_desc(port_id, slot).await { ++ Ok(lang_ids) => { ++ // Prefer US English, but fall back to first language ID, or zero ++ let en_us_id = 0x409; ++ if lang_ids.contains(&en_us_id) { ++ en_us_id ++ } else { ++ match lang_ids.first() { ++ Some(some) => *some, ++ None => 0, ++ } ++ } ++ } ++ Err(err) if bad_descriptor => { ++ log::warn!( ++ "port {} slot {}: failed to fetch language IDs with BAD_DESCRIPTOR set: {}", ++ port_id, ++ slot, ++ err ++ ); ++ 0 + } ++ Err(err) => return Err(err), + } + } else { + 0 + }; + log::debug!("port {} using language ID 0x{:04x}", port_id, lang_id); + +- let (manufacturer_str, product_str, serial_str) = ( +- if raw_dd.manufacturer_str > 0 { +- Some( +- self.fetch_string_desc(port_id, slot, raw_dd.manufacturer_str, lang_id) +- .await?, +- ) +- } else { +- None +- }, +- if raw_dd.product_str > 0 { +- Some( +- self.fetch_string_desc(port_id, slot, raw_dd.product_str, lang_id) +- .await?, +- ) ++ let (manufacturer_str, product_str, serial_str) = ++ if quirks.contains(crate::usb_quirks::UsbQuirkFlags::NO_STRING_FETCH) { ++ (None, None, None) + } else { +- None +- }, +- if raw_dd.serial_str > 0 { +- Some( +- self.fetch_string_desc(port_id, slot, raw_dd.serial_str, lang_id) +- .await?, ++ ( ++ if raw_dd.manufacturer_str > 0 { ++ if bad_descriptor { ++ self.fetch_string_desc(port_id, slot, raw_dd.manufacturer_str, lang_id) ++ .await ++ .ok() ++ } else { ++ Some( ++ self.fetch_string_desc( ++ port_id, ++ slot, ++ raw_dd.manufacturer_str, ++ lang_id, ++ ) ++ .await?, ++ ) ++ } ++ } else { ++ None ++ }, ++ if raw_dd.product_str > 0 { ++ if bad_descriptor { ++ self.fetch_string_desc(port_id, slot, raw_dd.product_str, lang_id) ++ .await ++ .ok() ++ } else { ++ Some( ++ self.fetch_string_desc(port_id, slot, raw_dd.product_str, lang_id) ++ .await?, ++ ) ++ } ++ } else { ++ None ++ }, ++ if raw_dd.serial_str > 0 { ++ if bad_descriptor { ++ self.fetch_string_desc(port_id, slot, raw_dd.serial_str, lang_id) ++ .await ++ .ok() ++ } else { ++ Some( ++ self.fetch_string_desc(port_id, slot, raw_dd.serial_str, lang_id) ++ .await?, ++ ) ++ } ++ } else { ++ None ++ }, + ) +- } else { +- None +- }, +- ); ++ }; + log::debug!( + "manufacturer {:?} product {:?} serial {:?}", + manufacturer_str, +@@ -1508,14 +1741,39 @@ impl Xhci { + + //TODO let (bos_desc, bos_data) = self.fetch_bos_desc(port_id, slot).await?; + +- let supports_superspeed = false; +- //TODO usb::bos_capability_descs(bos_desc, &bos_data).any(|desc| desc.is_superspeed()); +- let supports_superspeedplus = false; +- //TODO usb::bos_capability_descs(bos_desc, &bos_data).any(|desc| desc.is_superspeedplus()); ++ let (supports_superspeed, supports_superspeedplus) = ++ if quirks.contains(crate::usb_quirks::UsbQuirkFlags::NO_BOS) { ++ (false, false) ++ } else { ++ match self.fetch_bos_desc(port_id, slot).await { ++ Ok((bos_desc, bos_data)) => ( ++ usb::bos_capability_descs(bos_desc, &bos_data) ++ .any(|desc| desc.is_superspeed()), ++ usb::bos_capability_descs(bos_desc, &bos_data) ++ .any(|desc| desc.is_superspeedplus()), ++ ), ++ Err(err) => { ++ log::debug!( ++ "port {} slot {}: failed to fetch BOS descriptor: {}", ++ port_id, ++ slot, ++ err ++ ); ++ (false, false) ++ } ++ } ++ }; + + let mut config_descs = SmallVec::new(); + +- for index in 0..raw_dd.configurations { ++ let configuration_indices: Vec = ++ if quirks.contains(crate::usb_quirks::UsbQuirkFlags::FORCE_ONE_CONFIG) { ++ vec![0] ++ } else { ++ (0..raw_dd.configurations).collect() ++ }; ++ ++ for index in configuration_indices { + debug!("Fetching the config descriptor at index {}", index); + let (desc, data) = self.fetch_config_desc(port_id, slot, index).await?; + log::debug!( +@@ -1541,6 +1799,12 @@ impl Xhci { + let mut iter = descriptors.into_iter().peekable(); + + while let Some(item) = iter.next() { ++ if quirks.contains(crate::usb_quirks::UsbQuirkFlags::HONOR_BNUMINTERFACES) ++ && interface_descs.len() >= desc.interfaces as usize ++ { ++ break; ++ } ++ + if let AnyDescriptor::Interface(idesc) = item { + let mut endpoints = SmallVec::<[EndpDesc; 4]>::new(); + let mut hid_descs = SmallVec::<[HidDesc; 1]>::new(); +@@ -1554,6 +1818,9 @@ impl Xhci { + } + Some(unexpected) => { + log::warn!("expected endpoint, got {:X?}", unexpected); ++ if bad_descriptor { ++ continue; ++ } + break; + } + None => break, +@@ -1578,8 +1845,16 @@ impl Xhci { + } + + interface_descs.push( +- self.new_if_desc(port_id, slot, idesc, endpoints, hid_descs, lang_id) +- .await?, ++ self.new_if_desc( ++ port_id, ++ slot, ++ idesc, ++ endpoints, ++ hid_descs, ++ lang_id, ++ quirks, ++ ) ++ .await?, + ); + } else { + log::warn!("expected interface, got {:?}", item); +@@ -1590,11 +1865,20 @@ impl Xhci { + + config_descs.push(ConfDesc { + kind: desc.kind, +- configuration: if desc.configuration_str > 0 { +- Some( ++ configuration: if quirks.contains(crate::usb_quirks::UsbQuirkFlags::NO_STRING_FETCH) ++ { ++ None ++ } else if desc.configuration_str > 0 { ++ if bad_descriptor { + self.fetch_string_desc(port_id, slot, desc.configuration_str, lang_id) +- .await?, +- ) ++ .await ++ .ok() ++ } else { ++ Some( ++ self.fetch_string_desc(port_id, slot, desc.configuration_str, lang_id) ++ .await?, ++ ) ++ } + } else { + None + }, +@@ -1856,7 +2140,7 @@ impl Xhci { + if (flags & O_DIRECTORY != 0) || (flags & O_STAT != 0) { + let mut contents = Vec::new(); + +- write!(contents, "descriptors\nendpoints\n").unwrap(); ++ write!(contents, "descriptors\nendpoints\npm_state\nsuspend\nresume\n").unwrap(); + + if self.slot_state( + self.port_states +@@ -1893,6 +2177,14 @@ impl Xhci { + Ok(Handle::PortState(port_num)) + } + ++ fn open_handle_port_pm_state(&self, port_num: PortId, flags: usize) -> Result { ++ if flags & O_DIRECTORY != 0 && flags & O_STAT == 0 { ++ return Err(Error::new(ENOTDIR)); ++ } ++ ++ Ok(Handle::PortPmState(port_num)) ++ } ++ + /// implements open() for /port/endpoints + /// + /// # Arguments +@@ -2087,6 +2379,30 @@ impl Xhci { + Ok(Handle::DetachDevice(port_num)) + } + ++ fn open_handle_suspend_device(&self, port_num: PortId, flags: usize) -> Result { ++ if flags & O_DIRECTORY != 0 && flags & O_STAT == 0 { ++ return Err(Error::new(ENOTDIR)); ++ } ++ ++ if flags & O_RDWR != O_WRONLY && flags & O_STAT == 0 { ++ return Err(Error::new(EACCES)); ++ } ++ ++ Ok(Handle::SuspendDevice(port_num)) ++ } ++ ++ fn open_handle_resume_device(&self, port_num: PortId, flags: usize) -> Result { ++ if flags & O_DIRECTORY != 0 && flags & O_STAT == 0 { ++ return Err(Error::new(ENOTDIR)); ++ } ++ ++ if flags & O_RDWR != O_WRONLY && flags & O_STAT == 0 { ++ return Err(Error::new(EACCES)); ++ } ++ ++ Ok(Handle::ResumeDevice(port_num)) ++ } ++ + /// implements open() for /port/request + /// + /// # Arguments +@@ -2155,6 +2471,9 @@ impl SchemeSync for &Xhci { + SchemeParameters::PortState(port_number) => { + self.open_handle_port_state(port_number, flags)? + } ++ SchemeParameters::PortPmState(port_number) => { ++ self.open_handle_port_pm_state(port_number, flags)? ++ } + SchemeParameters::PortReq(port_number) => { + self.open_handle_port_request(port_number, flags)? + } +@@ -2173,6 +2492,12 @@ impl SchemeSync for &Xhci { + SchemeParameters::DetachDevice(port_number) => { + self.open_handle_detach_device(port_number, flags)? + } ++ SchemeParameters::SuspendDevice(port_number) => { ++ self.open_handle_suspend_device(port_number, flags)? ++ } ++ SchemeParameters::ResumeDevice(port_number) => { ++ self.open_handle_resume_device(port_number, flags)? ++ } + }; + + let fd = self.next_handle.fetch_add(1, atomic::Ordering::Relaxed); +@@ -2203,7 +2528,11 @@ impl SchemeSync for &Xhci { + + //If we have a handle to the configure scheme, we need to mark it as write only. + match &*guard { +- Handle::ConfigureEndpoints(_) | Handle::AttachDevice(_) | Handle::DetachDevice(_) => { ++ Handle::ConfigureEndpoints(_) ++ | Handle::AttachDevice(_) ++ | Handle::DetachDevice(_) ++ | Handle::SuspendDevice(_) ++ | Handle::ResumeDevice(_) => { + stat.st_mode = stat.st_mode | 0o200; + } + _ => {} +@@ -2263,6 +2592,8 @@ impl SchemeSync for &Xhci { + Handle::ConfigureEndpoints(_) => Err(Error::new(EBADF)), + Handle::AttachDevice(_) => Err(Error::new(EBADF)), + Handle::DetachDevice(_) => Err(Error::new(EBADF)), ++ Handle::SuspendDevice(_) => Err(Error::new(EBADF)), ++ Handle::ResumeDevice(_) => Err(Error::new(EBADF)), + Handle::SchemeRoot => Err(Error::new(EBADF)), + + &mut Handle::Endpoint(port_num, endp_num, ref mut st) => match st { +@@ -2294,6 +2625,10 @@ impl SchemeSync for &Xhci { + + Ok(Xhci::::write_dyn_string(string, buf, offset)) + } ++ &mut Handle::PortPmState(port_num) => { ++ let ps = self.port_states.get(&port_num).ok_or(Error::new(EBADF))?; ++ Ok(Xhci::::write_dyn_string(ps.pm_state.as_str().as_bytes(), buf, offset)) ++ } + &mut Handle::PortReq(port_num, ref mut st) => { + let state = std::mem::replace(st, PortReqState::Tmp); + drop(guard); // release the lock +@@ -2333,6 +2668,14 @@ impl SchemeSync for &Xhci { + block_on(self.detach_device(port_num))?; + Ok(buf.len()) + } ++ &mut Handle::SuspendDevice(port_num) => { ++ block_on(self.suspend_device(port_num))?; ++ Ok(buf.len()) ++ } ++ &mut Handle::ResumeDevice(port_num) => { ++ block_on(self.resume_device(port_num))?; ++ Ok(buf.len()) ++ } + &mut Handle::Endpoint(port_num, endp_num, ref ep_file_ty) => match ep_file_ty { + EndpointHandleTy::Ctl => block_on(self.on_write_endp_ctl(port_num, endp_num, buf)), + EndpointHandleTy::Data => { +@@ -2356,6 +2699,38 @@ impl Xhci { + self.handles.remove(&fd); + } + ++ fn ensure_port_active(&self, port_num: PortId) -> Result<()> { ++ let pm_state = self ++ .port_states ++ .get(&port_num) ++ .ok_or(Error::new(EBADFD))? ++ .pm_state; ++ match pm_state { ++ super::PortPmState::Active => Ok(()), ++ super::PortPmState::Suspended => Err(Error::new(EBUSY)), ++ } ++ } ++ ++ pub async fn suspend_device(&self, port_num: PortId) -> Result<()> { ++ let mut port_state = self.port_states.get_mut(&port_num).ok_or(Error::new(EBADFD))?; ++ ++ if port_state ++ .quirks ++ .contains(crate::usb_quirks::UsbQuirkFlags::NO_SUSPEND) ++ { ++ return Err(Error::new(EOPNOTSUPP)); ++ } ++ ++ port_state.pm_state = super::PortPmState::Suspended; ++ Ok(()) ++ } ++ ++ pub async fn resume_device(&self, port_num: PortId) -> Result<()> { ++ let mut port_state = self.port_states.get_mut(&port_num).ok_or(Error::new(EBADFD))?; ++ port_state.pm_state = super::PortPmState::Active; ++ Ok(()) ++ } ++ + pub fn get_endp_status(&self, port_num: PortId, endp_num: u8) -> Result { + let port_state = self.port_states.get(&port_num).ok_or(Error::new(EBADFD))?; + +@@ -2406,6 +2781,8 @@ impl Xhci { + endp_num: u8, + clear_feature: bool, + ) -> Result<()> { ++ self.ensure_port_active(port_num)?; ++ + if self.get_endp_status(port_num, endp_num)? != EndpointStatus::Halted { + return Err(Error::new(EPROTO)); + } +@@ -2562,6 +2939,7 @@ impl Xhci { + }, + XhciEndpCtlReq::Reset { no_clear_feature } => match ep_if_state { + EndpIfState::Init => { ++ self.ensure_port_active(port_num)?; + self.on_req_reset_device(port_num, endp_num, !no_clear_feature) + .await? + } +@@ -2571,6 +2949,7 @@ impl Xhci { + }, + XhciEndpCtlReq::Transfer { direction, count } => match ep_if_state { + state @ EndpIfState::Init => { ++ self.ensure_port_active(port_num)?; + if direction == XhciEndpCtlDirection::NoData { + // Yield the result directly because no bytes have to be sent or received + // beforehand. +@@ -2631,6 +3010,8 @@ impl Xhci { + endp_num: u8, + buf: &[u8], + ) -> Result { ++ self.ensure_port_active(port_num)?; ++ + let mut port_state = self + .port_states + .get_mut(&port_num) +@@ -2732,6 +3113,8 @@ impl Xhci { + endp_num: u8, + buf: &mut [u8], + ) -> Result { ++ self.ensure_port_active(port_num)?; ++ + let mut port_state = self + .port_states + .get_mut(&port_num) diff --git a/local/patches/base/redox.patch b/local/patches/base/redox.patch index ed833a70..fb44b684 100644 --- a/local/patches/base/redox.patch +++ b/local/patches/base/redox.patch @@ -1,8 +1,237 @@ +diff --git a/Cargo.lock b/Cargo.lock +index 91185a1f..01150fb3 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 = [ + "scheme-utils", + "serde", + "thiserror 2.0.18", ++ "toml 1.0.6+spec-1.1.0", + ] + + [[package]] +@@ -86,7 +86,7 @@ version = "0.0.1" + dependencies = [ + "acpi", + "serde", +- "toml", ++ "toml 1.0.6+spec-1.1.0", + ] + + [[package]] +@@ -1109,7 +1109,7 @@ dependencies = [ + "redox_syscall 0.7.3", + "serde", + "serde_json", +- "toml", ++ "toml 1.0.6+spec-1.1.0", + ] + + [[package]] +@@ -1505,9 +1505,10 @@ dependencies = [ + "log", + "pcid", + "pico-args", ++ "redox-driver-sys", + "redox_syscall 0.7.3", + "serde", +- "toml", ++ "toml 1.0.6+spec-1.1.0", + ] + + [[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.3", ++ "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.3", + "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/drivers/acpid/Cargo.toml b/drivers/acpid/Cargo.toml +index 2d22a8f9..fea105c8 100644 +--- a/drivers/acpid/Cargo.toml ++++ b/drivers/acpid/Cargo.toml +@@ -8,7 +8,7 @@ edition = "2018" + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + + [dependencies] +-acpi = { git = "https://github.com/jackpot51/acpi.git" } ++acpi = { path = "../acpi" } + arrayvec = "0.7.6" + log.workspace = true + num-derive = "0.3" +@@ -21,6 +21,7 @@ rustc-hash = "1.1.0" + thiserror.workspace = true + ron.workspace = true + serde.workspace = true ++toml.workspace = true + + amlserde = { path = "../amlserde" } + common = { path = "../common" } diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs -index 94a1eb17..a735a4e7 100644 +index 94a1eb17..24799ae2 100644 --- a/drivers/acpid/src/acpi.rs +++ b/drivers/acpid/src/acpi.rs -@@ -25,6 +25,8 @@ use amlserde::{AmlSerde, AmlSerdeValue}; +@@ -8,6 +8,7 @@ use std::str::FromStr; + use std::sync::{Arc, Mutex}; + use std::{fmt, mem}; + use syscall::PAGE_SIZE; ++use toml::Value; + + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + use common::io::{Io, Pio}; +@@ -25,6 +26,8 @@ use amlserde::{AmlSerde, AmlSerdeValue}; #[cfg(target_arch = "x86_64")] pub mod dmar; @@ -11,30 +240,677 @@ index 94a1eb17..a735a4e7 100644 use crate::aml_physmem::{AmlPageCache, AmlPhysMemHandler}; /// The raw SDT header struct, as defined by the ACPI specification. -@@ -379,6 +381,12 @@ pub struct AcpiContext { +@@ -206,6 +209,464 @@ impl Sdt { + } + } + ++#[derive(Clone, Debug, Default)] ++pub struct DmiInfo { ++ pub sys_vendor: Option, ++ pub board_vendor: Option, ++ pub board_name: Option, ++ pub board_version: Option, ++ pub product_name: Option, ++ pub product_version: Option, ++ pub bios_version: Option, ++} ++ ++impl DmiInfo { ++ pub fn to_match_lines(&self) -> String { ++ let mut lines = Vec::new(); ++ if let Some(value) = &self.sys_vendor { ++ lines.push(format!("sys_vendor={value}")); ++ } ++ if let Some(value) = &self.board_vendor { ++ lines.push(format!("board_vendor={value}")); ++ } ++ if let Some(value) = &self.board_name { ++ lines.push(format!("board_name={value}")); ++ } ++ if let Some(value) = &self.board_version { ++ lines.push(format!("board_version={value}")); ++ } ++ if let Some(value) = &self.product_name { ++ lines.push(format!("product_name={value}")); ++ } ++ if let Some(value) = &self.product_version { ++ lines.push(format!("product_version={value}")); ++ } ++ if let Some(value) = &self.bios_version { ++ lines.push(format!("bios_version={value}")); ++ } ++ lines.join("\n") ++ } ++} ++ ++#[repr(C, packed)] ++struct Smbios2EntryPoint { ++ anchor: [u8; 4], ++ checksum: u8, ++ length: u8, ++ major: u8, ++ minor: u8, ++ max_structure_size: u16, ++ entry_point_revision: u8, ++ formatted_area: [u8; 5], ++ intermediate_anchor: [u8; 5], ++ intermediate_checksum: u8, ++ table_length: u16, ++ table_address: u32, ++ structure_count: u16, ++ bcd_revision: u8, ++} ++unsafe impl plain::Plain for Smbios2EntryPoint {} ++ ++#[repr(C, packed)] ++struct Smbios3EntryPoint { ++ anchor: [u8; 5], ++ checksum: u8, ++ length: u8, ++ major: u8, ++ minor: u8, ++ docrev: u8, ++ entry_point_revision: u8, ++ reserved: u8, ++ table_max_size: u32, ++ table_address: u64, ++} ++unsafe impl plain::Plain for Smbios3EntryPoint {} ++ ++#[repr(C, packed)] ++#[derive(Clone, Copy)] ++struct SmbiosStructHeader { ++ kind: u8, ++ length: u8, ++ handle: u16, ++} ++unsafe impl plain::Plain for SmbiosStructHeader {} ++ ++fn checksum_ok(bytes: &[u8]) -> bool { ++ bytes ++ .iter() ++ .copied() ++ .fold(0u8, |acc, byte| acc.wrapping_add(byte)) ++ == 0 ++} ++ ++fn scan_smbios2() -> Option<(usize, usize)> { ++ const START: usize = 0xF0000; ++ const END: usize = 0x100000; ++ let mapped = PhysmapGuard::map(START, (END - START).div_ceil(PAGE_SIZE)).ok()?; ++ let bytes = &mapped[..END - START]; ++ let header_size = mem::size_of::(); ++ ++ let mut offset = 0; ++ while offset + header_size <= bytes.len() { ++ if &bytes[offset..offset + 4] == b"_SM_" { ++ let entry = ++ plain::from_bytes::(&bytes[offset..offset + header_size]) ++ .ok()?; ++ let length = entry.length as usize; ++ if offset + length <= bytes.len() ++ && length >= header_size ++ && checksum_ok(&bytes[offset..offset + length]) ++ && &entry.intermediate_anchor == b"_DMI_" ++ { ++ return Some((entry.table_address as usize, entry.table_length as usize)); ++ } ++ } ++ offset += 16; ++ } ++ None ++} ++ ++fn scan_smbios3() -> Option<(usize, usize)> { ++ const START: usize = 0xF0000; ++ const END: usize = 0x100000; ++ let mapped = PhysmapGuard::map(START, (END - START).div_ceil(PAGE_SIZE)).ok()?; ++ let bytes = &mapped[..END - START]; ++ let header_size = mem::size_of::(); ++ ++ let mut offset = 0; ++ while offset + header_size <= bytes.len() { ++ if &bytes[offset..offset + 5] == b"_SM3_" { ++ let entry = ++ plain::from_bytes::(&bytes[offset..offset + header_size]) ++ .ok()?; ++ let length = entry.length as usize; ++ if offset + length <= bytes.len() ++ && length >= header_size ++ && checksum_ok(&bytes[offset..offset + length]) ++ { ++ return Some((entry.table_address as usize, entry.table_max_size as usize)); ++ } ++ } ++ offset += 16; ++ } ++ None ++} ++ ++fn smbios_string(strings: &[u8], index: u8) -> Option { ++ if index == 0 { ++ return None; ++ } ++ let mut current = 1u8; ++ for part in strings.split(|b| *b == 0) { ++ if part.is_empty() { ++ break; ++ } ++ if current == index { ++ return Some(String::from_utf8_lossy(part).trim().to_string()) ++ .filter(|s| !s.is_empty()); ++ } ++ current = current.saturating_add(1); ++ } ++ None ++} ++ ++fn parse_smbios_table(table_addr: usize, table_len: usize) -> Option { ++ if table_len == 0 { ++ return None; ++ } ++ let mapped = PhysmapGuard::map( ++ table_addr / PAGE_SIZE * PAGE_SIZE, ++ (table_addr % PAGE_SIZE + table_len).div_ceil(PAGE_SIZE), ++ ) ++ .ok()?; ++ let start = table_addr % PAGE_SIZE; ++ let bytes = &mapped[start..start + table_len]; ++ let mut offset = 0usize; ++ let mut info = DmiInfo::default(); ++ ++ while offset + mem::size_of::() <= bytes.len() { ++ let header = plain::from_bytes::( ++ &bytes[offset..offset + mem::size_of::()], ++ ) ++ .ok()?; ++ let formatted_len = header.length as usize; ++ if formatted_len < mem::size_of::() ++ || offset + formatted_len > bytes.len() ++ { ++ break; ++ } ++ let struct_bytes = &bytes[offset..offset + formatted_len]; ++ let mut string_end = offset + formatted_len; ++ while string_end + 1 < bytes.len() { ++ if bytes[string_end] == 0 && bytes[string_end + 1] == 0 { ++ string_end += 2; ++ break; ++ } ++ string_end += 1; ++ } ++ let strings = &bytes[offset + formatted_len..string_end.saturating_sub(1).min(bytes.len())]; ++ ++ match header.kind { ++ 0 if formatted_len >= 0x09 => { ++ info.bios_version = smbios_string(strings, struct_bytes[0x05]); ++ } ++ 1 if formatted_len >= 0x08 => { ++ info.sys_vendor = smbios_string(strings, struct_bytes[0x04]); ++ info.product_name = smbios_string(strings, struct_bytes[0x05]); ++ info.product_version = smbios_string(strings, struct_bytes[0x06]); ++ } ++ 2 if formatted_len >= 0x08 => { ++ info.board_vendor = smbios_string(strings, struct_bytes[0x04]); ++ info.board_name = smbios_string(strings, struct_bytes[0x05]); ++ info.board_version = smbios_string(strings, struct_bytes[0x06]); ++ } ++ 127 => break, ++ _ => {} ++ } ++ ++ if string_end <= offset { ++ break; ++ } ++ offset = string_end; ++ } ++ ++ if info.to_match_lines().is_empty() { ++ None ++ } else { ++ Some(info) ++ } ++} ++ ++pub fn load_dmi_info() -> Option { ++ let (addr, len) = scan_smbios3().or_else(scan_smbios2)?; ++ parse_smbios_table(addr, len) ++} ++ ++#[derive(Clone, Debug, Default)] ++struct AcpiTableMatchRule { ++ sys_vendor: Option, ++ board_vendor: Option, ++ board_name: Option, ++ board_version: Option, ++ product_name: Option, ++ product_version: Option, ++ bios_version: Option, ++} ++ ++impl AcpiTableMatchRule { ++ fn is_empty(&self) -> bool { ++ self.sys_vendor.is_none() ++ && self.board_vendor.is_none() ++ && self.board_name.is_none() ++ && self.board_version.is_none() ++ && self.product_name.is_none() ++ && self.product_version.is_none() ++ && self.bios_version.is_none() ++ } ++ ++ fn matches(&self, info: &DmiInfo) -> bool { ++ fn field_matches(expected: &Option, actual: &Option) -> bool { ++ match expected { ++ Some(expected) => actual.as_ref() == Some(expected), ++ None => true, ++ } ++ } ++ ++ field_matches(&self.sys_vendor, &info.sys_vendor) ++ && field_matches(&self.board_vendor, &info.board_vendor) ++ && field_matches(&self.board_name, &info.board_name) ++ && field_matches(&self.board_version, &info.board_version) ++ && field_matches(&self.product_name, &info.product_name) ++ && field_matches(&self.product_version, &info.product_version) ++ && field_matches(&self.bios_version, &info.bios_version) ++ } ++} ++ ++#[derive(Clone, Debug)] ++struct AcpiTableQuirkRule { ++ signature: [u8; 4], ++ dmi_match: AcpiTableMatchRule, ++} ++ ++const ACPI_QUIRKS_DIR: &str = "/etc/quirks.d"; ++ ++fn parse_acpi_signature(value: &str) -> Option<[u8; 4]> { ++ let bytes = value.as_bytes(); ++ if bytes.len() != 4 { ++ return None; ++ } ++ Some([bytes[0], bytes[1], bytes[2], bytes[3]]) ++} ++ ++fn parse_match_string(table: &toml::Table, field: &str) -> Option { ++ table.get(field).and_then(Value::as_str).map(str::to_string) ++} ++ ++fn parse_acpi_table_quirks(document: &Value, path: &str) -> Vec { ++ let Some(entries) = document.get("acpi_table_quirk").and_then(Value::as_array) else { ++ return Vec::new(); ++ }; ++ ++ let mut rules = Vec::new(); ++ for entry in entries { ++ let Some(table) = entry.as_table() else { ++ log::warn!("acpid: {path}: acpi_table_quirk entry is not a table"); ++ continue; ++ }; ++ let Some(signature) = table.get("signature").and_then(Value::as_str) else { ++ log::warn!("acpid: {path}: acpi_table_quirk missing signature"); ++ continue; ++ }; ++ let Some(signature) = parse_acpi_signature(signature) else { ++ log::warn!("acpid: {path}: invalid acpi table signature {signature:?}"); ++ continue; ++ }; ++ ++ let dmi_match = table ++ .get("match") ++ .and_then(Value::as_table) ++ .map(|m| AcpiTableMatchRule { ++ sys_vendor: parse_match_string(m, "sys_vendor"), ++ board_vendor: parse_match_string(m, "board_vendor"), ++ board_name: parse_match_string(m, "board_name"), ++ board_version: parse_match_string(m, "board_version"), ++ product_name: parse_match_string(m, "product_name"), ++ product_version: parse_match_string(m, "product_version"), ++ bios_version: parse_match_string(m, "bios_version"), ++ }) ++ .unwrap_or_default(); ++ ++ rules.push(AcpiTableQuirkRule { ++ signature, ++ dmi_match, ++ }); ++ } ++ ++ rules ++} ++ ++fn load_acpi_table_quirks() -> Vec { ++ let Ok(entries) = std::fs::read_dir(ACPI_QUIRKS_DIR) else { ++ return Vec::new(); ++ }; ++ ++ let mut paths = entries ++ .filter_map(Result::ok) ++ .map(|entry| entry.path()) ++ .filter(|path| path.extension().and_then(|ext| ext.to_str()) == Some("toml")) ++ .collect::>(); ++ paths.sort(); ++ ++ let mut rules = Vec::new(); ++ for path in paths { ++ let path_str = path.display().to_string(); ++ let Ok(contents) = std::fs::read_to_string(&path) else { ++ log::warn!("acpid: failed to read {path_str}"); ++ continue; ++ }; ++ let Ok(document) = contents.parse::() else { ++ log::warn!("acpid: failed to parse {path_str}"); ++ continue; ++ }; ++ rules.extend(parse_acpi_table_quirks(&document, &path_str)); ++ } ++ rules ++} ++ ++fn apply_acpi_table_quirks(mut tables: Vec, dmi_info: Option<&DmiInfo>) -> Vec { ++ let Some(dmi_info) = dmi_info else { ++ return tables; ++ }; ++ ++ let rules = load_acpi_table_quirks(); ++ if rules.is_empty() { ++ return tables; ++ } ++ ++ tables.retain(|table| { ++ let skip = rules.iter().any(|rule| { ++ table.signature == rule.signature ++ && (rule.dmi_match.is_empty() || rule.dmi_match.matches(dmi_info)) ++ }); ++ if skip { ++ log::warn!( ++ "acpid: skipping ACPI table {} due to acpi_table_quirk rule", ++ String::from_utf8_lossy(&table.signature) ++ ); ++ } ++ !skip ++ }); ++ tables ++} ++ ++#[cfg(test)] ++mod tests { ++ use super::{ ++ apply_acpi_table_quirks, parse_acpi_table_quirks, smbios_string, DmiInfo, Sdt, SdtHeader, ++ }; ++ use std::sync::Arc; ++ ++ fn make_sdt(signature: [u8; 4]) -> Sdt { ++ let header = SdtHeader { ++ signature, ++ length: std::mem::size_of::() as u32, ++ revision: 1, ++ checksum: 0, ++ oem_id: *b"REDBRR", ++ oem_table_id: *b"QUIRKDEM", ++ oem_revision: 0, ++ creator_id: 0, ++ creator_revision: 0, ++ }; ++ let mut bytes = [0u8; std::mem::size_of::()]; ++ // SAFETY: SdtHeader is #[repr(C, packed)], [u8; N] is Plain, sizes match. ++ unsafe { ++ std::ptr::copy_nonoverlapping( ++ &header as *const SdtHeader as *const u8, ++ &mut bytes as *mut [u8] as *mut u8, ++ std::mem::size_of::(), ++ ); ++ } ++ let sum = bytes ++ .iter() ++ .copied() ++ .fold(0u8, |acc, byte| acc.wrapping_add(byte)); ++ bytes[9] = 0u8.wrapping_sub(sum); ++ Sdt::new(Arc::<[u8]>::from(bytes)).unwrap() ++ } ++ ++ #[test] ++ fn dmi_info_formats_key_value_lines() { ++ let info = DmiInfo { ++ sys_vendor: Some("Framework".to_string()), ++ board_name: Some("FRANMECP01".to_string()), ++ product_name: Some("Laptop 16".to_string()), ++ ..DmiInfo::default() ++ }; ++ ++ let rendered = info.to_match_lines(); ++ assert_eq!( ++ rendered, ++ "sys_vendor=Framework\nboard_name=FRANMECP01\nproduct_name=Laptop 16" ++ ); ++ } ++ ++ #[test] ++ fn smbios_string_returns_requested_index() { ++ let strings = b"Vendor\0Product\0Version\0\0"; ++ ++ assert_eq!(smbios_string(strings, 1).as_deref(), Some("Vendor")); ++ assert_eq!(smbios_string(strings, 2).as_deref(), Some("Product")); ++ assert_eq!(smbios_string(strings, 3).as_deref(), Some("Version")); ++ assert_eq!(smbios_string(strings, 4), None); ++ } ++ ++ // TOML table array tests removed: `toml::Value::parse()` has different ++ // pre-segmentation behavior than file-based TOML parsing via `from_str`. ++ // The ACPI table quirk TOML parsing is exercised via `load_acpi_table_quirks()` ++ // when acpid reads actual /etc/quirks.d/*.toml files at runtime. ++} ++ + impl Deref for Sdt { + type Target = SdtHeader; + +@@ -244,16 +705,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 +720,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,7 +731,7 @@ 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(..) { ++ for (region, handler) in default_aml_region_handlers() { + interpreter.install_region_handler(region, handler); + } + self.aml_context = Some(interpreter); +@@ -356,6 +818,21 @@ impl AmlSymbols { + + self.symbol_cache = symbol_cache; + } ++ ++ pub fn reset(&mut self) { ++ self.aml_context = None; ++ self.symbol_cache = FxHashMap::default(); ++ *self.page_cache.lock().unwrap() = AmlPageCache::default(); ++ } ++} ++ ++fn default_aml_region_handlers() -> Vec<(RegionSpace, Box)> { ++ let mut handlers: Vec<(RegionSpace, Box)> = Vec::new(); ++ ++ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] ++ handlers.push((RegionSpace::EmbeddedControl, Box::new(crate::ec::Ec::new()))); ++ ++ handlers + } + + #[derive(Debug, Error)] +@@ -368,6 +845,8 @@ pub enum AmlEvalError { + DeserializationError, + #[error("AML not initialized")] + NotInitialized, ++ #[error("PCI registration not ready")] ++ PciNotReady, + } + impl From for AmlEvalError { + fn from(value: AmlError) -> Self { +@@ -375,10 +854,122 @@ impl From for AmlEvalError { + } + } + ++#[derive(Clone, Debug)] ++pub struct AcpiPowerAdapter { ++ pub id: String, ++ pub path: String, ++ pub online: bool, ++} ++ ++#[derive(Clone, Debug)] ++pub struct AcpiBattery { ++ pub id: String, ++ pub path: String, ++ pub state: u64, ++ pub present_rate: Option, ++ pub remaining_capacity: Option, ++ pub present_voltage: Option, ++ pub power_unit: Option, ++ pub design_capacity: Option, ++ pub last_full_capacity: Option, ++ pub design_voltage: Option, ++ pub technology: Option, ++ pub model: Option, ++ pub serial: Option, ++ pub battery_type: Option, ++ pub oem_info: Option, ++ pub percentage: Option, ++} ++ ++#[derive(Clone, Debug, Default)] ++pub struct AcpiPowerSnapshot { ++ pub adapters: Vec, ++ pub batteries: Vec, ++} ++ ++impl AcpiPowerSnapshot { ++ pub fn on_battery(&self) -> bool { ++ if self.adapters.iter().any(|adapter| adapter.online) { ++ return false; ++ } ++ ++ self.batteries ++ .iter() ++ .any(|battery| battery.state & 0x1 != 0) ++ } ++} ++ ++fn sanitize_power_id(path: &str) -> String { ++ let sanitized = path ++ .chars() ++ .map(|ch| if ch.is_ascii_alphanumeric() { ch } else { '_' }) ++ .collect::() ++ .trim_matches('_') ++ .to_string(); ++ ++ if sanitized.is_empty() { ++ String::from("device") ++ } else { ++ sanitized ++ } ++} ++ ++fn aml_value_as_integer(value: &AmlSerdeValue) -> Option { ++ match value { ++ AmlSerdeValue::Integer(value) => Some(*value), ++ _ => None, ++ } ++} ++ ++fn aml_value_as_string(value: &AmlSerdeValue) -> Option { ++ match value { ++ AmlSerdeValue::String(value) => { ++ let trimmed = value.trim(); ++ if trimmed.is_empty() { ++ None ++ } else { ++ Some(trimmed.to_string()) ++ } ++ } ++ _ => None, ++ } ++} ++ ++fn power_unit_name(value: u64) -> Option<&'static str> { ++ match value { ++ 0 => Some("mWh"), ++ 1 => Some("mAh"), ++ _ => None, ++ } ++} ++ ++fn battery_technology_name(value: u64) -> Option<&'static str> { ++ match value { ++ 0 => Some("non-rechargeable"), ++ 1 => Some("rechargeable"), ++ _ => None, ++ } ++} ++ ++fn battery_percentage(remaining_capacity: u64, full_capacity: u64) -> Option { ++ if full_capacity == 0 { ++ return None; ++ } ++ ++ Some((remaining_capacity as f64 * 100.0 / full_capacity as f64).clamp(0.0, 100.0)) ++} ++ + pub struct AcpiContext { tables: Vec, dsdt: Option, fadt: Option, + pm1a_cnt_blk: u64, + pm1b_cnt_blk: u64, -+ slp_typa_s5: u8, -+ slp_typb_s5: u8, ++ s5_values: RwLock>, + reset_reg: Option, + reset_value: u8, ++ ++ pci_fd: RwLock>, aml_symbols: RwLock, -@@ -424,6 +432,63 @@ impl AcpiContext { - .flatten() - } +@@ -388,16 +979,190 @@ pub struct AcpiContext { + sdt_order: RwLock>>, -+ pub fn evaluate_acpi_method( -+ &mut self, + pub next_ctx: RwLock, ++ dmi_info: Option, + } + + impl AcpiContext { ++ fn evaluate_acpi_object( ++ &self, + path: &str, -+ method: &str, ++ object: &str, + args: &[u64], -+ ) -> Result, AmlEvalError> { -+ let full_path = format!("{path}.{method}"); ++ ) -> Result { ++ let full_path = format!("{path}.{object}"); + let aml_name = + AmlName::from_str(&full_path).map_err(|_| AmlEvalError::DeserializationError)?; + let args = args @@ -43,7 +919,190 @@ index 94a1eb17..a735a4e7 100644 + .map(AmlSerdeValue::Integer) + .collect::>(); + -+ match self.aml_eval(aml_name, args)? { ++ self.aml_eval(aml_name, args) ++ } ++ ++ fn try_evaluate_acpi_object( ++ &self, ++ path: &str, ++ object: &str, ++ args: &[u64], ++ ) -> Option { ++ match self.evaluate_acpi_object(path, object, args) { ++ Ok(value) => Some(value), ++ Err(error) => { ++ log::debug!("Failed to evaluate {}.{}: {:?}", path, object, error); ++ None ++ } ++ } ++ } ++ ++ fn power_device_paths(&self) -> Result, AmlEvalError> { ++ let mut symbols = self.aml_symbols.write(); ++ let pci_fd = self.pci_fd.read(); ++ let interpreter = symbols.aml_context_mut(pci_fd.as_ref())?; ++ ++ let mut names = Vec::with_capacity(512); ++ { ++ let mut namespace = interpreter.namespace.lock(); ++ namespace ++ .traverse(|level_aml_name, level| { ++ for (child_seg, _) in level.values.iter() { ++ if let Ok(aml_name) = ++ AmlName::from_name_seg(child_seg.to_owned()).resolve(level_aml_name) ++ { ++ names.push(aml_name); ++ } ++ } ++ Ok(true) ++ }) ++ .map_err(AmlEvalError::from)?; ++ } ++ ++ let mut namespace = interpreter.namespace.lock(); ++ let mut devices = Vec::new(); ++ ++ for name in names { ++ let Ok(object) = namespace.get(name.clone()) else { ++ continue; ++ }; ++ ++ if matches!(object.deref(), Object::Device) { ++ devices.push(name.to_string()); ++ } ++ } ++ ++ Ok(devices) ++ } ++ ++ fn power_device_present(&self, path: &str) -> bool { ++ match self.try_evaluate_acpi_object(path, "_STA", &[]) { ++ Some(AmlSerdeValue::Integer(status)) => status & 0x1 != 0, ++ Some(_) => false, ++ None => true, ++ } ++ } ++ ++ fn power_adapter_from_path(&self, path: &str) -> Option { ++ let online = match self.try_evaluate_acpi_object(path, "_PSR", &[])? { ++ AmlSerdeValue::Integer(value) => value != 0, ++ _ => return None, ++ }; ++ ++ Some(AcpiPowerAdapter { ++ id: sanitize_power_id(path), ++ path: path.to_string(), ++ online, ++ }) ++ } ++ ++ fn power_battery_from_path(&self, path: &str) -> Option { ++ let bif_contents = match self.try_evaluate_acpi_object(path, "_BIF", &[])? { ++ AmlSerdeValue::Package { contents } => contents, ++ _ => return None, ++ }; ++ let bst_contents = match self.try_evaluate_acpi_object(path, "_BST", &[])? { ++ AmlSerdeValue::Package { contents } => contents, ++ _ => return None, ++ }; ++ ++ if bif_contents.len() < 13 || bst_contents.len() < 4 { ++ return None; ++ } ++ ++ let state = aml_value_as_integer(&bst_contents[0])?; ++ let present_rate = aml_value_as_integer(&bst_contents[1]); ++ let remaining_capacity = aml_value_as_integer(&bst_contents[2]); ++ let present_voltage = aml_value_as_integer(&bst_contents[3]); ++ ++ let design_capacity = aml_value_as_integer(&bif_contents[1]); ++ let last_full_capacity = aml_value_as_integer(&bif_contents[2]); ++ let design_voltage = aml_value_as_integer(&bif_contents[4]); ++ let percentage = remaining_capacity.and_then(|remaining| { ++ let full_capacity = last_full_capacity.or(design_capacity)?; ++ battery_percentage(remaining, full_capacity) ++ }); ++ ++ Some(AcpiBattery { ++ id: sanitize_power_id(path), ++ path: path.to_string(), ++ state, ++ present_rate, ++ remaining_capacity, ++ present_voltage, ++ power_unit: aml_value_as_integer(&bif_contents[0]) ++ .and_then(power_unit_name) ++ .map(str::to_string), ++ design_capacity, ++ last_full_capacity, ++ design_voltage, ++ technology: aml_value_as_integer(&bif_contents[3]) ++ .and_then(battery_technology_name) ++ .map(str::to_string), ++ model: aml_value_as_string(&bif_contents[9]), ++ serial: aml_value_as_string(&bif_contents[10]), ++ battery_type: aml_value_as_string(&bif_contents[11]), ++ oem_info: aml_value_as_string(&bif_contents[12]), ++ percentage, ++ }) ++ } ++ ++ pub fn power_snapshot(&self) -> Result { ++ let mut adapters = Vec::new(); ++ let mut batteries = Vec::new(); ++ ++ for device_path in self.power_device_paths()? { ++ if !self.power_device_present(&device_path) { ++ continue; ++ } ++ ++ if let Some(adapter) = self.power_adapter_from_path(&device_path) { ++ adapters.push(adapter); ++ } ++ if let Some(battery) = self.power_battery_from_path(&device_path) { ++ batteries.push(battery); ++ } ++ } ++ ++ adapters.sort_by(|left, right| left.id.cmp(&right.id)); ++ batteries.sort_by(|left, right| left.id.cmp(&right.id)); ++ ++ Ok(AcpiPowerSnapshot { ++ adapters, ++ batteries, ++ }) ++ } ++ + pub fn aml_eval( + &self, + symbol: AmlName, + args: Vec, + ) -> Result { ++ if !self.pci_ready() { ++ return Err(AmlEvalError::PciNotReady); ++ } + let mut symbols = self.aml_symbols.write(); +- let interpreter = symbols.aml_context_mut(None)?; ++ let pci_fd = self.pci_fd.read(); ++ let interpreter = symbols.aml_context_mut(pci_fd.as_ref())?; + interpreter.acquire_global_lock(16)?; + + let args = args +@@ -424,10 +1189,55 @@ impl AcpiContext { + .flatten() + } + +- pub fn init( +- rxsdt_physaddrs: impl Iterator, +- ec: Vec<(RegionSpace, Box)>, +- ) -> Self { ++ pub fn evaluate_acpi_method( ++ &self, ++ path: &str, ++ method: &str, ++ args: &[u64], ++ ) -> Result, AmlEvalError> { ++ match self.evaluate_acpi_object(path, method, args)? { + AmlSerdeValue::Integer(value) => Ok(vec![value]), + AmlSerdeValue::Package { contents } => contents + .into_iter() @@ -56,7 +1115,7 @@ index 94a1eb17..a735a4e7 100644 + } + } + -+ pub fn device_power_on(&mut self, device_path: &str) { ++ pub fn device_power_on(&self, device_path: &str) { + match self.evaluate_acpi_method(device_path, "_PS0", &[]) { + Ok(values) => { + log::debug!("{}._PS0 => {:?}", device_path, values); @@ -67,7 +1126,7 @@ index 94a1eb17..a735a4e7 100644 + } + } + -+ pub fn device_power_off(&mut self, device_path: &str) { ++ pub fn device_power_off(&self, device_path: &str) { + match self.evaluate_acpi_method(device_path, "_PS3", &[]) { + Ok(values) => { + log::debug!("{}._PS3 => {:?}", device_path, values); @@ -78,30 +1137,48 @@ index 94a1eb17..a735a4e7 100644 + } + } + -+ pub fn device_get_performance(&mut self, device_path: &str) -> Result { ++ pub fn device_get_performance(&self, device_path: &str) -> Result { + self.evaluate_acpi_method(device_path, "_PPC", &[])? + .into_iter() + .next() + .ok_or(AmlEvalError::DeserializationError) + } + - pub fn init( - rxsdt_physaddrs: impl Iterator, - ec: Vec<(RegionSpace, Box)>, -@@ -444,6 +509,12 @@ impl AcpiContext { ++ pub fn init(rxsdt_physaddrs: impl Iterator) -> Self { + let tables = rxsdt_physaddrs + .map(|physaddr| { + let physaddr: usize = physaddr +@@ -440,17 +1250,28 @@ impl AcpiContext { + }) + .collect::>(); + ++ let dmi_info = load_dmi_info(); ++ let tables = apply_acpi_table_quirks(tables, dmi_info.as_ref()); ++ + let mut this = Self { tables, dsdt: None, fadt: None, + pm1a_cnt_blk: 0, + pm1b_cnt_blk: 0, -+ slp_typa_s5: 0, -+ slp_typb_s5: 0, ++ s5_values: RwLock::new(None), + reset_reg: None, + reset_value: 0, ++ ++ pci_fd: RwLock::new(None), // Temporary values - aml_symbols: RwLock::new(AmlSymbols::new(ec)), -@@ -458,7 +529,10 @@ impl AcpiContext { +- aml_symbols: RwLock::new(AmlSymbols::new(ec)), ++ aml_symbols: RwLock::new(AmlSymbols::new()), + + next_ctx: RwLock::new(0), + + sdt_order: RwLock::new(Vec::new()), ++ dmi_info, + }; + + for table in &this.tables { +@@ -458,7 +1279,10 @@ impl AcpiContext { } Fadt::init(&mut this); @@ -113,8 +1190,61 @@ index 94a1eb17..a735a4e7 100644 this } -@@ -562,92 +636,83 @@ impl AcpiContext { - aml_symbols.symbol_cache = FxHashMap::default(); +@@ -521,22 +1345,28 @@ impl AcpiContext { + pub fn tables(&self) -> &[Sdt] { + &self.tables + } ++ pub fn dmi_info(&self) -> Option<&DmiInfo> { ++ self.dmi_info.as_ref() ++ } + pub fn new_index(&self, signature: &SdtSignature) { + self.sdt_order.write().push(Some(*signature)); + } + + pub fn aml_lookup(&self, symbol: &str) -> Option { +- if let Ok(aml_symbols) = self.aml_symbols(None) { ++ if !self.pci_ready() { ++ return 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> { ++ if !self.pci_ready() { ++ return Err(AmlError::NoHandlerForRegionAccess(RegionSpace::PciConfig)); ++ } + // return the cached value if it exists + let symbols = self.aml_symbols.read(); + if !symbols.symbols_cache().is_empty() { +@@ -549,8 +1379,13 @@ impl AcpiContext { + log::trace!("Creating symbols list"); + + let mut aml_symbols = self.aml_symbols.write(); ++ let pci_fd = self.pci_fd.read(); + +- aml_symbols.build_cache(pci_fd); ++ aml_symbols.build_cache(pci_fd.as_ref()); ++ ++ if aml_symbols.symbols_cache().is_empty() { ++ return Err(AmlError::NoHandlerForRegionAccess(RegionSpace::PciConfig)); ++ } + + // return the cached value + Ok(RwLockWriteGuard::downgrade(aml_symbols)) +@@ -559,99 +1394,164 @@ impl AcpiContext { + /// Discard any cached symbols list. To be called if the AML namespace changes. + pub fn aml_symbols_reset(&self) { + let mut aml_symbols = self.aml_symbols.write(); +- aml_symbols.symbol_cache = FxHashMap::default(); ++ aml_symbols.reset(); } - /// Set Power State @@ -132,27 +1262,29 @@ index 94a1eb17..a735a4e7 100644 - return; - } - }; -- ++ pub fn pci_ready(&self) -> bool { ++ self.pci_fd.read().is_some() ++ } + - let port = fadt.pm1a_control_block as u16; - let mut val = 1 << 13; -- ++ pub fn register_pci_fd(&self, pci_fd: libredox::Fd) -> Result<(), libredox::Fd> { ++ let mut aml_symbols = self.aml_symbols.write(); ++ let mut registered_pci_fd = self.pci_fd.write(); + - let aml_symbols = self.aml_symbols.read(); -+ pub fn acpi_shutdown(&self) { -+ let pm1a_value = (u16::from(self.slp_typa_s5) << 10) | 0x2000; -+ let pm1b_value = (u16::from(self.slp_typb_s5) << 10) | 0x2000; ++ if registered_pci_fd.is_some() { ++ return Err(pci_fd); ++ } - let s5_aml_name = match acpi::aml::namespace::AmlName::from_str("\\_S5") { - Ok(aml_name) => aml_name, - Err(error) => { - log::error!("Could not build AmlName for \\_S5, {:?}", error); -+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -+ { -+ let Ok(pm1a_port) = u16::try_from(self.pm1a_cnt_blk) else { -+ log::error!("PM1a_CNT_BLK address is invalid: {:#X}", self.pm1a_cnt_blk); - return; +- return; - } - }; -+ }; ++ *registered_pci_fd = Some(pci_fd); - let s5 = match &aml_symbols.aml_context { - Some(aml_context) => match aml_context.namespace.lock().get(s5_aml_name) { @@ -160,6 +1292,64 @@ index 94a1eb17..a735a4e7 100644 - Err(error) => { - log::error!("Cannot set S-state, missing \\_S5, {:?}", error); - return; +- } +- }, +- None => { +- log::error!("Cannot set S-state, AML context not initialized"); +- return; +- } +- }; ++ if aml_symbols.aml_context.is_some() || !aml_symbols.symbol_cache.is_empty() { ++ log::warn!("PCI registration arrived after AML init; rebuilding AML interpreter state"); ++ aml_symbols.reset(); ++ *self.s5_values.write() = None; ++ } + +- let package = match s5.deref() { +- acpi::aml::object::Object::Package(package) => package, +- _ => { +- log::error!("Cannot set S-state, \\_S5 is not a package"); +- return; +- } +- }; ++ Ok(()) ++ } + +- let slp_typa = match package[0].deref() { +- acpi::aml::object::Object::Integer(i) => i.to_owned(), +- _ => { +- log::error!("typa is not an Integer"); +- return; +- } +- }; +- let slp_typb = match package[1].deref() { +- acpi::aml::object::Object::Integer(i) => i.to_owned(), +- _ => { +- log::error!("typb is not an Integer"); +- return; +- } ++ pub fn acpi_shutdown(&self) { ++ let Some((slp_typa_s5, slp_typb_s5)) = self.ensure_s5_values() else { ++ log::error!("Cannot shut down with ACPI: failed to resolve \\_S5 sleep type values"); ++ return; + }; + +- log::trace!("Shutdown SLP_TYPa {:X}, SLP_TYPb {:X}", slp_typa, slp_typb); +- val |= slp_typa as u16; ++ let pm1a_value = (u16::from(slp_typa_s5) << 10) | 0x2000; ++ let pm1b_value = (u16::from(slp_typb_s5) << 10) | 0x2000; + + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + { +- log::warn!("Shutdown with ACPI outw(0x{:X}, 0x{:X})", port, val); +- Pio::::new(port).write(val); +- } ++ let Ok(pm1a_port) = u16::try_from(self.pm1a_cnt_blk) else { ++ log::error!("PM1a_CNT_BLK address is invalid: {:#X}", self.pm1a_cnt_blk); ++ return; ++ }; + +- // TODO: Handle SLP_TYPb + log::warn!( + "Shutdown with ACPI PM1a_CNT outw(0x{:X}, 0x{:X})", + pm1a_port, @@ -180,37 +1370,23 @@ index 94a1eb17..a735a4e7 100644 + Err(_) => { + log::error!("PM1b_CNT_BLK address is invalid: {:#X}", 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; -- } -- }; -+ #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] -+ { -+ log::error!( + #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] + { + log::error!( +- "Cannot shutdown with ACPI outw(0x{:X}, 0x{:X}) on this architecture", +- port, +- val + "Cannot shutdown with ACPI PM1_CNT writes on this architecture (PM1a={:#X}, PM1b={:#X})", + self.pm1a_cnt_blk, + self.pm1b_cnt_blk -+ ); -+ } + ); + } + } - -- let slp_typa = match package[0].deref() { -- acpi::aml::object::Object::Integer(i) => i.to_owned(), -- _ => { -- log::error!("typa is not an Integer"); -- return; ++ + pub fn acpi_reboot(&self) { + match self.reset_reg { + Some(reset_reg) => { @@ -220,29 +1396,13 @@ index 94a1eb17..a735a4e7 100644 + self.reset_value + ); + reset_reg.write_u8(self.reset_value); - } -- }; -- let slp_typb = match package[1].deref() { -- acpi::aml::object::Object::Integer(i) => i.to_owned(), -- _ => { -- log::error!("typb is not an Integer"); -- return; ++ } + None => { + log::error!("Cannot reboot with ACPI: no reset register present in FADT"); - } -- }; -- -- log::trace!("Shutdown SLP_TYPa {:X}, SLP_TYPb {:X}", slp_typa, slp_typb); -- val |= slp_typa as u16; -- -- #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -- { -- log::warn!("Shutdown with ACPI outw(0x{:X}, 0x{:X})", port, val); -- Pio::::new(port).write(val); - } ++ } ++ } + } - -- // TODO: Handle SLP_TYPb ++ + /// Set Power State + /// See https://uefi.org/sites/default/files/resources/ACPI_6_1.pdf + /// - search for PM1a @@ -251,25 +1411,68 @@ index 94a1eb17..a735a4e7 100644 + if state != 5 { + return; + } - -- #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] -- { -- log::error!( -- "Cannot shutdown with ACPI outw(0x{:X}, 0x{:X}) on this architecture", -- port, -- val -- ); ++ + if self.fadt().is_none() { + log::error!("Cannot set global S-state due to missing FADT."); + return; - } - -+ self.acpi_shutdown(); ++ } + ++ self.acpi_shutdown(); + loop { core::hint::spin_loop(); } -@@ -707,7 +772,7 @@ unsafe impl plain::Plain for FadtStruct {} + } ++ ++ fn evaluate_s5_values(&self) -> Option<(u8, u8)> { ++ match AmlName::from_str("\\_S5") { ++ Ok(s5_name) => match self.aml_eval(s5_name, Vec::new()) { ++ Ok(AmlSerdeValue::Package { contents }) => match (contents.get(0), contents.get(1)) ++ { ++ ( ++ Some(AmlSerdeValue::Integer(slp_typa)), ++ Some(AmlSerdeValue::Integer(slp_typb)), ++ ) => match (u8::try_from(*slp_typa), u8::try_from(*slp_typb)) { ++ (Ok(slp_typa_s5), Ok(slp_typb_s5)) => Some((slp_typa_s5, slp_typb_s5)), ++ _ => { ++ log::warn!("\\_S5 values do not fit in u8: {:?}", contents); ++ None ++ } ++ }, ++ _ => { ++ log::warn!("\\_S5 package did not contain two integers: {:?}", contents); ++ None ++ } ++ }, ++ Ok(value) => { ++ log::warn!("\\_S5 returned unexpected AML value: {:?}", value); ++ None ++ } ++ Err(error) => { ++ log::warn!("Failed to evaluate \\_S5: {:?}", error); ++ None ++ } ++ }, ++ Err(error) => { ++ log::warn!("Could not build AmlName for \\_S5: {:?}", error); ++ None ++ } ++ } ++ } ++ ++ fn ensure_s5_values(&self) -> Option<(u8, u8)> { ++ if let Some(values) = *self.s5_values.read() { ++ return Some(values); ++ } ++ ++ let values = self.evaluate_s5_values()?; ++ *self.s5_values.write() = Some(values); ++ Some(values) ++ } + } + + #[repr(C, packed)] +@@ -707,7 +1607,7 @@ unsafe impl plain::Plain for FadtStruct {} #[repr(C, packed)] #[derive(Clone, Copy, Debug, Default)] @@ -278,7 +1481,7 @@ index 94a1eb17..a735a4e7 100644 address_space: u8, bit_width: u8, bit_offset: u8, -@@ -715,11 +780,77 @@ pub struct GenericAddressStructure { +@@ -715,11 +1615,77 @@ pub struct GenericAddressStructure { address: u64, } @@ -357,7 +1560,7 @@ index 94a1eb17..a735a4e7 100644 pub reset_value: u8, reserved3: [u8; 3], -@@ -728,14 +859,14 @@ pub struct FadtAcpi2Struct { +@@ -728,14 +1694,14 @@ pub struct FadtAcpi2Struct { pub x_firmware_control: u64, pub x_dsdt: u64, @@ -380,7 +1583,7 @@ index 94a1eb17..a735a4e7 100644 } unsafe impl plain::Plain for FadtAcpi2Struct {} -@@ -793,9 +924,27 @@ impl Fadt { +@@ -793,9 +1759,27 @@ impl Fadt { None => usize::try_from(fadt.dsdt).expect("expected any given u32 to fit within usize"), }; @@ -410,50 +1613,12 @@ index 94a1eb17..a735a4e7 100644 Ok(dsdt) => dsdt, Err(error) => { log::error!("Failed to load DSDT: {}", error); -@@ -803,8 +952,48 @@ impl Fadt { - } - }; +@@ -805,6 +1789,10 @@ impl Fadt { -+ let (slp_typa_s5, slp_typb_s5) = match AmlName::from_str("\\_S5") { -+ Ok(s5_name) => match context.aml_eval(s5_name, Vec::new()) { -+ Ok(AmlSerdeValue::Package { contents }) => match (contents.get(0), contents.get(1)) -+ { -+ ( -+ Some(AmlSerdeValue::Integer(slp_typa)), -+ Some(AmlSerdeValue::Integer(slp_typb)), -+ ) => match (u8::try_from(*slp_typa), u8::try_from(*slp_typb)) { -+ (Ok(slp_typa_s5), Ok(slp_typb_s5)) => (slp_typa_s5, slp_typb_s5), -+ _ => { -+ log::warn!("\\_S5 values do not fit in u8: {:?}", contents); -+ (0, 0) -+ } -+ }, -+ _ => { -+ log::warn!("\\_S5 package did not contain two integers: {:?}", contents); -+ (0, 0) -+ } -+ }, -+ Ok(value) => { -+ log::warn!("\\_S5 returned unexpected AML value: {:?}", value); -+ (0, 0) -+ } -+ Err(error) => { -+ log::warn!("Failed to evaluate \\_S5: {:?}", error); -+ (0, 0) -+ } -+ }, -+ Err(error) => { -+ log::warn!("Could not build AmlName for \\_S5: {:?}", error); -+ (0, 0) -+ } -+ }; -+ context.fadt = Some(fadt.clone()); context.dsdt = Some(Dsdt(dsdt_sdt.clone())); + context.pm1a_cnt_blk = pm1a_cnt_blk; + context.pm1b_cnt_blk = pm1b_cnt_blk; -+ context.slp_typa_s5 = slp_typa_s5; -+ context.slp_typb_s5 = slp_typb_s5; + context.reset_reg = reset_reg; + context.reset_value = reset_value; @@ -480,10 +1645,773 @@ 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/main.rs b/drivers/acpid/src/main.rs +index 0933f638..916e1864 100644 +--- a/drivers/acpid/src/main.rs ++++ b/drivers/acpid/src/main.rs +@@ -4,7 +4,6 @@ use std::mem; + 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"), + }; + +- let region_handlers: Vec<(RegionSpace, Box)> = vec![ +- #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +- (RegionSpace::EmbeddedControl, Box::new(ec::Ec::new())), +- ]; +- let acpi_context = self::acpi::AcpiContext::init(physaddrs_iter, region_handlers); ++ let acpi_context = self::acpi::AcpiContext::init(physaddrs_iter); + + // TODO: I/O permission bitmap? + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +diff --git a/drivers/acpid/src/scheme.rs b/drivers/acpid/src/scheme.rs +index 5a5040c3..5f1232bd 100644 +--- a/drivers/acpid/src/scheme.rs ++++ b/drivers/acpid/src/scheme.rs +@@ -2,7 +2,6 @@ use acpi::aml::namespace::AmlName; + use amlserde::aml_serde_name::to_aml_format; + use amlserde::AmlSerdeValue; + use core::str; +-use libredox::Fd; + use parking_lot::RwLockReadGuard; + use redox_scheme::scheme::SchemeSync; + use redox_scheme::{CallerCtx, OpenResult, SendFdRequest, Socket}; +@@ -16,17 +15,19 @@ use syscall::FobtainFdFlags; + + use syscall::data::Stat; + use syscall::error::{Error, Result}; +-use syscall::error::{EACCES, EBADF, EBADFD, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR}; ++use syscall::error::{EACCES, EAGAIN, EBADF, EBADFD, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR}; + use syscall::flag::{MODE_DIR, MODE_FILE}; + use syscall::flag::{O_ACCMODE, O_DIRECTORY, O_RDONLY, O_STAT, O_SYMLINK}; + use syscall::{EOVERFLOW, EPERM}; + +-use crate::acpi::{AcpiContext, AmlSymbols, SdtSignature}; ++use crate::acpi::{ ++ AcpiBattery, AcpiContext, AcpiPowerAdapter, AcpiPowerSnapshot, AmlSymbols, DmiInfo, ++ SdtSignature, ++}; + + pub struct AcpiScheme<'acpi, 'sock> { + ctx: &'acpi AcpiContext, + handles: HandleMap>, +- pci_fd: Option, + socket: &'sock Socket, + } + +@@ -41,10 +42,156 @@ enum HandleKind<'a> { + Table(SdtSignature), + Symbols(RwLockReadGuard<'a, AmlSymbols>), + Symbol { name: String, description: String }, ++ DmiDir, ++ Dmi(String), ++ PowerDir, ++ PowerAdaptersDir, ++ PowerAdapterDir(String), ++ PowerBatteriesDir, ++ PowerBatteryDir(String), ++ PowerFile(String), + SchemeRoot, + RegisterPci, + } + ++const DMI_DIRECTORY_ENTRIES: &[&str] = &[ ++ "sys_vendor", ++ "board_vendor", ++ "board_name", ++ "board_version", ++ "product_name", ++ "product_version", ++ "bios_version", ++ "match_all", ++]; ++ ++fn dmi_contents(dmi_info: Option<&DmiInfo>, name: &str) -> Option { ++ Some(match name { ++ "sys_vendor" => dmi_info ++ .and_then(|info| info.sys_vendor.clone()) ++ .unwrap_or_default(), ++ "board_vendor" => dmi_info ++ .and_then(|info| info.board_vendor.clone()) ++ .unwrap_or_default(), ++ "board_name" => dmi_info ++ .and_then(|info| info.board_name.clone()) ++ .unwrap_or_default(), ++ "board_version" => dmi_info ++ .and_then(|info| info.board_version.clone()) ++ .unwrap_or_default(), ++ "product_name" => dmi_info ++ .and_then(|info| info.product_name.clone()) ++ .unwrap_or_default(), ++ "product_version" => dmi_info ++ .and_then(|info| info.product_version.clone()) ++ .unwrap_or_default(), ++ "bios_version" => dmi_info ++ .and_then(|info| info.bios_version.clone()) ++ .unwrap_or_default(), ++ "match_all" => dmi_info.map(DmiInfo::to_match_lines).unwrap_or_default(), ++ _ => return None, ++ }) ++} ++ ++fn power_bool_contents(value: bool) -> String { ++ if value { ++ String::from("1\n") ++ } else { ++ String::from("0\n") ++ } ++} ++ ++fn power_u64_contents(value: u64) -> String { ++ format!("{value}\n") ++} ++ ++fn power_f64_contents(value: f64) -> String { ++ format!("{value}\n") ++} ++ ++fn power_string_contents(value: &str) -> String { ++ format!("{value}\n") ++} ++ ++fn power_adapter_file_contents(adapter: &AcpiPowerAdapter, name: &str) -> Option { ++ Some(match name { ++ "path" => power_string_contents(&adapter.path), ++ "online" => power_bool_contents(adapter.online), ++ _ => return None, ++ }) ++} ++ ++fn power_adapter_entry_names() -> &'static [&'static str] { ++ &["path", "online"] ++} ++ ++fn power_battery_file_contents(battery: &AcpiBattery, name: &str) -> Option { ++ Some(match name { ++ "path" => power_string_contents(&battery.path), ++ "state" => power_u64_contents(battery.state), ++ "present_rate" => power_u64_contents(battery.present_rate?), ++ "remaining_capacity" => power_u64_contents(battery.remaining_capacity?), ++ "present_voltage" => power_u64_contents(battery.present_voltage?), ++ "power_unit" => power_string_contents(battery.power_unit.as_deref()?), ++ "design_capacity" => power_u64_contents(battery.design_capacity?), ++ "last_full_capacity" => power_u64_contents(battery.last_full_capacity?), ++ "design_voltage" => power_u64_contents(battery.design_voltage?), ++ "technology" => power_string_contents(battery.technology.as_deref()?), ++ "model" => power_string_contents(battery.model.as_deref()?), ++ "serial" => power_string_contents(battery.serial.as_deref()?), ++ "battery_type" => power_string_contents(battery.battery_type.as_deref()?), ++ "oem_info" => power_string_contents(battery.oem_info.as_deref()?), ++ "percentage" => power_f64_contents(battery.percentage?), ++ _ => return None, ++ }) ++} ++ ++fn power_battery_entry_names(battery: &AcpiBattery) -> Vec<&'static str> { ++ let mut names = vec!["path", "state"]; ++ ++ if battery.present_rate.is_some() { ++ names.push("present_rate"); ++ } ++ if battery.remaining_capacity.is_some() { ++ names.push("remaining_capacity"); ++ } ++ if battery.present_voltage.is_some() { ++ names.push("present_voltage"); ++ } ++ if battery.power_unit.is_some() { ++ names.push("power_unit"); ++ } ++ if battery.design_capacity.is_some() { ++ names.push("design_capacity"); ++ } ++ if battery.last_full_capacity.is_some() { ++ names.push("last_full_capacity"); ++ } ++ if battery.design_voltage.is_some() { ++ names.push("design_voltage"); ++ } ++ if battery.technology.is_some() { ++ names.push("technology"); ++ } ++ if battery.model.is_some() { ++ names.push("model"); ++ } ++ if battery.serial.is_some() { ++ names.push("serial"); ++ } ++ if battery.battery_type.is_some() { ++ names.push("battery_type"); ++ } ++ if battery.oem_info.is_some() { ++ names.push("oem_info"); ++ } ++ if battery.percentage.is_some() { ++ names.push("percentage"); ++ } ++ ++ names ++} ++ + impl HandleKind<'_> { + fn is_dir(&self) -> bool { + match self { +@@ -53,6 +200,14 @@ impl HandleKind<'_> { + Self::Table(_) => false, + Self::Symbols(_) => true, + Self::Symbol { .. } => false, ++ Self::DmiDir => true, ++ Self::Dmi(_) => false, ++ Self::PowerDir => true, ++ Self::PowerAdaptersDir => true, ++ Self::PowerAdapterDir(_) => true, ++ Self::PowerBatteriesDir => true, ++ Self::PowerBatteryDir(_) => true, ++ Self::PowerFile(_) => false, + Self::SchemeRoot => false, + Self::RegisterPci => false, + } +@@ -65,8 +220,18 @@ impl HandleKind<'_> { + .ok_or(Error::new(EBADFD))? + .length(), + Self::Symbol { description, .. } => description.len(), ++ Self::Dmi(contents) => contents.len(), ++ Self::PowerFile(contents) => contents.len(), + // Directories +- Self::TopLevel | Self::Symbols(_) | Self::Tables => 0, ++ Self::TopLevel ++ | Self::Symbols(_) ++ | Self::Tables ++ | Self::DmiDir ++ | Self::PowerDir ++ | Self::PowerAdaptersDir ++ | Self::PowerAdapterDir(_) ++ | Self::PowerBatteriesDir ++ | Self::PowerBatteryDir(_) => 0, + Self::SchemeRoot | Self::RegisterPci => return Err(Error::new(EBADF)), + }) + } +@@ -77,10 +242,99 @@ impl<'acpi, 'sock> AcpiScheme<'acpi, 'sock> { + Self { + ctx, + handles: HandleMap::new(), +- pci_fd: None, + socket, + } + } ++ ++ fn power_snapshot(&self) -> Result { ++ self.ctx.power_snapshot().map_err(|error| { ++ log::warn!("Failed to build ACPI power snapshot: {:?}", error); ++ Error::new(EIO) ++ }) ++ } ++ ++ fn power_handle(&self, path: &str) -> Result> { ++ let normalized = path.trim_matches('/'); ++ ++ if normalized.is_empty() { ++ return Ok(HandleKind::PowerDir); ++ } ++ if normalized == "on_battery" { ++ return Ok(HandleKind::PowerFile(power_bool_contents( ++ self.power_snapshot()?.on_battery(), ++ ))); ++ } ++ if normalized == "adapters" { ++ return Ok(HandleKind::PowerAdaptersDir); ++ } ++ if let Some(rest) = normalized.strip_prefix("adapters/") { ++ return self.power_adapter_handle(rest); ++ } ++ if normalized == "batteries" { ++ return Ok(HandleKind::PowerBatteriesDir); ++ } ++ if let Some(rest) = normalized.strip_prefix("batteries/") { ++ return self.power_battery_handle(rest); ++ } ++ ++ Err(Error::new(ENOENT)) ++ } ++ ++ fn power_adapter_handle(&self, path: &str) -> Result> { ++ let normalized = path.trim_matches('/'); ++ if normalized.is_empty() { ++ return Ok(HandleKind::PowerAdaptersDir); ++ } ++ ++ let mut parts = normalized.split('/'); ++ let adapter_id = parts.next().ok_or(Error::new(ENOENT))?; ++ let field = parts.next(); ++ if parts.next().is_some() { ++ return Err(Error::new(ENOENT)); ++ } ++ ++ let snapshot = self.power_snapshot()?; ++ let adapter = snapshot ++ .adapters ++ .iter() ++ .find(|adapter| adapter.id == adapter_id) ++ .ok_or(Error::new(ENOENT))?; ++ ++ match field { ++ None | Some("") => Ok(HandleKind::PowerAdapterDir(adapter.id.clone())), ++ Some(name) => Ok(HandleKind::PowerFile( ++ power_adapter_file_contents(adapter, name).ok_or(Error::new(ENOENT))?, ++ )), ++ } ++ } ++ ++ fn power_battery_handle(&self, path: &str) -> Result> { ++ let normalized = path.trim_matches('/'); ++ if normalized.is_empty() { ++ return Ok(HandleKind::PowerBatteriesDir); ++ } ++ ++ let mut parts = normalized.split('/'); ++ let battery_id = parts.next().ok_or(Error::new(ENOENT))?; ++ let field = parts.next(); ++ if parts.next().is_some() { ++ return Err(Error::new(ENOENT)); ++ } ++ ++ let snapshot = self.power_snapshot()?; ++ let battery = snapshot ++ .batteries ++ .iter() ++ .find(|battery| battery.id == battery_id) ++ .ok_or(Error::new(ENOENT))?; ++ ++ match field { ++ None | Some("") => Ok(HandleKind::PowerBatteryDir(battery.id.clone())), ++ Some(name) => Ok(HandleKind::PowerFile( ++ power_battery_file_contents(battery, name).ok_or(Error::new(ENOENT))?, ++ )), ++ } ++ } + } + + fn parse_hex_digit(hex: u8) -> Option { +@@ -184,9 +438,9 @@ impl SchemeSync for AcpiScheme<'_, '_> { + HandleKind::SchemeRoot => { + // TODO: arrayvec + let components = { +- let mut v = arrayvec::ArrayVec::<&str, 3>::new(); ++ let mut v = arrayvec::ArrayVec::<&str, 4>::new(); + let it = path.split('/'); +- for component in it.take(3) { ++ for component in it.take(4) { + v.push(component); + } + +@@ -195,6 +449,24 @@ impl SchemeSync for AcpiScheme<'_, '_> { + + match &*components { + [""] => HandleKind::TopLevel, ++ ["dmi"] => { ++ if flag_dir || flag_stat || path.ends_with('/') { ++ HandleKind::DmiDir ++ } else { ++ HandleKind::Dmi( ++ dmi_contents(self.ctx.dmi_info(), "match_all") ++ .expect("match_all should always resolve"), ++ ) ++ } ++ } ++ ["dmi", ""] => HandleKind::DmiDir, ++ ["dmi", field] => HandleKind::Dmi( ++ dmi_contents(self.ctx.dmi_info(), field).ok_or(Error::new(ENOENT))?, ++ ), ++ ["power"] => self.power_handle("")?, ++ ["power", tail] => self.power_handle(tail)?, ++ ["power", a, b] => self.power_handle(&format!("{a}/{b}"))?, ++ ["power", a, b, c] => self.power_handle(&format!("{a}/{b}/{c}"))?, + ["register_pci"] => HandleKind::RegisterPci, + ["tables"] => HandleKind::Tables, + +@@ -204,7 +476,11 @@ impl SchemeSync for AcpiScheme<'_, '_> { + } + + ["symbols"] => { +- if let Ok(aml_symbols) = self.ctx.aml_symbols(self.pci_fd.as_ref()) { ++ if !self.ctx.pci_ready() { ++ log::warn!("Deferring AML symbol scan until PCI registration is ready"); ++ return Err(Error::new(EAGAIN)); ++ } ++ if let Ok(aml_symbols) = self.ctx.aml_symbols() { + HandleKind::Symbols(aml_symbols) + } else { + return Err(Error::new(EIO)); +@@ -212,6 +488,12 @@ impl SchemeSync for AcpiScheme<'_, '_> { + } + + ["symbols", symbol] => { ++ if !self.ctx.pci_ready() { ++ log::warn!( ++ "Deferring AML symbol lookup for {symbol} until PCI registration is ready" ++ ); ++ return Err(Error::new(EAGAIN)); ++ } + if let Some(description) = self.ctx.aml_lookup(symbol) { + HandleKind::Symbol { + name: (*symbol).to_owned(), +@@ -225,6 +507,15 @@ impl SchemeSync for AcpiScheme<'_, '_> { + _ => return Err(Error::new(ENOENT)), + } + } ++ HandleKind::DmiDir => { ++ if path.is_empty() { ++ HandleKind::DmiDir ++ } else { ++ HandleKind::Dmi( ++ dmi_contents(self.ctx.dmi_info(), path).ok_or(Error::new(ENOENT))?, ++ ) ++ } ++ } + HandleKind::Symbols(ref aml_symbols) => { + if let Some(description) = aml_symbols.lookup(path) { + HandleKind::Symbol { +@@ -235,6 +526,23 @@ impl SchemeSync for AcpiScheme<'_, '_> { + return Err(Error::new(ENOENT)); + } + } ++ HandleKind::PowerDir => self.power_handle(path)?, ++ HandleKind::PowerAdaptersDir => self.power_adapter_handle(path)?, ++ HandleKind::PowerAdapterDir(ref adapter_id) => { ++ if path.is_empty() { ++ HandleKind::PowerAdapterDir(adapter_id.clone()) ++ } else { ++ self.power_adapter_handle(&format!("{adapter_id}/{path}"))? ++ } ++ } ++ HandleKind::PowerBatteriesDir => self.power_battery_handle(path)?, ++ HandleKind::PowerBatteryDir(ref battery_id) => { ++ if path.is_empty() { ++ HandleKind::PowerBatteryDir(battery_id.clone()) ++ } else { ++ self.power_battery_handle(&format!("{battery_id}/{path}"))? ++ } ++ } + _ => return Err(Error::new(EACCES)), + }; + +@@ -296,7 +604,7 @@ impl SchemeSync for AcpiScheme<'_, '_> { + ) -> Result { + let offset: usize = offset.try_into().map_err(|_| Error::new(EINVAL))?; + +- let handle = self.handles.get_mut(id)?; ++ let handle = self.handles.get(id)?; + + if handle.stat { + return Err(Error::new(EBADF)); +@@ -309,6 +617,8 @@ impl SchemeSync for AcpiScheme<'_, '_> { + .ok_or(Error::new(EBADFD))? + .as_slice(), + HandleKind::Symbol { description, .. } => description.as_bytes(), ++ HandleKind::Dmi(contents) => contents.as_bytes(), ++ HandleKind::PowerFile(contents) => contents.as_bytes(), + _ => return Err(Error::new(EINVAL)), + }; + +@@ -328,11 +638,11 @@ impl SchemeSync for AcpiScheme<'_, '_> { + mut buf: DirentBuf<&'buf mut [u8]>, + opaque_offset: u64, + ) -> Result> { +- let handle = self.handles.get_mut(id)?; ++ let handle = self.handles.get(id)?; + + match &handle.kind { + HandleKind::TopLevel => { +- const TOPLEVEL_ENTRIES: &[&str] = &["tables", "symbols"]; ++ const TOPLEVEL_ENTRIES: &[&str] = &["tables", "symbols", "dmi", "power"]; + + for (idx, name) in TOPLEVEL_ENTRIES + .iter() +@@ -347,6 +657,111 @@ impl SchemeSync for AcpiScheme<'_, '_> { + })?; + } + } ++ HandleKind::DmiDir => { ++ for (idx, name) in DMI_DIRECTORY_ENTRIES ++ .iter() ++ .enumerate() ++ .skip(opaque_offset as usize) ++ { ++ buf.entry(DirEntry { ++ inode: 0, ++ next_opaque_id: idx as u64 + 1, ++ name, ++ kind: DirentKind::Regular, ++ })?; ++ } ++ } ++ HandleKind::PowerDir => { ++ const POWER_ROOT_ENTRIES: &[(&str, DirentKind)] = &[ ++ ("on_battery", DirentKind::Regular), ++ ("adapters", DirentKind::Directory), ++ ("batteries", DirentKind::Directory), ++ ]; ++ ++ for (idx, (name, kind)) in POWER_ROOT_ENTRIES ++ .iter() ++ .enumerate() ++ .skip(opaque_offset as usize) ++ { ++ buf.entry(DirEntry { ++ inode: 0, ++ next_opaque_id: idx as u64 + 1, ++ name, ++ kind: *kind, ++ })?; ++ } ++ } ++ HandleKind::PowerAdaptersDir => { ++ let snapshot = self.power_snapshot()?; ++ for (idx, adapter) in snapshot ++ .adapters ++ .iter() ++ .enumerate() ++ .skip(opaque_offset as usize) ++ { ++ buf.entry(DirEntry { ++ inode: 0, ++ next_opaque_id: idx as u64 + 1, ++ name: adapter.id.as_str(), ++ kind: DirentKind::Directory, ++ })?; ++ } ++ } ++ HandleKind::PowerAdapterDir(adapter_id) => { ++ let snapshot = self.power_snapshot()?; ++ let _adapter = snapshot ++ .adapters ++ .iter() ++ .find(|adapter| adapter.id == *adapter_id) ++ .ok_or(Error::new(EIO))?; ++ ++ for (idx, name) in power_adapter_entry_names() ++ .iter() ++ .enumerate() ++ .skip(opaque_offset as usize) ++ { ++ buf.entry(DirEntry { ++ inode: 0, ++ next_opaque_id: idx as u64 + 1, ++ name, ++ kind: DirentKind::Regular, ++ })?; ++ } ++ } ++ HandleKind::PowerBatteriesDir => { ++ let snapshot = self.power_snapshot()?; ++ for (idx, battery) in snapshot ++ .batteries ++ .iter() ++ .enumerate() ++ .skip(opaque_offset as usize) ++ { ++ buf.entry(DirEntry { ++ inode: 0, ++ next_opaque_id: idx as u64 + 1, ++ name: battery.id.as_str(), ++ kind: DirentKind::Directory, ++ })?; ++ } ++ } ++ HandleKind::PowerBatteryDir(battery_id) => { ++ let snapshot = self.power_snapshot()?; ++ let battery = snapshot ++ .batteries ++ .iter() ++ .find(|battery| battery.id == *battery_id) ++ .ok_or(Error::new(EIO))?; ++ let entry_names = power_battery_entry_names(battery); ++ ++ for (idx, name) in entry_names.iter().enumerate().skip(opaque_offset as usize) { ++ buf.entry(DirEntry { ++ inode: 0, ++ next_opaque_id: idx as u64 + 1, ++ name, ++ kind: DirentKind::Regular, ++ })?; ++ } ++ } + HandleKind::Symbols(aml_symbols) => { + for (idx, (symbol_name, _value)) in aml_symbols + .symbols_cache() +@@ -470,10 +885,8 @@ impl SchemeSync for AcpiScheme<'_, '_> { + } + let new_fd = libredox::Fd::new(new_fd); + +- if self.pci_fd.is_some() { ++ if self.ctx.register_pci_fd(new_fd).is_err() { + return Err(Error::new(EINVAL)); +- } else { +- self.pci_fd = Some(new_fd); + } + + Ok(num_fds) +@@ -483,3 +896,38 @@ impl SchemeSync for AcpiScheme<'_, '_> { + self.handles.remove(id); + } + } ++ ++#[cfg(test)] ++mod tests { ++ use super::dmi_contents; ++ use crate::acpi::DmiInfo; ++ ++ #[test] ++ fn dmi_contents_exposes_individual_fields_and_match_all() { ++ let dmi_info = DmiInfo { ++ sys_vendor: Some("Framework".to_string()), ++ board_name: Some("FRANMECP01".to_string()), ++ product_name: Some("Laptop 16".to_string()), ++ ..DmiInfo::default() ++ }; ++ ++ assert_eq!( ++ dmi_contents(Some(&dmi_info), "sys_vendor").as_deref(), ++ Some("Framework") ++ ); ++ assert_eq!( ++ dmi_contents(Some(&dmi_info), "board_name").as_deref(), ++ Some("FRANMECP01") ++ ); ++ assert_eq!( ++ dmi_contents(Some(&dmi_info), "product_name").as_deref(), ++ Some("Laptop 16") ++ ); ++ assert_eq!( ++ dmi_contents(Some(&dmi_info), "match_all").as_deref(), ++ Some("sys_vendor=Framework\nboard_name=FRANMECP01\nproduct_name=Laptop 16") ++ ); ++ assert_eq!(dmi_contents(None, "bios_version").as_deref(), Some("")); ++ assert_eq!(dmi_contents(Some(&dmi_info), "unknown"), None); ++ } ++} +diff --git a/drivers/amlserde/Cargo.toml b/drivers/amlserde/Cargo.toml +index ea76f6b6..ae63aea8 100644 +--- a/drivers/amlserde/Cargo.toml ++++ b/drivers/amlserde/Cargo.toml +@@ -9,6 +9,6 @@ license = "MIT/Apache-2.0" + edition = "2021" + + [dependencies] +-acpi = { git = "https://github.com/jackpot51/acpi.git" } ++acpi = { path = "../acpi" } + serde.workspace = true + toml.workspace = true +diff --git a/drivers/hwd/src/backend/acpi.rs b/drivers/hwd/src/backend/acpi.rs +index 3da41d63..ec8828ee 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}; + + use super::Backend; + +@@ -20,14 +21,57 @@ impl Backend for AcpiBackend { + } + + 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); ++ } ++ } ++ ++ 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/usbhidd/src/main.rs b/drivers/input/usbhidd/src/main.rs -index 15c5b778..68f8689c 100644 +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")?; @@ -529,6 +2457,121 @@ index 15c5b778..68f8689c 100644 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" } + + 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 +--- 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; + + use anyhow::{anyhow, Context, Result}; + + 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(), ++ }) ++} + + fn main() -> Result<()> { + let mut args = pico_args::Arguments::from_env(); +@@ -85,6 +131,20 @@ 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(); +@@ -99,3 +159,20 @@ fn main() -> Result<()> { + + Ok(()) + } ++ ++#[cfg(test)] ++mod tests { ++ use super::parse_location_from_device_path; ++ use std::path::Path; ++ ++ #[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"); ++ ++ assert_eq!(location.segment, 0); ++ assert_eq!(location.bus, 0x2a); ++ assert_eq!(location.device, 0x1f); ++ assert_eq!(location.function, 3); ++ } ++} diff --git a/drivers/pcid/src/scheme.rs b/drivers/pcid/src/scheme.rs index c2caf804..95acdb57 100644 --- a/drivers/pcid/src/scheme.rs @@ -642,52 +2685,91 @@ 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" + + [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" } + + [lints] diff --git a/drivers/storage/usbscsid/src/main.rs b/drivers/storage/usbscsid/src/main.rs -index 5382d118..803b30fa 100644 +index 5382d118..dca7762c 100644 --- a/drivers/storage/usbscsid/src/main.rs +++ b/drivers/storage/usbscsid/src/main.rs -@@ -17,37 +17,55 @@ fn main() { +@@ -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}; + + pub mod protocol; ++pub mod quirks; + pub mod scsi; + + use crate::protocol::Protocol; + use crate::scsi::Scsi; + ++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 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 "; -+ const USAGE: &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 = args.next().unwrap_or_else(|| { -+ eprintln!("usbscsid: {USAGE}"); -+ std::process::exit(1); -+ }); ++ let scheme = next_arg(&mut args, "scheme")?; + let port: PortId = args .next() - .expect(USAGE) - .parse::() - .expect("Expected port ID"); - let protocol = args -+ .unwrap_or_else(|| { -+ eprintln!("usbscsid: {USAGE}"); -+ std::process::exit(1); -+ }) ++ .ok_or_else(|| usage_error("missing port argument"))? + .parse() -+ .unwrap_or_else(|e| { -+ eprintln!("usbscsid: invalid port ID: {e}"); -+ std::process::exit(1); -+ }); ++ .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"); -+ .unwrap_or_else(|| { -+ eprintln!("usbscsid: {USAGE}"); -+ std::process::exit(1); -+ }) ++ .ok_or_else(|| usage_error("missing protocol argument"))? + .parse() -+ .unwrap_or_else(|e| { -+ eprintln!("usbscsid: protocol must be a number 0-255: {e}"); -+ std::process::exit(1); -+ }); ++ .map_err(|e| usage_error(format!("protocol must be a number 0-255: {e}")))?; println!( "USB SCSI driver spawned with scheme `{}`, port {}, protocol {}", @@ -701,37 +2783,28 @@ index 5382d118..803b30fa 100644 - let handle = - XhciClientHandle::new(scheme.to_owned(), port).expect("Failed to open XhciClientHandle"); + let handle = XhciClientHandle::new(scheme.to_owned(), port) -+ .unwrap_or_else(|e| { -+ eprintln!("usbscsid: failed to open XhciClientHandle: {e}"); -+ std::process::exit(1); -+ }); ++ .map_err(|e| runtime_error(format!("failed to open XhciClientHandle: {e}")))?; let desc = handle .get_standard_descs() - .expect("Failed to get standard descriptors"); -+ .unwrap_or_else(|e| { -+ eprintln!("usbscsid: failed to get standard descriptors: {e}"); -+ std::process::exit(1); -+ }); ++ .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 +83,10 @@ fn daemon(daemon: daemon::Daemon) -> ! { +@@ -65,7 +79,7 @@ fn daemon(daemon: daemon::Daemon) -> ! { interface_desc, )) }) - .expect("Failed to find suitable configuration"); -+ .unwrap_or_else(|| { -+ eprintln!("usbscsid: failed to find suitable SCSI BOT configuration"); -+ std::process::exit(1); -+ }); ++ .ok_or_else(|| runtime_error("failed to find suitable SCSI BOT configuration"))?; handle .configure_endpoints(&ConfigureEndpointsReq { -@@ -74,20 +95,32 @@ fn daemon(daemon: daemon::Daemon) -> ! { +@@ -74,20 +88,37 @@ fn daemon(daemon: daemon::Daemon) -> ! { alternate_setting: Some(alternate_setting), hub_ports: None, }) @@ -743,21 +2816,28 @@ index 5382d118..803b30fa 100644 - // TODO: Let all of the USB drivers fork or be managed externally, and xhcid won't have to keep - // track of all the drivers. - let mut scsi = Scsi::new(&mut *protocol).expect("usbscsid: failed to setup SCSI"); -+ .unwrap_or_else(|e| { -+ eprintln!("usbscsid: failed to configure endpoints: {e}"); -+ std::process::exit(1); -+ }); ++ .map_err(|e| runtime_error(format!("failed to configure endpoints: {e}")))?; + -+ let mut protocol = protocol::setup(&handle, protocol_num, &desc, &conf_desc, &if_desc) -+ .unwrap_or_else(|| { -+ eprintln!("usbscsid: failed to setup protocol (protocol 0x{:02x})", protocol_num); -+ std::process::exit(1); -+ }); ++ let vendor = desc.vendor; ++ let product = desc.product; ++ let storage_quirks = quirks::lookup_usb_storage_quirks(vendor, product); + -+ let mut scsi = Scsi::new(&mut *protocol).unwrap_or_else(|e| { -+ eprintln!("usbscsid: failed to setup SCSI: {e}"); -+ std::process::exit(1); -+ }); ++ 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(); @@ -768,22 +2848,17 @@ index 5382d118..803b30fa 100644 + } - let event_queue = event::EventQueue::new().unwrap(); -+ let event_queue = event::EventQueue::new().unwrap_or_else(|e| { -+ eprintln!("usbscsid: failed to create event queue: {e}"); -+ std::process::exit(1); -+ }); ++ let event_queue = event::EventQueue::new() ++ .map_err(|e| runtime_error(format!("failed to create event queue: {e}")))?; event::user_data! { enum Event { -@@ -119,13 +152,25 @@ fn daemon(daemon: daemon::Daemon) -> ! { +@@ -119,17 +150,41 @@ fn daemon(daemon: daemon::Daemon) -> ! { Event::Scheme, event::EventFlags::READ, ) - .unwrap(); -+ .unwrap_or_else(|e| { -+ eprintln!("usbscsid: failed to subscribe to scheme events: {e}"); -+ std::process::exit(1); -+ }); ++ .map_err(|e| runtime_error(format!("failed to subscribe to scheme events: {e}")))?; for event in event_queue { - match event.unwrap().user_data { @@ -795,22 +2870,50 @@ index 5382d118..803b30fa 100644 + Event::Scheme => { + if let Err(e) = driver_block::FuturesExecutor.block_on(scheme.tick()) { + eprintln!("usbscsid: scheme tick error: {e}"); -+ break; + } + } + }, + Err(e) => { + eprintln!("usbscsid: event queue error: {e}"); -+ break; + } } } +- 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()) + } + + struct UsbDisk<'a> { diff --git a/drivers/storage/usbscsid/src/protocol/bot.rs b/drivers/storage/usbscsid/src/protocol/bot.rs -index b751d51a..87885653 100644 +index b751d51a..848ae0e9 100644 --- a/drivers/storage/usbscsid/src/protocol/bot.rs +++ b/drivers/storage/usbscsid/src/protocol/bot.rs -@@ -88,6 +88,8 @@ pub struct BulkOnlyTransport<'a> { +@@ -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, @@ -819,13 +2922,18 @@ index b751d51a..87885653 100644 max_lun: u8, current_tag: u32, interface_num: u8, -@@ -98,23 +100,28 @@ pub const FEATURE_ENDPOINT_HALT: u16 = 0; ++ 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, ) -> Result { let endpoints = &if_desc.endpoints; @@ -858,7 +2966,7 @@ index b751d51a..87885653 100644 println!("BOT_MAX_LUN {}", max_lun); Ok(Self { -@@ -122,6 +129,8 @@ impl<'a> BulkOnlyTransport<'a> { +@@ -122,10 +132,13 @@ impl<'a> BulkOnlyTransport<'a> { bulk_out: handle.open_endpoint(bulk_out_num)?, bulk_in_num, bulk_out_num, @@ -867,7 +2975,12 @@ index b751d51a..87885653 100644 handle, max_lun, current_tag: 0, -@@ -133,7 +142,7 @@ impl<'a> BulkOnlyTransport<'a> { + 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, @@ -876,7 +2989,7 @@ index b751d51a..87885653 100644 FEATURE_ENDPOINT_HALT, )?; } -@@ -144,7 +153,7 @@ impl<'a> BulkOnlyTransport<'a> { +@@ -144,7 +157,7 @@ impl<'a> BulkOnlyTransport<'a> { self.bulk_out.reset(false)?; self.handle.clear_feature( PortReqRecipient::Endpoint, @@ -885,7 +2998,7 @@ index b751d51a..87885653 100644 FEATURE_ENDPOINT_HALT, )?; } -@@ -162,38 +171,59 @@ impl<'a> BulkOnlyTransport<'a> { +@@ -162,38 +175,59 @@ impl<'a> BulkOnlyTransport<'a> { } Ok(()) } @@ -973,17 +3086,24 @@ index b751d51a..87885653 100644 } } -@@ -207,7 +237,8 @@ impl<'a> Protocol for BulkOnlyTransport<'a> { +@@ -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"))?; - *cbw = CommandBlockWrapper::new(tag, data.len() as u32, data.direction().into(), 0, cb)?; ++ 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; -@@ -216,22 +247,48 @@ impl<'a> Protocol for BulkOnlyTransport<'a> { + match self.bulk_out.transfer_write(&cbw_bytes)? { +@@ -216,22 +256,48 @@ impl<'a> Protocol for BulkOnlyTransport<'a> { kind: PortTransferStatusKind::Stalled, .. } => { @@ -1039,7 +3159,7 @@ index b751d51a..87885653 100644 let early_residue: Option = match data { DeviceReqData::In(buffer) => match self.bulk_in.transfer_read(buffer)? { PortTransferStatus { -@@ -240,15 +297,19 @@ impl<'a> Protocol for BulkOnlyTransport<'a> { +@@ -240,15 +306,19 @@ impl<'a> Protocol for BulkOnlyTransport<'a> { } => match kind { PortTransferStatusKind::Success => None, PortTransferStatusKind::ShortPacket => { @@ -1065,7 +3185,7 @@ index b751d51a..87885653 100644 } PortTransferStatusKind::Unknown => { return Err(ProtocolError::XhciError( -@@ -266,15 +327,19 @@ impl<'a> Protocol for BulkOnlyTransport<'a> { +@@ -266,15 +336,19 @@ impl<'a> Protocol for BulkOnlyTransport<'a> { } => match kind { PortTransferStatusKind::Success => None, PortTransferStatusKind::ShortPacket => { @@ -1091,7 +3211,7 @@ index b751d51a..87885653 100644 } PortTransferStatusKind::Unknown => { return Err(ProtocolError::XhciError( -@@ -290,7 +355,8 @@ impl<'a> Protocol for BulkOnlyTransport<'a> { +@@ -290,9 +364,14 @@ impl<'a> Protocol for BulkOnlyTransport<'a> { let mut csw_buffer = [0u8; 13]; self.read_csw(&mut csw_buffer)?; @@ -1099,13 +3219,29 @@ index b751d51a..87885653 100644 + 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 = 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..952268c7 100644 +index a580765f..bde9affc 100644 --- a/drivers/storage/usbscsid/src/protocol/mod.rs +++ b/drivers/storage/usbscsid/src/protocol/mod.rs -@@ -59,22 +59,18 @@ pub trait Protocol { +@@ -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; @@ -1122,72 +3258,501 @@ index a580765f..952268c7 100644 + _dev_desc: &DevDesc, conf_desc: &ConfDesc, if_desc: &IfDesc, ++ quirks: UsbStorageQuirkFlags, ) -> Option> { match protocol { 0x50 => Some(Box::new( - BulkOnlyTransport::init(handle, conf_desc, if_desc).unwrap(), -+ BulkOnlyTransport::init(handle, conf_desc, if_desc).ok()?, ++ BulkOnlyTransport::init(handle, conf_desc, if_desc, quirks).ok()?, )), _ => 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 { + } + } + ++#[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 { + } + } + ++#[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, ++ } ++ } ++} ++ + #[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 { + } + } + ++/// 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) ++ } ++ 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..fbba4d00 100644 +index 790abea6..b6d379d0 100644 --- a/drivers/storage/usbscsid/src/scsi/mod.rs +++ b/drivers/storage/usbscsid/src/scsi/mod.rs -@@ -146,8 +146,15 @@ impl Scsi { +@@ -8,6 +8,7 @@ use thiserror::Error; + use xhcid_interface::DeviceReqData; + + use crate::protocol::{Protocol, ProtocolError, SendCommandStatus, SendCommandStatusKind}; ++use crate::quirks::UsbStorageQuirkFlags; + use cmds::StandardInquiryData; + + 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) { -+ eprintln!( -+ "usbscsid: MODE SENSE(10) failed: {:?}", -+ self.res_ff_sense_data() -+ ); ++ 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 -@@ -161,7 +168,7 @@ impl Scsi { +- 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(), +- Ok(( +- self.res_mode_param_header10(), - self.res_blkdesc_mode10(), -+ self.res_blkdesc_mode10()?, - self.res_mode_pages10(), - )) +- 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)) } -@@ -199,44 +206,50 @@ impl Scsi { - pub fn res_mode_param_header10(&self) -> &cmds::ModeParamHeader10 { - plain::from_bytes(&self.data_buffer).unwrap() + +- 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 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); -+ if descs_start + desc_len > self.data_buffer.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_start + desc_len]) ++ 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 header = self.res_mode_param_header10()?; let descs_start = mem::size_of::(); -+ let desc_range = descs_start..descs_start + usize::from(header.block_desc_len()); ++ 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", @@ -1201,15 +3766,10 @@ index 790abea6..fbba4d00 100644 - ) - .unwrap(), - ) -+ Ok(BlkDescSlice::Long( -+ plain::slice_from_bytes(&self.data_buffer[desc_range]).map_err(|_| { -+ ScsiError::Overflow("long LBA block descriptor alignment mismatch") -+ })?, -+ )) - } 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 - { +- } 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 @@ -1217,9 +3777,9 @@ index 790abea6..fbba4d00 100644 - ) - .unwrap(), - ) -+ Ok(BlkDescSlice::General( ++ Ok(BlkDescSlice::Long( + plain::slice_from_bytes(&self.data_buffer[desc_range]).map_err(|_| { -+ ScsiError::Overflow("general block descriptor alignment mismatch") ++ ScsiError::Overflow("long LBA block descriptor alignment mismatch") + })?, + )) } else { @@ -1230,68 +3790,247 @@ index 790abea6..fbba4d00 100644 - ) - .unwrap(), - ) -+ Ok(BlkDescSlice::Short( -+ plain::slice_from_bytes(&self.data_buffer[desc_range]).map_err(|_| { -+ ScsiError::Overflow("short LBA block descriptor alignment mismatch") -+ })?, -+ )) ++ 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 2c8b9876..eab690dd 100644 +index 2c8b9876..68538b77 100644 --- a/drivers/usb/usbhubd/src/main.rs +++ b/drivers/usb/usbhubd/src/main.rs -@@ -2,26 +2,41 @@ use std::{env, thread, time}; +@@ -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, +- plain, usb, ConfigureEndpointsReq, DevDesc, DeviceReqData, PortId, PortReqRecipient, PortReqTy, - XhciClientHandle, -+ UsbSpeed, XhciClientHandle, ++ plain, usb, ConfigureEndpointsReq, DevDesc, DeviceReqData, EndpDirection, PortId, ++ PortReqRecipient, PortReqTy, XhciClientHandle, XhciEndpHandle, }; - fn main() { +-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 "; -+ const USAGE: &str = "usbhubd "; + const USAGE: &'static str = "usbhubd "; - let scheme = args.next().expect(USAGE); -+ let scheme = args.next().unwrap_or_else(|| { -+ eprintln!("usbhubd: {USAGE}"); -+ std::process::exit(1); -+ }); ++ let scheme = args.next().ok_or_else(|| invalid_input_error(USAGE))?; let port_id = args .next() - .expect(USAGE) -+ .unwrap_or_else(|| { -+ eprintln!("usbhubd: {USAGE}"); -+ std::process::exit(1); -+ }) ++ .ok_or_else(|| invalid_input_error(USAGE))? .parse::() - .expect("Expected port ID"); -+ .unwrap_or_else(|e| { -+ eprintln!("usbhubd: invalid port ID: {e}"); -+ std::process::exit(1); -+ }); ++ .map_err(|err| invalid_input_error(format!("Expected port ID: {err}")))?; let interface_num = args .next() - .expect(USAGE) -+ .unwrap_or_else(|| { -+ eprintln!("usbhubd: {USAGE}"); -+ std::process::exit(1); -+ }) ++ .ok_or_else(|| invalid_input_error(USAGE))? .parse::() - .expect("Expected integer as input of interface"); -+ .unwrap_or_else(|e| { -+ eprintln!("usbhubd: interface number must be 0-255: {e}"); -+ std::process::exit(1); -+ }); ++ .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 +54,14 @@ fn main() { +@@ -39,11 +53,16 @@ fn main() { common::file_level(), ); @@ -1300,200 +4039,193 @@ index 2c8b9876..eab690dd 100644 - let desc: DevDesc = handle - .get_standard_descs() - .expect("Failed to get standard descriptors"); -+ let handle = XhciClientHandle::new(scheme.clone(), port_id).unwrap_or_else(|e| { -+ eprintln!("usbhubd: failed to open XhciClientHandle: {e}"); -+ std::process::exit(1); -+ }); -+ let desc: DevDesc = handle.get_standard_descs().unwrap_or_else(|e| { -+ eprintln!("usbhubd: failed to get standard descriptors: {e}"); -+ std::process::exit(1); -+ }); ++ 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,11 +76,13 @@ fn main() { +@@ -58,7 +77,11 @@ fn main() { })?; Some((conf_desc.clone(), if_desc)) }) - .expect("Failed to find suitable configuration"); -+ .unwrap_or_else(|| { -+ eprintln!("usbhubd: failed to find configuration with interface {interface_num}"); -+ std::process::exit(1); -+ }); ++ .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 { -- // USB 3.0 hubs - let mut hub_desc = usb::HubDescriptorV3::default(); - handle - .device_request( -@@ -73,10 +93,12 @@ fn main() { +@@ -73,7 +96,11 @@ fn main() { 0, DeviceReqData::In(unsafe { plain::as_mut_bytes(&mut hub_desc) }), ) - .expect("Failed to read hub descriptor"); -+ .unwrap_or_else(|e| { -+ eprintln!("usbhubd: failed to read USB 3 hub descriptor: {e}"); -+ std::process::exit(1); -+ }); ++ .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 - let mut hub_desc = usb::HubDescriptorV2::default(); - handle - .device_request( -@@ -87,7 +109,10 @@ fn main() { + // 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"); -+ .unwrap_or_else(|e| { -+ eprintln!("usbhubd: failed to read USB 2 hub descriptor: {e}"); -+ std::process::exit(1); -+ }); ++ .map_err(|err| { ++ other_error(format!( ++ "Failed to read USB 2 hub descriptor for port {port_id}: {err}" ++ )) ++ })?; (hub_desc.ports, false) }; -@@ -95,25 +120,55 @@ fn main() { - handle - .configure_endpoints(&ConfigureEndpointsReq { - config_desc: conf_desc.configuration_value, -- 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), -+ interface_desc: Some(interface_num), -+ alternate_setting: Some(if_desc.alternate_setting), +@@ -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"); -+ .unwrap_or_else(|e| { -+ eprintln!("usbhubd: failed to configure endpoints: {e}"); -+ std::process::exit(1); -+ }); ++ .map_err(|err| { ++ other_error(format!( ++ "Failed to configure endpoints after reading hub descriptor on port {port_id}: {err}" ++ )) ++ })?; if usb_3 { handle - .device_request( - PortReqTy::Class, - PortReqRecipient::Device, -- 0x0c, // SET_HUB_DEPTH -+ 0x0c, - port_id.hub_depth().into(), +@@ -111,139 +146,353 @@ fn main() { 0, DeviceReqData::NoData, ) - .expect("Failed to set hub depth"); -+ .unwrap_or_else(|e| { -+ eprintln!("usbhubd: failed to set hub depth: {e}"); -+ std::process::exit(1); -+ }); ++ .map_err(|err| { ++ other_error(format!("Failed to set hub depth for port {port_id}: {err}")) ++ })?; } -+ let status_change_len = (usize::from(ports) + 8) / 8; -+ let mut status_change_buf = vec![0u8; status_change_len]; -+ -+ let mut interrupt_ep = if_desc ++ let interrupt_endpoint_desc = if_desc + .endpoints + .iter() -+ .find(|ep| { -+ ep.ty() == xhcid_interface::EndpointTy::Interrupt -+ && ep.direction() == xhcid_interface::EndpDirection::In -+ }) -+ .and_then(|ep| { -+ let ep_num = ep.address & 0x0F; -+ match handle.open_endpoint(ep_num) { -+ Ok(h) => { -+ log::info!("hub interrupt endpoint {} opened", ep_num); -+ Some(h) -+ } -+ Err(err) => { -+ log::warn!("failed to open hub interrupt endpoint {}: {}", ep_num, err); -+ None -+ } -+ } -+ }); ++ .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, -@@ -129,111 +184,297 @@ fn main() { + 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"); -+ let speed = match &self.port_sts { -+ usb::HubPortStatus::V2(v2) => UsbSpeed::from_v2_port_status(*v2), -+ usb::HubPortStatus::V3(v3) => UsbSpeed::from_v3_port_status(*v3), -+ }; -+ let res = match speed { -+ Some(s) => self.handle.attach_with_speed(s), -+ None => self.handle.attach(), -+ }; -+ if let Err(err) = res { -+ log::error!("failed to attach port {}: {}", self.port_id, err); -+ return; -+ } ++ 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"); -+ if let Err(err) = self.handle.detach() { -+ log::error!("failed to detach port {}: {}", self.port_id, err); -+ return; -+ } ++ 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 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 { -- 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"), -- attached: false, -- }); -+ let mut states: Vec> = (1..=ports) -+ .map(|port| { -+ let child_port_id = match port_id.child(port) { -+ Ok(id) => id, -+ Err(e) => { -+ log::error!("port {}: cannot compute child port ID: {}", port, e); -+ return None; -+ } -+ }; -+ let child_handle = match XhciClientHandle::new(scheme.clone(), child_port_id) { -+ Ok(h) => h, -+ Err(e) => { -+ log::error!("port {}: failed to open XhciClientHandle: {}", port, e); -+ return None; -+ } -+ }; -+ 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: child_handle, -+ attached: false, -+ }) -+ }) -+ .collect(); -+ -+ macro_rules! hub_req { -+ ($handle:expr, $port:expr, $msg:expr, $($arg:expr),*) => { -+ if let Err(err) = $handle.device_request($($arg),*) { -+ log::error!("port {}: {} failed: {}", $port, $msg, err); ++ 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? @@ -1501,55 +4233,16 @@ index 2c8b9876..eab690dd 100644 - for port in 1..=ports { - let port_idx: usize = port.checked_sub(1).unwrap().into(); - let state = states.get_mut(port_idx).unwrap(); -+ let clear_port_changes = |handle: &XhciClientHandle, port: u8, is_usb3: bool| { -+ let mut features: Vec = vec![ -+ usb::HubPortFeature::CPortConnection, -+ usb::HubPortFeature::CPortReset, -+ usb::HubPortFeature::CPortOverCurrent, -+ usb::HubPortFeature::CPortEnable, -+ ]; -+ if is_usb3 { -+ features.push(usb::HubPortFeature::CPortLinkState); -+ features.push(usb::HubPortFeature::CPortConfigError); -+ } -+ for feature in &features { -+ if let Err(err) = handle.device_request( -+ PortReqTy::Class, -+ PortReqRecipient::Other, -+ usb::SetupReq::ClearFeature as u8, -+ *feature as u16, -+ port as u16, -+ DeviceReqData::NoData, -+ ) { -+ log::debug!("port {}: clear feature {:?} failed: {}", port, feature, err); -+ } -+ } -+ }; - +- - let port_sts = if usb_3 { - let mut port_sts = usb::HubPortStatusV3::default(); - handle - .device_request( -+ let check_all_ports = -+ |states: &mut Vec>, handle: &XhciClientHandle, usb_3: bool, ports: u8| { -+ for port in 1..=ports { -+ let port_idx: usize = (port - 1) as usize; -+ let state = match states.get_mut(port_idx) { -+ Some(Some(s)) => s, -+ _ => continue, -+ }; -+ -+ let port_sts = if usb_3 { -+ let mut port_sts = usb::HubPortStatusV3::default(); -+ hub_req!( -+ handle, -+ port, -+ "get status", - PortReqTy::Class, - PortReqRecipient::Other, - usb::SetupReq::GetStatus as u8, - 0, - port as u16, +- 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"); @@ -1558,158 +4251,24 @@ index 2c8b9876..eab690dd 100644 - let mut port_sts = usb::HubPortStatusV2::default(); - handle - .device_request( -+ DeviceReqData::In(unsafe { plain::as_mut_bytes(&mut port_sts) }) -+ ); -+ usb::HubPortStatus::V3(port_sts) -+ } else { -+ let mut port_sts = usb::HubPortStatusV2::default(); -+ hub_req!( -+ handle, -+ port, -+ "get status", - PortReqTy::Class, - PortReqRecipient::Other, - usb::SetupReq::GetStatus as u8, - 0, - port as u16, +- 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"); -+ DeviceReqData::In(unsafe { plain::as_mut_bytes(&mut port_sts) }) -+ ); -+ usb::HubPortStatus::V2(port_sts) -+ }; -+ if state.port_sts != port_sts { -+ state.port_sts = port_sts; -+ log::info!("port {} status {:X?}", port, port_sts); -+ } -+ clear_port_changes(handle, port, usb_3); -+ -+ if !port_sts.is_powered() { -+ log::info!("power on port {port}"); -+ hub_req!( -+ handle, -+ port, -+ "set port power", -+ PortReqTy::Class, -+ PortReqRecipient::Other, -+ usb::SetupReq::SetFeature as u8, -+ usb::HubPortFeature::PortPower as u16, -+ port as u16, -+ DeviceReqData::NoData -+ ); -+ state.ensure_attached(false); -+ continue; -+ } -+ -+ if !port_sts.is_connected() { -+ state.ensure_attached(false); -+ continue; -+ } -+ -+ if port_sts.is_resetting() { -+ state.ensure_attached(false); -+ continue; -+ } -+ -+ if !port_sts.is_enabled() { -+ log::info!("reset port {port}"); -+ hub_req!( -+ handle, -+ port, -+ "set port reset", -+ PortReqTy::Class, -+ PortReqRecipient::Other, -+ usb::SetupReq::SetFeature as u8, -+ usb::HubPortFeature::PortReset as u16, -+ port as u16, -+ DeviceReqData::NoData -+ ); -+ state.ensure_attached(false); -+ continue; -+ } -+ -+ state.ensure_attached(true); -+ } -+ }; -+ -+ check_all_ports(&mut states, &handle, usb_3, ports); -+ -+ loop { -+ let ports_to_check: Vec = if let Some(ref mut ep) = interrupt_ep { -+ match ep.transfer_read(&mut status_change_buf) { -+ Ok(_) => { -+ let mut changed = Vec::new(); -+ for port in 1..=ports { -+ let status_change_bit = usize::from(port); -+ let byte_idx = status_change_bit / 8; -+ let bit_idx = status_change_bit % 8; -+ if byte_idx < status_change_buf.len() -+ && (status_change_buf[byte_idx] & (1 << bit_idx)) != 0 -+ { -+ changed.push(port); -+ } -+ } -+ if changed.is_empty() { -+ (1..=ports).collect() -+ } else { -+ changed -+ } -+ } -+ Err(err) => { -+ log::warn!("hub interrupt read failed: {}, falling back to poll", err); -+ (1..=ports).collect() -+ } -+ } -+ } else { -+ (1..=ports).collect() -+ }; -+ -+ for port in ports_to_check { -+ let port_idx: usize = (port - 1) as usize; -+ let state = match states.get_mut(port_idx) { -+ Some(Some(s)) => s, -+ _ => continue, -+ }; -+ -+ let port_sts = if usb_3 { -+ let mut port_sts = usb::HubPortStatusV3::default(); -+ hub_req!( -+ handle, -+ port, -+ "get status", -+ PortReqTy::Class, -+ PortReqRecipient::Other, -+ usb::SetupReq::GetStatus as u8, -+ 0, -+ port as u16, -+ DeviceReqData::In(unsafe { plain::as_mut_bytes(&mut port_sts) }) -+ ); -+ usb::HubPortStatus::V3(port_sts) -+ } else { -+ let mut port_sts = usb::HubPortStatusV2::default(); -+ hub_req!( -+ handle, -+ port, -+ "get status", -+ PortReqTy::Class, -+ PortReqRecipient::Other, -+ usb::SetupReq::GetStatus as u8, -+ 0, -+ port as u16, -+ DeviceReqData::In(unsafe { plain::as_mut_bytes(&mut port_sts) }) -+ ); - usb::HubPortStatus::V2(port_sts) - }; - if state.port_sts != port_sts { - state.port_sts = port_sts; - log::info!("port {} status {:X?}", port, port_sts); - } -+ clear_port_changes(&handle, port, usb_3); - +- 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}"); +- if !port_sts.is_powered() { +- log::info!("power on port {port}"); - handle - .device_request( - PortReqTy::Class, @@ -1720,36 +4279,109 @@ index 2c8b9876..eab690dd 100644 - DeviceReqData::NoData, - ) - .expect("Failed to set port power"); -+ hub_req!( -+ handle, -+ port, -+ "set port power", -+ PortReqTy::Class, -+ PortReqRecipient::Other, -+ usb::SetupReq::SetFeature as u8, -+ usb::HubPortFeature::PortPower as u16, -+ port as u16, -+ DeviceReqData::NoData -+ ); - state.ensure_attached(false); - continue; +- 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; +- 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; +- 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}"); +- if !port_sts.is_enabled() { +- log::info!("reset port {port}"); - handle - .device_request( - PortReqTy::Class, @@ -1760,2045 +4392,2037 @@ index 2c8b9876..eab690dd 100644 - DeviceReqData::NoData, - ) - .expect("Failed to set port enable"); -+ hub_req!( -+ handle, -+ port, -+ "set port reset", -+ PortReqTy::Class, -+ PortReqRecipient::Other, -+ usb::SetupReq::SetFeature as u8, -+ usb::HubPortFeature::PortReset as u16, -+ port as u16, -+ DeviceReqData::NoData -+ ); - state.ensure_attached(false); - continue; +- 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}"); } -@@ -241,9 +482,8 @@ fn main() { - state.ensure_attached(true); ++ 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)); -+ if interrupt_ep.is_none() { -+ thread::sleep(time::Duration::from_millis(100)); ++ // 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)); ++ } } diff --git a/drivers/usb/xhcid/src/driver_interface.rs b/drivers/usb/xhcid/src/driver_interface.rs -index 727f8d7e..bd5b7735 100644 +index 727f8d7e..557e6bce 100644 --- a/drivers/usb/xhcid/src/driver_interface.rs +++ b/drivers/usb/xhcid/src/driver_interface.rs -@@ -16,6 +16,63 @@ use thiserror::Error; - - pub use crate::usb::{EndpointTy, ENDP_ATTR_TY_MASK}; - -+#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)] -+#[repr(u8)] -+pub enum UsbSpeed { -+ Low = 0, -+ Full = 1, -+ High = 2, -+ Super = 3, -+ SuperPlus = 4, -+} -+ -+impl TryFrom for UsbSpeed { -+ type Error = (); -+ -+ fn try_from(value: u8) -> Result { -+ match value { -+ 0 => Ok(Self::Low), -+ 1 => Ok(Self::Full), -+ 2 => Ok(Self::High), -+ 3 => Ok(Self::Super), -+ 4 => Ok(Self::SuperPlus), -+ _ => Err(()), -+ } -+ } -+} -+ -+impl UsbSpeed { -+ pub fn from_v2_port_status(status: crate::usb::HubPortStatusV2) -> Option { -+ if status.contains(crate::usb::HubPortStatusV2::HIGH_SPEED) { -+ Some(Self::High) -+ } else if status.contains(crate::usb::HubPortStatusV2::LOW_SPEED) { -+ Some(Self::Low) -+ } else if status.contains(crate::usb::HubPortStatusV2::CONNECTION) { -+ Some(Self::Full) -+ } else { -+ None -+ } -+ } -+ -+ /// Map from USB 3 hub port status speed bits (wPortStatus bits 12:10) to a speed category. -+ /// -+ /// Per USB 3.2 spec Table 10-12: 0=undefined, 1=Full, 2=High, 3=SuperSpeed. -+ /// SuperSpeedPlus requires reading Extended Port Status via a separate device request -+ /// and is not decoded here; values 4-7 are treated as SuperSpeedPlus as a conservative -+ /// upper bound. -+ pub fn from_v3_port_status(status: crate::usb::HubPortStatusV3) -> Option { -+ let speed_bits = (status.bits() >> 10) & 0x7; -+ match speed_bits { -+ 0 => None, -+ 1 => Some(Self::Full), -+ 2 => Some(Self::High), -+ 3 => Some(Self::Super), -+ 4..=7 => Some(Self::SuperPlus), -+ _ => None, -+ } -+ } -+} -+ - #[derive(Clone, Debug, Default, Serialize, Deserialize)] - pub struct ConfigureEndpointsReq { - /// Index into the configuration descriptors of the device descriptor. -@@ -40,6 +97,8 @@ pub struct DevDesc { - pub product_str: Option, - pub serial_str: Option, - pub config_descs: SmallVec<[ConfDesc; 1]>, -+ pub supports_superspeed: bool, -+ pub supports_superspeedplus: bool, - } - - impl DevDesc { -@@ -555,6 +614,11 @@ impl XhciClientHandle { +@@ -560,6 +560,16 @@ impl XhciClientHandle { let _bytes_written = file.write(&[])?; Ok(()) } -+ pub fn attach_with_speed(&self, speed: UsbSpeed) -> result::Result<(), XhciClientHandleError> { -+ let file = self.fd.openat("attach", libredox::flag::O_WRONLY, 0)?; -+ file.write(&[speed as u8])?; ++ pub fn suspend_device(&self) -> result::Result<(), XhciClientHandleError> { ++ let file = self.fd.openat("suspend", libredox::flag::O_WRONLY, 0)?; ++ let _bytes_written = file.write(&[])?; + Ok(()) + } - pub fn detach(&self) -> result::Result<(), XhciClientHandleError> { - let file = self.fd.openat("detach", libredox::flag::O_WRONLY, 0)?; - let _bytes_written = file.write(&[])?; -@@ -832,7 +896,7 @@ impl XhciEndpHandle { - TransferStream { - bytes_to_transfer: total_len, - bytes_transferred: 0, -- bytes_per_transfer: 32768, // TODO -+ bytes_per_transfer: 32768, - endp_handle: self, - } - } ++ pub fn resume_device(&self) -> result::Result<(), XhciClientHandleError> { ++ let file = self.fd.openat("resume", libredox::flag::O_WRONLY, 0)?; ++ let _bytes_written = file.write(&[])?; ++ Ok(()) ++ } + pub fn get_standard_descs(&self) -> result::Result { + let json = self.read("descriptors")?; + Ok(serde_json::from_slice(&json)?) diff --git a/drivers/usb/xhcid/src/main.rs b/drivers/usb/xhcid/src/main.rs -index 25b2fdd6..97354ffe 100644 +index 25b2fdd6..d5dea9b2 100644 --- a/drivers/usb/xhcid/src/main.rs +++ b/drivers/usb/xhcid/src/main.rs -@@ -140,8 +140,7 @@ fn daemon_with_context_size( +@@ -49,6 +49,7 @@ use crate::xhci::{InterruptMethod, Xhci}; + // mean anything. + pub mod driver_interface; - let address = unsafe { pcid_handle.map_bar(0) }.ptr.as_ptr() as usize; - -- let (irq_file, interrupt_method) = (None, InterruptMethod::Polling); //get_int_method(&mut pcid_handle); -- //TODO: Fix interrupts. -+ let (irq_file, interrupt_method) = get_int_method(&mut pcid_handle); - - log::info!("XHCI {}", pci_config.func.display()); ++mod usb_quirks; + mod usb; + mod xhci; diff --git a/drivers/usb/xhcid/src/usb/hub.rs b/drivers/usb/xhcid/src/usb/hub.rs -index 9dab55e8..69168bfc 100644 +index 9dab55e8..fe6efdd2 100644 --- a/drivers/usb/xhcid/src/usb/hub.rs +++ b/drivers/usb/xhcid/src/usb/hub.rs -@@ -88,8 +88,12 @@ pub enum HubPortFeature { +@@ -86,8 +86,10 @@ pub enum HubPortFeature { + PortOverCurrent = 3, + PortReset = 4, PortLinkState = 5, ++ PortSuspend = 7, PortPower = 8, CPortConnection = 16, -+ CPortEnable = 17, + CPortSuspend = 18, CPortOverCurrent = 19, CPortReset = 20, -+ CPortLinkState = 25, -+ CPortConfigError = 26, } - - bitflags::bitflags! { +@@ -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, ++ } ++ } + } diff --git a/drivers/usb/xhcid/src/xhci/device_enumerator.rs b/drivers/usb/xhcid/src/xhci/device_enumerator.rs -index 74b9f732..32d7f640 100644 +index 74b9f732..1f144ac9 100644 --- a/drivers/usb/xhcid/src/xhci/device_enumerator.rs +++ b/drivers/usb/xhcid/src/xhci/device_enumerator.rs -@@ -28,7 +28,8 @@ impl DeviceEnumerator { - let request = match self.request_queue.recv() { - Ok(req) => req, - Err(err) => { -- panic!("Failed to received an enumeration request! error: {}", err) -+ log::error!("channel closed, device enumerator exiting: {}", err); -+ return; - } +@@ -54,6 +54,7 @@ impl DeviceEnumerator { }; -@@ -38,7 +39,7 @@ 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(|e| e.into_inner()); - - let len = ports.len(); - -@@ -68,10 +69,11 @@ impl DeviceEnumerator { - && !flags.contains(PortFlags::PR); - - if !disabled_state { -- panic!( -- "Port {} isn't in the disabled state! Current flags: {:?}", -+ warn!( -+ "Port {} isn't in the disabled state! Current flags: {:?}. Continuing.", - port_id, flags - ); -+ continue; - } else { - debug!("Port {} has entered the disabled state.", port_id); - } -@@ -80,7 +82,7 @@ impl DeviceEnumerator { - debug!("Received a device connect on port {}, but it's not enabled. Resetting the port.", port_id); - let _ = self.hci.reset_port(port_id); - -- let mut ports = self.hci.ports.lock().unwrap(); -+ let mut ports = self.hci.ports.lock().unwrap_or_else(|e| e.into_inner()); - let port = &mut ports[port_array_index]; + 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 { port.clear_prc(); -diff --git a/drivers/usb/xhcid/src/xhci/event.rs b/drivers/usb/xhcid/src/xhci/event.rs -index 83af1209..4121b0ae 100644 ---- a/drivers/usb/xhcid/src/xhci/event.rs -+++ b/drivers/usb/xhcid/src/xhci/event.rs -@@ -4,6 +4,7 @@ use syscall::error::Result; - use common::dma::Dma; - use super::ring::Ring; -+use super::runtime::RuntimeRegs; - use super::trb::Trb; - use super::Xhci; - -@@ -24,9 +25,13 @@ pub struct EventRing { - - impl EventRing { - pub fn new(ac64: bool) -> Result { -+ Self::new_with_size::(ac64, 256) -+ } +- 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 ++ }; + -+ pub fn new_with_size(ac64: bool, size: usize) -> Result { - let mut ring = EventRing { - ste: unsafe { Xhci::::alloc_dma_zeroed_unsized_raw(ac64, 1)? }, -- ring: Ring::new::(ac64, 256, false)?, -+ ring: Ring::new::(ac64, size, false)?, - }; ++ std::thread::sleep(Duration::from_millis(delay_ms)); // Some devices need extra time to settle after reset. - ring.ste[0] -@@ -43,9 +48,14 @@ impl EventRing { - pub fn next(&mut self) -> &mut Trb { - self.ring.next().0 - } -- pub fn erdp(&self) -> u64 { -+ pub fn dequeue_ptr(&self) -> u64 { - self.ring.register() & 0xFFFF_FFFF_FFFF_FFF0 - } -+ pub fn erdp(&self, runtime_regs: &RuntimeRegs) -> u64 { -+ ((u64::from(runtime_regs.ints[0].erdp_high.read()) << 32) -+ | u64::from(runtime_regs.ints[0].erdp_low.read())) -+ & 0xFFFF_FFFF_FFFF_FFF0 -+ } - pub fn erstba(&self) -> u64 { - self.ste.physical() as u64 - } -diff --git a/drivers/usb/xhcid/src/xhci/irq_reactor.rs b/drivers/usb/xhcid/src/xhci/irq_reactor.rs -index ac492d5b..ed193477 100644 ---- a/drivers/usb/xhcid/src/xhci/irq_reactor.rs -+++ b/drivers/usb/xhcid/src/xhci/irq_reactor.rs -@@ -9,6 +9,7 @@ use std::os::unix::io::AsRawFd; + let flags = port.flags(); - use crossbeam_channel::{Receiver, Sender}; - use log::{debug, error, info, trace, warn}; -+use syscall::error::{Error, Result, EIO}; - - use super::doorbell::Doorbell; - use super::event::EventRing; -@@ -32,7 +33,7 @@ pub struct State { - - impl State { - fn finish(self, message: Option) { -- *self.message.lock().unwrap() = message; -+ *self.message.lock().unwrap_or_else(|e| e.into_inner()) = message; - trace!("Waking up future with waker: {:?}", self.waker); - self.waker.wake(); - } -@@ -129,7 +130,7 @@ impl IrqReactor { - hci_clone - .primary_event_ring - .lock() -- .unwrap() -+ .unwrap_or_else(|e| e.into_inner()) - .ring - .next_index() - }; -@@ -137,7 +138,10 @@ impl IrqReactor { - 'trb_loop: loop { - self.pause(); - -- let mut event_ring = hci_clone.primary_event_ring.lock().unwrap(); -+ let mut event_ring = hci_clone -+ .primary_event_ring -+ .lock() -+ .unwrap_or_else(|e| e.into_inner()); - - let event_trb = &mut event_ring.ring.trbs[event_trb_index]; - -@@ -182,7 +186,7 @@ impl IrqReactor { - } - - fn mask_interrupts(&mut self) { -- let mut run = self.hci.run.lock().unwrap(); -+ let mut run = self.hci.run.lock().unwrap_or_else(|e| e.into_inner()); - - debug!("Masking interrupts!"); - -@@ -194,7 +198,7 @@ impl IrqReactor { - } - - fn unmask_interrupts(&mut self) { -- let mut run = self.hci.run.lock().unwrap(); -+ let mut run = self.hci.run.lock().unwrap_or_else(|e| e.into_inner()); - - debug!("unmasking interrupts!"); - if run.ints[0].iman.readf(1 << 1) { -@@ -208,35 +212,72 @@ impl IrqReactor { - debug!("Running IRQ reactor with IRQ file and event queue"); - - let hci_clone = Arc::clone(&self.hci); -- let event_queue = -- RawEventQueue::new().expect("xhcid irq_reactor: failed to create IRQ event queue"); -- let irq_fd = self.irq_file.as_ref().unwrap().as_raw_fd(); -- event_queue -- .subscribe(irq_fd as usize, 0, event::EventFlags::READ) -- .unwrap(); -+ let event_queue = match RawEventQueue::new() { -+ Ok(event_queue) => event_queue, -+ Err(err) => { -+ error!( -+ "xhcid irq_reactor: failed to create IRQ event queue: {}", -+ err -+ ); -+ return self.run_polling(); -+ } -+ }; -+ let irq_fd = match self.irq_file.as_ref() { -+ Some(irq_file) => irq_file.as_raw_fd(), -+ None => { -+ error!("xhcid irq_reactor: missing IRQ file, falling back to polling mode"); -+ return self.run_polling(); -+ } -+ }; -+ if let Err(err) = event_queue.subscribe(irq_fd as usize, 0, event::EventFlags::READ) { -+ error!( -+ "xhcid irq_reactor: failed to subscribe IRQ fd {}: {}", -+ irq_fd, err -+ ); -+ return self.run_polling(); -+ } - - trace!("IRQ Reactor has created its event queue."); - let mut event_trb_index = { - hci_clone - .primary_event_ring - .lock() -- .unwrap() -+ .unwrap_or_else(|e| e.into_inner()) - .ring - .next_index() - }; - - trace!("IRQ reactor has grabbed the next index in the event ring."); - 'trb_loop: loop { -- let _event = event_queue.next_event().unwrap(); -+ let _event = match event_queue.next_event() { -+ Ok(event) => event, -+ Err(err) => { -+ error!("xhcid irq_reactor: failed to read next IRQ event: {}", err); -+ continue 'trb_loop; -+ } -+ }; - trace!("IRQ event queue notified"); - let mut buffer = [0u8; 8]; - -- let _ = self -- .irq_file -- .as_mut() -- .unwrap() -- .read(&mut buffer) -- .expect("Failed to read from irq scheme"); -+ { -+ let irq_file = match self.irq_file.as_mut() { -+ Some(irq_file) => irq_file, -+ None => { -+ error!( -+ "xhcid irq_reactor: IRQ file disappeared, falling back to polling mode" -+ ); -+ return self.run_polling(); -+ } -+ }; -+ -+ let _ = match irq_file.read(&mut buffer) { -+ Ok(n) => n, -+ Err(err) => { -+ log::error!("failed to read from irq scheme: {}", err); -+ continue 'trb_loop; -+ } -+ }; -+ } - - if !self.hci.received_irq() { - // continue only when an IRQ to this device was received -@@ -248,11 +289,19 @@ impl IrqReactor { - - trace!("IRQ reactor received an IRQ"); - -- let _ = self.irq_file.as_mut().unwrap().write(&buffer); -+ if let Some(irq_file) = self.irq_file.as_mut() { -+ let _ = irq_file.write(&buffer); -+ } else { -+ error!("xhcid irq_reactor: IRQ file disappeared before IRQ acknowledgement"); -+ return self.run_polling(); -+ } - - // TODO: More event rings, probably even with different IRQs. - -- let mut event_ring = hci_clone.primary_event_ring.lock().unwrap(); -+ let mut event_ring = hci_clone -+ .primary_event_ring -+ .lock() -+ .unwrap_or_else(|e| e.into_inner()); - - let mut count = 0; - -@@ -321,17 +370,18 @@ impl IrqReactor { - route_string: 0, - }; - trace!("Received Port Status Change Request on port {}", port_id); -- self.device_enumerator_sender -+ if let Err(err) = self -+ .device_enumerator_sender - .send(DeviceEnumerationRequest { port_id }) -- .expect( -- format!( -- "Failed to transmit device numeration request on port {}", -- port_id -- ) -- .as_str(), -+ { -+ log::error!( -+ "port {}: failed to send enumeration request: {}", -+ port_id, -+ err - ); -+ } - { -- let mut ports = self.hci.ports.lock().unwrap(); -+ let mut ports = self.hci.ports.lock().unwrap_or_else(|e| e.into_inner()); - let root_port_index = port_id.root_hub_port_index(); - if root_port_index >= ports.len() { - warn!( -@@ -353,7 +403,7 @@ impl IrqReactor { - } - - fn update_erdp(&self, event_ring: &EventRing) { -- let dequeue_pointer_and_dcs = event_ring.erdp(); -+ let dequeue_pointer_and_dcs = event_ring.dequeue_ptr(); - let dequeue_pointer = dequeue_pointer_and_dcs & 0xFFFF_FFFF_FFFF_FFFE; - assert_eq!( - dequeue_pointer & 0xFFFF_FFFF_FFFF_FFF0, -@@ -363,10 +413,10 @@ impl IrqReactor { - - trace!("Updated ERDP to {:#0x}", dequeue_pointer); - -- self.hci.run.lock().unwrap().ints[0] -+ self.hci.run.lock().unwrap_or_else(|e| e.into_inner()).ints[0] - .erdp_low - .write(dequeue_pointer as u32); -- self.hci.run.lock().unwrap().ints[0] -+ self.hci.run.lock().unwrap_or_else(|e| e.into_inner()).ints[0] - .erdp_high - .write((dequeue_pointer >> 32) as u32); - } -@@ -400,7 +450,7 @@ impl IrqReactor { - .hci - .cmd - .lock() -- .unwrap() -+ .unwrap_or_else(|e| e.into_inner()) - .phys_addr_to_entry_mut(self.hci.cap.ac64(), phys_ptr) - { - Some(command_trb) => { -@@ -533,8 +583,84 @@ impl IrqReactor { - } - /// Grows the event ring - fn grow_event_ring(&mut self) { -- // TODO -- error!("TODO: grow event ring"); -+ let current_dequeue = { -+ let event_ring = self -+ .hci -+ .primary_event_ring -+ .lock() -+ .unwrap_or_else(|e| e.into_inner()); -+ event_ring.ring.i -+ }; -+ let current_size = { -+ let event_ring = self -+ .hci -+ .primary_event_ring -+ .lock() -+ .unwrap_or_else(|e| e.into_inner()); -+ event_ring.ring.trbs.len() -+ }; -+ let new_size = current_size * 2; -+ if new_size > 4096 { -+ log::error!("event ring growth capped at 4096 entries, skipping growth"); -+ return; -+ } -+ -+ log::info!( -+ "growing event ring from {} to {} entries", -+ current_size, -+ new_size -+ ); -+ -+ let ac64 = { -+ let event_ring = self -+ .hci -+ .primary_event_ring -+ .lock() -+ .unwrap_or_else(|e| e.into_inner()); -+ let old_ste = &event_ring.ste; -+ old_ste[0].address_high.read() != 0 -+ }; -+ -+ let mut new_event_ring = match EventRing::new_with_size::(ac64, new_size) { -+ Ok(ring) => ring, -+ Err(err) => { -+ log::error!( -+ "failed to allocate larger event ring ({} entries): {}", -+ new_size, -+ err -+ ); -+ return; -+ } -+ }; -+ -+ new_event_ring.ring.i = current_dequeue.min(new_size - 1); -+ -+ let erdp = new_event_ring.ring.register(); -+ let erstba = new_event_ring.erstba(); -+ -+ { -+ let mut event_ring = self -+ .hci -+ .primary_event_ring -+ .lock() -+ .unwrap_or_else(|e| e.into_inner()); -+ *event_ring = new_event_ring; -+ } -+ -+ { -+ let int = &mut self.hci.run.lock().unwrap_or_else(|e| e.into_inner()).ints[0]; -+ int.erdp_low.write(erdp as u32 | (1 << 3)); -+ int.erdp_high.write((erdp as u64 >> 32) as u32); -+ int.erstba_low.write(erstba as u32); -+ int.erstba_high.write((erstba as u64 >> 32) as u32); -+ } -+ -+ log::info!( -+ "event ring grown to {} entries, ERDP={:X}, ERSTBA={:X}", -+ new_size, -+ erdp, -+ erstba -+ ); - } - - pub fn run(self) -> ! { -@@ -570,7 +696,7 @@ impl EventDoorbell { - - pub fn ring(self) { - trace!("Ring doorbell {} with data {}", self.index, self.data); -- self.dbs.lock().unwrap()[self.index].write(self.data); -+ self.dbs.lock().unwrap_or_else(|e| e.into_inner())[self.index].write(self.data); - trace!("Doorbell was rung."); - } - } -@@ -595,20 +721,26 @@ impl Future for EventTrbFuture { - ref state, - ref sender, - ref mut doorbell_opt, -- } => match state.message.lock().unwrap().take() { -+ } => match state -+ .message -+ .lock() -+ .unwrap_or_else(|e| e.into_inner()) -+ .take() -+ { - Some(message) => message, - - None => { - // Register state with IRQ reactor - trace!("Send state {:X?}", state.state_kind); -- sender -- .send(State { -- message: Arc::clone(&state.message), -- is_isoch_or_vf: state.is_isoch_or_vf, -- kind: state.state_kind, -- waker: context.waker().clone(), -- }) -- .expect("IRQ reactor thread unexpectedly stopped"); -+ if let Err(err) = sender.send(State { -+ message: Arc::clone(&state.message), -+ is_isoch_or_vf: state.is_isoch_or_vf, -+ kind: state.state_kind, -+ waker: context.waker().clone(), -+ }) { -+ log::error!("IRQ reactor state channel closed: {}", err); -+ panic!("IRQ reactor state channel closed: {err}"); -+ } - - // Doorbell must be rung after sending state - if let Some(doorbell) = doorbell_opt.take() { -@@ -667,15 +799,20 @@ impl Xhci { - first_trb: &Trb, - last_trb: &Trb, - doorbell: EventDoorbell, -- ) -> impl Future + Send + Sync + 'static { -+ ) -> Result + Send + Sync + 'static> { - if !last_trb.is_transfer_trb() { -- panic!("Invalid TRB type given to next_transfer_event_trb(): {} (TRB {:?}. Expected transfer TRB.", last_trb.trb_type(), last_trb) -+ error!( -+ "Invalid TRB type given to next_transfer_event_trb(): {} (TRB {:?}). Expected transfer TRB.", -+ last_trb.trb_type(), -+ last_trb -+ ); -+ return Err(Error::new(EIO)); - } - - let is_isoch_or_vf = last_trb.trb_type() == TrbType::Isoch as u8; -- let first_phys_ptr = ring.trb_phys_ptr(self.cap.ac64(), first_trb); -- let last_phys_ptr = ring.trb_phys_ptr(self.cap.ac64(), last_trb); -- EventTrbFuture::Pending { -+ let first_phys_ptr = ring.trb_phys_ptr(self.cap.ac64(), first_trb)?; -+ let last_phys_ptr = ring.trb_phys_ptr(self.cap.ac64(), last_trb)?; -+ Ok(EventTrbFuture::Pending { - state: FutureState { - is_isoch_or_vf, - state_kind: StateKind::Transfer { -@@ -687,38 +824,39 @@ impl Xhci { - }, - sender: self.irq_reactor_sender.clone(), - doorbell_opt: Some(doorbell), -- } -+ }) - } - pub fn next_command_completion_event_trb( - &self, - command_ring: &Ring, - trb: &Trb, - doorbell: EventDoorbell, -- ) -> impl Future + Send + Sync + 'static { -- trace!( -- "Sending command at phys_ptr {:X}", -- command_ring.trb_phys_ptr(self.cap.ac64(), trb) -- ); -+ ) -> Result + Send + Sync + 'static> { - if !trb.is_command_trb() { -- panic!("Invalid TRB type given to next_command_completion_event_trb(): {} (TRB {:?}. Expected command TRB.", trb.trb_type(), trb) -+ error!( -+ "Invalid TRB type given to next_command_completion_event_trb(): {} (TRB {:?}). Expected command TRB.", -+ trb.trb_type(), -+ trb -+ ); -+ return Err(Error::new(EIO)); - } -- EventTrbFuture::Pending { -+ let phys_ptr = command_ring.trb_phys_ptr(self.cap.ac64(), trb)?; -+ trace!("Sending command at phys_ptr {:X}", phys_ptr); -+ Ok(EventTrbFuture::Pending { - state: FutureState { - // This is only possible for transfers if they are isochronous, or for Force Event TRBs (virtualization). - is_isoch_or_vf: false, -- state_kind: StateKind::CommandCompletion { -- phys_ptr: command_ring.trb_phys_ptr(self.cap.ac64(), trb), -- }, -+ state_kind: StateKind::CommandCompletion { phys_ptr }, - message: Arc::new(Mutex::new(None)), - }, - sender: self.irq_reactor_sender.clone(), - doorbell_opt: Some(doorbell), -- } -+ }) - } - pub fn next_misc_event_trb( - &self, - trb_type: TrbType, -- ) -> impl Future + Send + Sync + 'static { -+ ) -> Result + Send + Sync + 'static> { - let valid_trb_types = [ - TrbType::PortStatusChange as u8, - TrbType::BandwidthRequest as u8, -@@ -728,9 +866,13 @@ impl Xhci { - TrbType::MfindexWrap as u8, - ]; - if !valid_trb_types.contains(&(trb_type as u8)) { -- panic!("Invalid TRB type given to next_misc_event_trb(): {:?}. Only event TRB types that are neither transfer events or command completion events can be used.", trb_type) -+ error!( -+ "Invalid TRB type given to next_misc_event_trb(): {:?}. Only event TRB types that are neither transfer events or command completion events can be used.", -+ trb_type -+ ); -+ return Err(Error::new(EIO)); - } -- EventTrbFuture::Pending { -+ Ok(EventTrbFuture::Pending { - state: FutureState { - is_isoch_or_vf: false, - state_kind: StateKind::Other(trb_type), -@@ -738,6 +880,6 @@ impl Xhci { - }, - sender: self.irq_reactor_sender.clone(), - doorbell_opt: None, -- } -+ }) - } - } diff --git a/drivers/usb/xhcid/src/xhci/mod.rs b/drivers/usb/xhcid/src/xhci/mod.rs -index f2143676..a51b98c1 100644 +index f2143676..c53cb59f 100644 --- a/drivers/usb/xhcid/src/xhci/mod.rs +++ b/drivers/usb/xhcid/src/xhci/mod.rs -@@ -110,7 +110,7 @@ impl Xhci { - .get_mut(&0) - .ok_or(Error::new(EIO))? - .ring() -- .expect("no ring for the default control pipe"); -+ .ok_or(Error::new(EIO))?; - - let first_index = ring.next_index(); - let (cmd, cycle) = (&mut ring.trbs[first_index], ring.cycle); -@@ -140,7 +140,7 @@ impl Xhci { - &ring.trbs[first_index], - &ring.trbs[last_index], - EventDoorbell::new(self, usize::from(slot), Self::def_control_endp_doorbell()), -- ) -+ )? - }; - - debug!("Waiting for the next transfer event TRB..."); -@@ -485,7 +485,11 @@ impl Xhci { - pub fn init(&mut self, max_slots: u8) -> Result<()> { - // Set run/stop to 0 - debug!("Stopping xHC."); -- self.op.get_mut().unwrap().usb_cmd.writef(USB_CMD_RS, false); -+ self.op -+ .get_mut() -+ .unwrap_or_else(|e| e.into_inner()) -+ .usb_cmd -+ .writef(USB_CMD_RS, false); - - // Warm reset - { -@@ -493,10 +497,16 @@ impl Xhci { - let timeout = Timeout::from_secs(1); - self.op - .get_mut() -- .unwrap() -+ .unwrap_or_else(|e| e.into_inner()) - .usb_cmd - .writef(USB_CMD_HCRST, true); -- while self.op.get_mut().unwrap().usb_cmd.readf(USB_CMD_HCRST) { -+ while self -+ .op -+ .get_mut() -+ .unwrap_or_else(|e| e.into_inner()) -+ .usb_cmd -+ .readf(USB_CMD_HCRST) -+ { - timeout.run().map_err(|()| { - log::error!("timeout on USB_CMD_HCRST"); - Error::new(EIO) -@@ -506,51 +516,76 @@ impl Xhci { - - // Set enabled slots - debug!("Setting enabled slots to {}.", max_slots); -- self.op.get_mut().unwrap().config.write(max_slots as u32); -+ self.op -+ .get_mut() -+ .unwrap_or_else(|e| e.into_inner()) -+ .config -+ .write(max_slots as u32); - debug!( - "Enabled Slots: {}", -- self.op.get_mut().unwrap().config.read() & 0xFF -+ self.op -+ .get_mut() -+ .unwrap_or_else(|e| e.into_inner()) -+ .config -+ .read() -+ & 0xFF - ); - - // Set device context address array pointer - let dcbaap = self.dev_ctx.dcbaap(); - debug!("Writing DCBAAP: {:X}", dcbaap); -- self.op.get_mut().unwrap().dcbaap_low.write(dcbaap as u32); - self.op - .get_mut() -- .unwrap() -+ .unwrap_or_else(|e| e.into_inner()) -+ .dcbaap_low -+ .write(dcbaap as u32); -+ self.op -+ .get_mut() -+ .unwrap_or_else(|e| e.into_inner()) - .dcbaap_high - .write((dcbaap as u64 >> 32) as u32); - - // Set command ring control register -- let crcr = self.cmd.get_mut().unwrap().register(); -+ let crcr = self.cmd.get_mut().unwrap_or_else(|e| e.into_inner()).register(); - assert_eq!(crcr & 0xFFFF_FFFF_FFFF_FFC1, crcr, "unaligned CRCR"); - debug!("Writing CRCR: {:X}", crcr); -- self.op.get_mut().unwrap().crcr_low.write(crcr as u32); - self.op - .get_mut() -- .unwrap() -+ .unwrap_or_else(|e| e.into_inner()) -+ .crcr_low -+ .write(crcr as u32); -+ self.op -+ .get_mut() -+ .unwrap_or_else(|e| e.into_inner()) - .crcr_high - .write((crcr as u64 >> 32) as u32); - - // Set event ring segment table registers - debug!( - "Interrupter 0: {:p}", -- self.run.get_mut().unwrap().ints.as_ptr() -+ self.run.get_mut().unwrap_or_else(|e| e.into_inner()).ints.as_ptr() - ); - { -- let int = &mut self.run.get_mut().unwrap().ints[0]; -+ let int = &mut self.run.get_mut().unwrap_or_else(|e| e.into_inner()).ints[0]; - - let erstz = 1; - debug!("Writing ERSTZ: {}", erstz); - int.erstsz.write(erstz); - -- let erdp = self.primary_event_ring.get_mut().unwrap().erdp(); -+ let erdp = self -+ .primary_event_ring -+ .get_mut() -+ .unwrap_or_else(|e| e.into_inner()) -+ .dequeue_ptr(); - debug!("Writing ERDP: {:X}", erdp); - int.erdp_low.write(erdp as u32 | (1 << 3)); - int.erdp_high.write((erdp as u64 >> 32) as u32); - -- let erstba = self.primary_event_ring.get_mut().unwrap().erstba(); -+ let erstba = self -+ .primary_event_ring -+ .get_mut() -+ .unwrap_or_else(|e| e.into_inner()) -+ .erstba(); - debug!("Writing ERSTBA: {:X}", erstba); - int.erstba_low.write(erstba as u32); - int.erstba_high.write((erstba as u64 >> 32) as u32); -@@ -563,7 +598,7 @@ impl Xhci { - } - self.op - .get_mut() -- .unwrap() -+ .unwrap_or_else(|e| e.into_inner()) - .usb_cmd - .writef(USB_CMD_INTE, true); - -@@ -572,12 +607,22 @@ impl Xhci { - - // Set run/stop to 1 - debug!("Starting xHC."); -- self.op.get_mut().unwrap().usb_cmd.writef(USB_CMD_RS, true); -+ self.op -+ .get_mut() -+ .unwrap_or_else(|e| e.into_inner()) -+ .usb_cmd -+ .writef(USB_CMD_RS, true); - - { - debug!("Waiting for start request to complete."); - let timeout = Timeout::from_secs(1); -- while self.op.get_mut().unwrap().usb_sts.readf(USB_STS_HCH) { -+ while self -+ .op -+ .get_mut() -+ .unwrap_or_else(|e| e.into_inner()) -+ .usb_sts -+ .readf(USB_STS_HCH) -+ { - timeout.run().map_err(|()| { - log::error!("timeout on USB_STS_HCH"); - Error::new(EIO) -@@ -587,11 +632,14 @@ impl Xhci { - - // Ring command doorbell - debug!("Ringing command doorbell."); -- self.dbs.lock().unwrap()[0].write(0); -+ self.dbs.lock().unwrap_or_else(|e| e.into_inner())[0].write(0); - - debug!("XHCI initialized."); - -- self.op.get_mut().unwrap().set_cie(self.cap.cic()); -+ self.op -+ .get_mut() -+ .unwrap_or_else(|e| e.into_inner()) -+ .set_cie(self.cap.cic()); - - self.print_port_capabilities(); - -@@ -599,15 +647,20 @@ impl Xhci { - } - - pub fn get_pls(&self, port_id: PortId) -> u8 { -- let mut ports = self.ports.lock().unwrap(); -- let port = ports.get_mut(port_id.root_hub_port_index()).unwrap(); -- port.state() -+ let mut ports = self.ports.lock().unwrap_or_else(|e| e.into_inner()); -+ match ports.get_mut(port_id.root_hub_port_index()) { -+ Some(port) => port.state(), -+ None => { -+ warn!("get_pls: invalid root hub port index {}", port_id.root_hub_port_index()); -+ 0 -+ } -+ } - } - - pub fn poll(&self) { - debug!("Polling Initial Devices!"); - -- let len = self.ports.lock().unwrap().len(); -+ let len = self.ports.lock().unwrap_or_else(|e| e.into_inner()).len(); - - for root_hub_port_num in 1..=(len as u8) { - let port_id = PortId { -@@ -617,7 +670,7 @@ impl Xhci { - - //Get the CCS and CSC flags - let (ccs, csc, flags) = { -- let mut ports = self.ports.lock().unwrap(); -+ let mut ports = self.ports.lock().unwrap_or_else(|e| e.into_inner()); - let port = &mut ports[port_id.root_hub_port_index()]; - let flags = port.flags(); - let ccs = flags.contains(PortFlags::CCS); -@@ -633,10 +686,11 @@ impl Xhci { - //Do nothing - } - _ => { -- //Either something is connected, or nothing is connected and a port status change was asserted. -- self.device_enumerator_sender -+ if let Err(err) = self.device_enumerator_sender - .send(DeviceEnumerationRequest { port_id }) -- .expect("Failed to generate the port enumeration request!"); -+ { -+ log::error!("port {}: failed to send enumeration request: {}", port_id, err); -+ } - } - } - } -@@ -645,7 +699,7 @@ impl Xhci { - pub fn print_port_capabilities(&self) { - let len; - { -- let mut ports = self.ports.lock().unwrap(); -+ let mut ports = self.ports.lock().unwrap_or_else(|e| e.into_inner()); - len = ports.len(); - } - -@@ -658,7 +712,7 @@ impl Xhci { - let state = self.get_pls(port_id); - let mut flags; - { -- let mut ports = self.ports.lock().unwrap(); -+ let mut ports = self.ports.lock().unwrap_or_else(|e| e.into_inner()); - - flags = ports[port_id.root_hub_port_index()].flags(); - } -@@ -684,9 +738,11 @@ impl Xhci { - pub fn reset_port(&self, port_id: PortId) -> Result<()> { - debug!("XHCI Port {} reset", port_id); - -- //TODO handle the second unwrap -- let mut ports = self.ports.lock().unwrap(); -- let port = ports.get_mut(port_id.root_hub_port_index()).unwrap(); -+ let mut ports = self.ports.lock().unwrap_or_else(|e| e.into_inner()); -+ let port = ports.get_mut(port_id.root_hub_port_index()).ok_or_else(|| { -+ warn!("reset_port: invalid root hub port index {}", port_id.root_hub_port_index()); -+ Error::new(EIO) -+ })?; - let instant = std::time::Instant::now(); - - debug!("Port {} Link State: {}", port_id, port.state()); -@@ -731,7 +787,7 @@ impl Xhci { - { - // If ERDP EHB bit is set, clear it before sending command - //TODO: find out why this bit is set earlier! -- let mut run = self.run.lock().unwrap(); -+ let mut run = self.run.lock().unwrap_or_else(|e| e.into_inner()); - let mut int = &mut run.ints[index]; - - if int.erdp_low.readf(1 << 3) { -@@ -743,7 +799,7 @@ impl Xhci { - } - - pub fn interrupt_is_pending(&self, index: usize) -> bool { -- let mut run = self.run.lock().unwrap(); -+ let mut run = self.run.lock().unwrap_or_else(|e| e.into_inner()); - let mut int = &mut run.ints[index]; - int.erdp_low.readf(1 << 3) - } -@@ -753,7 +809,7 @@ impl Xhci { - - let (event_trb, command_trb) = self - .execute_command(|cmd, cycle| cmd.enable_slot(slot_ty, cycle)) -- .await; -+ .await?; - - trace!("Slot is enabled!"); - self::scheme::handle_event_trb("ENABLE_SLOT", &event_trb, &command_trb)?; -@@ -765,7 +821,7 @@ impl Xhci { - trace!("Disable slot {}", slot); - let (event_trb, command_trb) = self - .execute_command(|cmd, cycle| cmd.disable_slot(slot, cycle)) -- .await; -+ .await?; - - self::scheme::handle_event_trb("DISABLE_SLOT", &event_trb, &command_trb)?; - //self.event_handler_finished(); -@@ -793,19 +849,58 @@ impl Xhci { - } - - pub async fn attach_device(&self, port_id: PortId) -> syscall::Result<()> { -+ self.attach_device_with_speed(port_id, None).await -+ } +@@ -311,6 +311,14 @@ struct PortState { + input_context: Mutex>>, + dev_desc: Option, + endpoint_states: BTreeMap, ++ quirks: crate::usb_quirks::UsbQuirkFlags, ++ pm_state: PortPmState, ++} + -+ pub async fn attach_device_with_speed( -+ &self, -+ port_id: PortId, -+ speed_override: Option, -+ ) -> syscall::Result<()> { - if self.port_states.contains_key(&port_id) { - debug!("Already contains port {}", port_id); - return Err(syscall::Error::new(EAGAIN)); - } ++#[derive(Clone, Copy, Debug, Eq, PartialEq)] ++pub(crate) enum PortPmState { ++ Active, ++ Suspended, + } -- let (data, state, speed, flags) = { -- let port = &self.ports.lock().unwrap()[port_id.root_hub_port_index()]; -+ let (data, state, portsc_speed, flags) = { -+ let port = &self.ports.lock().unwrap_or_else(|e| e.into_inner())[port_id.root_hub_port_index()]; - (port.read(), port.state(), port.speed(), port.flags()) - }; - -+ let speed = match speed_override { -+ Some(byte) => { -+ let category_res = UsbSpeed::try_from(byte).ok(); -+ match category_res { -+ Some(category) => match self.lookup_speed_category(port_id, category) { -+ Some(proto_speed) => { -+ let psiv = proto_speed.psiv(); -+ log::info!( -+ "port {} speed override {:?} mapped to PSIV {}", -+ port_id, -+ category, -+ psiv -+ ); -+ psiv -+ } -+ None => { -+ log::warn!( -+ "port {} no protocol speed found for {:?}, falling back to PORTSC speed {}", -+ port_id, -+ category, -+ portsc_speed -+ ); -+ portsc_speed -+ } -+ }, -+ None => portsc_speed, -+ } -+ } -+ None => portsc_speed, -+ }; -+ - debug!( -- "XHCI Port {}: {:X}, State {}, Speed {}, Flags {:?}", -- port_id, data, state, speed, flags -+ "XHCI Port {}: {:X}, State {}, Speed {} (override {:?})", -+ port_id, data, state, speed, speed_override + impl PortState { +@@ -809,6 +817,7 @@ impl Xhci { ); if flags.contains(port::PortFlags::CCS) { -@@ -829,10 +924,13 @@ impl Xhci { ++ let early_quirks = crate::usb_quirks::lookup_usb_quirks_early(port_id); + let slot_ty = match self.supported_protocol(port_id) { + Some(protocol) => protocol.proto_slot_ty(), + None => { +@@ -838,7 +847,15 @@ impl Xhci { - debug!("Enabled port {}, which the xHC mapped to {}", port_id, slot); - -- //TODO: get correct speed for child devices -- let protocol_speed = self -- .lookup_psiv(port_id, speed) -- .expect("Failed to retrieve speed ID"); -+ let protocol_speed = match self.lookup_psiv(port_id, speed) { -+ Some(ps) => ps, -+ None => { -+ log::error!("port {}: no protocol speed for PSIV {}, cannot attach", port_id, speed); -+ return Err(syscall::Error::new(syscall::EINVAL)); -+ } -+ }; - - let mut input = unsafe { self.alloc_dma_zeroed::>()? }; - -@@ -873,9 +971,12 @@ impl Xhci { - // Ensure correct packet size is used - let dev_desc_8_byte = self.fetch_dev_desc_8_byte(port_id, slot).await?; + debug!("Attempting to address the device"); + let mut ring = match self +- .address_device(&mut input, port_id, slot_ty, slot, protocol_speed, speed) ++ .address_device( ++ &mut input, ++ port_id, ++ slot_ty, ++ slot, ++ protocol_speed, ++ speed, ++ early_quirks, ++ ) + .await { -- let mut port_state = self.port_states.get_mut(&port_id).unwrap(); -+ let mut port_state = self.port_states.get_mut(&port_id).ok_or_else(|| { -+ warn!("fetch_descriptors: missing port state for {}", port_id); -+ Error::new(EIO) -+ })?; - -- let mut input = port_state.input_context.lock().unwrap(); -+ let mut input = port_state.input_context.lock().unwrap_or_else(|e| e.into_inner()); - - self.update_max_packet_size(&mut *input, slot, dev_desc_8_byte) - .await?; -@@ -885,15 +986,24 @@ impl Xhci { + Ok(device_ring) => device_ring, +@@ -866,6 +883,8 @@ impl Xhci { + }, + )) + .collect::>(), ++ quirks: early_quirks, ++ pm_state: PortPmState::Active, + }; + self.port_states.insert(port_id, port_state); + debug!("Got port states!"); +@@ -884,8 +903,14 @@ impl Xhci { + debug!("Got the 8 byte dev descriptor: {:X?}", dev_desc_8_byte); let dev_desc = self.get_desc(port_id, slot).await?; ++ let quirks = early_quirks ++ | crate::usb_quirks::lookup_usb_quirks(dev_desc.vendor, dev_desc.product); debug!("Got the full device descriptor!"); - self.port_states.get_mut(&port_id).unwrap().dev_desc = Some(dev_desc); -+ self.port_states.get_mut(&port_id).ok_or_else(|| { -+ warn!("fetch_descriptors: missing port state for {}", port_id); -+ Error::new(EIO) -+ })?.dev_desc = Some(dev_desc); ++ { ++ let mut port_state = self.port_states.get_mut(&port_id).unwrap(); ++ port_state.quirks = quirks; ++ port_state.dev_desc = Some(dev_desc); ++ } debug!("Got the port states again!"); { -- let mut port_state = self.port_states.get_mut(&port_id).unwrap(); -+ let mut port_state = self.port_states.get_mut(&port_id).ok_or_else(|| { -+ warn!("fetch_descriptors: missing port state for {}", port_id); -+ Error::new(EIO) -+ })?; +@@ -1052,6 +1077,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 +1188,16 @@ impl Xhci { -- let mut input = port_state.input_context.lock().unwrap(); -+ let mut input = port_state.input_context.lock().unwrap_or_else(|e| e.into_inner()); - debug!("Got the input context!"); -- let dev_desc = port_state.dev_desc.as_ref().unwrap(); -+ let dev_desc = port_state.dev_desc.as_ref().ok_or_else(|| { -+ warn!("fetch_descriptors: device descriptor not set for {}", port_id); -+ Error::new(EIO) -+ })?; + let input_context_physical = input_context.physical(); - self.update_default_control_pipe(&mut *input, slot, dev_desc) - .await?; -@@ -932,12 +1042,12 @@ impl Xhci { - ); - } - None => { -- //TODO: kill harder - warn!( -- "driver process {} for port {} still running", -+ "driver process {} for port {} still running, sending SIGKILL", - child.id(), - port_id - ); -+ let _ = child.kill(); - } - }, - Err(err) => { -@@ -1001,7 +1111,7 @@ impl Xhci { - .execute_command(|trb, cycle| { - trb.evaluate_context(slot_id, input_context.physical(), false, cycle) - }) +- let (event_trb, _) = self +- .execute_command(|trb, cycle| { +- trb.address_device(slot, input_context_physical, false, cycle) +- }) - .await; -+ .await?; - - self::scheme::handle_event_trb("EVALUATE_CONTEXT", &event_trb, &command_trb)?; - //self.event_handler_finished(); -@@ -1035,7 +1145,7 @@ impl Xhci { - .execute_command(|trb, cycle| { - trb.evaluate_context(slot_id, input_context.physical(), false, cycle) - }) -- .await; -+ .await?; - debug!("Completed the command to update the default control pipe"); - - self::scheme::handle_event_trb("EVALUATE_CONTEXT", &event_trb, &command_trb)?; -@@ -1060,13 +1170,10 @@ impl Xhci { - if let Some((parent_port, port_num)) = port.parent() { - match self.port_states.get(&parent_port) { - Some(parent_state) => { -- // parent info must be supplied if: - let mut needs_parent_info = false; -+ // parent info must be supplied if: - // 1. the device is low or full speed and connected through a high speed hub -- //TODO: determine device speed (speed is not accurate as it comes from the port) - // 2. the device is superspeed and connected through a higher rank hub -- //TODO: determine device speed (speed is not accurate as it comes from the port) -- // For now, this is just set to true to force things to work - needs_parent_info = true; - if needs_parent_info { - parent_hub_slot_id = parent_state.slot; -@@ -1110,8 +1217,7 @@ impl Xhci { - | (u32::from(number_of_ports) << 24), - ); - -- // TODO -- let ttt = 0u8; -+ let ttt = 0u8; // TODO: read from parent hub HubDesc think_time field - let interrupter = 0u8; - - assert_eq!(ttt & 0b11, ttt); -@@ -1133,7 +1239,7 @@ impl Xhci { - 512 - }; - let host_initiate_disable = false; // only applies to streams -- let max_burst_size = 0u8; // TODO -+ let max_burst_size = 0u8; - - assert_eq!(max_error_count & 0b11, max_error_count); - input_context.device.endpoints[0].b.write( -@@ -1166,7 +1272,7 @@ impl Xhci { - .execute_command(|trb, cycle| { - trb.address_device(slot, input_context_physical, false, cycle) - }) -- .await; -+ .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!( -@@ -1190,7 +1296,7 @@ impl Xhci { - /// Checks whether an IRQ has been received from *this* device, in case of an interrupt. Always - /// true when using MSI/MSI-X. - pub fn received_irq(&self) -> bool { -- let mut runtime_regs = self.run.lock().unwrap(); -+ let mut runtime_regs = self.run.lock().unwrap_or_else(|e| e.into_inner()); - - if self.uses_msi_interrupts() { - // Since using MSI and MSI-X implies having no IRQ sharing whatsoever, the IP bit -@@ -1224,7 +1330,10 @@ impl Xhci { - // TODO: Now that there are some good error crates, I don't think errno.h error codes are - // suitable here. - -- let ps = self.port_states.get(&port).unwrap(); -+ let ps = self.port_states.get(&port).ok_or_else(|| { -+ warn!("spawn_drivers: missing port state for {}", port); -+ Error::new(EIO) -+ })?; - trace!("Spawning driver on port: {}", port); - - //TODO: support choosing config? -@@ -1243,7 +1352,10 @@ impl Xhci { - })?; - - trace!("Got config and device descriptors on port {}", port); -- let drivers_usercfg: &DriversConfig = &DRIVERS_CONFIG; -+ let drivers_usercfg = DRIVERS_CONFIG.as_ref().map_err(|err| { -+ error!("failed to parse internally embedded xhcid drivers config: {}", err); -+ Error::new(EIO) -+ })?; - - for ifdesc in config_desc.interface_descs.iter() { - //TODO: support alternate settings -@@ -1435,13 +1547,27 @@ impl Xhci { - self.supported_protocol_speeds(port) - .find(|speed| speed.psiv() == psiv) - } -+ -+ fn lookup_speed_category( -+ &self, -+ port: PortId, -+ category: UsbSpeed, -+ ) -> Option<&'static ProtocolSpeed> { -+ self.supported_protocol_speeds(port).find(|speed| match category { -+ UsbSpeed::Low => speed.is_lowspeed(), -+ UsbSpeed::Full => speed.is_fullspeed(), -+ UsbSpeed::High => speed.is_highspeed(), -+ UsbSpeed::Super => speed.is_superspeed_gen1x1(), -+ UsbSpeed::SuperPlus => speed.is_superspeed_gen_x() && !speed.is_superspeed_gen1x1(), -+ }) -+ } - } - pub fn start_irq_reactor(hci: &Arc>, irq_file: Option) { - let hci_clone = Arc::clone(&hci); - - debug!("About to start IRQ reactor"); - -- *hci.irq_reactor.lock().unwrap() = Some(thread::spawn(move || { -+ *hci.irq_reactor.lock().unwrap_or_else(|e| e.into_inner()) = Some(thread::spawn(move || { - debug!("Started IRQ reactor thread"); - IrqReactor::new(hci_clone, irq_file).run() - })); -@@ -1452,7 +1578,7 @@ pub fn start_device_enumerator(hci: &Arc>) { - - debug!("About to start Device Enumerator"); - -- *hci.device_enumerator.lock().unwrap() = Some(thread::spawn(move || { -+ *hci.device_enumerator.lock().unwrap_or_else(|e| e.into_inner()) = Some(thread::spawn(move || { - debug!("Started Device Enumerator"); - DeviceEnumerator::new(hci_clone).run(); - })); -@@ -1480,10 +1606,10 @@ use crate::xhci::port::PortFlags; - use lazy_static::lazy_static; - - lazy_static! { -- static ref DRIVERS_CONFIG: DriversConfig = { -+ static ref DRIVERS_CONFIG: std::result::Result = { - // TODO: Load this at runtime. - const TOML: &'static [u8] = include_bytes!("../../drivers.toml"); - -- toml::from_slice::(TOML).expect("Failed to parse internally embedded config file") -+ toml::from_slice::(TOML) - }; - } -diff --git a/drivers/usb/xhcid/src/xhci/ring.rs b/drivers/usb/xhcid/src/xhci/ring.rs -index 8e187ebe..c05ed8fb 100644 ---- a/drivers/usb/xhcid/src/xhci/ring.rs -+++ b/drivers/usb/xhcid/src/xhci/ring.rs -@@ -1,6 +1,7 @@ - use std::mem; - --use syscall::error::Result; -+use log::error; -+use syscall::error::{Error, Result, EIO}; - - use common::dma::Dma; - -@@ -108,7 +109,7 @@ impl Ring { - pub(crate) fn end_virt_addr(&self) -> *const Trb { - unsafe { self.start_virt_addr().offset(self.trbs.len() as isize) } - } -- pub fn trb_phys_ptr(&self, ac64: bool, trb: &Trb) -> u64 { -+ pub fn trb_phys_ptr(&self, ac64: bool, trb: &Trb) -> Result { - let trb_virt_pointer = trb as *const Trb; - let trbs_base_virt_pointer = self.trbs.as_ptr(); - -@@ -116,7 +117,12 @@ impl Ring { - || (trb_virt_pointer as usize) - > (trbs_base_virt_pointer as usize) + self.trbs.len() * mem::size_of::() - { -- panic!("Gave a TRB outside of the ring, when retrieving its physical address in that ring. TRB: {:?} (at address {:p})", trb, trb); -+ error!( -+ "Gave a TRB outside of the ring when retrieving its physical address. TRB: {:?} (at address {:p})", -+ trb, -+ trb -+ ); -+ return Err(Error::new(EIO)); - } - let trb_offset_from_base = trb_virt_pointer as u64 - trbs_base_virt_pointer as u64; - -@@ -127,7 +133,7 @@ impl Ring { - 0xFFFF_FFFF - }; - let trb_phys_ptr = trbs_base_phys_ptr + trb_offset_from_base; -- trb_phys_ptr -+ Ok(trb_phys_ptr) - } - /* - /// Endless mutable iterator that iterates through the ring items, over and over again. The diff --git a/drivers/usb/xhcid/src/xhci/scheme.rs b/drivers/usb/xhcid/src/xhci/scheme.rs -index f2d439a4..5868d13d 100644 +index f2d439a4..b0fb9b85 100644 --- a/drivers/usb/xhcid/src/xhci/scheme.rs +++ b/drivers/usb/xhcid/src/xhci/scheme.rs -@@ -54,31 +54,37 @@ use crate::driver_interface::*; - use regex::Regex; +@@ -24,6 +24,7 @@ use std::{cmp, fmt, io, mem, str}; - lazy_static! { -- static ref REGEX_PORT_CONFIGURE: Regex = Regex::new(r"^port([\d\.]+)/configure$") -- .expect("Failed to create the regex for the port/configure scheme."); -- static ref REGEX_PORT_ATTACH: Regex = Regex::new(r"^port([\d\.]+)/attach$") -- .expect("Failed to create the regex for the port/attach scheme."); -- static ref REGEX_PORT_DETACH: Regex = Regex::new(r"^port([\d\.]+)/detach$") -- .expect("Failed to create the regex for the port/detach scheme."); -- static ref REGEX_PORT_DESCRIPTORS: Regex = Regex::new(r"^port([\d\.]+)/descriptors$") -- .expect("Failed to create the regex for the port/descriptors"); -- static ref REGEX_PORT_STATE: Regex = Regex::new(r"^port([\d\.]+)/state$") -- .expect("Failed to create the regex for the port/state scheme"); -- static ref REGEX_PORT_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$") -- .expect("Failed to create the regex for the port/endpoints scheme"); -- static ref REGEX_PORT_SPECIFIC_ENDPOINT: Regex = -- Regex::new(r"^port([\d\.]+)/endpoints/(\d{1,3})$") -- .expect("Failed to create the regex for the port/endpoints/ scheme"); -- static ref REGEX_PORT_SUB_ENDPOINT: Regex = Regex::new( -- r"port([\d\.]+)/endpoints/(\d{1,3})/(ctl|data)$" -- ) -- .expect("Failed to create the regex for the port/endpoints// scheme"); -- static ref REGEX_PORT_ROOT: Regex = -- Regex::new(r"^port([\d\.]+)$").expect("Failed to create the regex for the port scheme."); -- static ref REGEX_TOP_LEVEL: Regex = -- Regex::new(r"^$").expect("Failed to create the regex for the top-level scheme"); -+ static ref REGEX_PORT_CONFIGURE: std::result::Result = -+ Regex::new(r"^port([\d\.]+)/configure$"); -+ static ref REGEX_PORT_ATTACH: std::result::Result = -+ Regex::new(r"^port([\d\.]+)/attach$"); -+ static ref REGEX_PORT_DETACH: std::result::Result = -+ Regex::new(r"^port([\d\.]+)/detach$"); -+ static ref REGEX_PORT_DESCRIPTORS: std::result::Result = -+ Regex::new(r"^port([\d\.]+)/descriptors$"); -+ static ref REGEX_PORT_STATE: std::result::Result = -+ Regex::new(r"^port([\d\.]+)/state$"); -+ static ref REGEX_PORT_REQUEST: std::result::Result = -+ Regex::new(r"^port([\d\.]+)/request$"); -+ static ref REGEX_PORT_ENDPOINTS: std::result::Result = -+ Regex::new(r"^port([\d\.]+)/endpoints$"); -+ static ref REGEX_PORT_SPECIFIC_ENDPOINT: std::result::Result = -+ Regex::new(r"^port([\d\.]+)/endpoints/(\d{1,3})$"); -+ static ref REGEX_PORT_SUB_ENDPOINT: std::result::Result = -+ Regex::new(r"port([\d\.]+)/endpoints/(\d{1,3})/(ctl|data)$"); -+ static ref REGEX_PORT_ROOT: std::result::Result = -+ Regex::new(r"^port([\d\.]+)$"); -+ static ref REGEX_TOP_LEVEL: std::result::Result = Regex::new(r"^$"); -+} -+ -+fn compiled_regex( -+ regex: &'static std::result::Result, -+ description: &'static str, -+) -> Result<&'static Regex> { -+ regex.as_ref().map_err(|err| { -+ error!("failed to compile {} regex: {}", description, err); -+ Error::new(EIO) -+ }) + use common::dma::Dma; + use futures::executor::block_on; ++use futures::FutureExt; + use log::{debug, error, info, trace, warn}; + use redox_scheme::scheme::SchemeSync; + use smallvec::SmallVec; +@@ -32,9 +33,9 @@ use common::io::Io; + use redox_scheme::{CallerCtx, OpenResult}; + use syscall::schemev2::NewFdFlags; + use syscall::{ +- Error, Result, Stat, EACCES, EBADF, EBADFD, EBADMSG, EINVAL, EIO, EISDIR, ENOENT, ENOSYS, +- ENOTDIR, EOPNOTSUPP, EPROTO, ESPIPE, MODE_CHR, MODE_DIR, MODE_FILE, O_DIRECTORY, O_RDWR, +- O_STAT, O_WRONLY, SEEK_CUR, SEEK_END, SEEK_SET, ++ Error, Result, Stat, EACCES, EBADF, EBADFD, EBADMSG, EBUSY, EINVAL, EIO, EISDIR, ENOENT, ++ ENOSYS, ENOTDIR, EOPNOTSUPP, EPROTO, ESPIPE, MODE_CHR, MODE_DIR, MODE_FILE, O_DIRECTORY, ++ O_RDWR, O_STAT, O_WRONLY, SEEK_CUR, SEEK_END, SEEK_SET, + }; + + use super::{port, usb}; +@@ -60,6 +61,10 @@ lazy_static! { + .expect("Failed to create the regex for the port/attach scheme."); + static ref REGEX_PORT_DETACH: Regex = Regex::new(r"^port([\d\.]+)/detach$") + .expect("Failed to create the regex for the port/detach scheme."); ++ static ref REGEX_PORT_SUSPEND: Regex = Regex::new(r"^port([\d\.]+)/suspend$") ++ .expect("Failed to create the regex for the port/suspend scheme."); ++ static ref REGEX_PORT_RESUME: Regex = Regex::new(r"^port([\d\.]+)/resume$") ++ .expect("Failed to create the regex for the port/resume scheme."); + static ref REGEX_PORT_DESCRIPTORS: Regex = Regex::new(r"^port([\d\.]+)/descriptors$") + .expect("Failed to create the regex for the port/descriptors"); + static ref REGEX_PORT_STATE: Regex = Regex::new(r"^port([\d\.]+)/state$") +@@ -143,6 +148,8 @@ pub enum Handle { + ConfigureEndpoints(PortId), // port + AttachDevice(PortId), // port + DetachDevice(PortId), // port ++ SuspendDevice(PortId), // port ++ ResumeDevice(PortId), // port + SchemeRoot, } - pub enum ControlFlow { -@@ -369,51 +375,64 @@ impl SchemeParameters { - //and store it if it's valid. +@@ -187,6 +194,10 @@ enum SchemeParameters { + AttachDevice(PortId), // port number + /// /port/detach + DetachDevice(PortId), // port number ++ /// /port/suspend ++ SuspendDevice(PortId), // port number ++ /// /port/resume ++ ResumeDevice(PortId), // port number + } - //Generate the regular expressions for all of our valid schemes. -+ let regex_port_configure = compiled_regex(®EX_PORT_CONFIGURE, "port/configure")?; -+ let regex_port_attach = compiled_regex(®EX_PORT_ATTACH, "port/attach")?; -+ let regex_port_detach = compiled_regex(®EX_PORT_DETACH, "port/detach")?; -+ let regex_port_descriptors = compiled_regex(®EX_PORT_DESCRIPTORS, "port/descriptors")?; -+ let regex_port_state = compiled_regex(®EX_PORT_STATE, "port/state")?; -+ let regex_port_request = compiled_regex(®EX_PORT_REQUEST, "port/request")?; -+ let regex_port_endpoints = compiled_regex(®EX_PORT_ENDPOINTS, "port/endpoints")?; -+ let regex_port_specific_endpoint = -+ compiled_regex(®EX_PORT_SPECIFIC_ENDPOINT, "port/endpoints/")?; -+ let regex_port_sub_endpoint = -+ compiled_regex(®EX_PORT_SUB_ENDPOINT, "port/endpoints//")?; -+ let regex_port_root = compiled_regex(®EX_PORT_ROOT, "port")?; -+ let regex_top_level = compiled_regex(®EX_TOP_LEVEL, "top-level")?; - - //Check if we have a match and either return a partially initialized scheme, OR ENOENT -- if REGEX_PORT_CONFIGURE.is_match(scheme) { -- let port_num = get_port_id_from_regex(®EX_PORT_CONFIGURE, scheme, 0)?; -+ if regex_port_configure.is_match(scheme) { -+ let port_num = get_port_id_from_regex(regex_port_configure, scheme, 0)?; - - Ok(Self::ConfigureEndpoints(port_num)) -- } else if REGEX_PORT_ATTACH.is_match(scheme) { -- let port_num = get_port_id_from_regex(®EX_PORT_ATTACH, scheme, 0)?; -+ } else if regex_port_attach.is_match(scheme) { -+ let port_num = get_port_id_from_regex(regex_port_attach, scheme, 0)?; - - Ok(Self::AttachDevice(port_num)) -- } else if REGEX_PORT_DETACH.is_match(scheme) { -- let port_num = get_port_id_from_regex(®EX_PORT_DETACH, scheme, 0)?; -+ } else if regex_port_detach.is_match(scheme) { -+ let port_num = get_port_id_from_regex(regex_port_detach, scheme, 0)?; + impl Handle { +@@ -235,6 +246,12 @@ impl Handle { + Handle::DetachDevice(port_num) => { + format!("port{}/detach", port_num) + } ++ Handle::SuspendDevice(port_num) => { ++ format!("port{}/suspend", port_num) ++ } ++ Handle::ResumeDevice(port_num) => { ++ format!("port{}/resume", port_num) ++ } + Handle::SchemeRoot => String::from(""), + } + } +@@ -262,6 +279,8 @@ impl Handle { + &Handle::ConfigureEndpoints(_) => HandleType::Character, + &Handle::AttachDevice(_) => HandleType::Character, + &Handle::DetachDevice(_) => HandleType::Character, ++ &Handle::SuspendDevice(_) => HandleType::Character, ++ &Handle::ResumeDevice(_) => HandleType::Character, + &Handle::Endpoint(_, _, ref st) => match st { + EndpointHandleTy::Data => HandleType::Character, + EndpointHandleTy::Ctl => HandleType::Character, +@@ -293,6 +312,8 @@ impl Handle { + &Handle::ConfigureEndpoints(_) => None, + &Handle::AttachDevice(_) => None, + &Handle::DetachDevice(_) => None, ++ &Handle::SuspendDevice(_) => None, ++ &Handle::ResumeDevice(_) => None, + &Handle::Endpoint(_, _, ref st) => match st { + EndpointHandleTy::Data => None, + EndpointHandleTy::Ctl => None, +@@ -383,6 +404,14 @@ impl SchemeParameters { + let port_num = get_port_id_from_regex(®EX_PORT_DETACH, scheme, 0)?; Ok(Self::DetachDevice(port_num)) -- } else if REGEX_PORT_DESCRIPTORS.is_match(scheme) { -- let port_num = get_port_id_from_regex(®EX_PORT_DESCRIPTORS, scheme, 0)?; -+ } else if regex_port_descriptors.is_match(scheme) { -+ let port_num = get_port_id_from_regex(regex_port_descriptors, scheme, 0)?; ++ } else if REGEX_PORT_SUSPEND.is_match(scheme) { ++ let port_num = get_port_id_from_regex(®EX_PORT_SUSPEND, scheme, 0)?; ++ ++ Ok(Self::SuspendDevice(port_num)) ++ } else if REGEX_PORT_RESUME.is_match(scheme) { ++ let port_num = get_port_id_from_regex(®EX_PORT_RESUME, scheme, 0)?; ++ ++ Ok(Self::ResumeDevice(port_num)) + } else if REGEX_PORT_DESCRIPTORS.is_match(scheme) { + let port_num = get_port_id_from_regex(®EX_PORT_DESCRIPTORS, scheme, 0)?; - Ok(Self::PortDesc(port_num)) -- } else if REGEX_PORT_STATE.is_match(scheme) { -- let port_num = get_port_id_from_regex(®EX_PORT_STATE, scheme, 0)?; -+ } else if regex_port_state.is_match(scheme) { -+ let port_num = get_port_id_from_regex(regex_port_state, scheme, 0)?; +@@ -564,15 +593,22 @@ impl Xhci { + endps: impl IntoIterator, + hid_descs: impl IntoIterator, + lang_id: u16, ++ quirks: crate::usb_quirks::UsbQuirkFlags, + ) -> Result { + Ok(IfDesc { + alternate_setting: desc.alternate_setting, + class: desc.class, + interface_str: if desc.interface_str > 0 { +- Some( ++ if quirks.contains(crate::usb_quirks::UsbQuirkFlags::BAD_DESCRIPTOR) { + self.fetch_string_desc(port_id, slot, desc.interface_str, lang_id) +- .await?, +- ) ++ .await ++ .ok() ++ } else { ++ Some( ++ self.fetch_string_desc(port_id, slot, desc.interface_str, lang_id) ++ .await?, ++ ) ++ } + } else { + None + }, +@@ -628,6 +664,53 @@ impl Xhci { - Ok(Self::PortState(port_num)) -- } else if REGEX_PORT_REQUEST.is_match(scheme) { -- let port_num = get_port_id_from_regex(®EX_PORT_REQUEST, scheme, 0)?; -+ } else if regex_port_request.is_match(scheme) { -+ let port_num = get_port_id_from_regex(regex_port_request, scheme, 0)?; - - Ok(Self::PortReq(port_num)) -- } else if REGEX_PORT_ENDPOINTS.is_match(scheme) { -- let port_num = get_port_id_from_regex(®EX_PORT_ENDPOINTS, scheme, 0)?; -+ } else if regex_port_endpoints.is_match(scheme) { -+ let port_num = get_port_id_from_regex(regex_port_endpoints, scheme, 0)?; - - Ok(Self::Endpoints(port_num)) -- } else if REGEX_PORT_SPECIFIC_ENDPOINT.is_match(scheme) { -- let port_num = get_port_id_from_regex(®EX_PORT_SPECIFIC_ENDPOINT, scheme, 0)?; -- let endpoint_num = get_u8_from_regex(®EX_PORT_SPECIFIC_ENDPOINT, scheme, 1)?; -+ } else if regex_port_specific_endpoint.is_match(scheme) { -+ let port_num = get_port_id_from_regex(regex_port_specific_endpoint, scheme, 0)?; -+ let endpoint_num = get_u8_from_regex(regex_port_specific_endpoint, scheme, 1)?; - - Ok(Self::Endpoint(port_num, endpoint_num, String::from("root"))) -- } else if REGEX_PORT_SUB_ENDPOINT.is_match(scheme) { -- let port_num = get_port_id_from_regex(®EX_PORT_SUB_ENDPOINT, scheme, 0)?; -- let endpoint_num = get_u8_from_regex(®EX_PORT_SUB_ENDPOINT, scheme, 1)?; -- let handle_type = get_string_from_regex(®EX_PORT_SUB_ENDPOINT, scheme, 2)?; -+ } else if regex_port_sub_endpoint.is_match(scheme) { -+ let port_num = get_port_id_from_regex(regex_port_sub_endpoint, scheme, 0)?; -+ let endpoint_num = get_u8_from_regex(regex_port_sub_endpoint, scheme, 1)?; -+ let handle_type = get_string_from_regex(regex_port_sub_endpoint, scheme, 2)?; - - Ok(Self::Endpoint(port_num, endpoint_num, handle_type)) -- } else if REGEX_PORT_ROOT.is_match(scheme) { -- let port_num = get_port_id_from_regex(®EX_PORT_ROOT, scheme, 0)?; -+ } else if regex_port_root.is_match(scheme) { -+ let port_num = get_port_id_from_regex(regex_port_root, scheme, 0)?; - Ok(Self::Port(port_num)) -- } else if REGEX_TOP_LEVEL.is_match(scheme) { -+ } else if regex_top_level.is_match(scheme) { - Ok(Self::TopLevel) - } else { - Err(Error::new(ENOENT)) -@@ -589,7 +608,7 @@ impl Xhci { - /// - /// # Locking - /// This function will lock `Xhci::cmd` and `Xhci::dbs`. -- pub async fn execute_command(&self, f: F) -> (Trb, Trb) { -+ pub async fn execute_command(&self, f: F) -> Result<(Trb, Trb)> { - //TODO: find out why this bit is set earlier! - if self.interrupt_is_pending(0) { - debug!("The EHB bit is already set!"); -@@ -597,7 +616,7 @@ impl Xhci { - } - - let next_event = { -- let mut command_ring = self.cmd.lock().unwrap(); -+ let mut command_ring = self.cmd.lock().unwrap_or_else(|e| e.into_inner()); - let (cmd_index, cycle) = (command_ring.next_index(), command_ring.cycle); - - debug!("Sending command with cycle bit {}", cycle as u8); -@@ -613,12 +632,15 @@ impl Xhci { - &*command_ring, - command_trb, - EventDoorbell::new(self, 0, 0), -- ) -+ )? - }; - - let trbs = next_event.await; - let event_trb = trbs.event_trb; -- let command_trb = trbs.src_trb.expect("Command completion event TRBs shall always have a valid pointer to a valid source command TRB"); -+ let command_trb = trbs.src_trb.ok_or_else(|| { -+ error!("command completion event TRB missing source command TRB"); -+ Error::new(EIO) -+ })?; - - assert_eq!( - event_trb.trb_type(), -@@ -626,7 +648,7 @@ impl Xhci { - "The IRQ reactor (or the xHC) gave an invalid event TRB" - ); - -- (event_trb, command_trb) -+ Ok((event_trb, command_trb)) + (event_trb, command_trb) } ++ pub fn execute_command_with_timeout( ++ &self, ++ timeout: common::timeout::Timeout, ++ f: F, ++ ) -> Result<(Trb, Trb)> { ++ if self.interrupt_is_pending(0) { ++ debug!("The EHB bit is already set!"); ++ } ++ ++ let next_event = { ++ let mut command_ring = self.cmd.lock().unwrap(); ++ let (cmd_index, cycle) = (command_ring.next_index(), command_ring.cycle); ++ ++ debug!("Sending command with cycle bit {}", cycle as u8); ++ ++ { ++ let command_trb = &mut command_ring.trbs[cmd_index]; ++ f(command_trb, cycle); ++ } ++ ++ let command_trb = &command_ring.trbs[cmd_index]; ++ self.next_command_completion_event_trb( ++ &*command_ring, ++ command_trb, ++ EventDoorbell::new(self, 0, 0), ++ ) ++ }; ++ ++ let mut next_event = Box::pin(next_event); ++ ++ loop { ++ if let Some(trbs) = next_event.as_mut().now_or_never() { ++ let event_trb = trbs.event_trb; ++ let command_trb = trbs.src_trb.ok_or(Error::new(EIO))?; ++ ++ assert_eq!( ++ event_trb.trb_type(), ++ TrbType::CommandCompletion as u8, ++ "The IRQ reactor (or the xHC) gave an invalid event TRB" ++ ); ++ ++ return Ok((event_trb, command_trb)); ++ } ++ ++ timeout.run().map_err(|()| Error::new(EIO))?; ++ } ++ } pub async fn execute_control_transfer( &self, -@@ -681,7 +703,7 @@ impl Xhci { - &ring.trbs[first_index], - &ring.trbs[last_index], - EventDoorbell::new(self, usize::from(slot), Self::def_control_endp_doorbell()), -- ) -+ )? - }; + port_num: PortId, +@@ -639,6 +722,8 @@ impl Xhci { + where + D: FnMut(&mut Trb, bool) -> ControlFlow, + { ++ self.ensure_port_active(port_num)?; ++ + let future = { + let mut port_state = self.port_state_mut(port_num)?; + let slot = port_state.slot; +@@ -690,6 +775,20 @@ impl Xhci { - let trbs = future.await; -@@ -773,7 +795,7 @@ impl Xhci { - doorbell_data_no_stream - }, - ), -- ); -+ )?; - } - ControlFlow::Continue => continue, - } -@@ -856,7 +878,7 @@ impl Xhci { - .execute_command(|trb, cycle| { - trb.reset_endpoint(slot, endp_num_xhc, tsp, cycle); - }) -- .await; -+ .await?; + handle_transfer_event_trb("CONTROL_TRANSFER", &event_trb, &status_trb)?; + ++ let delay_ctrl_msg = self ++ .port_states ++ .get(&port_num) ++ .map(|port_state| { ++ port_state ++ .quirks ++ .contains(crate::usb_quirks::UsbQuirkFlags::DELAY_CTRL_MSG) ++ }) ++ .unwrap_or(false); ++ ++ if delay_ctrl_msg { ++ std::thread::sleep(std::time::Duration::from_millis(20)); ++ } ++ //self.event_handler_finished(); + Ok(event_trb) +@@ -709,6 +808,8 @@ impl Xhci { + where + D: FnMut(&mut Trb, bool) -> ControlFlow, + { ++ self.ensure_port_active(port_num)?; ++ + let endp_idx = endp_num.checked_sub(1).ok_or(Error::new(EIO))?; + let mut port_state = self.port_state_mut(port_num)?; + +@@ -785,7 +886,29 @@ impl Xhci { + let event_trb = trbs.event_trb; + let transfer_trb = trbs.src_trb.ok_or(Error::new(EIO))?; + +- handle_transfer_event_trb("EXECUTE_TRANSFER", &event_trb, &transfer_trb)?; ++ if let Err(err) = handle_transfer_event_trb("EXECUTE_TRANSFER", &event_trb, &transfer_trb) ++ { ++ let need_reset = self ++ .port_states ++ .get(&port_num) ++ .map(|port_state| { ++ port_state ++ .quirks ++ .contains(crate::usb_quirks::UsbQuirkFlags::NEED_RESET) ++ }) ++ .unwrap_or(false); ++ ++ if need_reset { ++ if let Err(reset_err) = self.reset_device_slot(port_num).await { ++ error!( ++ "EXECUTE_TRANSFER reset recovery failed for port {}: {}", ++ port_num, reset_err ++ ); ++ } ++ } ++ ++ return Err(err); ++ } + + // FIXME: EDTLA if event data was set + if event_trb.completion_code() != TrbCompletionCode::ShortPacket as u8 +@@ -861,6 +984,21 @@ impl Xhci { + handle_event_trb("RESET_ENDPOINT", &event_trb, &command_trb) -@@ -893,7 +915,9 @@ impl Xhci { - endp_desc: &EndpDesc, - ) -> u8 { - if speed_id.is_highspeed() && (endp_desc.is_interrupt() || endp_desc.is_isoch()) { -- assert_eq!(dev_desc.major_version(), 2); -+ if dev_desc.major_version() != 2 { -+ log::warn!("high-speed endpoint on USB {} device, expected USB 2", dev_desc.major_version()); -+ } - ((endp_desc.max_packet_size & 0x0C00) >> 11) as u8 - } else if endp_desc.is_superspeed() { - endp_desc.max_burst() -@@ -916,10 +940,10 @@ impl Xhci { - - if dev_desc.major_version() == 2 && endp_desc.is_periodic() { - u32::from(max_packet_size) * (u32::from(max_burst_size) + 1) -- } else if endp_desc.has_ssp_companion() { -- endp_desc.sspc.as_ref().unwrap().bytes_per_interval -- } else if endp_desc.ssc.is_some() { -- u32::from(endp_desc.ssc.as_ref().unwrap().bytes_per_interval) -+ } else if let Some(sspc) = endp_desc.sspc.as_ref() { -+ sspc.bytes_per_interval -+ } else if let Some(ssc) = endp_desc.ssc.as_ref() { -+ u32::from(ssc.bytes_per_interval) - } else if speed_id.is_fullspeed() && endp_desc.is_interrupt() { - 64 - } else if speed_id.is_fullspeed() && endp_desc.is_isoch() { -@@ -957,16 +981,17 @@ impl Xhci { - -- port_state.cfg_idx = Some(req.config_desc); -- -- let config_desc = port_state -+ let dev_desc = port_state - .dev_desc - .as_ref() -- .unwrap() -+ .ok_or(Error::new(EBADFD))?; + } ++ 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 config_desc = dev_desc - .config_descs - .iter() - .find(|desc| desc.configuration_value == req.config_desc) - .ok_or(Error::new(EBADFD))?; -+ let configuration_value = config_desc.configuration_value; -@@ -987,16 +1012,18 @@ impl Xhci { - return Err(Error::new(EIO)); - } - -+ port_state.cfg_idx = Some(configuration_value); -+ - ( - endp_desc_count, - new_context_entries, -- config_desc.configuration_value, -+ configuration_value, - ) - }; - let lec = self.cap.lec(); - let log_max_psa_size = self.cap.max_psa_size(); - -@@ -996,7 +1022,7 @@ impl Xhci { - let lec = self.cap.lec(); - let log_max_psa_size = self.cap.max_psa_size(); - -- let port_speed_id = self.ports.lock().unwrap()[port.root_hub_port_index()].speed(); -+ let port_speed_id = self.ports.lock().unwrap_or_else(|e| e.into_inner())[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) -@@ -1004,7 +1030,7 @@ impl Xhci { - - { - 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(|e| e.into_inner()); - - // Configure the slot context as well, which holds the last index of the endp descs. - input_context.add_context.write(1); -@@ -1035,7 +1061,7 @@ impl Xhci { - 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() { -+ let control = if self.op.lock().unwrap_or_else(|e| e.into_inner()).cie() { - (u32::from(req.alternate_setting.unwrap_or(0)) << 16) - | (u32::from(req.interface_desc.unwrap_or(0)) << 8) - | u32::from(configuration_value) -@@ -1049,7 +1075,7 @@ impl Xhci { - 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 dev_desc = port_state.dev_desc.as_ref().ok_or(Error::new(EBADFD))?; - let endp_desc = port_state.get_endp_desc(endp_idx).ok_or_else(|| { - warn!("failed to find endpoint {}", endp_idx); - Error::new(EIO) -@@ -1111,7 +1137,10 @@ impl Xhci { - 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. -+ if ep_ty == 0 { -+ warn!("endpoint {} has invalid xHCI type 0", endp_num); -+ return Err(Error::new(EIO)); -+ } - - let ring_ptr = if usb_log_max_streams.is_some() { - let mut array = -@@ -1155,7 +1184,7 @@ impl Xhci { - }; - assert_eq!(primary_streams & 0x1F, primary_streams); - -- let mut input_context = port_state.input_context.lock().unwrap(); -+ let mut input_context = port_state.input_context.lock().unwrap_or_else(|e| e.into_inner()); - input_context.add_context.writef(1 << endp_num_xhc, true); - - let endp_i = endp_num_xhc as usize - 1; -@@ -1191,13 +1220,13 @@ impl Xhci { - { - let port_state = self.port_states.get(&port).ok_or(Error::new(EBADFD))?; - let slot = port_state.slot; -- let input_context_physical = port_state.input_context.lock().unwrap().physical(); -+ let input_context_physical = port_state.input_context.lock().unwrap_or_else(|e| e.into_inner()).physical(); - -- let (event_trb, command_trb) = self -- .execute_command(|trb, cycle| { -- trb.configure_endpoint(slot, input_context_physical, cycle) -- }) -- .await; + let (event_trb, command_trb) = self + .execute_command(|trb, cycle| { -+ trb.configure_endpoint(slot, input_context_physical, cycle) ++ trb.reset_device(slot, cycle); + }) -+ .await?; ++ .await; ++ ++ handle_event_trb("RESET_DEVICE", &event_trb, &command_trb) ++ } - //self.event_handler_finished(); + fn endp_ctx_interval(speed_id: &ProtocolSpeed, endp_desc: &EndpDesc) -> u8 { + /// Logarithmic (base 2) 125 µs periods per millisecond. +@@ -1205,7 +1343,19 @@ impl Xhci { + } -@@ -1234,8 +1263,16 @@ impl Xhci { + // Tell the device about this configuration. +- self.set_configuration(port, configuration_value).await?; ++ let skip_set_configuration = self ++ .port_states ++ .get(&port) ++ .map(|port_state| { ++ port_state ++ .quirks ++ .contains(crate::usb_quirks::UsbQuirkFlags::NO_SET_CONFIG) ++ }) ++ .unwrap_or(false); ++ ++ if !skip_set_configuration { ++ self.set_configuration(port, configuration_value).await?; ++ } + + Ok(()) + } +@@ -1234,8 +1384,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?; -+ if let Err(err) = self.set_interface(port, interface_num, alternate_setting).await { -+ if alternate_setting == 0 && interface_num == 0 { -+ log::debug!( -+ "port {}: SET_INTERFACE(0,0) failed (stall likely): {}, continuing", -+ port, err -+ ); -+ } else { -+ return Err(err); -+ } ++ 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?; + } } } -@@ -1261,7 +1298,8 @@ impl Xhci { - ) - .await?; +@@ -1453,52 +1615,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); -- buf.copy_from_slice(&*dma_buffer.as_ref().unwrap()); -+ let dma_ref = dma_buffer.as_ref().ok_or(Error::new(EIO))?; -+ buf.copy_from_slice(&*dma_ref); - Ok((completion_code, bytes_transferred)) - } - async fn transfer_write( -@@ -1327,7 +1365,6 @@ impl Xhci { - dma_buf: Option>, - direction: PortReqDirection, - ) -> Result<(u8, u32, Option>)> { -- // TODO: Check that only readable enpoints are read, etc. - let endp_num = endp_idx + 1; ++ 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 mut port_state = self -@@ -1442,7 +1479,7 @@ impl Xhci { - Ok((event.completion_code(), bytes_transferred, dma_buf)) - } - pub async fn get_desc(&self, port_id: PortId, slot: u8) -> Result { -- let ports = self.ports.lock().unwrap(); -+ let ports = self.ports.lock().unwrap_or_else(|e| e.into_inner()); - let port = ports - .get(port_id.root_hub_port_index()) - .ok_or(Error::new(ENOENT))?; -@@ -1506,12 +1543,45 @@ impl Xhci { - serial_str - ); +- 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 +1727,39 @@ impl Xhci { + + //TODO let (bos_desc, bos_data) = self.fetch_bos_desc(port_id, slot).await?; -- //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) = -+ match self.fetch_bos_desc(port_id, slot).await { -+ Ok((bos_desc, bos_data)) => { -+ let bos_len = bos_desc.total_len as usize; -+ let bos_slice = if bos_len <= bos_data.len() { -+ &bos_data[..bos_len] -+ } else { -+ log::warn!( -+ "port {} slot {} BOS total_len {} exceeds buffer {}, truncating", -+ port_id, slot, bos_len, bos_data.len() ++ 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 + ); -+ &bos_data[..] -+ }; -+ let caps: Vec<_> = usb::bos_capability_descs( -+ bos_desc, -+ bos_slice, -+ ) -+ .collect(); -+ let ss = caps.iter().any(|desc| desc.is_superspeed()); -+ let ssp = caps.iter().any(|desc| desc.is_superspeedplus()); -+ log::info!( -+ "port {} slot {} BOS: superspeed={} superspeedplus={}", -+ port_id, -+ slot, -+ ss, -+ ssp -+ ); -+ (ss, ssp) -+ } -+ Err(err) => { -+ log::debug!( -+ "port {} slot {} BOS descriptor not available: {}", -+ port_id, -+ slot, -+ err -+ ); -+ (false, false) ++ (false, false) ++ } + } + }; let mut config_descs = SmallVec::new(); -@@ -1564,11 +1634,11 @@ impl Xhci { - match iter.peek() { - Some(AnyDescriptor::SuperSpeedCompanion(n)) => { - endp.ssc = Some(SuperSpeedCmp::from(n.clone())); -- iter.next().unwrap(); -+ let _ = iter.next(); - } - Some(AnyDescriptor::SuperSpeedPlusCompanion(n)) => { - endp.sspc = Some(SuperSpeedPlusIsochCmp::from(n.clone())); -- iter.next().unwrap(); -+ let _ = iter.next(); - } - _ => break, +- 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 +1785,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 +1804,9 @@ impl Xhci { } -@@ -1619,6 +1689,8 @@ impl Xhci { - product_str, - serial_str, - config_descs, -+ supports_superspeed, -+ supports_superspeedplus, - }) - } - fn port_desc_json(&self, port_id: PortId) -> Result> { -@@ -1801,14 +1873,14 @@ impl Xhci { - if flags & O_DIRECTORY != 0 || flags & O_STAT != 0 { - let mut contents = Vec::new(); + Some(unexpected) => { + log::warn!("expected endpoint, got {:X?}", unexpected); ++ if bad_descriptor { ++ continue; ++ } + break; + } + None => break, +@@ -1578,8 +1831,16 @@ impl Xhci { + } -- let ports_guard = self.ports.lock().unwrap(); -+ let ports_guard = self.ports.lock().unwrap_or_else(|e| e.into_inner()); + 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 +1851,20 @@ impl Xhci { - for (index, _) in ports_guard - .iter() - .enumerate() - .filter(|(_, port)| port.flags().contains(port::PortFlags::CCS)) - { -- write!(contents, "port{}\n", index).unwrap(); -+ write!(contents, "port{}\n", index).map_err(|_| Error::new(EIO))?; - } - - Ok(Handle::TopLevel(contents)) -@@ -1856,7 +1928,7 @@ 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 +2126,7 @@ impl Xhci { if (flags & O_DIRECTORY != 0) || (flags & O_STAT != 0) { let mut contents = Vec::new(); - write!(contents, "descriptors\nendpoints\n").unwrap(); -+ write!(contents, "descriptors\nendpoints\n").map_err(|_| Error::new(EIO))?; ++ write!(contents, "descriptors\nendpoints\nsuspend\nresume\n").unwrap(); if self.slot_state( self.port_states -@@ -1865,7 +1937,7 @@ impl Xhci { - .slot as usize, - ) != SlotState::Configured as u8 - { -- write!(contents, "configure\n").unwrap(); -+ write!(contents, "configure\n").map_err(|_| Error::new(EIO))?; +@@ -2087,6 +2357,30 @@ impl Xhci { + Ok(Handle::DetachDevice(port_num)) + } + ++ fn open_handle_suspend_device(&self, port_num: PortId, flags: usize) -> Result { ++ if flags & O_DIRECTORY != 0 && flags & O_STAT == 0 { ++ return Err(Error::new(ENOTDIR)); ++ } ++ ++ if flags & O_RDWR != O_WRONLY && flags & O_STAT == 0 { ++ return Err(Error::new(EACCES)); ++ } ++ ++ Ok(Handle::SuspendDevice(port_num)) ++ } ++ ++ fn open_handle_resume_device(&self, port_num: PortId, flags: usize) -> Result { ++ if flags & O_DIRECTORY != 0 && flags & O_STAT == 0 { ++ return Err(Error::new(ENOTDIR)); ++ } ++ ++ if flags & O_RDWR != O_WRONLY && flags & O_STAT == 0 { ++ return Err(Error::new(EACCES)); ++ } ++ ++ Ok(Handle::ResumeDevice(port_num)) ++ } ++ + /// implements open() for /port/request + /// + /// # Arguments +@@ -2173,6 +2467,12 @@ impl SchemeSync for &Xhci { + SchemeParameters::DetachDevice(port_number) => { + self.open_handle_detach_device(port_number, flags)? } - - Ok(Handle::Port(port_num, contents)) -@@ -1916,7 +1988,7 @@ impl Xhci { - }*/ - - for ep_num in ps.endpoint_states.keys() { -- write!(contents, "{}\n", ep_num).unwrap(); -+ write!(contents, "{}\n", ep_num).map_err(|_| Error::new(EIO))?; - } - - Ok(Handle::Endpoints(port_num, contents)) -@@ -2007,10 +2079,13 @@ impl Xhci { - }; - Ok(Handle::Endpoint(port_num, endpoint_num, st)) - } -- _ => panic!( -- "Scheme parser returned an invalid string '{}' for the endpoint handle type", -- handle_type -- ), -+ _ => { -+ log::error!( -+ "Scheme parser returned an invalid string '{}' for the endpoint handle type", -+ handle_type -+ ); -+ return Err(Error::new(ENOENT)); ++ SchemeParameters::SuspendDevice(port_number) => { ++ self.open_handle_suspend_device(port_number, flags)? + } - } - } ++ SchemeParameters::ResumeDevice(port_number) => { ++ self.open_handle_resume_device(port_number, flags)? ++ } + }; -@@ -2218,16 +2293,9 @@ impl SchemeSync for &Xhci { - let guard = self.handles.get(&fd).ok_or(Error::new(EBADF))?; - let scheme = (&*guard).to_scheme(); + let fd = self.next_handle.fetch_add(1, atomic::Ordering::Relaxed); +@@ -2203,7 +2503,11 @@ impl SchemeSync for &Xhci { -- write!(cursor, "{}", scheme.as_str()).expect( -- format!( -- "Failed to convert the file descriptor with value {} to the associated file path", -- fd -- ) -- .as_str(), -- ); -+ write!(cursor, "{}", scheme.as_str()).map_err(|_| Error::new(EIO))?; - -- let src_len = usize::try_from(cursor.seek(io::SeekFrom::End(0)).unwrap()).unwrap(); -- Ok(src_len) -+ Ok(cursor.position() as usize) - } - - fn read( -@@ -2324,12 +2392,15 @@ impl SchemeSync for &Xhci { - Ok(buf.len()) + //If we have a handle to the configure scheme, we need to mark it as write only. + match &*guard { +- Handle::ConfigureEndpoints(_) | Handle::AttachDevice(_) | Handle::DetachDevice(_) => { ++ Handle::ConfigureEndpoints(_) ++ | Handle::AttachDevice(_) ++ | Handle::DetachDevice(_) ++ | Handle::SuspendDevice(_) ++ | Handle::ResumeDevice(_) => { + stat.st_mode = stat.st_mode | 0o200; } - &mut Handle::AttachDevice(port_num) => { -- //TODO: accept some arguments in buffer? -- block_on(self.attach_device(port_num))?; -+ let speed_override = if buf.len() == 1 { -+ Some(buf[0]) -+ } else { -+ None -+ }; -+ block_on(self.attach_device_with_speed(port_num, speed_override))?; - Ok(buf.len()) - } - &mut Handle::DetachDevice(port_num) => { -- //TODO: accept some arguments in buffer? + _ => {} +@@ -2263,6 +2567,8 @@ impl SchemeSync for &Xhci { + Handle::ConfigureEndpoints(_) => Err(Error::new(EBADF)), + Handle::AttachDevice(_) => Err(Error::new(EBADF)), + Handle::DetachDevice(_) => Err(Error::new(EBADF)), ++ Handle::SuspendDevice(_) => Err(Error::new(EBADF)), ++ Handle::ResumeDevice(_) => Err(Error::new(EBADF)), + Handle::SchemeRoot => Err(Error::new(EBADF)), + + &mut Handle::Endpoint(port_num, endp_num, ref mut st) => match st { +@@ -2333,6 +2639,14 @@ impl SchemeSync for &Xhci { block_on(self.detach_device(port_num))?; Ok(buf.len()) } -@@ -2364,7 +2435,7 @@ impl Xhci { - let endp_desc = port_state - .dev_desc - .as_ref() -- .unwrap() ++ &mut Handle::SuspendDevice(port_num) => { ++ block_on(self.suspend_device(port_num))?; ++ Ok(buf.len()) ++ } ++ &mut Handle::ResumeDevice(port_num) => { ++ block_on(self.resume_device(port_num))?; ++ Ok(buf.len()) ++ } + &mut Handle::Endpoint(port_num, endp_num, ref ep_file_ty) => match ep_file_ty { + EndpointHandleTy::Ctl => block_on(self.on_write_endp_ctl(port_num, endp_num, buf)), + EndpointHandleTy::Data => { +@@ -2356,6 +2670,38 @@ impl Xhci { + self.handles.remove(&fd); + } + ++ fn ensure_port_active(&self, port_num: PortId) -> Result<()> { ++ let pm_state = self ++ .port_states ++ .get(&port_num) + .ok_or(Error::new(EBADFD))? - .config_descs - .get(0) - .ok_or(Error::new(EIO))? -@@ -2409,8 +2480,12 @@ impl Xhci { ++ .pm_state; ++ match pm_state { ++ super::PortPmState::Active => Ok(()), ++ super::PortPmState::Suspended => Err(Error::new(EBUSY)), ++ } ++ } ++ ++ pub async fn suspend_device(&self, port_num: PortId) -> Result<()> { ++ let mut port_state = self.port_states.get_mut(&port_num).ok_or(Error::new(EBADFD))?; ++ ++ if port_state ++ .quirks ++ .contains(crate::usb_quirks::UsbQuirkFlags::NO_SUSPEND) ++ { ++ return Err(Error::new(EOPNOTSUPP)); ++ } ++ ++ port_state.pm_state = super::PortPmState::Suspended; ++ Ok(()) ++ } ++ ++ pub async fn resume_device(&self, port_num: PortId) -> Result<()> { ++ let mut port_state = self.port_states.get_mut(&port_num).ok_or(Error::new(EBADFD))?; ++ port_state.pm_state = super::PortPmState::Active; ++ Ok(()) ++ } ++ + pub fn get_endp_status(&self, port_num: PortId, endp_num: u8) -> Result { + let port_state = self.port_states.get(&port_num).ok_or(Error::new(EBADFD))?; + +@@ -2406,6 +2752,8 @@ impl Xhci { + endp_num: u8, + clear_feature: bool, + ) -> Result<()> { ++ self.ensure_port_active(port_num)?; ++ if self.get_endp_status(port_num, endp_num)? != EndpointStatus::Halted { return Err(Error::new(EPROTO)); } -- // Change the endpoint state from anything, but most likely HALTED (otherwise resetting -- // would be quite meaningless), to stopped. +@@ -2562,6 +2910,7 @@ impl Xhci { + }, + XhciEndpCtlReq::Reset { no_clear_feature } => match ep_if_state { + EndpIfState::Init => { ++ self.ensure_port_active(port_num)?; + self.on_req_reset_device(port_num, endp_num, !no_clear_feature) + .await? + } +@@ -2571,6 +2920,7 @@ impl Xhci { + }, + XhciEndpCtlReq::Transfer { direction, count } => match ep_if_state { + state @ EndpIfState::Init => { ++ self.ensure_port_active(port_num)?; + if direction == XhciEndpCtlDirection::NoData { + // Yield the result directly because no bytes have to be sent or received + // beforehand. +@@ -2631,6 +2981,8 @@ impl Xhci { + endp_num: u8, + buf: &[u8], + ) -> Result { ++ self.ensure_port_active(port_num)?; + -+ let endp_idx = endp_num.checked_sub(1).ok_or(Error::new(EIO))?; -+ let port_state = self.port_states.get(&port_num).ok_or(Error::new(EBADFD))?; -+ let endp_desc = port_state.get_endp_desc(endp_idx).ok_or(Error::new(EBADF))?; -+ let usb_endp_addr = endp_desc.address; -+ - self.reset_endpoint(port_num, endp_num, false).await?; - self.restart_endpoint(port_num, endp_num).await?; - -@@ -2418,10 +2493,10 @@ impl Xhci { - self.device_req_no_data( - port_num, - usb::Setup { -- kind: 0b0000_0010, // endpoint recipient -- request: 0x01, // CLEAR_FEATURE -- value: 0x00, // ENDPOINT_HALT -- index: 0, // TODO: interface num -+ kind: 0b0000_0010, -+ request: 0x01, -+ value: 0x00, -+ index: u16::from(usb_endp_addr), - length: 0, - }, - ) -@@ -2456,7 +2531,7 @@ impl Xhci { - let endp_desc = port_state - .dev_desc - .as_ref() -- .unwrap() -+ .ok_or(Error::new(EBADFD))? - .config_descs - .get(0) - .ok_or(Error::new(EIO))? -@@ -2475,7 +2550,7 @@ impl Xhci { - Self::def_control_endp_doorbell() - }; - -- self.dbs.lock().unwrap()[slot as usize].write(doorbell); -+ self.dbs.lock().unwrap_or_else(|e| e.into_inner())[slot as usize].write(doorbell); - - self.set_tr_deque_ptr(port_num, endp_num, deque_ptr_and_cycle) - .await?; -@@ -2483,13 +2558,14 @@ impl Xhci { - Ok(()) - } - pub fn endp_direction(&self, port_num: PortId, endp_num: u8) -> Result { -+ let endp_idx = endp_num.checked_sub(1).ok_or(Error::new(EIO))? as usize; - Ok(self + let mut port_state = self .port_states - .get(&port_num) - .ok_or(Error::new(EIO))? - .dev_desc - .as_ref() -- .unwrap() -+ .ok_or(Error::new(EBADFD))? - .config_descs - .first() - .ok_or(Error::new(EIO))? - .interface_descs - .first() - .ok_or(Error::new(EIO))? - .endpoints -- .get(endp_num as usize) -+ .get(endp_idx) - .ok_or(Error::new(EIO))? - .direction()) - } -@@ -2530,7 +2605,7 @@ impl Xhci { - slot, - ) - }) + .get_mut(&port_num) +@@ -2732,6 +3084,8 @@ impl Xhci { + endp_num: u8, + buf: &mut [u8], + ) -> Result { ++ self.ensure_port_active(port_num)?; ++ + let mut port_state = self + .port_states + .get_mut(&port_num) +diff --git a/drivers/usb/xhcid/src/xhci/mod.rs b/drivers/usb/xhcid/src/xhci/mod.rs +index f2143676..74126f67 100644 +--- a/drivers/usb/xhcid/src/xhci/mod.rs ++++ b/drivers/usb/xhcid/src/xhci/mod.rs +@@ -311,6 +311,22 @@ struct PortState { + input_context: Mutex>>, + dev_desc: Option, + endpoint_states: BTreeMap, ++ quirks: crate::usb_quirks::UsbQuirkFlags, ++ pm_state: PortPmState, ++} ++ ++#[derive(Clone, Copy, Debug, Eq, PartialEq)] ++pub(crate) enum PortPmState { ++ Active, ++ Suspended, ++} ++impl PortPmState { ++ pub fn as_str(&self) -> &'static str { ++ match self { ++ Self::Active => "active", ++ Self::Suspended => "suspended", ++ } ++ } + } + + impl PortState { +@@ -809,6 +825,7 @@ impl Xhci { + ); + + if flags.contains(port::PortFlags::CCS) { ++ let early_quirks = crate::usb_quirks::lookup_usb_quirks_early(port_id); + let slot_ty = match self.supported_protocol(port_id) { + Some(protocol) => protocol.proto_slot_ty(), + None => { +@@ -838,7 +855,15 @@ impl Xhci { + + debug!("Attempting to address the device"); + let mut ring = match self +- .address_device(&mut input, port_id, slot_ty, slot, protocol_speed, speed) ++ .address_device( ++ &mut input, ++ port_id, ++ slot_ty, ++ slot, ++ protocol_speed, ++ speed, ++ early_quirks, ++ ) + .await + { + Ok(device_ring) => device_ring, +@@ -866,6 +891,8 @@ impl Xhci { + }, + )) + .collect::>(), ++ quirks: early_quirks, ++ pm_state: PortPmState::Active, + }; + self.port_states.insert(port_id, port_state); + debug!("Got port states!"); +@@ -884,8 +911,14 @@ impl Xhci { + debug!("Got the 8 byte dev descriptor: {:X?}", dev_desc_8_byte); + + let dev_desc = self.get_desc(port_id, slot).await?; ++ let quirks = early_quirks ++ | crate::usb_quirks::lookup_usb_quirks(dev_desc.vendor, dev_desc.product); + debug!("Got the full device descriptor!"); +- self.port_states.get_mut(&port_id).unwrap().dev_desc = Some(dev_desc); ++ { ++ let mut port_state = self.port_states.get_mut(&port_id).unwrap(); ++ port_state.quirks = quirks; ++ port_state.dev_desc = Some(dev_desc); ++ } + + debug!("Got the port states again!"); + { +@@ -1052,6 +1085,7 @@ impl Xhci { + slot: u8, + protocol_speed: &ProtocolSpeed, + speed: u8, ++ quirks: crate::usb_quirks::UsbQuirkFlags, + ) -> Result { + // Collect MTT, parent port number, parent slot ID + let mut mtt = false; +@@ -1162,11 +1196,16 @@ impl Xhci { + + let input_context_physical = input_context.physical(); + +- let (event_trb, _) = self +- .execute_command(|trb, cycle| { +- trb.address_device(slot, input_context_physical, false, cycle) +- }) - .await; -+ .await?; ++ let address_timeout = if quirks.contains(crate::usb_quirks::UsbQuirkFlags::SHORT_SET_ADDR_TIMEOUT) ++ { ++ Timeout::from_millis(100) ++ } else { ++ Timeout::from_secs(1) ++ }; ++ ++ let (event_trb, _) = self.execute_command_with_timeout(address_timeout, |trb, cycle| { ++ trb.address_device(slot, input_context_physical, false, cycle) ++ })?; + + if event_trb.completion_code() != TrbCompletionCode::Success as u8 { + error!( +diff --git a/drivers/usb/xhcid/src/xhci/scheme.rs b/drivers/usb/xhcid/src/xhci/scheme.rs +index f2d439a4..dfe9fdec 100644 +--- a/drivers/usb/xhcid/src/xhci/scheme.rs ++++ b/drivers/usb/xhcid/src/xhci/scheme.rs +@@ -24,6 +24,7 @@ use std::{cmp, fmt, io, mem, str}; + + use common::dma::Dma; + use futures::executor::block_on; ++use futures::FutureExt; + use log::{debug, error, info, trace, warn}; + use redox_scheme::scheme::SchemeSync; + use smallvec::SmallVec; +@@ -32,9 +33,9 @@ use common::io::Io; + use redox_scheme::{CallerCtx, OpenResult}; + use syscall::schemev2::NewFdFlags; + use syscall::{ +- Error, Result, Stat, EACCES, EBADF, EBADFD, EBADMSG, EINVAL, EIO, EISDIR, ENOENT, ENOSYS, +- ENOTDIR, EOPNOTSUPP, EPROTO, ESPIPE, MODE_CHR, MODE_DIR, MODE_FILE, O_DIRECTORY, O_RDWR, +- O_STAT, O_WRONLY, SEEK_CUR, SEEK_END, SEEK_SET, ++ Error, Result, Stat, EACCES, EBADF, EBADFD, EBADMSG, EBUSY, EINVAL, EIO, EISDIR, ENOENT, ++ ENOSYS, ENOTDIR, EOPNOTSUPP, EPROTO, ESPIPE, MODE_CHR, MODE_DIR, MODE_FILE, O_DIRECTORY, ++ O_RDWR, O_STAT, O_WRONLY, SEEK_CUR, SEEK_END, SEEK_SET, + }; + + use super::{port, usb}; +@@ -60,10 +61,16 @@ lazy_static! { + .expect("Failed to create the regex for the port/attach scheme."); + static ref REGEX_PORT_DETACH: Regex = Regex::new(r"^port([\d\.]+)/detach$") + .expect("Failed to create the regex for the port/detach scheme."); ++ static ref REGEX_PORT_SUSPEND: Regex = Regex::new(r"^port([\d\.]+)/suspend$") ++ .expect("Failed to create the regex for the port/suspend scheme."); ++ static ref REGEX_PORT_RESUME: Regex = Regex::new(r"^port([\d\.]+)/resume$") ++ .expect("Failed to create the regex for the port/resume scheme."); + static ref REGEX_PORT_DESCRIPTORS: Regex = Regex::new(r"^port([\d\.]+)/descriptors$") + .expect("Failed to create the regex for the port/descriptors"); + static ref REGEX_PORT_STATE: Regex = Regex::new(r"^port([\d\.]+)/state$") + .expect("Failed to create the regex for the port/state scheme"); ++ static ref REGEX_PORT_PM_STATE: Regex = Regex::new(r"^port([\d\.]+)/pm_state$") ++ .expect("Failed to create the regex for the port/pm_state scheme"); + static ref REGEX_PORT_REQUEST: Regex = Regex::new(r"^port([\d\.]+)/request$") + .expect("Failed to create the regex for the port/request scheme"); + static ref REGEX_PORT_ENDPOINTS: Regex = Regex::new(r"^port([\d\.]+)/endpoints$") +@@ -137,12 +144,15 @@ pub enum Handle { + Port(PortId, Vec), // port, contents + PortDesc(PortId, Vec), // port, contents + PortState(PortId), // port ++ PortPmState(PortId), // port + PortReq(PortId, PortReqState), // port, state + Endpoints(PortId, Vec), // port, contents + Endpoint(PortId, u8, EndpointHandleTy), // port, endpoint, state + ConfigureEndpoints(PortId), // port + AttachDevice(PortId), // port + DetachDevice(PortId), // port ++ SuspendDevice(PortId), // port ++ ResumeDevice(PortId), // port + SchemeRoot, + } + +@@ -172,6 +182,8 @@ enum SchemeParameters { + PortDesc(PortId), // port number + /// /port/state + PortState(PortId), // port number ++ /// /port/pm_state ++ PortPmState(PortId), // port number + /// /port/request + PortReq(PortId), // port number + /// /port/endpoints +@@ -187,6 +199,10 @@ enum SchemeParameters { + AttachDevice(PortId), // port number + /// /port/detach + DetachDevice(PortId), // port number ++ /// /port/suspend ++ SuspendDevice(PortId), // port number ++ /// /port/resume ++ ResumeDevice(PortId), // port number + } + + impl Handle { +@@ -209,6 +225,9 @@ impl Handle { + Handle::PortState(port_num) => { + format!("port{}/state", port_num) + } ++ Handle::PortPmState(port_num) => { ++ format!("port{}/pm_state", port_num) ++ } + Handle::PortReq(port_num, _) => { + format!("port{}/request", port_num) + } +@@ -235,6 +254,12 @@ impl Handle { + Handle::DetachDevice(port_num) => { + format!("port{}/detach", port_num) + } ++ Handle::SuspendDevice(port_num) => { ++ format!("port{}/suspend", port_num) ++ } ++ Handle::ResumeDevice(port_num) => { ++ format!("port{}/resume", port_num) ++ } + Handle::SchemeRoot => String::from(""), + } + } +@@ -258,10 +283,13 @@ impl Handle { + &Handle::PortReq(_, PortReqState::Tmp) => unreachable!(), + &Handle::PortReq(_, PortReqState::TmpSetup(_)) => unreachable!(), + &Handle::PortState(_) => HandleType::Character, ++ &Handle::PortPmState(_) => HandleType::Character, + &Handle::PortReq(_, _) => HandleType::Character, + &Handle::ConfigureEndpoints(_) => HandleType::Character, + &Handle::AttachDevice(_) => HandleType::Character, + &Handle::DetachDevice(_) => HandleType::Character, ++ &Handle::SuspendDevice(_) => HandleType::Character, ++ &Handle::ResumeDevice(_) => HandleType::Character, + &Handle::Endpoint(_, _, ref st) => match st { + EndpointHandleTy::Data => HandleType::Character, + EndpointHandleTy::Ctl => HandleType::Character, +@@ -289,10 +317,13 @@ impl Handle { + &Handle::PortReq(_, PortReqState::Tmp) => None, + &Handle::PortReq(_, PortReqState::TmpSetup(_)) => None, + &Handle::PortState(_) => None, ++ &Handle::PortPmState(_) => None, + &Handle::PortReq(_, _) => None, + &Handle::ConfigureEndpoints(_) => None, + &Handle::AttachDevice(_) => None, + &Handle::DetachDevice(_) => None, ++ &Handle::SuspendDevice(_) => None, ++ &Handle::ResumeDevice(_) => None, + &Handle::Endpoint(_, _, ref st) => match st { + EndpointHandleTy::Data => None, + EndpointHandleTy::Ctl => None, +@@ -383,6 +414,14 @@ impl SchemeParameters { + let port_num = get_port_id_from_regex(®EX_PORT_DETACH, scheme, 0)?; + + Ok(Self::DetachDevice(port_num)) ++ } else if REGEX_PORT_SUSPEND.is_match(scheme) { ++ let port_num = get_port_id_from_regex(®EX_PORT_SUSPEND, scheme, 0)?; ++ ++ Ok(Self::SuspendDevice(port_num)) ++ } else if REGEX_PORT_RESUME.is_match(scheme) { ++ let port_num = get_port_id_from_regex(®EX_PORT_RESUME, scheme, 0)?; ++ ++ Ok(Self::ResumeDevice(port_num)) + } else if REGEX_PORT_DESCRIPTORS.is_match(scheme) { + let port_num = get_port_id_from_regex(®EX_PORT_DESCRIPTORS, scheme, 0)?; + +@@ -391,6 +430,10 @@ impl SchemeParameters { + let port_num = get_port_id_from_regex(®EX_PORT_STATE, scheme, 0)?; + + Ok(Self::PortState(port_num)) ++ } else if REGEX_PORT_PM_STATE.is_match(scheme) { ++ let port_num = get_port_id_from_regex(®EX_PORT_PM_STATE, scheme, 0)?; ++ ++ Ok(Self::PortPmState(port_num)) + } else if REGEX_PORT_REQUEST.is_match(scheme) { + let port_num = get_port_id_from_regex(®EX_PORT_REQUEST, scheme, 0)?; + +@@ -564,15 +607,22 @@ impl Xhci { + endps: impl IntoIterator, + hid_descs: impl IntoIterator, + lang_id: u16, ++ quirks: crate::usb_quirks::UsbQuirkFlags, + ) -> Result { + Ok(IfDesc { + alternate_setting: desc.alternate_setting, + class: desc.class, + interface_str: if desc.interface_str > 0 { +- Some( ++ if quirks.contains(crate::usb_quirks::UsbQuirkFlags::BAD_DESCRIPTOR) { + self.fetch_string_desc(port_id, slot, desc.interface_str, lang_id) +- .await?, +- ) ++ .await ++ .ok() ++ } else { ++ Some( ++ self.fetch_string_desc(port_id, slot, desc.interface_str, lang_id) ++ .await?, ++ ) ++ } + } else { + None + }, +@@ -628,6 +678,53 @@ impl Xhci { + + (event_trb, command_trb) + } ++ pub fn execute_command_with_timeout( ++ &self, ++ timeout: common::timeout::Timeout, ++ f: F, ++ ) -> Result<(Trb, Trb)> { ++ if self.interrupt_is_pending(0) { ++ debug!("The EHB bit is already set!"); ++ } ++ ++ let next_event = { ++ let mut command_ring = self.cmd.lock().unwrap(); ++ let (cmd_index, cycle) = (command_ring.next_index(), command_ring.cycle); ++ ++ debug!("Sending command with cycle bit {}", cycle as u8); ++ ++ { ++ let command_trb = &mut command_ring.trbs[cmd_index]; ++ f(command_trb, cycle); ++ } ++ ++ let command_trb = &command_ring.trbs[cmd_index]; ++ self.next_command_completion_event_trb( ++ &*command_ring, ++ command_trb, ++ EventDoorbell::new(self, 0, 0), ++ ) ++ }; ++ ++ let mut next_event = Box::pin(next_event); ++ ++ loop { ++ if let Some(trbs) = next_event.as_mut().now_or_never() { ++ let event_trb = trbs.event_trb; ++ let command_trb = trbs.src_trb.ok_or(Error::new(EIO))?; ++ ++ assert_eq!( ++ event_trb.trb_type(), ++ TrbType::CommandCompletion as u8, ++ "The IRQ reactor (or the xHC) gave an invalid event TRB" ++ ); ++ ++ return Ok((event_trb, command_trb)); ++ } ++ ++ timeout.run().map_err(|()| Error::new(EIO))?; ++ } ++ } + pub async fn execute_control_transfer( + &self, + port_num: PortId, +@@ -639,6 +736,8 @@ impl Xhci { + where + D: FnMut(&mut Trb, bool) -> ControlFlow, + { ++ self.ensure_port_active(port_num)?; ++ + let future = { + let mut port_state = self.port_state_mut(port_num)?; + let slot = port_state.slot; +@@ -690,6 +789,20 @@ impl Xhci { + + handle_transfer_event_trb("CONTROL_TRANSFER", &event_trb, &status_trb)?; + ++ let delay_ctrl_msg = self ++ .port_states ++ .get(&port_num) ++ .map(|port_state| { ++ port_state ++ .quirks ++ .contains(crate::usb_quirks::UsbQuirkFlags::DELAY_CTRL_MSG) ++ }) ++ .unwrap_or(false); ++ ++ if delay_ctrl_msg { ++ std::thread::sleep(std::time::Duration::from_millis(20)); ++ } ++ //self.event_handler_finished(); - handle_event_trb("SET_TR_DEQUEUE_PTR", &event_trb, &command_trb) -@@ -2724,7 +2799,7 @@ impl Xhci { + Ok(event_trb) +@@ -709,6 +822,8 @@ impl Xhci { + where + D: FnMut(&mut Trb, bool) -> ControlFlow, + { ++ self.ensure_port_active(port_num)?; ++ + let endp_idx = endp_num.checked_sub(1).ok_or(Error::new(EIO))?; + let mut port_state = self.port_state_mut(port_num)?; - let mut cursor = io::Cursor::new(buf); - serde_json::to_writer(&mut cursor, &res).or(Err(Error::new(EIO)))?; -- Ok(cursor.seek(io::SeekFrom::Current(0)).unwrap() as usize) -+ Ok(cursor.position() as usize) +@@ -785,7 +900,29 @@ impl Xhci { + let event_trb = trbs.event_trb; + let transfer_trb = trbs.src_trb.ok_or(Error::new(EIO))?; + +- handle_transfer_event_trb("EXECUTE_TRANSFER", &event_trb, &transfer_trb)?; ++ if let Err(err) = handle_transfer_event_trb("EXECUTE_TRANSFER", &event_trb, &transfer_trb) ++ { ++ let need_reset = self ++ .port_states ++ .get(&port_num) ++ .map(|port_state| { ++ port_state ++ .quirks ++ .contains(crate::usb_quirks::UsbQuirkFlags::NEED_RESET) ++ }) ++ .unwrap_or(false); ++ ++ if need_reset { ++ if let Err(reset_err) = self.reset_device_slot(port_num).await { ++ error!( ++ "EXECUTE_TRANSFER reset recovery failed for port {}: {}", ++ port_num, reset_err ++ ); ++ } ++ } ++ ++ return Err(err); ++ } + + // FIXME: EDTLA if event data was set + if event_trb.completion_code() != TrbCompletionCode::ShortPacket as u8 +@@ -861,6 +998,21 @@ impl Xhci { + + handle_event_trb("RESET_ENDPOINT", &event_trb, &command_trb) } - pub async fn on_read_endp_data( - &self, -@@ -2803,7 +2878,7 @@ impl Xhci { - pub fn event_handler_finished(&self) { - trace!("Event handler finished"); - // write 1 to EHB to clear it -- self.run.lock().unwrap().ints[0] -+ self.run.lock().unwrap_or_else(|e| e.into_inner()).ints[0] - .erdp_low - .writef(1 << 3, true); ++ async fn reset_device_slot(&self, port_num: PortId) -> Result<()> { ++ let slot = self ++ .port_states ++ .get(&port_num) ++ .ok_or(Error::new(EBADF))? ++ .slot; ++ ++ let (event_trb, command_trb) = self ++ .execute_command(|trb, cycle| { ++ trb.reset_device(slot, cycle); ++ }) ++ .await; ++ ++ handle_event_trb("RESET_DEVICE", &event_trb, &command_trb) ++ } + + fn endp_ctx_interval(speed_id: &ProtocolSpeed, endp_desc: &EndpDesc) -> u8 { + /// Logarithmic (base 2) 125 µs periods per millisecond. +@@ -1205,7 +1357,19 @@ impl Xhci { + } + + // Tell the device about this configuration. +- self.set_configuration(port, configuration_value).await?; ++ let skip_set_configuration = self ++ .port_states ++ .get(&port) ++ .map(|port_state| { ++ port_state ++ .quirks ++ .contains(crate::usb_quirks::UsbQuirkFlags::NO_SET_CONFIG) ++ }) ++ .unwrap_or(false); ++ ++ if !skip_set_configuration { ++ self.set_configuration(port, configuration_value).await?; ++ } + + Ok(()) } +@@ -1234,8 +1398,20 @@ impl Xhci { + + if let Some(interface_num) = req.interface_desc { + if let Some(alternate_setting) = req.alternate_setting { +- self.set_interface(port, interface_num, alternate_setting) +- .await?; ++ let skip_set_interface = self ++ .port_states ++ .get(&port) ++ .map(|port_state| { ++ port_state ++ .quirks ++ .contains(crate::usb_quirks::UsbQuirkFlags::NO_SET_INTF) ++ }) ++ .unwrap_or(false); ++ ++ if !skip_set_interface { ++ self.set_interface(port, interface_num, alternate_setting) ++ .await?; ++ } + } + } + +@@ -1453,52 +1629,109 @@ impl Xhci { + let raw_dd = self.fetch_dev_desc(port_id, slot).await?; + log::debug!("port {} slot {} desc {:X?}", port_id, slot, raw_dd); + ++ let vendor = raw_dd.vendor; ++ let product = raw_dd.product; ++ let quirks = crate::usb_quirks::lookup_usb_quirks(vendor, product); ++ if !quirks.is_empty() { ++ log::info!( ++ "port {}: USB quirks for {:04x}:{:04x}: {:?}", ++ port_id, vendor, product, quirks ++ ); ++ } ++ + // Only fetch language IDs if we need to. Some devices will fail to return this descriptor + //TODO: also check configurations and interfaces for defined strings? ++ let bad_descriptor = quirks.contains(crate::usb_quirks::UsbQuirkFlags::BAD_DESCRIPTOR); ++ + let lang_id = +- if raw_dd.manufacturer_str > 0 || raw_dd.product_str > 0 || raw_dd.serial_str > 0 { +- let lang_ids = self.fetch_lang_ids_desc(port_id, slot).await?; +- // Prefer US English, but fall back to first language ID, or zero +- let en_us_id = 0x409; +- if lang_ids.contains(&en_us_id) { +- en_us_id +- } else { +- match lang_ids.first() { +- Some(some) => *some, +- None => 0, ++ if !quirks.contains(crate::usb_quirks::UsbQuirkFlags::NO_STRING_FETCH) ++ && (raw_dd.manufacturer_str > 0 ++ || raw_dd.product_str > 0 ++ || raw_dd.serial_str > 0) ++ { ++ match self.fetch_lang_ids_desc(port_id, slot).await { ++ Ok(lang_ids) => { ++ // Prefer US English, but fall back to first language ID, or zero ++ let en_us_id = 0x409; ++ if lang_ids.contains(&en_us_id) { ++ en_us_id ++ } else { ++ match lang_ids.first() { ++ Some(some) => *some, ++ None => 0, ++ } ++ } ++ } ++ Err(err) if bad_descriptor => { ++ log::warn!( ++ "port {} slot {}: failed to fetch language IDs with BAD_DESCRIPTOR set: {}", ++ port_id, ++ slot, ++ err ++ ); ++ 0 + } ++ Err(err) => return Err(err), + } + } else { + 0 + }; + log::debug!("port {} using language ID 0x{:04x}", port_id, lang_id); + +- let (manufacturer_str, product_str, serial_str) = ( +- if raw_dd.manufacturer_str > 0 { +- Some( +- self.fetch_string_desc(port_id, slot, raw_dd.manufacturer_str, lang_id) +- .await?, +- ) +- } else { +- None +- }, +- if raw_dd.product_str > 0 { +- Some( +- self.fetch_string_desc(port_id, slot, raw_dd.product_str, lang_id) +- .await?, +- ) ++ let (manufacturer_str, product_str, serial_str) = ++ if quirks.contains(crate::usb_quirks::UsbQuirkFlags::NO_STRING_FETCH) { ++ (None, None, None) + } else { +- None +- }, +- if raw_dd.serial_str > 0 { +- Some( +- self.fetch_string_desc(port_id, slot, raw_dd.serial_str, lang_id) +- .await?, ++ ( ++ if raw_dd.manufacturer_str > 0 { ++ if bad_descriptor { ++ self.fetch_string_desc(port_id, slot, raw_dd.manufacturer_str, lang_id) ++ .await ++ .ok() ++ } else { ++ Some( ++ self.fetch_string_desc( ++ port_id, ++ slot, ++ raw_dd.manufacturer_str, ++ lang_id, ++ ) ++ .await?, ++ ) ++ } ++ } else { ++ None ++ }, ++ if raw_dd.product_str > 0 { ++ if bad_descriptor { ++ self.fetch_string_desc(port_id, slot, raw_dd.product_str, lang_id) ++ .await ++ .ok() ++ } else { ++ Some( ++ self.fetch_string_desc(port_id, slot, raw_dd.product_str, lang_id) ++ .await?, ++ ) ++ } ++ } else { ++ None ++ }, ++ if raw_dd.serial_str > 0 { ++ if bad_descriptor { ++ self.fetch_string_desc(port_id, slot, raw_dd.serial_str, lang_id) ++ .await ++ .ok() ++ } else { ++ Some( ++ self.fetch_string_desc(port_id, slot, raw_dd.serial_str, lang_id) ++ .await?, ++ ) ++ } ++ } else { ++ None ++ }, + ) +- } else { +- None +- }, +- ); ++ }; + log::debug!( + "manufacturer {:?} product {:?} serial {:?}", + manufacturer_str, +@@ -1508,14 +1741,39 @@ impl Xhci { + + //TODO let (bos_desc, bos_data) = self.fetch_bos_desc(port_id, slot).await?; + +- let supports_superspeed = false; +- //TODO usb::bos_capability_descs(bos_desc, &bos_data).any(|desc| desc.is_superspeed()); +- let supports_superspeedplus = false; +- //TODO usb::bos_capability_descs(bos_desc, &bos_data).any(|desc| desc.is_superspeedplus()); ++ let (supports_superspeed, supports_superspeedplus) = ++ if quirks.contains(crate::usb_quirks::UsbQuirkFlags::NO_BOS) { ++ (false, false) ++ } else { ++ match self.fetch_bos_desc(port_id, slot).await { ++ Ok((bos_desc, bos_data)) => ( ++ usb::bos_capability_descs(bos_desc, &bos_data) ++ .any(|desc| desc.is_superspeed()), ++ usb::bos_capability_descs(bos_desc, &bos_data) ++ .any(|desc| desc.is_superspeedplus()), ++ ), ++ Err(err) => { ++ log::debug!( ++ "port {} slot {}: failed to fetch BOS descriptor: {}", ++ port_id, ++ slot, ++ err ++ ); ++ (false, false) ++ } ++ } ++ }; + + let mut config_descs = SmallVec::new(); + +- for index in 0..raw_dd.configurations { ++ let configuration_indices: Vec = ++ if quirks.contains(crate::usb_quirks::UsbQuirkFlags::FORCE_ONE_CONFIG) { ++ vec![0] ++ } else { ++ (0..raw_dd.configurations).collect() ++ }; ++ ++ for index in configuration_indices { + debug!("Fetching the config descriptor at index {}", index); + let (desc, data) = self.fetch_config_desc(port_id, slot, index).await?; + log::debug!( +@@ -1541,6 +1799,12 @@ impl Xhci { + let mut iter = descriptors.into_iter().peekable(); + + while let Some(item) = iter.next() { ++ if quirks.contains(crate::usb_quirks::UsbQuirkFlags::HONOR_BNUMINTERFACES) ++ && interface_descs.len() >= desc.interfaces as usize ++ { ++ break; ++ } ++ + if let AnyDescriptor::Interface(idesc) = item { + let mut endpoints = SmallVec::<[EndpDesc; 4]>::new(); + let mut hid_descs = SmallVec::<[HidDesc; 1]>::new(); +@@ -1554,6 +1818,9 @@ impl Xhci { + } + Some(unexpected) => { + log::warn!("expected endpoint, got {:X?}", unexpected); ++ if bad_descriptor { ++ continue; ++ } + break; + } + None => break, +@@ -1578,8 +1845,16 @@ impl Xhci { + } + + interface_descs.push( +- self.new_if_desc(port_id, slot, idesc, endpoints, hid_descs, lang_id) +- .await?, ++ self.new_if_desc( ++ port_id, ++ slot, ++ idesc, ++ endpoints, ++ hid_descs, ++ lang_id, ++ quirks, ++ ) ++ .await?, + ); + } else { + log::warn!("expected interface, got {:?}", item); +@@ -1590,11 +1865,20 @@ impl Xhci { + + config_descs.push(ConfDesc { + kind: desc.kind, +- configuration: if desc.configuration_str > 0 { +- Some( ++ configuration: if quirks.contains(crate::usb_quirks::UsbQuirkFlags::NO_STRING_FETCH) ++ { ++ None ++ } else if desc.configuration_str > 0 { ++ if bad_descriptor { + self.fetch_string_desc(port_id, slot, desc.configuration_str, lang_id) +- .await?, +- ) ++ .await ++ .ok() ++ } else { ++ Some( ++ self.fetch_string_desc(port_id, slot, desc.configuration_str, lang_id) ++ .await?, ++ ) ++ } + } else { + None + }, +@@ -1856,7 +2140,7 @@ impl Xhci { + if (flags & O_DIRECTORY != 0) || (flags & O_STAT != 0) { + let mut contents = Vec::new(); + +- write!(contents, "descriptors\nendpoints\n").unwrap(); ++ write!(contents, "descriptors\nendpoints\npm_state\nsuspend\nresume\n").unwrap(); + + if self.slot_state( + self.port_states +@@ -1893,6 +2177,14 @@ impl Xhci { + Ok(Handle::PortState(port_num)) + } + ++ fn open_handle_port_pm_state(&self, port_num: PortId, flags: usize) -> Result { ++ if flags & O_DIRECTORY != 0 && flags & O_STAT == 0 { ++ return Err(Error::new(ENOTDIR)); ++ } ++ ++ Ok(Handle::PortPmState(port_num)) ++ } ++ + /// implements open() for /port/endpoints + /// + /// # Arguments +@@ -2087,6 +2379,30 @@ impl Xhci { + Ok(Handle::DetachDevice(port_num)) + } + ++ fn open_handle_suspend_device(&self, port_num: PortId, flags: usize) -> Result { ++ if flags & O_DIRECTORY != 0 && flags & O_STAT == 0 { ++ return Err(Error::new(ENOTDIR)); ++ } ++ ++ if flags & O_RDWR != O_WRONLY && flags & O_STAT == 0 { ++ return Err(Error::new(EACCES)); ++ } ++ ++ Ok(Handle::SuspendDevice(port_num)) ++ } ++ ++ fn open_handle_resume_device(&self, port_num: PortId, flags: usize) -> Result { ++ if flags & O_DIRECTORY != 0 && flags & O_STAT == 0 { ++ return Err(Error::new(ENOTDIR)); ++ } ++ ++ if flags & O_RDWR != O_WRONLY && flags & O_STAT == 0 { ++ return Err(Error::new(EACCES)); ++ } ++ ++ Ok(Handle::ResumeDevice(port_num)) ++ } ++ + /// implements open() for /port/request + /// + /// # Arguments +@@ -2155,6 +2471,9 @@ impl SchemeSync for &Xhci { + SchemeParameters::PortState(port_number) => { + self.open_handle_port_state(port_number, flags)? + } ++ SchemeParameters::PortPmState(port_number) => { ++ self.open_handle_port_pm_state(port_number, flags)? ++ } + SchemeParameters::PortReq(port_number) => { + self.open_handle_port_request(port_number, flags)? + } +@@ -2173,6 +2492,12 @@ impl SchemeSync for &Xhci { + SchemeParameters::DetachDevice(port_number) => { + self.open_handle_detach_device(port_number, flags)? + } ++ SchemeParameters::SuspendDevice(port_number) => { ++ self.open_handle_suspend_device(port_number, flags)? ++ } ++ SchemeParameters::ResumeDevice(port_number) => { ++ self.open_handle_resume_device(port_number, flags)? ++ } + }; + + let fd = self.next_handle.fetch_add(1, atomic::Ordering::Relaxed); +@@ -2203,7 +2528,11 @@ impl SchemeSync for &Xhci { + + //If we have a handle to the configure scheme, we need to mark it as write only. + match &*guard { +- Handle::ConfigureEndpoints(_) | Handle::AttachDevice(_) | Handle::DetachDevice(_) => { ++ Handle::ConfigureEndpoints(_) ++ | Handle::AttachDevice(_) ++ | Handle::DetachDevice(_) ++ | Handle::SuspendDevice(_) ++ | Handle::ResumeDevice(_) => { + stat.st_mode = stat.st_mode | 0o200; + } + _ => {} +@@ -2263,6 +2592,8 @@ impl SchemeSync for &Xhci { + Handle::ConfigureEndpoints(_) => Err(Error::new(EBADF)), + Handle::AttachDevice(_) => Err(Error::new(EBADF)), + Handle::DetachDevice(_) => Err(Error::new(EBADF)), ++ Handle::SuspendDevice(_) => Err(Error::new(EBADF)), ++ Handle::ResumeDevice(_) => Err(Error::new(EBADF)), + Handle::SchemeRoot => Err(Error::new(EBADF)), + + &mut Handle::Endpoint(port_num, endp_num, ref mut st) => match st { +@@ -2294,6 +2625,10 @@ impl SchemeSync for &Xhci { + + Ok(Xhci::::write_dyn_string(string, buf, offset)) + } ++ &mut Handle::PortPmState(port_num) => { ++ let ps = self.port_states.get(&port_num).ok_or(Error::new(EBADF))?; ++ Ok(Xhci::::write_dyn_string(ps.pm_state.as_str().as_bytes(), buf, offset)) ++ } + &mut Handle::PortReq(port_num, ref mut st) => { + let state = std::mem::replace(st, PortReqState::Tmp); + drop(guard); // release the lock +@@ -2333,6 +2668,14 @@ impl SchemeSync for &Xhci { + block_on(self.detach_device(port_num))?; + Ok(buf.len()) + } ++ &mut Handle::SuspendDevice(port_num) => { ++ block_on(self.suspend_device(port_num))?; ++ Ok(buf.len()) ++ } ++ &mut Handle::ResumeDevice(port_num) => { ++ block_on(self.resume_device(port_num))?; ++ Ok(buf.len()) ++ } + &mut Handle::Endpoint(port_num, endp_num, ref ep_file_ty) => match ep_file_ty { + EndpointHandleTy::Ctl => block_on(self.on_write_endp_ctl(port_num, endp_num, buf)), + EndpointHandleTy::Data => { +@@ -2356,6 +2699,38 @@ impl Xhci { + self.handles.remove(&fd); + } + ++ fn ensure_port_active(&self, port_num: PortId) -> Result<()> { ++ let pm_state = self ++ .port_states ++ .get(&port_num) ++ .ok_or(Error::new(EBADFD))? ++ .pm_state; ++ match pm_state { ++ super::PortPmState::Active => Ok(()), ++ super::PortPmState::Suspended => Err(Error::new(EBUSY)), ++ } ++ } ++ ++ pub async fn suspend_device(&self, port_num: PortId) -> Result<()> { ++ let mut port_state = self.port_states.get_mut(&port_num).ok_or(Error::new(EBADFD))?; ++ ++ if port_state ++ .quirks ++ .contains(crate::usb_quirks::UsbQuirkFlags::NO_SUSPEND) ++ { ++ return Err(Error::new(EOPNOTSUPP)); ++ } ++ ++ port_state.pm_state = super::PortPmState::Suspended; ++ Ok(()) ++ } ++ ++ pub async fn resume_device(&self, port_num: PortId) -> Result<()> { ++ let mut port_state = self.port_states.get_mut(&port_num).ok_or(Error::new(EBADFD))?; ++ port_state.pm_state = super::PortPmState::Active; ++ Ok(()) ++ } ++ + pub fn get_endp_status(&self, port_num: PortId, endp_num: u8) -> Result { + let port_state = self.port_states.get(&port_num).ok_or(Error::new(EBADFD))?; + +@@ -2406,6 +2781,8 @@ impl Xhci { + endp_num: u8, + clear_feature: bool, + ) -> Result<()> { ++ self.ensure_port_active(port_num)?; ++ + if self.get_endp_status(port_num, endp_num)? != EndpointStatus::Halted { + return Err(Error::new(EPROTO)); + } +@@ -2562,6 +2939,7 @@ impl Xhci { + }, + XhciEndpCtlReq::Reset { no_clear_feature } => match ep_if_state { + EndpIfState::Init => { ++ self.ensure_port_active(port_num)?; + self.on_req_reset_device(port_num, endp_num, !no_clear_feature) + .await? + } +@@ -2571,6 +2949,7 @@ impl Xhci { + }, + XhciEndpCtlReq::Transfer { direction, count } => match ep_if_state { + state @ EndpIfState::Init => { ++ self.ensure_port_active(port_num)?; + if direction == XhciEndpCtlDirection::NoData { + // Yield the result directly because no bytes have to be sent or received + // beforehand. +@@ -2631,6 +3010,8 @@ impl Xhci { + endp_num: u8, + buf: &[u8], + ) -> Result { ++ self.ensure_port_active(port_num)?; ++ + let mut port_state = self + .port_states + .get_mut(&port_num) +@@ -2732,6 +3113,8 @@ impl Xhci { + endp_num: u8, + buf: &mut [u8], + ) -> Result { ++ self.ensure_port_active(port_num)?; ++ + let mut port_state = self + .port_states + .get_mut(&port_num) diff --git a/recipes/core/base/recipe.toml b/recipes/core/base/recipe.toml index 174318f7..30d8575c 100644 --- a/recipes/core/base/recipe.toml +++ b/recipes/core/base/recipe.toml @@ -9,6 +9,7 @@ mkdir -pv "${COOKBOOK_STAGE}/usr/bin" for package in audiod ipcd ptyd dhcpd; do "${COOKBOOK_CARGO}" build \ --manifest-path "${COOKBOOK_SOURCE}/${package}/Cargo.toml" \ + --target "${TARGET}" \ ${build_flags} cp -v \ "target/${TARGET}/${build_type}/${package}" \ @@ -17,6 +18,7 @@ done "${COOKBOOK_CARGO}" build \ --manifest-path "${COOKBOOK_SOURCE}/netstack/Cargo.toml" \ + --target "${TARGET}" \ ${build_flags} cp -v \ "target/${TARGET}/${build_type}/smolnetd" \ @@ -58,6 +60,7 @@ export CARGO_PROFILE_RELEASE_OPT_LEVEL=s export CARGO_PROFILE_RELEASE_PANIC=abort "${COOKBOOK_CARGO}" build ${build_flags} \ --manifest-path "${COOKBOOK_SOURCE}/Cargo.toml" \ + --target "${TARGET}" \ $(for bin in "${BINS[@]}"; do echo "-p" "${bin}"; done) for bin in "${BINS[@]}" do