diff --git a/Cargo.lock b/Cargo.lock index 3986e775..87c1a277 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.4", "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.4", "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.4", + "serde", + "thiserror 2.0.18", + "toml 0.8.23", +] + [[package]] name = "redox-initfs" version = "0.2.0" @@ -2130,6 +2145,15 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + [[package]] name = "serde_spanned" version = "1.0.4" @@ -2331,6 +2355,18 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit", +] + [[package]] name = "toml" version = "1.0.6+spec-1.1.0" @@ -2339,13 +2375,22 @@ checksum = "399b1124a3c9e16766831c6bba21e50192572cdd98706ea114f9502509686ffc" dependencies = [ "indexmap", "serde_core", - "serde_spanned", - "toml_datetime", + "serde_spanned 1.0.4", + "toml_datetime 1.0.0+spec-1.1.0", "toml_parser", "toml_writer", "winnow", ] +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + [[package]] name = "toml_datetime" version = "1.0.0+spec-1.1.0" @@ -2355,6 +2400,20 @@ dependencies = [ "serde_core", ] +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow", +] + [[package]] name = "toml_parser" version = "1.0.9+spec-1.1.0" @@ -2364,6 +2423,12 @@ dependencies = [ "winnow", ] +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + [[package]] name = "toml_writer" version = "1.0.6+spec-1.1.0" @@ -2438,6 +2503,7 @@ name = "usbscsid" version = "0.1.0" dependencies = [ "base64 0.11.0", + "bitflags 2.11.0", "daemon", "driver-block", "libredox", @@ -2445,6 +2511,7 @@ dependencies = [ "redox_event", "redox_syscall 0.7.4", "thiserror 2.0.18", + "toml 1.0.6+spec-1.1.0", "xhcid", ] @@ -2801,6 +2868,9 @@ name = "winnow" version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr 2.8.0", +] [[package]] name = "wit-bindgen" @@ -2913,7 +2983,7 @@ dependencies = [ "serde_json", "smallvec 1.15.1", "thiserror 2.0.18", - "toml", + "toml 1.0.6+spec-1.1.0", ] [[package]] diff --git a/bootstrap/Cargo.lock b/bootstrap/Cargo.lock index e738c973..50057616 100644 --- a/bootstrap/Cargo.lock +++ b/bootstrap/Cargo.lock @@ -41,6 +41,7 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "generic-rt" version = "0.1.0" +source = "git+https://gitlab.redox-os.org/redox-os/relibc.git#c35c291beabda0818fd3369d5de5d38553a1759e" [[package]] name = "goblin" @@ -150,6 +151,7 @@ checksum = "436d45c2b6a5b159d43da708e62b25be3a4a3d5550d654b72216ade4c4bfd717" [[package]] name = "redox-rt" version = "0.1.0" +source = "git+https://gitlab.redox-os.org/redox-os/relibc.git#c35c291beabda0818fd3369d5de5d38553a1759e" dependencies = [ "bitflags", "generic-rt", 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 @@ -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..58bcc22d 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; @@ -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 diff --git a/drivers/acpid/src/acpi/dmar/mod.rs b/drivers/acpid/src/acpi/dmar/mod.rs index c42b379a..f4dff276 100644 --- a/drivers/acpid/src/acpi/dmar/mod.rs +++ b/drivers/acpid/src/acpi/dmar/mod.rs @@ -474,10 +474,13 @@ impl<'sdt> Iterator for DmarRawIter<'sdt> { let len_bytes = <[u8; 2]>::try_from(type_bytes) .expect("expected a 2-byte slice to be convertible to [u8; 2]"); - let ty = u16::from_ne_bytes(type_bytes); - let len = u16::from_ne_bytes(len_bytes); + let len = u16::from_ne_bytes(len_bytes) as usize; - let len = usize::try_from(len).expect("expected u16 to fit within usize"); + if len < 4 { + return None; + } + + let ty = u16::from_ne_bytes(type_bytes); if len > remainder.len() { log::warn!("DMAR remapping structure length was smaller than the remaining length of the table."); diff --git a/drivers/acpid/src/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/graphics/ihdgd/config.toml b/drivers/graphics/ihdgd/config.toml index acbb4e78..210731ae 100644 --- a/drivers/graphics/ihdgd/config.toml +++ b/drivers/graphics/ihdgd/config.toml @@ -51,5 +51,26 @@ ids = { 0x8086 = [ 0x56B3, # Pro A60 0x56C0, # GPU Flex 170 0x56C1, # GPU Flex 140 + # Alder Lake-S Desktop + 0x4680, 0x4682, 0x4688, 0x468A, 0x468B, + 0x4690, 0x4692, 0x4693, + # Alder Lake-P Mobile + 0x46A0, 0x46A1, 0x46A2, 0x46A3, 0x46A6, + 0x46A8, 0x46AA, 0x462A, 0x4626, 0x4628, + 0x46B0, 0x46B1, 0x46B2, 0x46B3, + 0x46C0, 0x46C1, 0x46C2, 0x46C3, + # Alder Lake-N Low-Power + 0x46D0, 0x46D1, 0x46D2, 0x46D3, 0x46D4, + # Raptor Lake-S Desktop + 0xA780, 0xA781, 0xA782, 0xA783, + 0xA788, 0xA789, 0xA78A, 0xA78B, + # Raptor Lake-P Mobile + 0xA720, 0xA7A0, 0xA7A8, 0xA7AA, 0xA7AB, + # Raptor Lake-U Mobile + 0xA721, 0xA7A1, 0xA7A9, 0xA7AC, 0xA7AD, + # Meteor Lake + 0x7D40, 0x7D45, 0x7D55, 0x7D60, 0x7DD5, + # Arrow Lake-H + 0x7D51, 0x7DD1, ] } command = ["ihdgd"] diff --git a/drivers/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/ps2d/src/controller.rs b/drivers/input/ps2d/src/controller.rs index d7af4cba..561aa527 100644 --- a/drivers/input/ps2d/src/controller.rs +++ b/drivers/input/ps2d/src/controller.rs @@ -13,7 +13,7 @@ use common::io::Pio; #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] use common::io::Mmio; -use log::{debug, error, info, trace, warn}; +use log::{debug, error, trace, warn}; use std::fmt; @@ -271,6 +271,20 @@ impl Ps2 { } } + pub fn probe(&mut self) -> bool { + let status = self.status(); + let status_bits = status.bits(); + + if status_bits == 0x00 || status_bits == 0xFF { + debug!( + "ps/2 controller probe returned suspicious status {:02X}", + status_bits + ); + } + + self.config().is_ok() + } + pub fn init_keyboard(&mut self) -> Result<(), Error> { let mut b; diff --git a/drivers/input/ps2d/src/main.rs b/drivers/input/ps2d/src/main.rs index db17de2a..faa02e99 100644 --- a/drivers/input/ps2d/src/main.rs +++ b/drivers/input/ps2d/src/main.rs @@ -20,7 +20,7 @@ mod mouse; mod state; mod vm; -fn daemon(daemon: daemon::Daemon) -> ! { +fn run() -> ! { common::setup_logging( "input", "ps2", @@ -29,9 +29,18 @@ fn daemon(daemon: daemon::Daemon) -> ! { common::file_level(), ); - acquire_port_io_rights().expect("ps2d: failed to get I/O permission"); + if let Err(err) = acquire_port_io_rights() { + log::error!("ps2d: failed to get I/O permission: {}", err); + process::exit(1); + } - let input = ProducerHandle::new().expect("ps2d: failed to open input producer"); + let input = match ProducerHandle::new() { + Ok(input) => input, + Err(err) => { + log::error!("ps2d: failed to open input producer: {}", err); + process::exit(1); + } + }; user_data! { enum Source { @@ -44,12 +53,19 @@ fn daemon(daemon: daemon::Daemon) -> ! { let event_queue: EventQueue = EventQueue::new().expect("ps2d: failed to create event queue"); - let mut key_file = OpenOptions::new() + let key_file = OpenOptions::new() .read(true) .write(true) .custom_flags(syscall::O_NONBLOCK as i32) - .open("/scheme/serio/0") - .expect("ps2d: failed to open /scheme/serio/0"); + .open("/scheme/serio/0"); + + let mut key_file = match key_file { + Ok(key_file) => key_file, + Err(err) => { + log::error!("ps2d: failed to open /scheme/serio/0: {}", err); + process::exit(1); + } + }; event_queue .subscribe( @@ -59,12 +75,19 @@ fn daemon(daemon: daemon::Daemon) -> ! { ) .unwrap(); - let mut mouse_file = OpenOptions::new() + let mouse_file = OpenOptions::new() .read(true) .write(true) .custom_flags(syscall::O_NONBLOCK as i32) - .open("/scheme/serio/1") - .expect("ps2d: failed to open /scheme/serio/1"); + .open("/scheme/serio/1"); + + let mut mouse_file = match mouse_file { + Ok(mouse_file) => mouse_file, + Err(err) => { + log::error!("ps2d: failed to open /scheme/serio/1: {}", err); + process::exit(1); + } + }; event_queue .subscribe( @@ -78,8 +101,15 @@ fn daemon(daemon: daemon::Daemon) -> ! { .read(true) .write(true) .custom_flags(syscall::O_NONBLOCK as i32) - .open(format!("/scheme/time/{}", syscall::CLOCK_MONOTONIC)) - .expect("ps2d: failed to open /scheme/time"); + .open(format!("/scheme/time/{}", syscall::CLOCK_MONOTONIC)); + + let time_file = match time_file { + Ok(time_file) => time_file, + Err(err) => { + log::error!("ps2d: failed to open /scheme/time: {}", err); + process::exit(1); + } + }; event_queue .subscribe( @@ -89,11 +119,15 @@ fn daemon(daemon: daemon::Daemon) -> ! { ) .unwrap(); - libredox::call::setrens(0, 0).expect("ps2d: failed to enter null namespace"); - - daemon.ready(); + if let Err(err) = libredox::call::setrens(0, 0) { + log::error!("ps2d: failed to enter null namespace: {}", err); + process::exit(1); + } - let mut ps2d = Ps2d::new(input, time_file); + let Some(mut ps2d) = Ps2d::new(input, time_file) else { + log::warn!("ps2d: no PS/2 hardware available, exiting"); + process::exit(0); + }; let mut data = [0; 256]; for event in event_queue.map(|e| e.expect("ps2d: failed to get next event").user_data) { @@ -131,5 +165,5 @@ fn daemon(daemon: daemon::Daemon) -> ! { } fn main() { - daemon::Daemon::new(daemon); + run(); } diff --git a/drivers/input/ps2d/src/state.rs b/drivers/input/ps2d/src/state.rs index 9018dc6b..2721c4fd 100644 --- a/drivers/input/ps2d/src/state.rs +++ b/drivers/input/ps2d/src/state.rs @@ -59,9 +59,18 @@ pub struct Ps2d { } impl Ps2d { - pub fn new(input: ProducerHandle, time_file: File) -> Self { + pub fn new(input: ProducerHandle, time_file: File) -> Option { let mut ps2 = Ps2::new(); - ps2.init().expect("failed to initialize"); + + if !ps2.probe() { + warn!("ps2d: no PS/2 controller detected, skipping initialization"); + return None; + } + + if let Err(err) = ps2.init() { + error!("ps2d: failed to initialize PS/2 controller: {:?}", err); + return None; + } // FIXME add an option for orbital to disable this when an app captures the mouse. let vmmouse_relative = false; @@ -70,7 +79,7 @@ impl Ps2d { // TODO: QEMU hack, maybe do this when Init timed out? if vmmouse { // 3 = MouseId::Intellimouse1 - MouseState::Bat.handle(3, &mut ps2); + let _ = MouseState::Bat.handle(3, &mut ps2); } let mut this = Ps2d { @@ -96,7 +105,7 @@ impl Ps2d { this.handle_mouse(None); } - this + Some(this) } pub fn irq(&mut self) { diff --git a/drivers/input/usbhidd/src/main.rs b/drivers/input/usbhidd/src/main.rs index 15c5b778..c67fb8bc 100644 --- a/drivers/input/usbhidd/src/main.rs +++ b/drivers/input/usbhidd/src/main.rs @@ -159,17 +159,17 @@ fn main() -> Result<()> { const USAGE: &'static str = "usbhidd "; - let scheme = args.next().expect(USAGE); + let scheme = args.next().ok_or_else(|| anyhow::anyhow!(USAGE))?; let port = args .next() - .expect(USAGE) + .ok_or_else(|| anyhow::anyhow!(USAGE))? .parse::() - .expect("Expected port ID"); + .map_err(|err| anyhow::anyhow!("Expected port ID: {err}"))?; let interface_num = args .next() - .expect(USAGE) + .ok_or_else(|| anyhow::anyhow!(USAGE))? .parse::() - .expect("Expected integer as input of interface"); + .context("Expected integer as input of interface")?; let name = format!("{}_{}_{}_hid", scheme, port, interface_num); common::setup_logging( @@ -247,7 +247,13 @@ fn main() -> Result<()> { reqs::set_idle(&handle, 1, 0, interface_num as u16).context("Failed to set idle")?; let report_desc_len = hid_desc.desc_len; - assert_eq!(hid_desc.desc_ty, REPORT_DESC_TY); + if hid_desc.desc_ty != REPORT_DESC_TY { + anyhow::bail!( + "unexpected HID descriptor type {:X}, expected {:X}", + hid_desc.desc_ty, + REPORT_DESC_TY + ); + } let mut report_desc_bytes = vec![0u8; report_desc_len as usize]; handle @@ -261,8 +267,8 @@ fn main() -> Result<()> { ) .context("Failed to retrieve report descriptor")?; - let mut handler = - ReportHandler::new(&report_desc_bytes).expect("failed to parse report descriptor"); + let mut handler = ReportHandler::new(&report_desc_bytes) + .map_err(|e| anyhow::anyhow!("failed to parse report descriptor: {}", e))?; let report_len = match endp_desc_opt { Some((_endp_num, endp_desc)) => endp_desc.max_packet_size as usize, @@ -318,10 +324,14 @@ fn main() -> Result<()> { let mut mouse_dy = 0i32; let mut scroll_y = 0i32; let mut buttons = last_buttons; - for event in handler - .handle(&report_buffer) - .expect("failed to parse report") - { + let events = match handler.handle(&report_buffer) { + Ok(events) => events, + Err(err) => { + log::warn!("failed to parse report: {}", err); + continue; + } + }; + for event in events { log::debug!("{}", event); if event.usage_page == UsagePage::GenericDesktop as u16 { if event.usage == GenericDesktopUsage::X as u16 { diff --git a/drivers/pcid-spawner/Cargo.toml b/drivers/pcid-spawner/Cargo.toml index 8c03f8d3..8d3b3899 100644 --- a/drivers/pcid-spawner/Cargo.toml +++ b/drivers/pcid-spawner/Cargo.toml @@ -13,6 +13,7 @@ pico-args.workspace = true redox_syscall.workspace = true serde.workspace = true toml.workspace = true +redox-driver-sys = { path = "../../../../../../local/recipes/drivers/redox-driver-sys/source" } 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 +++ b/drivers/pcid/src/scheme.rs @@ -21,6 +21,7 @@ enum Handle { TopLevel { entries: Vec }, Access, Device, + Config { addr: PciAddress }, Channel { addr: PciAddress, st: ChannelState }, SchemeRoot, } @@ -30,14 +31,20 @@ struct HandleWrapper { } impl Handle { fn is_file(&self) -> bool { - matches!(self, Self::Access | Self::Channel { .. }) + matches!( + self, + Self::Access | Self::Config { .. } | Self::Channel { .. } + ) } fn is_dir(&self) -> bool { !self.is_file() } // TODO: capability rather than root fn requires_root(&self) -> bool { - matches!(self, Self::Access | Self::Channel { .. }) + matches!( + self, + Self::Access | Self::Config { .. } | Self::Channel { .. } + ) } fn is_scheme_root(&self) -> bool { matches!(self, Self::SchemeRoot) @@ -132,6 +139,7 @@ impl SchemeSync for PciScheme { let (len, mode) = match handle.inner { Handle::TopLevel { ref entries } => (entries.len(), MODE_DIR | 0o755), Handle::Device => (DEVICE_CONTENTS.len(), MODE_DIR | 0o755), + Handle::Config { .. } => (256, MODE_CHR | 0o600), Handle::Access | Handle::Channel { .. } => (0, MODE_CHR | 0o600), Handle::SchemeRoot => return Err(Error::new(EBADF)), }; @@ -156,6 +164,18 @@ impl SchemeSync for PciScheme { match handle.inner { Handle::TopLevel { .. } => Err(Error::new(EISDIR)), Handle::Device => Err(Error::new(EISDIR)), + Handle::Config { addr } => { + let offset = _offset as u16; + let dword_offset = offset & !0x3; + let byte_offset = (offset & 0x3) as usize; + let bytes_to_read = buf.len().min(4 - byte_offset); + + let dword = unsafe { self.pcie.read(addr, dword_offset) }; + let bytes = dword.to_le_bytes(); + buf[..bytes_to_read] + .copy_from_slice(&bytes[byte_offset..byte_offset + bytes_to_read]); + Ok(bytes_to_read) + } Handle::Channel { addr: _, ref mut st, @@ -193,7 +213,9 @@ impl SchemeSync for PciScheme { return Ok(buf); } Handle::Device => DEVICE_CONTENTS, - Handle::Access | Handle::Channel { .. } => return Err(Error::new(ENOTDIR)), + Handle::Access | Handle::Config { .. } | Handle::Channel { .. } => { + return Err(Error::new(ENOTDIR)); + } Handle::SchemeRoot => return Err(Error::new(EBADF)), }; @@ -223,6 +245,20 @@ impl SchemeSync for PciScheme { } match handle.inner { + Handle::Config { addr } => { + let offset = _offset as u16; + let dword_offset = offset & !0x3; + let byte_offset = (offset & 0x3) as usize; + let bytes_to_write = buf.len().min(4 - byte_offset); + + let mut dword = unsafe { self.pcie.read(addr, dword_offset) }; + let mut bytes = dword.to_le_bytes(); + bytes[byte_offset..byte_offset + bytes_to_write] + .copy_from_slice(&buf[..bytes_to_write]); + dword = u32::from_le_bytes(bytes); + unsafe { self.pcie.write(addr, dword_offset, dword) }; + Ok(buf.len()) + } Handle::Channel { addr, ref mut st } => { Self::write_channel(&self.pcie, &mut self.tree, addr, st, buf) } @@ -318,6 +354,10 @@ impl PciScheme { func.enabled = false; } } + Some(HandleWrapper { + inner: Handle::Config { .. }, + .. + }) => {} _ => {} } } @@ -343,6 +383,7 @@ impl PciScheme { let path = &after[1..]; match path { + "config" => Handle::Config { addr }, "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..dca7762c 100644 --- a/drivers/storage/usbscsid/src/main.rs +++ b/drivers/storage/usbscsid/src/main.rs @@ -1,53 +1,67 @@ use std::collections::BTreeMap; use std::env; +use std::error::Error as StdError; +use std::io; use driver_block::{Disk, DiskScheme, ExecutorTrait}; use syscall::{Error, EIO}; use xhcid_interface::{ConfigureEndpointsReq, PortId, XhciClientHandle}; 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 exit_code = match run(daemon) { + Ok(()) => 0, + Err(err) => { + eprintln!("usbscsid: {err}"); + 1 + } + }; - const USAGE: &'static str = "usbscsid "; + std::process::exit(exit_code); +} + +fn run(daemon: daemon::Daemon) -> Result<()> { + let mut args = env::args().skip(1); - let scheme = args.next().expect(USAGE); - let port = args + let scheme = next_arg(&mut args, "scheme")?; + let port: PortId = args .next() - .expect(USAGE) - .parse::() - .expect("Expected port ID"); - let protocol = args + .ok_or_else(|| usage_error("missing port argument"))? + .parse() + .map_err(|e| usage_error(format!("invalid port ID: {e}")))?; + let protocol_num: u8 = args .next() - .expect(USAGE) - .parse::() - .expect("protocol has to be a number 0-255"); + .ok_or_else(|| usage_error("missing protocol argument"))? + .parse() + .map_err(|e| usage_error(format!("protocol must be a number 0-255: {e}")))?; println!( "USB SCSI driver spawned with scheme `{}`, port {}, protocol {}", - scheme, port, protocol + scheme, port, protocol_num ); let disk_scheme_name = format!("disk.usb-{scheme}+{port}-scsi"); - // TODO: Use eventfds. - let handle = - XhciClientHandle::new(scheme.to_owned(), port).expect("Failed to open XhciClientHandle"); + let handle = XhciClientHandle::new(scheme.to_owned(), port) + .map_err(|e| runtime_error(format!("failed to open XhciClientHandle: {e}")))?; let desc = handle .get_standard_descs() - .expect("Failed to get standard descriptors"); + .map_err(|e| runtime_error(format!("failed to get standard descriptors: {e}")))?; - // TODO: Perhaps the drivers should just be given the config, interface, and alternate setting - // from xhcid. let (conf_desc, configuration_value, (if_desc, interface_num, alternate_setting)) = desc .config_descs .iter() @@ -65,7 +79,7 @@ fn daemon(daemon: daemon::Daemon) -> ! { interface_desc, )) }) - .expect("Failed to find suitable configuration"); + .ok_or_else(|| runtime_error("failed to find suitable SCSI BOT configuration"))?; handle .configure_endpoints(&ConfigureEndpointsReq { @@ -74,20 +88,37 @@ fn daemon(daemon: daemon::Daemon) -> ! { alternate_setting: Some(alternate_setting), hub_ports: None, }) - .expect("Failed to configure endpoints"); - - let mut protocol = protocol::setup(&handle, protocol, &desc, &conf_desc, &if_desc) - .expect("Failed to setup protocol"); - - // TODO: Let all of the USB drivers fork or be managed externally, and xhcid won't have to keep - // track of all the drivers. - let mut scsi = Scsi::new(&mut *protocol).expect("usbscsid: failed to setup SCSI"); + .map_err(|e| runtime_error(format!("failed to configure endpoints: {e}")))?; + + let vendor = desc.vendor; + let product = desc.product; + let storage_quirks = quirks::lookup_usb_storage_quirks(vendor, product); + + let mut protocol = protocol::setup( + &handle, + protocol_num, + &desc, + &conf_desc, + &if_desc, + storage_quirks, + ) + .ok_or_else(|| { + runtime_error(format!( + "failed to setup protocol (protocol 0x{protocol_num:02x})" + )) + })?; + + let mut scsi = Scsi::new(&mut *protocol, storage_quirks) + .map_err(|e| runtime_error(format!("failed to setup SCSI: {e}")))?; println!("SCSI initialized"); let mut buffer = [0u8; 512]; - scsi.read(&mut *protocol, 0, &mut buffer).unwrap(); - println!("DISK CONTENT: {}", base64::encode(&buffer[..])); + match scsi.read(&mut *protocol, 0, &mut buffer) { + Ok(_) => println!("DISK CONTENT: {}", base64::encode(&buffer[..])), + Err(e) => eprintln!("usbscsid: initial sector read failed: {e}"), + } - let event_queue = event::EventQueue::new().unwrap(); + let event_queue = event::EventQueue::new() + .map_err(|e| runtime_error(format!("failed to create event queue: {e}")))?; event::user_data! { enum Event { @@ -119,17 +150,41 @@ fn daemon(daemon: daemon::Daemon) -> ! { Event::Scheme, event::EventFlags::READ, ) - .unwrap(); + .map_err(|e| runtime_error(format!("failed to subscribe to scheme events: {e}")))?; for event in event_queue { - match event.unwrap().user_data { - Event::Scheme => driver_block::FuturesExecutor - .block_on(scheme.tick()) - .unwrap(), + match event { + Ok(ev) => match ev.user_data { + Event::Scheme => { + if let Err(e) = driver_block::FuturesExecutor.block_on(scheme.tick()) { + eprintln!("usbscsid: scheme tick error: {e}"); + } + } + }, + Err(e) => { + eprintln!("usbscsid: event queue error: {e}"); + } } } - 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..848ae0e9 100644 --- a/drivers/storage/usbscsid/src/protocol/bot.rs +++ b/drivers/storage/usbscsid/src/protocol/bot.rs @@ -8,6 +8,7 @@ use xhcid_interface::{ }; use super::{Protocol, ProtocolError, SendCommandStatus, SendCommandStatusKind}; +use crate::quirks::UsbStorageQuirkFlags; pub const CBW_SIGNATURE: u32 = 0x43425355; @@ -88,9 +89,12 @@ pub struct BulkOnlyTransport<'a> { bulk_out: XhciEndpHandle, bulk_in_num: u8, bulk_out_num: u8, + bulk_in_addr: u8, + bulk_out_addr: u8, max_lun: u8, current_tag: u32, interface_num: u8, + quirks: UsbStorageQuirkFlags, } pub const FEATURE_ENDPOINT_HALT: u16 = 0; @@ -98,23 +102,29 @@ pub const FEATURE_ENDPOINT_HALT: u16 = 0; impl<'a> BulkOnlyTransport<'a> { pub fn init( handle: &'a XhciClientHandle, - config_desc: &ConfDesc, + _config_desc: &ConfDesc, if_desc: &IfDesc, + quirks: UsbStorageQuirkFlags, ) -> Result { let endpoints = &if_desc.endpoints; - let bulk_in_num = (endpoints + let (bulk_in_idx, bulk_in_desc) = endpoints .iter() - .position(|endpoint| endpoint.direction() == EndpDirection::In) - .unwrap() - + 1) as u8; - let bulk_out_num = (endpoints + .enumerate() + .find(|(_, endpoint)| endpoint.direction() == EndpDirection::In) + .ok_or(ProtocolError::ProtocolError("no bulk IN endpoint found"))?; + let (bulk_out_idx, bulk_out_desc) = endpoints .iter() - .position(|endpoint| endpoint.direction() == EndpDirection::Out) - .unwrap() - + 1) as u8; + .enumerate() + .find(|(_, endpoint)| endpoint.direction() == EndpDirection::Out) + .ok_or(ProtocolError::ProtocolError("no bulk OUT endpoint found"))?; - let max_lun = get_max_lun(handle, 0)?; + let bulk_in_num = (bulk_in_idx + 1) as u8; + let bulk_out_num = (bulk_out_idx + 1) as u8; + let bulk_in_addr = bulk_in_desc.address; + let bulk_out_addr = bulk_out_desc.address; + + let max_lun = get_max_lun(handle, if_desc.number.into())?; println!("BOT_MAX_LUN {}", max_lun); Ok(Self { @@ -122,10 +132,13 @@ impl<'a> BulkOnlyTransport<'a> { bulk_out: handle.open_endpoint(bulk_out_num)?, bulk_in_num, bulk_out_num, + bulk_in_addr, + bulk_out_addr, handle, max_lun, current_tag: 0, interface_num: if_desc.number, + quirks, }) } fn clear_stall_in(&mut self) -> Result<(), XhciClientHandleError> { @@ -133,7 +146,7 @@ impl<'a> BulkOnlyTransport<'a> { self.bulk_in.reset(false)?; self.handle.clear_feature( PortReqRecipient::Endpoint, - u16::from(self.bulk_in_num), + u16::from(self.bulk_in_addr), FEATURE_ENDPOINT_HALT, )?; } @@ -144,7 +157,7 @@ impl<'a> BulkOnlyTransport<'a> { self.bulk_out.reset(false)?; self.handle.clear_feature( PortReqRecipient::Endpoint, - u16::from(self.bulk_out_num), + u16::from(self.bulk_out_addr), FEATURE_ENDPOINT_HALT, )?; } @@ -162,38 +175,59 @@ impl<'a> BulkOnlyTransport<'a> { } Ok(()) } - fn read_csw_raw( - &mut self, - csw_buffer: &mut [u8; 13], - already: bool, - ) -> Result<(), ProtocolError> { - match self.bulk_in.transfer_read(&mut csw_buffer[..])? { - PortTransferStatus { - kind: PortTransferStatusKind::Stalled, - .. - } => { - if already { + fn read_csw(&mut self, csw_buffer: &mut [u8; 13]) -> Result<(), ProtocolError> { + let mut attempts = 0u8; + loop { + let status = self.bulk_in.transfer_read(&mut csw_buffer[..])?; + match status { + PortTransferStatus { + kind: PortTransferStatusKind::Stalled, + .. + } => { + attempts += 1; + if attempts >= 2 { + self.reset_recovery()?; + return Err(ProtocolError::ProtocolError( + "bulk IN stalled repeatedly when reading CSW", + )); + } + eprintln!("usbscsid: bulk IN stalled when reading CSW, clearing stall"); + self.clear_stall_in()?; + continue; + } + PortTransferStatus { + kind: PortTransferStatusKind::ShortPacket, + bytes_transferred, + } if bytes_transferred != 13 => { + eprintln!( + "usbscsid: short packet when reading CSW ({} != 13)", + bytes_transferred + ); self.reset_recovery()?; + return Err(ProtocolError::ProtocolError( + "short packet when reading CSW", + )); + } + PortTransferStatus { + kind: PortTransferStatusKind::Success, + .. + } + | PortTransferStatus { + kind: PortTransferStatusKind::ShortPacket, + bytes_transferred: 13, + } => return Ok(()), + _ => { + eprintln!( + "usbscsid: unexpected transfer status when reading CSW: {:?}", + status + ); + self.reset_recovery()?; + return Err(ProtocolError::ProtocolError( + "unexpected transfer status when reading CSW", + )); } - println!("bulk in endpoint stalled when reading CSW"); - self.clear_stall_in()?; - self.read_csw_raw(csw_buffer, true)?; - } - PortTransferStatus { - kind: PortTransferStatusKind::ShortPacket, - bytes_transferred, - } if bytes_transferred != 13 => { - panic!( - "received a short packet when reading CSW ({} != 13)", - bytes_transferred - ) } - _ => (), } - Ok(()) - } - fn read_csw(&mut self, csw_buffer: &mut [u8; 13]) -> Result<(), ProtocolError> { - self.read_csw_raw(csw_buffer, false) } } @@ -207,8 +241,14 @@ impl<'a> Protocol for BulkOnlyTransport<'a> { let tag = self.current_tag; let mut cbw_bytes = [0u8; 31]; - let cbw = plain::from_mut_bytes::(&mut cbw_bytes).unwrap(); - *cbw = CommandBlockWrapper::new(tag, data.len() as u32, data.direction().into(), 0, cb)?; + let cbw = plain::from_mut_bytes::(&mut cbw_bytes) + .map_err(|_| ProtocolError::ProtocolError("CBW buffer size mismatch"))?; + let lun: u8 = if self.quirks.contains(UsbStorageQuirkFlags::SINGLE_LUN) { + 0 + } else { + 0 + }; + *cbw = CommandBlockWrapper::new(tag, data.len() as u32, data.direction().into(), lun, cb)?; let cbw = *cbw; match self.bulk_out.transfer_write(&cbw_bytes)? { @@ -216,22 +256,48 @@ impl<'a> Protocol for BulkOnlyTransport<'a> { kind: PortTransferStatusKind::Stalled, .. } => { - // TODO: Error handling - panic!("bulk out endpoint stalled when sending CBW {:?}", cbw); - //self.clear_stall_out()?; - //dbg!(self.bulk_in.status()?, self.bulk_out.status()?); + eprintln!( + "usbscsid: bulk OUT endpoint stalled when sending CBW {:?}", + cbw + ); + self.clear_stall_out()?; + return Err(ProtocolError::ProtocolError( + "bulk OUT endpoint stalled when sending CBW", + )); } PortTransferStatus { bytes_transferred, .. } if bytes_transferred != 31 => { - panic!( - "received short packet when sending CBW ({} != 31)", + eprintln!( + "usbscsid: short packet when sending CBW ({} != 31)", bytes_transferred ); + self.reset_recovery()?; + return Err(ProtocolError::ProtocolError( + "short packet when sending CBW", + )); + } + PortTransferStatus { + kind: PortTransferStatusKind::Success, + .. + } => (), + PortTransferStatus { + kind: PortTransferStatusKind::ShortPacket, + bytes_transferred: 31, + } => (), + status => { + eprintln!( + "usbscsid: unexpected transfer status {:?} when sending CBW", + status + ); + self.reset_recovery()?; + return Err(ProtocolError::ProtocolError( + "unexpected transfer status when sending CBW", + )); } - _ => (), } + let data_len = data.len() as u32; let early_residue: Option = match data { DeviceReqData::In(buffer) => match self.bulk_in.transfer_read(buffer)? { PortTransferStatus { @@ -240,15 +306,19 @@ impl<'a> Protocol for BulkOnlyTransport<'a> { } => match kind { PortTransferStatusKind::Success => None, PortTransferStatusKind::ShortPacket => { - println!( - "received short packet (len {}) when transferring data", - bytes_transferred + let residue = data_len.saturating_sub(bytes_transferred); + eprintln!( + "usbscsid: short packet ({} of {} bytes) during data read", + bytes_transferred, data_len ); - NonZeroU32::new(bytes_transferred) + NonZeroU32::new(residue) } PortTransferStatusKind::Stalled => { - panic!("bulk in endpoint stalled when reading data"); - //self.clear_stall_in()?; + eprintln!("usbscsid: bulk IN endpoint stalled when reading data"); + self.clear_stall_in()?; + return Err(ProtocolError::ProtocolError( + "bulk IN endpoint stalled during data read", + )); } PortTransferStatusKind::Unknown => { return Err(ProtocolError::XhciError( @@ -266,15 +336,19 @@ impl<'a> Protocol for BulkOnlyTransport<'a> { } => match kind { PortTransferStatusKind::Success => None, PortTransferStatusKind::ShortPacket => { - println!( - "received short packet (len {}) when transferring data", - bytes_transferred + let residue = data_len.saturating_sub(bytes_transferred); + eprintln!( + "usbscsid: short packet ({} of {} bytes) during data write", + bytes_transferred, data_len ); - NonZeroU32::new(bytes_transferred) + NonZeroU32::new(residue) } PortTransferStatusKind::Stalled => { - panic!("bulk out endpoint stalled when reading data"); - //self.clear_stall_out()?; + eprintln!("usbscsid: bulk OUT endpoint stalled when writing data"); + self.clear_stall_out()?; + return Err(ProtocolError::ProtocolError( + "bulk OUT endpoint stalled during data write", + )); } PortTransferStatusKind::Unknown => { return Err(ProtocolError::XhciError( @@ -290,9 +364,14 @@ impl<'a> Protocol for BulkOnlyTransport<'a> { let mut csw_buffer = [0u8; 13]; self.read_csw(&mut csw_buffer)?; - let csw = plain::from_bytes::(&csw_buffer).unwrap(); + let csw = plain::from_bytes::(&csw_buffer) + .map_err(|_| ProtocolError::ProtocolError("CSW buffer size mismatch"))?; - let residue = early_residue.or(NonZeroU32::new(csw.data_residue)); + let residue = if self.quirks.contains(UsbStorageQuirkFlags::IGNORE_RESIDUE) { + None + } else { + early_residue.or(NonZeroU32::new(csw.data_residue)) + }; if csw.status == CswStatus::Failed as u8 { println!("CSW indicated failure (CSW {:?}, CBW {:?})", csw, cbw); diff --git a/drivers/storage/usbscsid/src/protocol/mod.rs b/drivers/storage/usbscsid/src/protocol/mod.rs index a580765f..bde9affc 100644 --- a/drivers/storage/usbscsid/src/protocol/mod.rs +++ b/drivers/storage/usbscsid/src/protocol/mod.rs @@ -6,6 +6,8 @@ use xhcid_interface::{ ConfDesc, DevDesc, DeviceReqData, IfDesc, XhciClientHandle, XhciClientHandleError, }; +use crate::quirks::UsbStorageQuirkFlags; + #[derive(Debug, Error)] pub enum ProtocolError { #[error("Too large command block ({0} > 16)")] @@ -59,22 +61,19 @@ pub trait Protocol { /// Bulk-only transport pub mod bot; -mod uas { - // TODO -} - use bot::BulkOnlyTransport; pub fn setup<'a>( handle: &'a XhciClientHandle, protocol: u8, - dev_desc: &DevDesc, + _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, 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..b6d379d0 100644 --- a/drivers/storage/usbscsid/src/scsi/mod.rs +++ b/drivers/storage/usbscsid/src/scsi/mod.rs @@ -8,6 +8,7 @@ use thiserror::Error; use xhcid_interface::DeviceReqData; 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) { + match self.res_ff_sense_data() { + Ok(sense_data) => { + eprintln!("usbscsid: MODE SENSE(10) failed: {:?}", sense_data); + } + Err(err) => { + eprintln!( + "usbscsid: MODE SENSE(10) failed and sense parsing failed: {err}" + ); + } + } + } + return Err(ScsiError::ProtocolError(ProtocolError::ProtocolError( + "MODE SENSE(10) command failed", + ))); } - let optimal_alloc_len = self.res_mode_param_header10().mode_data_len() + 2; // the length of the mode data field itself + let optimal_alloc_len = self.res_mode_param_header10()?.mode_data_len() + 2; // the length of the mode data field itself - let mode_sense10 = self.cmd_mode_sense10(); + let mode_sense10 = self.cmd_mode_sense10()?; *mode_sense10 = cmds::ModeSense10::get_block_desc(optimal_alloc_len, 0); self.data_buffer.resize(optimal_alloc_len as usize, 0); protocol.send_command( &self.command_buffer[..10], DeviceReqData::In(&mut self.data_buffer[..optimal_alloc_len as usize]), )?; - Ok(( - self.res_mode_param_header10(), - self.res_blkdesc_mode10(), - self.res_mode_pages10(), - )) + let header = self.res_mode_param_header10()?; + let blkdescs = self.res_blkdesc_mode10()?; + let mode_pages = self.res_mode_pages10()?; + Ok((header, blkdescs, mode_pages)) } - pub fn cmd_inquiry(&mut self) -> &mut cmds::Inquiry { - plain::from_mut_bytes(&mut self.command_buffer).unwrap() + pub fn cmd_inquiry(&mut self) -> Result<&mut cmds::Inquiry> { + parse_mut_bytes("INQUIRY command", &mut self.command_buffer) + } + pub fn cmd_mode_sense6(&mut self) -> Result<&mut cmds::ModeSense6> { + parse_mut_bytes("MODE SENSE(6) command", &mut self.command_buffer) } - pub fn cmd_mode_sense6(&mut self) -> &mut cmds::ModeSense6 { - plain::from_mut_bytes(&mut self.command_buffer).unwrap() + pub fn cmd_mode_sense10(&mut self) -> Result<&mut cmds::ModeSense10> { + parse_mut_bytes("MODE SENSE(10) command", &mut self.command_buffer) } - pub fn cmd_mode_sense10(&mut self) -> &mut cmds::ModeSense10 { - plain::from_mut_bytes(&mut self.command_buffer).unwrap() + pub fn cmd_request_sense(&mut self) -> Result<&mut cmds::RequestSense> { + parse_mut_bytes("REQUEST SENSE command", &mut self.command_buffer) } - pub fn cmd_request_sense(&mut self) -> &mut cmds::RequestSense { - plain::from_mut_bytes(&mut self.command_buffer).unwrap() + pub fn cmd_read_capacity10(&mut self) -> Result<&mut cmds::ReadCapacity10> { + parse_mut_bytes("READ CAPACITY(10) command", &mut self.command_buffer) } - pub fn cmd_read_capacity10(&mut self) -> &mut cmds::ReadCapacity10 { - plain::from_mut_bytes(&mut self.command_buffer).unwrap() + pub fn cmd_read16(&mut self) -> Result<&mut cmds::Read16> { + parse_mut_bytes("READ(16) command", &mut self.command_buffer) } - pub fn cmd_read16(&mut self) -> &mut cmds::Read16 { - plain::from_mut_bytes(&mut self.command_buffer).unwrap() + pub fn cmd_read10(&mut self) -> Result<&mut cmds::Read10> { + parse_mut_bytes("READ(10) command", &mut self.command_buffer) } - pub fn cmd_write16(&mut self) -> &mut cmds::Write16 { - plain::from_mut_bytes(&mut self.command_buffer).unwrap() + pub fn cmd_write16(&mut self) -> Result<&mut cmds::Write16> { + parse_mut_bytes("WRITE(16) command", &mut self.command_buffer) } - pub fn res_standard_inquiry_data(&self) -> &StandardInquiryData { - plain::from_bytes(&self.inquiry_buffer).unwrap() + pub fn cmd_write10(&mut self) -> Result<&mut cmds::Write10> { + parse_mut_bytes("WRITE(10) command", &mut self.command_buffer) } - pub fn res_ff_sense_data(&self) -> &cmds::FixedFormatSenseData { - plain::from_bytes(&self.data_buffer).unwrap() + pub fn res_standard_inquiry_data(&self) -> Result<&StandardInquiryData> { + parse_bytes("standard inquiry data", &self.inquiry_buffer) } - pub fn res_mode_param_header6(&self) -> &cmds::ModeParamHeader6 { - plain::from_bytes(&self.data_buffer).unwrap() + pub fn res_ff_sense_data(&self) -> Result<&cmds::FixedFormatSenseData> { + parse_bytes("fixed format sense data", &self.data_buffer) } - pub fn res_mode_param_header10(&self) -> &cmds::ModeParamHeader10 { - plain::from_bytes(&self.data_buffer).unwrap() + pub fn res_mode_param_header6(&self) -> Result<&cmds::ModeParamHeader6> { + parse_bytes("MODE SENSE(6) parameter header", &self.data_buffer) } - pub fn res_blkdesc_mode6(&self) -> &[cmds::ShortLbaModeParamBlkDesc] { - let header = self.res_mode_param_header6(); + pub fn res_mode_param_header10(&self) -> Result<&cmds::ModeParamHeader10> { + parse_bytes("MODE SENSE(10) parameter header", &self.data_buffer) + } + pub fn res_blkdesc_mode6(&self) -> Result<&[cmds::ShortLbaModeParamBlkDesc]> { + let header = self.res_mode_param_header6()?; let descs_start = mem::size_of::(); - plain::slice_from_bytes( - &self.data_buffer[descs_start..descs_start + usize::from(header.block_desc_len)], + let desc_len = usize::from(header.block_desc_len); + let descs_end = descs_start + .checked_add(desc_len) + .ok_or(ScsiError::Overflow("block descriptor length overflowed"))?; + if descs_end > self.data_buffer.len() { + return Err(ScsiError::Overflow( + "block descriptor length exceeds data buffer", + )); + } + Ok( + plain::slice_from_bytes(&self.data_buffer[descs_start..descs_end]) + .map_err(|_| ScsiError::Overflow("block descriptor alignment mismatch"))?, ) - .unwrap() } - pub fn res_blkdesc_mode10(&self) -> BlkDescSlice<'_> { - let header = self.res_mode_param_header10(); + pub fn res_blkdesc_mode10(&self) -> Result> { + let header = self.res_mode_param_header10()?; let descs_start = mem::size_of::(); + let descs_end = descs_start + .checked_add(usize::from(header.block_desc_len())) + .ok_or(ScsiError::Overflow("block descriptor length overflowed"))?; + let desc_range = descs_start..descs_end; + if desc_range.end > self.data_buffer.len() { + return Err(ScsiError::Overflow( + "block descriptor length exceeds data buffer", + )); + } if header.longlba() { - BlkDescSlice::Long( - plain::slice_from_bytes( - &self.data_buffer - [descs_start..descs_start + usize::from(header.block_desc_len())], - ) - .unwrap(), - ) - } else if self.res_standard_inquiry_data().periph_dev_ty() - != cmds::PeriphDeviceType::DirectAccess as u8 - && self.res_standard_inquiry_data().version() == cmds::InquiryVersion::Spc3 as u8 - { - BlkDescSlice::General( - plain::slice_from_bytes( - &self.data_buffer - [descs_start..descs_start + usize::from(header.block_desc_len())], - ) - .unwrap(), - ) + Ok(BlkDescSlice::Long( + plain::slice_from_bytes(&self.data_buffer[desc_range]).map_err(|_| { + ScsiError::Overflow("long LBA block descriptor alignment mismatch") + })?, + )) } else { - BlkDescSlice::Short( - plain::slice_from_bytes( - &self.data_buffer - [descs_start..descs_start + usize::from(header.block_desc_len())], - ) - .unwrap(), - ) + let inquiry = self.res_standard_inquiry_data()?; + if inquiry.periph_dev_ty() != cmds::PeriphDeviceType::DirectAccess as u8 + && inquiry.version() == cmds::InquiryVersion::Spc3 as u8 + { + Ok(BlkDescSlice::General( + plain::slice_from_bytes(&self.data_buffer[desc_range]).map_err(|_| { + ScsiError::Overflow("general block descriptor alignment mismatch") + })?, + )) + } else { + Ok(BlkDescSlice::Short( + plain::slice_from_bytes(&self.data_buffer[desc_range]).map_err(|_| { + ScsiError::Overflow("short LBA block descriptor alignment mismatch") + })?, + )) + } } } - pub fn res_mode_pages10(&self) -> impl Iterator> { - let header = self.res_mode_param_header10(); + pub fn res_mode_pages10(&self) -> Result> + '_> { + let header = self.res_mode_param_header10()?; let descs_start = mem::size_of::(); - let buffer = &self.data_buffer[descs_start + header.block_desc_len() as usize..]; - cmds::mode_page_iter(buffer) + let pages_start = descs_start + .checked_add(header.block_desc_len() as usize) + .ok_or(ScsiError::Overflow("mode page offset overflowed"))?; + if pages_start > self.data_buffer.len() { + return Err(ScsiError::Overflow("mode page offset exceeds data buffer")); + } + let buffer = &self.data_buffer[pages_start..]; + Ok(cmds::mode_page_iter(buffer)) + } + pub fn res_read_capacity10(&self) -> Result<&cmds::ReadCapacity10ParamData> { + parse_bytes("READ CAPACITY(10) parameter data", &self.data_buffer) + } + pub fn read_capacity16( + &mut self, + protocol: &mut dyn Protocol, + ) -> Result<&cmds::ReadCapacity16ParamData> { + let cmd = self.cmd_read_capacity16()?; + *cmd = cmds::ReadCapacity16::new(0); + self.data_buffer + .resize(mem::size_of::(), 0); + protocol.send_command( + &self.command_buffer[..16], + DeviceReqData::In(&mut self.data_buffer[..32]), + )?; + self.res_read_capacity16() } - pub fn res_read_capacity10(&self) -> &cmds::ReadCapacity10ParamData { - plain::from_bytes(&self.data_buffer).unwrap() + pub fn cmd_read_capacity16(&mut self) -> Result<&mut cmds::ReadCapacity16> { + parse_mut_bytes("READ CAPACITY(16) command", &mut self.command_buffer) + } + pub fn res_read_capacity16(&self) -> Result<&cmds::ReadCapacity16ParamData> { + parse_bytes("READ CAPACITY(16) parameter data", &self.data_buffer) } pub fn get_disk_size(&self) -> u64 { self.block_count * u64::from(self.block_size) @@ -258,44 +400,90 @@ impl Scsi { lba: u64, buffer: &mut [u8], ) -> Result { - let blocks_to_read = buffer.len() as u64 / u64::from(self.block_size); - let bytes_to_read = blocks_to_read as usize * self.block_size as usize; - let transfer_len = u32::try_from(blocks_to_read).or(Err(ScsiError::Overflow( - "number of blocks to read couldn't fit inside a u32", - )))?; + let mut blocks_to_read = buffer.len() as u64 / u64::from(self.block_size); + + if self.quirks.contains(UsbStorageQuirkFlags::MAX_SECTORS_64) + && blocks_to_read > MAX_SECTORS_64_LIMIT { - let read = self.cmd_read16(); - *read = cmds::Read16::new(lba, transfer_len, 0); + blocks_to_read = MAX_SECTORS_64_LIMIT; + } + + let bytes_to_read = blocks_to_read as usize * self.block_size as usize; + + if self.quirks.contains(UsbStorageQuirkFlags::INITIAL_READ10) { + let transfer_len = u16::try_from(blocks_to_read).or(Err(ScsiError::Overflow( + "number of blocks to read couldn't fit inside a u16 for READ(10)", + )))?; + { + let read = self.cmd_read10()?; + *read = cmds::Read10::new(lba, transfer_len, 0); + } + self.data_buffer.resize(bytes_to_read, 0u8); + let status = protocol.send_command( + &self.command_buffer[..10], + DeviceReqData::In(&mut self.data_buffer[..bytes_to_read]), + )?; + buffer[..bytes_to_read].copy_from_slice(&self.data_buffer[..bytes_to_read]); + Ok(status.bytes_transferred(bytes_to_read as u32)) + } else { + let transfer_len = u32::try_from(blocks_to_read).or(Err(ScsiError::Overflow( + "number of blocks to read couldn't fit inside a u32", + )))?; + { + let read = self.cmd_read16()?; + *read = cmds::Read16::new(lba, transfer_len, 0); + } + self.data_buffer.resize(bytes_to_read, 0u8); + let status = protocol.send_command( + &self.command_buffer[..16], + DeviceReqData::In(&mut self.data_buffer[..bytes_to_read]), + )?; + buffer[..bytes_to_read].copy_from_slice(&self.data_buffer[..bytes_to_read]); + Ok(status.bytes_transferred(bytes_to_read as u32)) } - // TODO: Use the to-be-written TransferReadStream instead of relying on everything being - // able to fit within a single buffer. - self.data_buffer.resize(bytes_to_read, 0u8); - let status = protocol.send_command( - &self.command_buffer[..16], - DeviceReqData::In(&mut self.data_buffer[..bytes_to_read]), - )?; - buffer[..bytes_to_read].copy_from_slice(&self.data_buffer[..bytes_to_read]); - Ok(status.bytes_transferred(bytes_to_read as u32)) } pub fn write(&mut self, protocol: &mut dyn Protocol, lba: u64, buffer: &[u8]) -> Result { - let blocks_to_write = buffer.len() as u64 / u64::from(self.block_size); - let bytes_to_write = blocks_to_write as usize * self.block_size as usize; - let transfer_len = u32::try_from(blocks_to_write).or(Err(ScsiError::Overflow( - "number of blocks to write couldn't fit inside a u32", - )))?; + let mut blocks_to_write = buffer.len() as u64 / u64::from(self.block_size); + + if self.quirks.contains(UsbStorageQuirkFlags::MAX_SECTORS_64) + && blocks_to_write > MAX_SECTORS_64_LIMIT { - let read = self.cmd_write16(); - *read = cmds::Write16::new(lba, transfer_len, 0); + blocks_to_write = MAX_SECTORS_64_LIMIT; + } + + let bytes_to_write = blocks_to_write as usize * self.block_size as usize; + + if self.quirks.contains(UsbStorageQuirkFlags::INITIAL_READ10) { + let transfer_len = u16::try_from(blocks_to_write).or(Err(ScsiError::Overflow( + "number of blocks to write couldn't fit inside a u16 for WRITE(10)", + )))?; + { + let write = self.cmd_write10()?; + *write = cmds::Write10::new(lba, transfer_len, 0); + } + self.data_buffer.resize(bytes_to_write, 0u8); + self.data_buffer[..bytes_to_write].copy_from_slice(&buffer[..bytes_to_write]); + let status = protocol.send_command( + &self.command_buffer[..10], + DeviceReqData::Out(&buffer[..bytes_to_write]), + )?; + Ok(status.bytes_transferred(bytes_to_write as u32)) + } else { + let transfer_len = u32::try_from(blocks_to_write).or(Err(ScsiError::Overflow( + "number of blocks to write couldn't fit inside a u32", + )))?; + { + let write = self.cmd_write16()?; + *write = cmds::Write16::new(lba, transfer_len, 0); + } + self.data_buffer.resize(bytes_to_write, 0u8); + self.data_buffer[..bytes_to_write].copy_from_slice(&buffer[..bytes_to_write]); + let status = protocol.send_command( + &self.command_buffer[..16], + DeviceReqData::Out(&buffer[..bytes_to_write]), + )?; + Ok(status.bytes_transferred(bytes_to_write as u32)) } - // TODO: Use the to-be-written TransferReadStream instead of relying on everything being - // able to fit within a single buffer. - self.data_buffer.resize(bytes_to_write, 0u8); - self.data_buffer[..bytes_to_write].copy_from_slice(&buffer[..bytes_to_write]); - let status = protocol.send_command( - &self.command_buffer[..16], - DeviceReqData::Out(&buffer[..bytes_to_write]), - )?; - Ok(status.bytes_transferred(bytes_to_write as u32)) } } #[derive(Debug)] diff --git a/drivers/usb/usbhubd/src/main.rs b/drivers/usb/usbhubd/src/main.rs index 2c8b9876..68538b77 100644 --- a/drivers/usb/usbhubd/src/main.rs +++ b/drivers/usb/usbhubd/src/main.rs @@ -1,27 +1,41 @@ -use std::{env, thread, time}; +use std::{env, error::Error, io, thread, time}; use xhcid_interface::{ - plain, usb, ConfigureEndpointsReq, DevDesc, DeviceReqData, PortId, PortReqRecipient, PortReqTy, - XhciClientHandle, + plain, usb, ConfigureEndpointsReq, DevDesc, DeviceReqData, EndpDirection, PortId, + PortReqRecipient, PortReqTy, XhciClientHandle, XhciEndpHandle, }; -fn main() { +fn invalid_input_error(message: impl Into) -> Box { + let message = message.into(); + log::error!("{message}"); + Box::new(io::Error::new(io::ErrorKind::InvalidInput, message)) +} + +fn other_error(message: impl Into) -> Box { + let message = message.into(); + log::error!("{message}"); + Box::new(io::Error::other(message)) +} + +fn main() -> Result<(), Box> { common::init(); let mut args = env::args().skip(1); const USAGE: &'static str = "usbhubd "; - let scheme = args.next().expect(USAGE); + let scheme = args.next().ok_or_else(|| invalid_input_error(USAGE))?; let port_id = args .next() - .expect(USAGE) + .ok_or_else(|| invalid_input_error(USAGE))? .parse::() - .expect("Expected port ID"); + .map_err(|err| invalid_input_error(format!("Expected port ID: {err}")))?; let interface_num = args .next() - .expect(USAGE) + .ok_or_else(|| invalid_input_error(USAGE))? .parse::() - .expect("Expected integer as input of interface"); + .map_err(|err| { + invalid_input_error(format!("Expected integer as input of interface: {err}")) + })?; log::info!( "USB HUB driver spawned with scheme `{}`, port {}, interface {}", @@ -39,11 +53,16 @@ fn main() { common::file_level(), ); - let handle = - XhciClientHandle::new(scheme.clone(), port_id).expect("Failed to open XhciClientHandle"); - let desc: DevDesc = handle - .get_standard_descs() - .expect("Failed to get standard descriptors"); + let handle = XhciClientHandle::new(scheme.clone(), port_id).map_err(|err| { + other_error(format!( + "Failed to open XhciClientHandle for scheme `{scheme}` port {port_id}: {err}" + )) + })?; + let desc: DevDesc = handle.get_standard_descs().map_err(|err| { + other_error(format!( + "Failed to get standard descriptors for hub on port {port_id}: {err}" + )) + })?; let (conf_desc, if_desc) = desc .config_descs @@ -58,7 +77,11 @@ fn main() { })?; Some((conf_desc.clone(), if_desc)) }) - .expect("Failed to find suitable configuration"); + .ok_or_else(|| { + other_error(format!( + "Failed to find suitable configuration for hub interface {interface_num}" + )) + })?; // Read hub descriptor let (ports, usb_3) = if desc.major_version() >= 3 { @@ -73,7 +96,11 @@ fn main() { 0, DeviceReqData::In(unsafe { plain::as_mut_bytes(&mut hub_desc) }), ) - .expect("Failed to read hub descriptor"); + .map_err(|err| { + other_error(format!( + "Failed to read USB 3 hub descriptor for port {port_id}: {err}" + )) + })?; (hub_desc.ports, true) } else { // USB 2.0 and earlier hubs @@ -87,7 +114,11 @@ fn main() { 0, DeviceReqData::In(unsafe { plain::as_mut_bytes(&mut hub_desc) }), ) - .expect("Failed to read hub descriptor"); + .map_err(|err| { + other_error(format!( + "Failed to read USB 2 hub descriptor for port {port_id}: {err}" + )) + })?; (hub_desc.ports, false) }; @@ -99,7 +130,11 @@ fn main() { alternate_setting: None, //TODO: stalls on USB 3 hub: Some(if_desc.alternate_setting), hub_ports: Some(ports), }) - .expect("Failed to configure endpoints after reading hub descriptor"); + .map_err(|err| { + other_error(format!( + "Failed to configure endpoints after reading hub descriptor on port {port_id}: {err}" + )) + })?; if usb_3 { handle @@ -111,139 +146,353 @@ fn main() { 0, DeviceReqData::NoData, ) - .expect("Failed to set hub depth"); + .map_err(|err| { + other_error(format!("Failed to set hub depth for port {port_id}: {err}")) + })?; } + let interrupt_endpoint_desc = if_desc + .endpoints + .iter() + .find(|endp_desc| endp_desc.is_interrupt() && endp_desc.direction() == EndpDirection::In) + .copied(); + let status_change_bitmap_size = (usize::from(ports) + 8) / 8; + // Initialize states struct PortState { port_id: PortId, port_sts: usb::HubPortStatus, handle: XhciClientHandle, attached: bool, + suspended: bool, } impl PortState { - pub fn ensure_attached(&mut self, attached: bool) { + pub fn ensure_attached(&mut self, attached: bool) -> io::Result<()> { if attached == self.attached { - return; + return Ok(()); } if attached { - self.handle.attach().expect("Failed to attach"); + self.handle.attach().map_err(|err| { + io::Error::other(format!( + "Failed to attach child device on port {}: {err}", + self.port_id + )) + })?; } else { - self.handle.detach().expect("Failed to detach"); + self.handle.detach().map_err(|err| { + io::Error::other(format!( + "Failed to detach child device on port {}: {err}", + self.port_id + )) + })?; } self.attached = attached; + Ok(()) + } + + pub fn ensure_suspended(&mut self, suspended: bool) -> io::Result<()> { + if suspended == self.suspended { + return Ok(()); + } + + if suspended { + self.handle.suspend_device().map_err(|err| { + io::Error::other(format!( + "Failed to suspend child device on port {}: {err}", + self.port_id + )) + })?; + } else { + self.handle.resume_device().map_err(|err| { + io::Error::other(format!( + "Failed to resume child device on port {}: {err}", + self.port_id + )) + })?; + } + + self.suspended = suspended; + Ok(()) } } let mut states = Vec::new(); for port in 1..=ports { - let child_port_id = port_id.child(port).expect("Cannot get child port ID"); - states.push(PortState { + let child_port_id = match port_id.child(port) { + Ok(child_port_id) => child_port_id, + Err(err) => { + log::warn!( + "Skipping hub port {port}: cannot derive child port ID from parent port {port_id}: {err}" + ); + states.push(None); + continue; + } + }; + + let child_handle = match XhciClientHandle::new(scheme.clone(), child_port_id) { + Ok(child_handle) => child_handle, + Err(err) => { + log::warn!( + "Skipping hub port {port} (child port {child_port_id}): failed to open XhciClientHandle: {err}" + ); + states.push(None); + continue; + } + }; + + states.push(Some(PortState { port_id: child_port_id, port_sts: if usb_3 { usb::HubPortStatus::V3(usb::HubPortStatusV3::default()) } else { usb::HubPortStatus::V2(usb::HubPortStatusV2::default()) }, - handle: XhciClientHandle::new(scheme.clone(), child_port_id) - .expect("Failed to open XhciClientHandle"), + handle: child_handle, attached: false, - }); + suspended: false, + })); } - //TODO: use change flags? - loop { - for port in 1..=ports { - let port_idx: usize = port.checked_sub(1).unwrap().into(); - let state = states.get_mut(port_idx).unwrap(); - - let port_sts = if usb_3 { - let mut port_sts = usb::HubPortStatusV3::default(); - handle - .device_request( - PortReqTy::Class, - PortReqRecipient::Other, - usb::SetupReq::GetStatus as u8, - 0, - port as u16, - DeviceReqData::In(unsafe { plain::as_mut_bytes(&mut port_sts) }), - ) - .expect("Failed to retrieve port status"); - usb::HubPortStatus::V3(port_sts) - } else { - let mut port_sts = usb::HubPortStatusV2::default(); - handle - .device_request( - PortReqTy::Class, - PortReqRecipient::Other, - usb::SetupReq::GetStatus as u8, - 0, - port as u16, - DeviceReqData::In(unsafe { plain::as_mut_bytes(&mut port_sts) }), - ) - .expect("Failed to retrieve port status"); - usb::HubPortStatus::V2(port_sts) - }; - if state.port_sts != port_sts { - state.port_sts = port_sts; - log::info!("port {} status {:X?}", port, port_sts); - } - - // Ensure port is powered on - if !port_sts.is_powered() { - log::info!("power on port {port}"); - handle - .device_request( - PortReqTy::Class, - PortReqRecipient::Other, - usb::SetupReq::SetFeature as u8, - usb::HubPortFeature::PortPower as u16, - port as u16, - DeviceReqData::NoData, - ) - .expect("Failed to set port power"); - state.ensure_attached(false); - continue; + let mut process_port = |port: u8| -> Result<(), Box> { + let port_idx: usize = match port.checked_sub(1) { + Some(port_idx) => port_idx.into(), + None => { + return Err(other_error(format!( + "Failed to derive zero-based index for hub port {port}" + ))); } + }; + let Some(state_entry) = states.get_mut(port_idx) else { + return Err(other_error(format!( + "Missing state entry for hub port {port} at index {port_idx}" + ))); + }; + let Some(state) = state_entry.as_mut() else { + return Ok(()); + }; - // Ignore disconnected port - if !port_sts.is_connected() { - state.ensure_attached(false); - continue; + let port_sts = if usb_3 { + let mut port_sts = usb::HubPortStatusV3::default(); + if let Err(err) = handle.device_request( + PortReqTy::Class, + PortReqRecipient::Other, + usb::SetupReq::GetStatus as u8, + 0, + port as u16, + DeviceReqData::In(unsafe { plain::as_mut_bytes(&mut port_sts) }), + ) { + log::warn!("Failed to retrieve USB 3 status for hub port {port}: {err}"); + if let Err(err) = state.ensure_attached(false) { + log::warn!( + "Failed to detach child device after status error on hub port {port}: {err}" + ); + } + return Ok(()); + } + usb::HubPortStatus::V3(port_sts) + } else { + let mut port_sts = usb::HubPortStatusV2::default(); + if let Err(err) = handle.device_request( + PortReqTy::Class, + PortReqRecipient::Other, + usb::SetupReq::GetStatus as u8, + 0, + port as u16, + DeviceReqData::In(unsafe { plain::as_mut_bytes(&mut port_sts) }), + ) { + log::warn!("Failed to retrieve USB 2 status for hub port {port}: {err}"); + if let Err(err) = state.ensure_attached(false) { + log::warn!( + "Failed to detach child device after status error on hub port {port}: {err}" + ); + } + return Ok(()); } + usb::HubPortStatus::V2(port_sts) + }; + if state.port_sts != port_sts { + state.port_sts = port_sts; + log::info!("port {} status {:X?}", port, port_sts); + } - // Ignore port in reset - if port_sts.is_resetting() { - state.ensure_attached(false); - continue; + // Ensure port is powered on + if !port_sts.is_powered() { + if let Err(err) = state.ensure_suspended(false) { + log::warn!("Failed to resume child device for unpowered hub port {port}: {err}"); } + log::info!("power on port {port}"); + if let Err(err) = handle.device_request( + PortReqTy::Class, + PortReqRecipient::Other, + usb::SetupReq::SetFeature as u8, + usb::HubPortFeature::PortPower as u16, + port as u16, + DeviceReqData::NoData, + ) { + log::warn!("Failed to set port power for hub port {port}: {err}"); + if let Err(err) = state.ensure_attached(false) { + log::warn!( + "Failed to detach child device after power error on hub port {port}: {err}" + ); + } + return Ok(()); + } + if let Err(err) = state.ensure_attached(false) { + log::warn!("Failed to detach child device for unpowered hub port {port}: {err}"); + } + return Ok(()); + } - // Ensure port is enabled - if !port_sts.is_enabled() { - log::info!("reset port {port}"); - handle - .device_request( - PortReqTy::Class, - PortReqRecipient::Other, - usb::SetupReq::SetFeature as u8, - usb::HubPortFeature::PortReset as u16, - port as u16, - DeviceReqData::NoData, - ) - .expect("Failed to set port enable"); - state.ensure_attached(false); - continue; + // Ignore disconnected port + if !port_sts.is_connected() { + if let Err(err) = state.ensure_suspended(false) { + log::warn!("Failed to resume child device for disconnected hub port {port}: {err}"); + } + if let Err(err) = state.ensure_attached(false) { + log::warn!("Failed to detach child device for disconnected hub port {port}: {err}"); } + return Ok(()); + } - state.ensure_attached(true); + // Ignore port in reset + if port_sts.is_resetting() { + if let Err(err) = state.ensure_suspended(false) { + log::warn!("Failed to resume child device for resetting hub port {port}: {err}"); + } + if let Err(err) = state.ensure_attached(false) { + log::warn!("Failed to detach child device for resetting hub port {port}: {err}"); + } + return Ok(()); } - //TODO: use interrupts or poll faster? - thread::sleep(time::Duration::new(1, 0)); + // Ensure port is enabled + if !port_sts.is_enabled() { + log::info!("reset port {port}"); + if let Err(err) = handle.device_request( + PortReqTy::Class, + PortReqRecipient::Other, + usb::SetupReq::SetFeature as u8, + usb::HubPortFeature::PortReset as u16, + port as u16, + DeviceReqData::NoData, + ) { + log::warn!("Failed to reset hub port {port}: {err}"); + if let Err(err) = state.ensure_attached(false) { + log::warn!( + "Failed to detach child device after reset error on hub port {port}: {err}" + ); + } + return Ok(()); + } + if let Err(err) = state.ensure_attached(false) { + log::warn!("Failed to detach child device while resetting hub port {port}: {err}"); + } + return Ok(()); + } + + if let Err(err) = state.ensure_suspended(port_sts.is_suspended()) { + log::warn!("Failed to synchronize child suspend state for hub port {port}: {err}"); + } + + if let Err(err) = state.ensure_attached(true) { + log::warn!("Failed to attach child device for hub port {port}: {err}"); + } + + Ok(()) + }; + + let try_open_interrupt_endpoint = || -> Option { + let Some(interrupt_endpoint_desc) = interrupt_endpoint_desc else { + return None; + }; + + let interrupt_endpoint_num = interrupt_endpoint_desc.address & 0x0F; + match handle.open_endpoint(interrupt_endpoint_num) { + Ok(interrupt_endpoint) => { + log::info!( + "Using hub interrupt endpoint {} IN (max packet size {})", + interrupt_endpoint_num, + interrupt_endpoint_desc.max_packet_size + ); + Some(interrupt_endpoint) + } + Err(err) => { + log::warn!( + "Failed to open hub interrupt endpoint {} on port {}: {}; falling back to polling", + interrupt_endpoint_num, + port_id, + err + ); + None + } + } + }; + + for port in 1..=ports { + process_port(port)?; + } + + if interrupt_endpoint_desc.is_none() { + log::warn!( + "No interrupt IN endpoint found for hub on port {}; falling back to polling", + port_id + ); } - //TODO: read interrupt port for changes + let mut interrupt_endpoint = try_open_interrupt_endpoint(); + let mut poll_iterations: u32 = 0; + + loop { + if let Some(endp) = interrupt_endpoint.as_mut() { + let mut change_bitmap = vec![0_u8; status_change_bitmap_size]; + match endp.transfer_read(&mut change_bitmap) { + Ok(_) => { + for port in 1..=ports { + let bit = usize::from(port); + let byte_idx = bit / 8; + let bit_idx = bit % 8; + + if change_bitmap + .get(byte_idx) + .is_some_and(|byte| ((byte >> bit_idx) & 1) != 0) + { + process_port(port)?; + } + } + poll_iterations = 0; + continue; + } + Err(err) => { + log::warn!( + "Failed to read hub interrupt endpoint on port {}: {}; falling back to polling", + port_id, + err + ); + interrupt_endpoint = None; + poll_iterations = 0; + } + } + } + + for port in 1..=ports { + process_port(port)?; + } + + poll_iterations = poll_iterations.saturating_add(1); + if interrupt_endpoint.is_none() + && interrupt_endpoint_desc.is_some() + && poll_iterations % 10 == 0 + { + interrupt_endpoint = try_open_interrupt_endpoint(); + if interrupt_endpoint.is_some() { + poll_iterations = 0; + continue; + } + } + + thread::sleep(time::Duration::new(1, 0)); + } } diff --git a/drivers/usb/xhcid/src/driver_interface.rs b/drivers/usb/xhcid/src/driver_interface.rs index 727f8d7e..557e6bce 100644 --- a/drivers/usb/xhcid/src/driver_interface.rs +++ b/drivers/usb/xhcid/src/driver_interface.rs @@ -560,6 +560,16 @@ impl XhciClientHandle { let _bytes_written = file.write(&[])?; Ok(()) } + pub fn suspend_device(&self) -> result::Result<(), XhciClientHandleError> { + let file = self.fd.openat("suspend", libredox::flag::O_WRONLY, 0)?; + let _bytes_written = file.write(&[])?; + Ok(()) + } + pub fn resume_device(&self) -> result::Result<(), XhciClientHandleError> { + let file = self.fd.openat("resume", libredox::flag::O_WRONLY, 0)?; + let _bytes_written = file.write(&[])?; + Ok(()) + } pub fn get_standard_descs(&self) -> result::Result { let json = self.read("descriptors")?; Ok(serde_json::from_slice(&json)?) diff --git a/drivers/usb/xhcid/src/main.rs b/drivers/usb/xhcid/src/main.rs index 25b2fdd6..d5dea9b2 100644 --- a/drivers/usb/xhcid/src/main.rs +++ b/drivers/usb/xhcid/src/main.rs @@ -49,6 +49,7 @@ use crate::xhci::{InterruptMethod, Xhci}; // mean anything. pub mod driver_interface; +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..fe6efdd2 100644 --- a/drivers/usb/xhcid/src/usb/hub.rs +++ b/drivers/usb/xhcid/src/usb/hub.rs @@ -86,8 +86,10 @@ pub enum HubPortFeature { PortOverCurrent = 3, PortReset = 4, PortLinkState = 5, + PortSuspend = 7, PortPower = 8, CPortConnection = 16, + CPortSuspend = 18, CPortOverCurrent = 19, CPortReset = 20, } @@ -184,4 +186,11 @@ impl HubPortStatus { Self::V3(x) => x.contains(HubPortStatusV3::ENABLE), } } + + pub fn is_suspended(&self) -> bool { + match self { + Self::V2(x) => x.contains(HubPortStatusV2::SUSPEND), + Self::V3(_) => false, + } + } } diff --git a/drivers/usb/xhcid/src/xhci/device_enumerator.rs b/drivers/usb/xhcid/src/xhci/device_enumerator.rs index 74b9f732..1f144ac9 100644 --- a/drivers/usb/xhcid/src/xhci/device_enumerator.rs +++ b/drivers/usb/xhcid/src/xhci/device_enumerator.rs @@ -54,6 +54,7 @@ impl DeviceEnumerator { }; 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(); - 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 + }; + + std::thread::sleep(Duration::from_millis(delay_ms)); // Some devices need extra time to settle after reset. let flags = port.flags(); diff --git a/drivers/usb/xhcid/src/xhci/mod.rs b/drivers/usb/xhcid/src/xhci/mod.rs index f2143676..c53cb59f 100644 --- a/drivers/usb/xhcid/src/xhci/mod.rs +++ b/drivers/usb/xhcid/src/xhci/mod.rs @@ -311,6 +311,14 @@ 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 PortState { @@ -809,6 +817,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 +847,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 +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); + { + 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 +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 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..b0fb9b85 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,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, } @@ -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 } 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_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)?; @@ -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 { (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 +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 { 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) } + 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 +1343,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 +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?; + 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 +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); + 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 +1727,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 +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 { } Some(unexpected) => { log::warn!("expected endpoint, got {:X?}", unexpected); + if bad_descriptor { + continue; + } break; } None => break, @@ -1578,8 +1831,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 +1851,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 +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\nsuspend\nresume\n").unwrap(); if self.slot_state( self.port_states @@ -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)? } + 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 +2503,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 +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()) } + &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))? + .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)); } @@ -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 mut port_state = self .port_states .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/init.initfs.d/40_ps2d.service b/init.initfs.d/40_ps2d.service index 881e75ea..bbee2699 100644 --- a/init.initfs.d/40_ps2d.service +++ b/init.initfs.d/40_ps2d.service @@ -5,4 +5,4 @@ condition_architecture = ["x86", "x86_64"] [service] cmd = "ps2d" -type = "notify" +type = "oneshot_async" diff --git a/init/src/scheduler.rs b/init/src/scheduler.rs index d42a4e57..f8ac5cd3 100644 --- a/init/src/scheduler.rs +++ b/init/src/scheduler.rs @@ -1,7 +1,8 @@ use std::collections::VecDeque; -use crate::InitConfig; +use crate::service::ServiceType; use crate::unit::{Unit, UnitId, UnitKind, UnitStore}; +use crate::InitConfig; pub struct Scheduler { pending: VecDeque, @@ -92,22 +93,31 @@ fn run(unit: &mut Unit, config: &mut InitConfig) { } UnitKind::Service { service } => { if config.skip_cmd.contains(&service.cmd) { - eprintln!("Skipping '{} {}'", service.cmd, service.args.join(" ")); + eprintln!("init: SKIP {} {}", service.cmd, service.args.join(" ")); return; } - if config.log_debug { - eprintln!( - "Starting {} ({})", - unit.info.description.as_ref().unwrap_or(&unit.id.0), - service.cmd, - ); + + // Always log blocking service types (notify, scheme, oneshot) + // since these can hang the boot if the child fails to signal. + // OneshotAsync services are fire-and-forget, only log at debug. + let is_blocking = !matches!(service.type_, ServiceType::OneshotAsync); + + if is_blocking || config.log_debug { + let desc = unit.info.description.as_ref().map(|s| s.as_str()).unwrap_or("-"); + eprintln!("init: START {desc} | {} {}", service.cmd, service.args.join(" ")); } + service.spawn(&config.envs); + + if is_blocking || config.log_debug { + let desc = unit.info.description.as_ref().map(|s| s.as_str()).unwrap_or("-"); + eprintln!("init: DONE {desc} | {} {}", service.cmd, service.args.join(" ")); + } } UnitKind::Target {} => { if config.log_debug { eprintln!( - "Reached target {}", + "init: TARGET {}", unit.info.description.as_ref().unwrap_or(&unit.id.0), ); } diff --git a/init/src/script.rs b/init/src/script.rs index d18e3a04..40bcf9a4 100644 --- a/init/src/script.rs +++ b/init/src/script.rs @@ -1,8 +1,8 @@ use std::collections::BTreeMap; use std::{env, io, iter}; -use crate::InitConfig; use crate::unit::UnitId; +use crate::InitConfig; pub fn subst_env<'a>(arg: &str) -> String { if arg.starts_with('$') {