Files
RedBear-OS/local/patches/base/redox.patch
T
vasilito 9880e0a5b2 Advance redbear-full Wayland, greeter, and Qt integration
Consolidate the active desktop path around redbear-full while landing the greeter/session stack and the runtime fixes needed to keep Wayland and KWin bring-up moving forward.
2026-04-19 17:59:58 +01:00

8299 lines
301 KiB
Diff

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<String>,
+ pub board_vendor: Option<String>,
+ pub board_name: Option<String>,
+ pub board_version: Option<String>,
+ pub product_name: Option<String>,
+ pub product_version: Option<String>,
+ pub bios_version: Option<String>,
+}
+
+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::<Smbios2EntryPoint>();
+
+ let mut offset = 0;
+ while offset + header_size <= bytes.len() {
+ if &bytes[offset..offset + 4] == b"_SM_" {
+ let entry =
+ plain::from_bytes::<Smbios2EntryPoint>(&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::<Smbios3EntryPoint>();
+
+ let mut offset = 0;
+ while offset + header_size <= bytes.len() {
+ if &bytes[offset..offset + 5] == b"_SM3_" {
+ let entry =
+ plain::from_bytes::<Smbios3EntryPoint>(&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<String> {
+ 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<DmiInfo> {
+ 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::<SmbiosStructHeader>() <= bytes.len() {
+ let header = plain::from_bytes::<SmbiosStructHeader>(
+ &bytes[offset..offset + mem::size_of::<SmbiosStructHeader>()],
+ )
+ .ok()?;
+ let formatted_len = header.length as usize;
+ if formatted_len < mem::size_of::<SmbiosStructHeader>()
+ || 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<DmiInfo> {
+ let (addr, len) = scan_smbios3().or_else(scan_smbios2)?;
+ parse_smbios_table(addr, len)
+}
+
+#[derive(Clone, Debug, Default)]
+struct AcpiTableMatchRule {
+ sys_vendor: Option<String>,
+ board_vendor: Option<String>,
+ board_name: Option<String>,
+ board_version: Option<String>,
+ product_name: Option<String>,
+ product_version: Option<String>,
+ bios_version: Option<String>,
+}
+
+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<String>, actual: &Option<String>) -> 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<String> {
+ table.get(field).and_then(Value::as_str).map(str::to_string)
+}
+
+fn parse_acpi_table_quirks(document: &Value, path: &str) -> Vec<AcpiTableQuirkRule> {
+ 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<AcpiTableQuirkRule> {
+ 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::<Vec<_>>();
+ 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::<Value>() 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<Sdt>, dmi_info: Option<&DmiInfo>) -> Vec<Sdt> {
+ 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::<SdtHeader>() 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::<SdtHeader>()];
+ // 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::<SdtHeader>(),
+ );
+ }
+ 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<String, String>,
page_cache: Arc<Mutex<AmlPageCache>>,
- aml_region_handlers: Vec<(RegionSpace, Box<dyn RegionHandler>)>,
}
impl AmlSymbols {
- pub fn new(aml_region_handlers: Vec<(RegionSpace, Box<dyn RegionHandler>)>) -> 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<dyn RegionHandler + 'static>)> = 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<Handle<'acpi>>,
- pci_fd: Option<Fd>,
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<String> {
+ 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<String> {
+ 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<String> {
+ 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<AcpiPowerSnapshot> {
+ 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<HandleKind<'acpi>> {
+ 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<HandleKind<'acpi>> {
+ 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<HandleKind<'acpi>> {
+ 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<u8> {
@@ -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<usize> {
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<DirentBuf<&'buf mut [u8]>> {
- 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/acpid/src/main.rs b/drivers/acpid/src/main.rs
index 916e1864..52d8c8b4 100644
--- a/drivers/acpid/src/main.rs
+++ b/drivers/acpid/src/main.rs
@@ -16,6 +16,7 @@ mod aml_physmem;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
mod ec;
+mod sleep;
mod scheme;
#[derive(Debug, Error)]
diff --git a/drivers/acpid/src/sleep.rs b/drivers/acpid/src/sleep.rs
new file mode 100644
index 00000000..f8095663
--- /dev/null
+++ b/drivers/acpid/src/sleep.rs
@@ -0,0 +1,84 @@
+use std::convert::TryFrom;
+
+#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
+pub enum SleepTarget {
+ S1,
+ S3,
+ S4,
+ S5,
+}
+
+impl SleepTarget {
+ pub fn aml_method_name(self) -> &'static str {
+ match self {
+ Self::S1 => "_S1",
+ Self::S3 => "_S3",
+ Self::S4 => "_S4",
+ Self::S5 => "_S5",
+ }
+ }
+
+ pub fn is_soft_off(self) -> bool {
+ matches!(self, Self::S5)
+ }
+}
+
+impl TryFrom<u8> for SleepTarget {
+ type Error = ();
+
+ fn try_from(value: u8) -> Result<Self, Self::Error> {
+ match value {
+ 1 => Ok(Self::S1),
+ 3 => Ok(Self::S3),
+ 4 => Ok(Self::S4),
+ 5 => Ok(Self::S5),
+ _ => Err(()),
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum SleepPhase {
+ Prepare,
+ Enter,
+ Resume,
+}
+
+#[cfg(test)]
+mod tests {
+ use std::convert::TryFrom;
+
+ use super::{SleepPhase, SleepTarget};
+
+ #[test]
+ fn sleep_target_maps_to_expected_aml_names() {
+ assert_eq!(SleepTarget::S1.aml_method_name(), "_S1");
+ assert_eq!(SleepTarget::S3.aml_method_name(), "_S3");
+ assert_eq!(SleepTarget::S4.aml_method_name(), "_S4");
+ assert_eq!(SleepTarget::S5.aml_method_name(), "_S5");
+ }
+
+ #[test]
+ fn sleep_target_parsing_accepts_expected_states() {
+ assert_eq!(SleepTarget::try_from(1), Ok(SleepTarget::S1));
+ assert_eq!(SleepTarget::try_from(3), Ok(SleepTarget::S3));
+ assert_eq!(SleepTarget::try_from(4), Ok(SleepTarget::S4));
+ assert_eq!(SleepTarget::try_from(5), Ok(SleepTarget::S5));
+ assert_eq!(SleepTarget::try_from(2), Err(()));
+ }
+
+ #[test]
+ fn only_s5_is_currently_treated_as_soft_off() {
+ assert!(!SleepTarget::S1.is_soft_off());
+ assert!(!SleepTarget::S3.is_soft_off());
+ assert!(!SleepTarget::S4.is_soft_off());
+ assert!(SleepTarget::S5.is_soft_off());
+ }
+
+ #[test]
+ fn sleep_phase_debug_surface_is_stable() {
+ assert_eq!(format!("{:?}", SleepPhase::Prepare), "Prepare");
+ assert_eq!(format!("{:?}", SleepPhase::Enter), "Enter");
+ assert_eq!(format!("{:?}", SleepPhase::Resume), "Resume");
+ }
+}
diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs
index 58bcc22d..4f817811 100644
--- a/drivers/acpid/src/acpi.rs
+++ b/drivers/acpid/src/acpi.rs
@@ -15,6 +15,7 @@ use common::io::{Io, Pio};
use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use thiserror::Error;
+use crate::sleep::{SleepPhase, SleepTarget};
use acpi::{
aml::{namespace::AmlName, AmlError, Interpreter},
@@ -952,7 +953,7 @@ pub struct AcpiContext {
fadt: Option<Fadt>,
pm1a_cnt_blk: u64,
pm1b_cnt_blk: u64,
- s5_values: RwLock<Option<(u8, u8)>>,
+ sleep_values: RwLock<std::collections::BTreeMap<SleepTarget, (u8, u8)>>,
reset_reg: Option<GenericAddressStructure>,
reset_value: u8,
@@ -1240,7 +1241,7 @@ impl AcpiContext {
fadt,
pm1a_cnt_blk,
pm1b_cnt_blk,
- s5_values: RwLock::new(None),
+ sleep_values: RwLock::new(std::collections::BTreeMap::new()),
reset_reg,
reset_value,
pci_fd: RwLock::new(None),
@@ -1385,23 +1386,35 @@ impl AcpiContext {
/// - search for PM1a
/// See https://forum.osdev.org/viewtopic.php?t=16990 for practical details
pub fn set_global_s_state(&self, state: u8) {
- if state != 5 {
- return;
- }
- let (slp_typa, slp_typb) = if let Some(values) = *self.s5_values.read() {
- values
- } else {
- let Ok(values) = self.evaluate_acpi_method("\\", "_S5", &[]) else {
- log::error!("Cannot set S-state, failed to evaluate \\_S5");
- return;
- };
- if values.len() < 2 {
- log::error!("Cannot set S-state, \\_S5 package too small");
- return;
- }
- let values = (values[0] as u8, values[1] as u8);
- *self.s5_values.write() = Some(values);
- values
- };
+ let target = match SleepTarget::try_from(state) {
+ Ok(target) => target,
+ Err(_) => {
+ log::error!("Cannot set S-state {state}, unsupported target");
+ return;
+ }
+ };
+
+ if !target.is_soft_off() {
+ log::warn!(
+ "ACPI sleep groundwork only: {} is recognized but not implemented yet",
+ target.aml_method_name()
+ );
+ return;
+ }
+
+ log::info!("acpid: {:?} {}", SleepPhase::Prepare, target.aml_method_name());
+
+ let (slp_typa, slp_typb) = match self.sleep_type_values(target) {
+ Some(values) => values,
+ None => return,
+ };
let mut val = 1 << 13;
log::trace!("Shutdown SLP_TYPa {:X}, SLP_TYPb {:X}", slp_typa, slp_typb);
@@ -1412,6 +1425,7 @@ impl AcpiContext {
{
if self.pm1a_cnt_blk != 0 {
let port = self.pm1a_cnt_blk as u16;
+ log::info!("acpid: {:?} {} via PM1a", SleepPhase::Enter, target.aml_method_name());
log::warn!("Shutdown with ACPI outw(0x{:X}, 0x{:X})", port, val);
Pio::<u16>::new(port).write(val);
}
@@ -1419,6 +1433,7 @@ impl AcpiContext {
if self.pm1b_cnt_blk != 0 {
let mut val_b = 1 << 13;
val_b |= u16::from(slp_typb);
+ log::info!("acpid: {:?} {} via PM1b", SleepPhase::Enter, target.aml_method_name());
let port = self.pm1b_cnt_blk as u16;
log::warn!("Shutdown with ACPI outw(0x{:X}, 0x{:X})", port, val_b);
Pio::<u16>::new(port).write(val_b);
@@ -1438,6 +1453,23 @@ impl AcpiContext {
}
}
+ fn sleep_type_values(&self, target: SleepTarget) -> Option<(u8, u8)> {
+ if let Some(values) = self.sleep_values.read().get(&target).copied() {
+ return Some(values);
+ }
+
+ let method_name = target.aml_method_name();
+ let Ok(values) = self.evaluate_acpi_method("\\", method_name, &[]) else {
+ log::error!("Cannot set S-state, failed to evaluate \\{method_name}");
+ return None;
+ };
+ if values.len() < 2 {
+ log::error!("Cannot set S-state, \\{method_name} package too small");
+ return None;
+ }
+ let values = (values[0] as u8, values[1] as u8);
+ self.sleep_values.write().insert(target, values);
+ Some(values)
+ }
+
pub fn acpi_reboot(&self) {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
if let Some(reset_reg) = &self.reset_reg {
diff --git a/drivers/graphics/ihdgd/config.toml b/drivers/graphics/ihdgd/config.toml
diff --git a/drivers/storage/usbscsid/src/quirks.rs b/drivers/storage/usbscsid/src/quirks.rs
index 5051f1b0..ae4c2e46 100644
--- a/drivers/storage/usbscsid/src/quirks.rs
+++ b/drivers/storage/usbscsid/src/quirks.rs
@@ -128,7 +128,7 @@ fn quirk_files() -> Option<Vec<PathBuf>> {
}
fn parse_runtime_quirks_from_toml(text: &str) -> Vec<RuntimeQuirkEntry> {
- let Ok(value) = text.parse::<Value>() else {
+ let Ok(value) = text.trim().parse::<Table>() else {
return Vec::new();
};
@@ -201,13 +201,7 @@ mod tests {
#[test]
fn runtime_toml_parser_keeps_supported_flags_and_skips_unknown_ones() {
let entries = parse_runtime_quirks_from_toml(
- r#"
- [[usb_storage_quirk]]
- vendor = 4660
- product = 22136
- flags = ["ignore_residue", "unknown_flag", "fix_capacity"]
- "#,
+ "[[usb_storage_quirk]]\nvendor = 4660\nproduct = 22136\nflags = [\"ignore_residue\", \"unknown_flag\", \"fix_capacity\"]\n",
);
assert_eq!(entries.len(), 1);
diff --git a/drivers/storage/usbscsid/src/scsi/cmds.rs b/drivers/storage/usbscsid/src/scsi/cmds.rs
index ddc12336..df5d0a0c 100644
--- a/drivers/storage/usbscsid/src/scsi/cmds.rs
+++ b/drivers/storage/usbscsid/src/scsi/cmds.rs
@@ -265,6 +265,57 @@ impl Write10 {
}
}
+#[repr(C, packed)]
+#[derive(Clone, Copy, Debug)]
+pub struct SynchronizeCache10 {
+ pub opcode: u8,
+ pub a: u8,
+ pub lba: u32,
+ pub group_num: u8,
+ pub blocks: u16,
+ pub control: u8,
+}
+unsafe impl plain::Plain for SynchronizeCache10 {}
+
+impl SynchronizeCache10 {
+ pub const fn new(lba: u64, blocks: u16, control: u8) -> Self {
+ Self {
+ opcode: Opcode::SyncCache10 as u8,
+ a: 0,
+ lba: u32::to_be(lba as u32),
+ group_num: 0,
+ blocks: u16::to_be(blocks),
+ control,
+ }
+ }
+}
+
+#[repr(C, packed)]
+#[derive(Clone, Copy, Debug)]
+pub struct SynchronizeCache16 {
+ pub opcode: u8,
+ pub a: u8,
+ pub lba: u64,
+ pub blocks: u32,
+ pub group_num: u8,
+ pub control: u8,
+}
+unsafe impl plain::Plain for SynchronizeCache16 {}
+
+impl SynchronizeCache16 {
+ pub const fn new(lba: u64, blocks: u32, control: u8) -> Self {
+ Self {
+ opcode: Opcode::SyncCache16 as u8,
+ a: 0,
+ lba: u64::to_be(lba),
+ blocks: u32::to_be(blocks),
+ group_num: 0,
+ control,
+ }
+ }
+}
+
#[repr(C, packed)]
#[derive(Clone, Copy, Debug)]
pub struct ModeSense6 {
diff --git a/drivers/storage/usbscsid/src/scsi/mod.rs b/drivers/storage/usbscsid/src/scsi/mod.rs
index b6d379d0..cf4a9707 100644
--- a/drivers/storage/usbscsid/src/scsi/mod.rs
+++ b/drivers/storage/usbscsid/src/scsi/mod.rs
@@ -25,6 +25,8 @@ const REQUEST_SENSE_CMD_LEN: u8 = 6;
const MIN_INQUIRY_ALLOC_LEN: u16 = 5;
const MIN_REPORT_SUPP_OPCODES_ALLOC_LEN: u32 = 4;
const MAX_SECTORS_64_LIMIT: u64 = 64;
+const SYNC_CACHE10_CMD_LEN: usize = 10;
+const SYNC_CACHE16_CMD_LEN: usize = 16;
@@ -286,6 +288,12 @@ impl Scsi {
pub fn cmd_write10(&mut self) -> Result<&mut cmds::Write10> {
parse_mut_bytes("WRITE(10) command", &mut self.command_buffer)
}
+ pub fn cmd_sync_cache10(&mut self) -> Result<&mut cmds::SynchronizeCache10> {
+ parse_mut_bytes("SYNCHRONIZE CACHE(10) command", &mut self.command_buffer)
+ }
+ pub fn cmd_sync_cache16(&mut self) -> Result<&mut cmds::SynchronizeCache16> {
+ parse_mut_bytes("SYNCHRONIZE CACHE(16) command", &mut self.command_buffer)
+ }
pub fn res_standard_inquiry_data(&self) -> Result<&StandardInquiryData> {
parse_bytes("standard inquiry data", &self.inquiry_buffer)
}
@@ -467,6 +475,10 @@ impl Scsi {
let status = protocol.send_command(
&self.command_buffer[..10],
DeviceReqData::Out(&buffer[..bytes_to_write]),
)?;
+ if self.quirks.contains(UsbStorageQuirkFlags::NEEDS_SYNC_CACHE)
+ && status.kind == SendCommandStatusKind::Success
+ {
+ self.sync_cache(protocol, lba, blocks_to_write)?;
+ }
Ok(status.bytes_transferred(bytes_to_write as u32))
} else {
@@ -482,8 +494,83 @@ impl Scsi {
let status = protocol.send_command(
&self.command_buffer[..16],
DeviceReqData::Out(&buffer[..bytes_to_write]),
)?;
+ if self.quirks.contains(UsbStorageQuirkFlags::NEEDS_SYNC_CACHE)
+ && status.kind == SendCommandStatusKind::Success
+ {
+ self.sync_cache(protocol, lba, blocks_to_write)?;
+ }
Ok(status.bytes_transferred(bytes_to_write as u32))
}
}
+
+ fn sync_cache(&mut self, protocol: &mut dyn Protocol, lba: u64, blocks: u64) -> Result<()> {
+ let use_sync_cache10 = self.quirks.contains(UsbStorageQuirkFlags::INITIAL_READ10)
+ && u32::try_from(lba).is_ok()
+ && u16::try_from(blocks).is_ok();
+
+ let status = if use_sync_cache10 {
+ let sync = self.cmd_sync_cache10()?;
+ *sync = cmds::SynchronizeCache10::new(
+ lba,
+ u16::try_from(blocks)
+ .map_err(|_| ScsiError::Overflow("sync cache(10) block count overflow"))?,
+ 0,
+ );
+ protocol.send_command(&self.command_buffer[..SYNC_CACHE10_CMD_LEN], DeviceReqData::NoData)?
+ } else {
+ let sync = self.cmd_sync_cache16()?;
+ *sync = cmds::SynchronizeCache16::new(
+ lba,
+ u32::try_from(blocks)
+ .map_err(|_| ScsiError::Overflow("sync cache(16) block count overflow"))?,
+ 0,
+ );
+ protocol.send_command(&self.command_buffer[..SYNC_CACHE16_CMD_LEN], DeviceReqData::NoData)?
+ };
+
+ if status.kind == SendCommandStatusKind::Success {
+ return Ok(());
+ }
+
+ if let Ok(()) = self.get_ff_sense(protocol, cmds::RequestSense::MINIMAL_ALLOC_LEN) {
+ if let Ok(sense) = self.res_ff_sense_data() {
+ if sense.add_sense_code == 0x3A
+ || sense.add_sense_code == 0x20
+ || (sense.add_sense_code == 0x04 && sense.add_sense_code_qual == 0x04)
+ || sense.sense_key() == cmds::SenseKey::IllegalRequest
+ {
+ return Ok(());
+ }
+ }
+ }
+
+ Err(ScsiError::ProtocolError(ProtocolError::ProtocolError(
+ "SYNCHRONIZE CACHE command failed",
+ )))
+ }
}
@@ -527,3 +614,53 @@ impl<'a> BlkDescSlice<'a> {
}
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::protocol::SendCommandStatus;
+ use crate::scsi::opcodes::Opcode;
+
+ struct MockProtocol {
+ commands: Vec<Vec<u8>>,
+ }
+
+ impl MockProtocol {
+ fn new() -> Self {
+ Self { commands: Vec::new() }
+ }
+ }
+
+ impl Protocol for MockProtocol {
+ fn send_command(&mut self, command: &[u8], _data: DeviceReqData) -> std::result::Result<SendCommandStatus, ProtocolError> {
+ self.commands.push(command.to_vec());
+ Ok(SendCommandStatus { residue: None, kind: SendCommandStatusKind::Success })
+ }
+ }
+
+ fn scsi_for_tests(quirks: UsbStorageQuirkFlags) -> Scsi {
+ Scsi {
+ command_buffer: [0u8; 16],
+ inquiry_buffer: [0u8; 259],
+ data_buffer: Vec::new(),
+ block_size: 512,
+ block_count: 1024,
+ quirks,
+ }
+ }
+
+ #[test]
+ fn sync_cache_uses_10_byte_command_for_initial_read10_quirk() {
+ let mut scsi = scsi_for_tests(UsbStorageQuirkFlags::INITIAL_READ10);
+ let mut protocol = MockProtocol::new();
+ scsi.sync_cache(&mut protocol, 7, 4).unwrap();
+ assert_eq!(protocol.commands.len(), 1);
+ assert_eq!(protocol.commands[0].len(), 10);
+ assert_eq!(protocol.commands[0][0], Opcode::SyncCache10 as u8);
+ }
+
+ #[test]
+ fn sync_cache_uses_16_byte_command_without_initial_read10_quirk() {
+ let mut scsi = scsi_for_tests(UsbStorageQuirkFlags::empty());
+ let mut protocol = MockProtocol::new();
+ scsi.sync_cache(&mut protocol, 7, 4).unwrap();
+ assert_eq!(protocol.commands.len(), 1);
+ assert_eq!(protocol.commands[0].len(), 16);
+ assert_eq!(protocol.commands[0][0], Opcode::SyncCache16 as u8);
+ }
+}
diff --git a/drivers/storage/usbscsid/src/quirks.rs b/drivers/storage/usbscsid/src/quirks.rs
index ae4c2e46..30380aea 100644
--- a/drivers/storage/usbscsid/src/quirks.rs
+++ b/drivers/storage/usbscsid/src/quirks.rs
@@ -128,7 +128,7 @@ fn quirk_files() -> Option<Vec<PathBuf>> {
}
fn parse_runtime_quirks_from_toml(text: &str) -> Vec<RuntimeQuirkEntry> {
- let Ok(value) = text.parse::<Value>() else {
+ let Ok(value) = text.trim().parse::<Table>() else {
return Vec::new();
};
@@ -201,13 +201,7 @@ mod tests {
#[test]
fn runtime_toml_parser_keeps_supported_flags_and_skips_unknown_ones() {
let entries = parse_runtime_quirks_from_toml(
- r#"
- [[usb_storage_quirk]]
- vendor = 4660
- product = 22136
- flags = ["ignore_residue", "unknown_flag", "fix_capacity"]
- "#,
+ "[[usb_storage_quirk]]\nvendor = 4660\nproduct = 22136\nflags = [\"ignore_residue\", \"unknown_flag\", \"fix_capacity\"]\n",
);
assert_eq!(entries.len(), 1);
diff --git a/drivers/storage/usbscsid/src/scsi/cmds.rs b/drivers/storage/usbscsid/src/scsi/cmds.rs
index df5d0a0c..0674c6b5 100644
--- a/drivers/storage/usbscsid/src/scsi/cmds.rs
+++ b/drivers/storage/usbscsid/src/scsi/cmds.rs
@@ -265,6 +265,57 @@ impl Write10 {
}
}
+#[repr(C, packed)]
+#[derive(Clone, Copy, Debug)]
+pub struct SynchronizeCache10 {
+ pub opcode: u8,
+ pub a: u8,
+ pub lba: u32,
+ pub group_num: u8,
+ pub blocks: u16,
+ pub control: u8,
+}
+unsafe impl plain::Plain for SynchronizeCache10 {}
+
+impl SynchronizeCache10 {
+ pub const fn new(lba: u64, blocks: u16, control: u8) -> Self {
+ Self {
+ opcode: Opcode::SyncCache10 as u8,
+ a: 0,
+ lba: u32::to_be(lba as u32),
+ group_num: 0,
+ blocks: u16::to_be(blocks),
+ control,
+ }
+ }
+}
+
+#[repr(C, packed)]
+#[derive(Clone, Copy, Debug)]
+pub struct SynchronizeCache16 {
+ pub opcode: u8,
+ pub a: u8,
+ pub lba: u64,
+ pub blocks: u32,
+ pub group_num: u8,
+ pub control: u8,
+}
+unsafe impl plain::Plain for SynchronizeCache16 {}
+
+impl SynchronizeCache16 {
+ pub const fn new(lba: u64, blocks: u32, control: u8) -> Self {
+ Self {
+ opcode: Opcode::SyncCache16 as u8,
+ a: 0,
+ lba: u64::to_be(lba),
+ blocks: u32::to_be(blocks),
+ group_num: 0,
+ control,
+ }
+ }
+}
+
#[repr(C, packed)]
#[derive(Clone, Copy, Debug)]
pub struct ModeSense6 {
diff --git a/drivers/storage/usbscsid/src/scsi/mod.rs b/drivers/storage/usbscsid/src/scsi/mod.rs
index cf4a9707..ad0565ca 100644
--- a/drivers/storage/usbscsid/src/scsi/mod.rs
+++ b/drivers/storage/usbscsid/src/scsi/mod.rs
@@ -25,6 +25,8 @@ const REQUEST_SENSE_CMD_LEN: u8 = 6;
const MIN_INQUIRY_ALLOC_LEN: u16 = 5;
const MIN_REPORT_SUPP_OPCODES_ALLOC_LEN: u32 = 4;
const MAX_SECTORS_64_LIMIT: u64 = 64;
+const SYNC_CACHE10_CMD_LEN: usize = 10;
+const SYNC_CACHE16_CMD_LEN: usize = 16;
@@ -286,6 +288,12 @@ impl Scsi {
pub fn cmd_write10(&mut self) -> Result<&mut cmds::Write10> {
parse_mut_bytes("WRITE(10) command", &mut self.command_buffer)
}
+ pub fn cmd_sync_cache10(&mut self) -> Result<&mut cmds::SynchronizeCache10> {
+ parse_mut_bytes("SYNCHRONIZE CACHE(10) command", &mut self.command_buffer)
+ }
+ pub fn cmd_sync_cache16(&mut self) -> Result<&mut cmds::SynchronizeCache16> {
+ parse_mut_bytes("SYNCHRONIZE CACHE(16) command", &mut self.command_buffer)
+ }
pub fn res_standard_inquiry_data(&self) -> Result<&StandardInquiryData> {
parse_bytes("standard inquiry data", &self.inquiry_buffer)
}
@@ -467,6 +475,10 @@ impl Scsi {
let status = protocol.send_command(
&self.command_buffer[..10],
DeviceReqData::Out(&buffer[..bytes_to_write]),
)?;
+ if self.quirks.contains(UsbStorageQuirkFlags::NEEDS_SYNC_CACHE)
+ && status.kind == SendCommandStatusKind::Success
+ {
+ self.sync_cache(protocol, lba, blocks_to_write)?;
+ }
Ok(status.bytes_transferred(bytes_to_write as u32))
} else {
@@ -482,8 +494,83 @@ impl Scsi {
let status = protocol.send_command(
&self.command_buffer[..16],
DeviceReqData::Out(&buffer[..bytes_to_write]),
)?;
+ if self.quirks.contains(UsbStorageQuirkFlags::NEEDS_SYNC_CACHE)
+ && status.kind == SendCommandStatusKind::Success
+ {
+ self.sync_cache(protocol, lba, blocks_to_write)?;
+ }
Ok(status.bytes_transferred(bytes_to_write as u32))
}
}
+
+ fn sync_cache(&mut self, protocol: &mut dyn Protocol, lba: u64, blocks: u64) -> Result<()> {
+ let use_sync_cache10 = self.quirks.contains(UsbStorageQuirkFlags::INITIAL_READ10)
+ && u32::try_from(lba).is_ok()
+ && u16::try_from(blocks).is_ok();
+
+ let status = if use_sync_cache10 {
+ let sync = self.cmd_sync_cache10()?;
+ *sync = cmds::SynchronizeCache10::new(
+ lba,
+ u16::try_from(blocks)
+ .map_err(|_| ScsiError::Overflow("sync cache(10) block count overflow"))?,
+ 0,
+ );
+ protocol.send_command(&self.command_buffer[..SYNC_CACHE10_CMD_LEN], DeviceReqData::NoData)?
+ } else {
+ let sync = self.cmd_sync_cache16()?;
+ *sync = cmds::SynchronizeCache16::new(
+ lba,
+ u32::try_from(blocks)
+ .map_err(|_| ScsiError::Overflow("sync cache(16) block count overflow"))?,
+ 0,
+ );
+ protocol.send_command(&self.command_buffer[..SYNC_CACHE16_CMD_LEN], DeviceReqData::NoData)?
+ };
+
+ if status.kind == SendCommandStatusKind::Success {
+ return Ok(());
+ }
+
+ if let Ok(()) = self.get_ff_sense(protocol, cmds::RequestSense::MINIMAL_ALLOC_LEN) {
+ if let Ok(sense) = self.res_ff_sense_data() {
+ if sense.add_sense_code == 0x3A
+ || sense.add_sense_code == 0x20
+ || (sense.add_sense_code == 0x04 && sense.add_sense_code_qual == 0x04)
+ || sense.sense_key() == cmds::SenseKey::IllegalRequest
+ {
+ return Ok(());
+ }
+ }
+ }
+
+ Err(ScsiError::ProtocolError(ProtocolError::ProtocolError(
+ "SYNCHRONIZE CACHE command failed",
+ )))
+ }
}
@@ -527,3 +614,53 @@ impl<'a> BlkDescSlice<'a> {
}
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::protocol::SendCommandStatus;
+ use crate::scsi::opcodes::Opcode;
+
+ struct MockProtocol {
+ commands: Vec<Vec<u8>>,
+ }
+
+ impl MockProtocol {
+ fn new() -> Self {
+ Self { commands: Vec::new() }
+ }
+ }
+
+ impl Protocol for MockProtocol {
+ fn send_command(&mut self, command: &[u8], _data: DeviceReqData) -> std::result::Result<SendCommandStatus, ProtocolError> {
+ self.commands.push(command.to_vec());
+ Ok(SendCommandStatus { residue: None, kind: SendCommandStatusKind::Success })
+ }
+ }
+
+ fn scsi_for_tests(quirks: UsbStorageQuirkFlags) -> Scsi {
+ Scsi {
+ command_buffer: [0u8; 16],
+ inquiry_buffer: [0u8; 259],
+ data_buffer: Vec::new(),
+ block_size: 512,
+ block_count: 1024,
+ quirks,
+ }
+ }
+
+ #[test]
+ fn sync_cache_uses_10_byte_command_for_initial_read10_quirk() {
+ let mut scsi = scsi_for_tests(UsbStorageQuirkFlags::INITIAL_READ10);
+ let mut protocol = MockProtocol::new();
+ scsi.sync_cache(&mut protocol, 7, 4).unwrap();
+ assert_eq!(protocol.commands.len(), 1);
+ assert_eq!(protocol.commands[0].len(), 10);
+ assert_eq!(protocol.commands[0][0], Opcode::SyncCache10 as u8);
+ }
+
+ #[test]
+ fn sync_cache_uses_16_byte_command_without_initial_read10_quirk() {
+ let mut scsi = scsi_for_tests(UsbStorageQuirkFlags::empty());
+ let mut protocol = MockProtocol::new();
+ scsi.sync_cache(&mut protocol, 7, 4).unwrap();
+ assert_eq!(protocol.commands.len(), 1);
+ assert_eq!(protocol.commands[0].len(), 16);
+ assert_eq!(protocol.commands[0][0], Opcode::SyncCache16 as u8);
+ }
+}
diff --git a/drivers/usb/xhcid/src/xhci/device_enumerator.rs b/drivers/usb/xhcid/src/xhci/device_enumerator.rs
index 1f144ac9..00000000 100644
--- a/drivers/usb/xhcid/src/xhci/device_enumerator.rs
+++ b/drivers/usb/xhcid/src/xhci/device_enumerator.rs
@@ -4,8 +4,12 @@ use crate::xhci::{PortId, Xhci};
use common::io::Io;
use crossbeam_channel;
use log::{debug, info, warn};
use std::sync::Arc;
-use std::time::Duration;
+use std::time::{Duration, Instant};
use syscall::EAGAIN;
+
+const DEFAULT_PORT_RESET_SETTLE_MS: u64 = 16;
+const RESET_DELAY_PORT_RESET_SETTLE_MS: u64 = 100;
+const HUB_SLOW_RESET_PORT_RESET_SETTLE_MS: u64 = 200;
pub struct DeviceEnumerationRequest {
pub port_id: PortId,
@@ -25,10 +29,14 @@ impl<const N: usize> DeviceEnumerator<N> {
loop {
debug!("Start Device Enumerator Loop");
let request = match self.request_queue.recv() {
Ok(req) => req,
Err(err) => {
- panic!("Failed to received an enumeration request! error: {}", err)
+ warn!(
+ "device enumerator stopping after request queue closed: {}",
+ err
+ );
+ break;
}
};
@@ -64,13 +72,12 @@ impl<const N: usize> DeviceEnumerator<N> {
//If the port isn't enabled (i.e. it's a USB2 port), we need to reset it if it isn't resetting already
//A USB3 port won't generate a Connect Status Change until it's already enabled, so this check
//will always be skipped for USB3 ports
if !flags.contains(PortFlags::PED) {
- let disabled_state = flags.contains(PortFlags::PP)
- && flags.contains(PortFlags::CCS)
- && !flags.contains(PortFlags::PED)
- && !flags.contains(PortFlags::PR);
+ let disabled_state = Self::port_is_disabled(&flags);
if !disabled_state {
- panic!(
- "Port {} isn't in the disabled state! Current flags: {:?}",
+ warn!(
+ "Port {} never reached the disabled state before reset-driven enumeration; current flags: {:?}",
port_id, flags
);
+ continue;
} else {
debug!("Port {} has entered the disabled state.", port_id);
}
@@ -89,17 +96,15 @@ impl<const N: usize> DeviceEnumerator<N> {
port.clear_prc();
- let delay_ms = if early_quirks
- .contains(crate::usb_quirks::UsbQuirkFlags::HUB_SLOW_RESET)
- {
- 200
- } else if early_quirks.contains(crate::usb_quirks::UsbQuirkFlags::RESET_DELAY) {
- 100
- } else {
- 16
- };
-
- std::thread::sleep(Duration::from_millis(delay_ms)); // Some devices need extra time to settle after reset.
+ }
+
+ let flags = self.wait_for_port_enabled_state(
+ port_array_index,
+ Duration::from_millis(Self::port_reset_settle_delay_ms(early_quirks)),
+ );
- let flags = port.flags();
-
- let enabled_state = flags.contains(PortFlags::PP)
- && flags.contains(PortFlags::CCS)
- && flags.contains(PortFlags::PED)
- && !flags.contains(PortFlags::PR);
+ let enabled_state = Self::port_is_enabled(&flags);
if !enabled_state {
warn!(
- "Port {} isn't in the enabled state! Current flags: {:?}",
+ "Port {} isn't in the enabled state after bounded reset settle; current flags: {:?}",
port_id, flags
);
+ continue;
} else {
@@ -140,4 +145,47 @@ impl<const N: usize> DeviceEnumerator<N> {
}
}
}
+
+ fn port_reset_settle_delay_ms(quirks: crate::usb_quirks::UsbQuirkFlags) -> u64 {
+ if quirks.contains(crate::usb_quirks::UsbQuirkFlags::HUB_SLOW_RESET) {
+ HUB_SLOW_RESET_PORT_RESET_SETTLE_MS
+ } else if quirks.contains(crate::usb_quirks::UsbQuirkFlags::RESET_DELAY) {
+ RESET_DELAY_PORT_RESET_SETTLE_MS
+ } else {
+ DEFAULT_PORT_RESET_SETTLE_MS
+ }
+ }
+
+ fn port_is_disabled(flags: &PortFlags) -> bool {
+ flags.contains(PortFlags::PP)
+ && flags.contains(PortFlags::CCS)
+ && !flags.contains(PortFlags::PED)
+ && !flags.contains(PortFlags::PR)
+ }
+
+ fn port_is_enabled(flags: &PortFlags) -> bool {
+ flags.contains(PortFlags::PP)
+ && flags.contains(PortFlags::CCS)
+ && flags.contains(PortFlags::PED)
+ && !flags.contains(PortFlags::PR)
+ }
+
+ fn wait_for_port_enabled_state(
+ &self,
+ port_array_index: usize,
+ settle_timeout: Duration,
+ ) -> PortFlags {
+ let start = Instant::now();
+
+ loop {
+ let flags = {
+ let ports = self
+ .hci
+ .ports
+ .lock()
+ .unwrap_or_else(|poisoned| poisoned.into_inner());
+ ports[port_array_index].flags()
+ };
+
+ if Self::port_is_enabled(&flags)
+ || !flags.contains(PortFlags::PR)
+ || start.elapsed() >= settle_timeout
+ {
+ return flags;
+ }
+
+ std::thread::sleep(Duration::from_millis(1));
+ }
+ }
diff --git a/drivers/usb/xhcid/src/xhci/device_enumerator.rs b/drivers/usb/xhcid/src/xhci/device_enumerator.rs
index 00000000..00000000 100644
--- a/drivers/usb/xhcid/src/xhci/device_enumerator.rs
+++ b/drivers/usb/xhcid/src/xhci/device_enumerator.rs
@@ -46,7 +46,11 @@ impl<const N: usize> DeviceEnumerator<N> {
debug!("Device Enumerator request for port {}", port_id);
let (len, flags) = {
- let ports = self.hci.ports.lock().unwrap();
+ let ports = self
+ .hci
+ .ports
+ .lock()
+ .unwrap_or_else(|poisoned| poisoned.into_inner());
let len = ports.len();
@@ -86,7 +90,11 @@ impl<const N: usize> DeviceEnumerator<N> {
debug!("Received a device connect on port {}, but it's not enabled. Resetting the port.", port_id);
if let Err(err) = self.hci.reset_port(port_id) {
warn!(
"failed to reset port {} before enumeration; skipping attach: {}",
port_id, err
);
continue;
}
- let mut ports = self.hci.ports.lock().unwrap();
+ let mut ports = self
+ .hci
+ .ports
+ .lock()
+ .unwrap_or_else(|poisoned| poisoned.into_inner());
let port = &mut ports[port_array_index];
port.clear_prc();
@@ -144,10 +152,16 @@ impl<const N: usize> DeviceEnumerator<N> {
let result = futures::executor::block_on(self.hci.detach_device(port_id));
match result {
Ok(was_connected) => {
if was_connected {
info!("Device on port {} was detached", port_id);
+ } else {
+ debug!(
+ "Ignoring duplicate or out-of-order detach event for unattached port {}",
+ port_id
+ );
}
}
Err(err) => {
- warn!("processing of device attach request failed! Error: {}", err);
+ warn!("processing of device detach request failed! Error: {}", err);
}
}
diff --git a/drivers/usb/xhcid/src/xhci/mod.rs b/drivers/usb/xhcid/src/xhci/mod.rs
index c53cb59f..814fdb4f 100644
--- a/drivers/usb/xhcid/src/xhci/mod.rs
+++ b/drivers/usb/xhcid/src/xhci/mod.rs
@@ -307,6 +307,7 @@ struct PortState<const N: usize> {
slot: u8,
protocol_speed: &'static ProtocolSpeed,
cfg_idx: Option<u8>,
+ active_alternates: BTreeMap<u8, u8>,
input_context: Mutex<Dma<InputContext<N>>>,
dev_desc: Option<DevDesc>,
endpoint_states: BTreeMap<u8, EndpointState>,
@@ -324,29 +325,37 @@ pub(crate) enum PortPmState {
impl<const N: usize> PortState<N> {
//TODO: fetch using endpoint number instead
fn get_endp_desc(&self, endp_idx: u8) -> Option<&EndpDesc> {
- let cfg_idx = self.cfg_idx?;
- let config_desc = self
- .dev_desc
- .as_ref()?
- .config_descs
- .iter()
- .find(|desc| desc.configuration_value == cfg_idx)?;
- let mut endp_count = 0;
- for if_desc in config_desc.interface_descs.iter() {
- let active_alternate = self
- .active_alternates
- .get(&if_desc.number)
- .copied()
- .unwrap_or(0);
- if if_desc.alternate_setting != active_alternate {
- continue;
- }
- for endp_desc in if_desc.endpoints.iter() {
- if endp_idx == endp_count {
- return Some(endp_desc);
- }
- endp_count += 1;
- }
- }
- None
+ active_endpoint_desc(
+ self.dev_desc.as_ref()?,
+ self.cfg_idx?,
+ &self.active_alternates,
+ endp_idx,
+ )
}
}
+
+fn active_configuration<'a>(dev_desc: &'a DevDesc, cfg_idx: u8) -> Option<&'a ConfDesc> {
+ dev_desc
+ .config_descs
+ .iter()
+ .find(|desc| desc.configuration_value == cfg_idx)
+}
+
+fn active_endpoint_desc<'a>(dev_desc: &'a DevDesc, cfg_idx: u8, active_alternates: &BTreeMap<u8, u8>, endp_idx: u8) -> Option<&'a EndpDesc> {
+ let config_desc = active_configuration(dev_desc, cfg_idx)?;
+ let mut endp_count = 0;
+ for if_desc in config_desc.interface_descs.iter() {
+ let active_alternate = active_alternates.get(&if_desc.number).copied().unwrap_or(0);
+ if if_desc.alternate_setting != active_alternate {
+ continue;
+ }
+ for endp_desc in if_desc.endpoints.iter() {
+ if endp_idx == endp_count {
+ return Some(endp_desc);
+ }
+ endp_count += 1;
+ }
+ }
+ None
+}
@@ -872,6 +881,7 @@ impl<const N: usize> Xhci<N> {
protocol_speed,
input_context: Mutex::new(input),
dev_desc: None,
+ active_alternates: BTreeMap::new(),
cfg_idx: None,
endpoint_states: std::iter::once((
0,
@@ -1516,6 +1526,67 @@ struct DriversConfig {
drivers: Vec<DriverConfig>,
}
+
+#[cfg(test)]
+mod tests {
+ use super::{active_endpoint_desc, BTreeMap, ConfDesc, DevDesc, EndpDesc, IfDesc};
+ use crate::driver_interface::EndpointTy;
+ use smallvec::smallvec;
+
+ fn endp(address: u8, attributes: u8) -> EndpDesc {
+ EndpDesc { kind: 5, address, attributes, max_packet_size: 64, interval: 1, ssc: None, sspc: None }
+ }
+
+ fn if_desc(number: u8, alternate_setting: u8, endpoints: Vec<EndpDesc>) -> IfDesc {
+ IfDesc {
+ kind: 4,
+ number,
+ alternate_setting,
+ class: 3,
+ sub_class: 1,
+ protocol: 1,
+ interface_str: None,
+ endpoints: endpoints.into_iter().collect(),
+ hid_descs: smallvec![],
+ }
+ }
+
+ fn sample_dev_desc() -> DevDesc {
+ DevDesc {
+ kind: 1,
+ usb: 0x0200,
+ class: 0,
+ sub_class: 0,
+ protocol: 0,
+ packet_size: 64,
+ vendor: 0x1234,
+ product: 0x5678,
+ release: 0x0100,
+ manufacturer_str: None,
+ product_str: None,
+ serial_str: None,
+ config_descs: smallvec![ConfDesc { kind: 2, configuration_value: 1, configuration: None, attributes: 0x80, max_power: 50, interface_descs: smallvec![ if_desc(0, 0, vec![endp(0x81, 0x03)]), if_desc(0, 1, vec![endp(0x82, 0x03), endp(0x02, 0x03)]), if_desc(1, 0, vec![endp(0x83, 0x02)]), ], }],
+ }
+ }
+
+ #[test]
+ fn active_endpoint_desc_uses_default_alternates_initially() {
+ let dev_desc = sample_dev_desc();
+ let active = BTreeMap::new();
+ let first = active_endpoint_desc(&dev_desc, 1, &active, 0).expect("endpoint 0");
+ let second = active_endpoint_desc(&dev_desc, 1, &active, 1).expect("endpoint 1");
+ assert_eq!(first.address, 0x81);
+ assert_eq!(first.ty(), EndpointTy::Interrupt);
+ assert_eq!(second.address, 0x83);
+ assert_eq!(second.ty(), EndpointTy::Bulk);
+ assert!(active_endpoint_desc(&dev_desc, 1, &active, 2).is_none());
+ }
+
+ #[test]
+ fn active_endpoint_desc_switches_to_selected_alternate() {
+ let dev_desc = sample_dev_desc();
+ let mut active = BTreeMap::new();
+ active.insert(0, 1);
+ let first = active_endpoint_desc(&dev_desc, 1, &active, 0).expect("endpoint 0");
+ let second = active_endpoint_desc(&dev_desc, 1, &active, 1).expect("endpoint 1");
+ let third = active_endpoint_desc(&dev_desc, 1, &active, 2).expect("endpoint 2");
+ assert_eq!(first.address, 0x82);
+ assert_eq!(second.address, 0x02);
+ assert_eq!(third.address, 0x83);
+ }
+}
diff --git a/drivers/usb/xhcid/src/xhci/scheme.rs b/drivers/usb/xhcid/src/xhci/scheme.rs
--- a/drivers/usb/xhcid/src/xhci/scheme.rs
+++ b/drivers/usb/xhcid/src/xhci/scheme.rs
@@ -3487,6 +3487,14 @@ impl EndpointContextSnapshot {
fn capture_values(a: u32, b: u32, trl: u32, trh: u32, c: u32) -> Self {
Self { a, b, trl, trh, c }
}
+
+ fn restore<const N: usize>(&self, ctx: &mut EndpointContext<N>) {
+ ctx.a.write(self.a);
+ ctx.b.write(self.b);
+ ctx.trl.write(self.trl);
+ ctx.trh.write(self.trh);
+ ctx.c.write(self.c);
+ }
}
@@ -1171,7 +1171,9 @@ impl<N> XhciScheme<N>
input_context.device.slot.c.write(snapshot.slot_c);
for (endp_i, endp_snapshot) in endpoint_snapshots {
- endp_snapshot.restore(&mut input_context.device.endpoints[*endp_i]);
+ let endpoint_ptr = core::ptr::addr_of_mut!(input_context.device.endpoints[*endp_i]);
+ let mut endpoint = unsafe { core::ptr::read_unaligned(endpoint_ptr) };
+ endp_snapshot.restore(&mut endpoint);
+ unsafe { core::ptr::write_unaligned(endpoint_ptr, endpoint) };
}
Ok(input_context.physical())
index b0fb9b85..bba6f232 100644
--- a/drivers/usb/xhcid/src/xhci/scheme.rs
+++ b/drivers/usb/xhcid/src/xhci/scheme.rs
@@ -1105,11 +1105,28 @@ impl<const N: usize> Xhci<N> {
.find(|desc| desc.configuration_value == req.config_desc)
.ok_or(Error::new(EBADFD))?;
+ let configuration_value = config_desc.configuration_value;
+
+ let interface_layout = config_desc
+ .interface_descs
+ .iter()
+ .map(|if_desc| {
+ (
+ if_desc.number,
+ if_desc.alternate_setting,
+ if_desc.endpoints.iter().map(|endp| *endp).collect::<Vec<_>>(),
+ )
+ })
+ .collect::<Vec<_>>();
- //TODO: USE ENDPOINTS FROM ALL INTERFACES
+ port_state.active_alternates.clear();
+ for (if_num, _, _) in &interface_layout {
+ port_state.active_alternates.entry(*if_num).or_insert(0);
+ }
+
let mut endp_desc_count = 0;
let mut new_context_entries = 1;
- for if_desc in config_desc.interface_descs.iter() {
- for endpoint in if_desc.endpoints.iter() {
+ for (if_num, alternate_setting, endpoints) in &interface_layout {
+ let active_alternate = port_state.active_alternates.get(if_num).copied().unwrap_or(0);
+ if *alternate_setting != active_alternate {
+ continue;
+ }
+ for endpoint in endpoints.iter() {
endp_desc_count += 1;
let entry = Self::endp_num_to_dci(endp_desc_count, endpoint);
if entry > new_context_entries {
@@ -1128,7 +1145,7 @@ impl<const N: usize> Xhci<N> {
(
endp_desc_count,
new_context_entries,
- config_desc.configuration_value,
+ configuration_value,
)
};
@@ -1397,6 +1414,10 @@ impl<const N: usize> Xhci<N> {
if !skip_set_interface {
self.set_interface(port, interface_num, alternate_setting)
.await?;
+ }
+
+ if let Some(mut port_state) = self.port_states.get_mut(&port) {
+ port_state.active_alternates.insert(interface_num, alternate_setting);
}
}
}
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<dyn Error>> {
- // 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<Source> =
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<Self> {
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
diff --git a/drivers/input/ps2d/src/controller.rs b/drivers/input/ps2d/src/controller.rs
index 561aa527..0310a367 100644
--- a/drivers/input/ps2d/src/controller.rs
+++ b/drivers/input/ps2d/src/controller.rs
@@ -283,8 +283,27 @@ impl Ps2 {
status_bits
);
}
+ let flushed = self.flush_output();
+ if flushed != 0 {
+ debug!("ps/2 controller probe drained {} stale byte(s)", flushed);
+ }
self.config().is_ok()
}
+
+ pub fn flush_output(&mut self) -> usize {
+ let mut flushed = 0;
+ while let Some((keyboard, data)) = self.next() {
+ flushed += 1;
+ trace!(
+ "ps/2 flush discarded {:02X} from {} channel",
+ data,
+ if keyboard { "keyboard" } else { "mouse" }
+ );
+ }
+ flushed
+ }
pub fn init_keyboard(&mut self) -> Result<(), Error> {
let mut b;
@@ -325,6 +344,11 @@ impl Ps2 {
self.command(Command::DisableSecond)?;
}
+ let flushed = self.flush_output();
+ if flushed != 0 {
+ debug!("ps/2 init discarded {} stale byte(s) before config", flushed);
+ }
+
// Disable clocks, disable interrupts, and disable translate
{
// Since the default config may have interrupts enabled, and the kernel may eat up
@@ -358,6 +382,11 @@ impl Ps2 {
warn!("self test unexpected value: {:02X}", r);
}
}
+
+ let flushed = self.flush_output();
+ if flushed != 0 {
+ debug!("ps/2 init discarded {} byte(s) after controller self-test", flushed);
+ }
// Initialize keyboard
if let Err(err) = self.init_keyboard() {
diff --git a/drivers/input/usbhidd/src/main.rs b/drivers/input/usbhidd/src/main.rs
index 15c5b778..c67fb8bc 100644
--- a/drivers/input/usbhidd/src/main.rs
+++ b/drivers/input/usbhidd/src/main.rs
@@ -159,17 +159,17 @@ fn main() -> Result<()> {
const USAGE: &'static str = "usbhidd <scheme> <port> <interface>";
- 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::<PortId>()
- .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::<u8>()
- .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<PciLocation> {
+ 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<PciDeviceInfo> {
+ 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<String> },
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/quirks.rs b/drivers/storage/usbscsid/src/quirks.rs
new file mode 100644
index 00000000..5051f1b0
--- /dev/null
+++ b/drivers/storage/usbscsid/src/quirks.rs
@@ -0,0 +1,212 @@
+use std::fs;
+use std::path::{Path, PathBuf};
+use std::sync::OnceLock;
+
+use bitflags::bitflags;
+use toml::Value;
+
+bitflags! {
+ #[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
+ pub struct UsbStorageQuirkFlags: u32 {
+ const IGNORE_RESIDUE = 1 << 0;
+ const FIX_CAPACITY = 1 << 1;
+ const SINGLE_LUN = 1 << 2;
+ const MAX_SECTORS_64 = 1 << 3;
+ const INITIAL_READ10 = 1 << 4;
+
+ const FIX_INQUIRY = 1 << 5;
+ const NOT_LOCKABLE = 1 << 6;
+ const SCM_MULT_TARG = 1 << 7;
+ const SANE_SENSE = 1 << 8;
+ const BULK_IGNORE_TAG = 1 << 9;
+ const NEEDS_SYNC_CACHE = 1 << 10;
+ const NO_WP_DETECT = 1 << 11;
+ const NO_READ_CAP16 = 1 << 12;
+ const IGNORE_DEVICE = 1 << 13;
+ }
+}
+
+#[derive(Clone, Copy)]
+struct CompiledQuirkEntry {
+ vendor: u16,
+ product: u16,
+ flags: UsbStorageQuirkFlags,
+}
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+struct RuntimeQuirkEntry {
+ vendor: u16,
+ product: u16,
+ flags: UsbStorageQuirkFlags,
+}
+
+const COMPILED_QUIRKS: &[CompiledQuirkEntry] = &[
+ CompiledQuirkEntry { vendor: 0x03EB, product: 0x2002, flags: UsbStorageQuirkFlags::IGNORE_RESIDUE },
+ CompiledQuirkEntry { vendor: 0x03F0, product: 0x4002, flags: UsbStorageQuirkFlags::FIX_CAPACITY },
+ CompiledQuirkEntry { vendor: 0x0409, product: 0x0040, flags: UsbStorageQuirkFlags::SINGLE_LUN },
+ CompiledQuirkEntry { vendor: 0x0421, product: 0x0019, flags: UsbStorageQuirkFlags::MAX_SECTORS_64 },
+ CompiledQuirkEntry { vendor: 0x090C, product: 0x6000, flags: UsbStorageQuirkFlags::INITIAL_READ10 },
+ CompiledQuirkEntry { vendor: 0x1B1C, product: 0x1AB5, flags: UsbStorageQuirkFlags::INITIAL_READ10 },
+];
+
+static RUNTIME_QUIRKS: OnceLock<Vec<RuntimeQuirkEntry>> = OnceLock::new();
+
+pub fn lookup_usb_storage_quirks(vendor: u16, product: u16) -> UsbStorageQuirkFlags {
+ let mut flags = UsbStorageQuirkFlags::empty();
+
+ for entry in COMPILED_QUIRKS {
+ if entry.vendor == vendor && entry.product == product {
+ flags |= entry.flags;
+ }
+ }
+
+ for entry in runtime_quirks() {
+ if entry.vendor == vendor && entry.product == product {
+ flags |= entry.flags;
+ }
+ }
+
+ flags
+}
+
+fn runtime_quirks() -> &'static [RuntimeQuirkEntry] {
+ RUNTIME_QUIRKS.get_or_init(load_runtime_quirks).as_slice()
+}
+
+fn load_runtime_quirks() -> Vec<RuntimeQuirkEntry> {
+ let mut entries = Vec::new();
+ let Some(dir_entries) = quirk_files() else {
+ return entries;
+ };
+
+ for path in dir_entries {
+ let Ok(text) = fs::read_to_string(&path) else {
+ continue;
+ };
+ entries.extend(parse_runtime_quirks_from_toml(&text));
+ }
+
+ entries
+}
+
+fn quirk_files() -> Option<Vec<PathBuf>> {
+ let quirks_dir = Path::new("/etc/quirks.d");
+ let read_dir = fs::read_dir(quirks_dir).ok()?;
+
+ let mut files = read_dir
+ .filter_map(|entry| entry.ok())
+ .map(|entry| entry.path())
+ .filter(|path| path.extension().and_then(|ext| ext.to_str()) == Some("toml"))
+ .collect::<Vec<_>>();
+ files.sort();
+ Some(files)
+}
+
+fn parse_runtime_quirks_from_toml(text: &str) -> Vec<RuntimeQuirkEntry> {
+ let Ok(value) = text.parse::<Value>() else {
+ return Vec::new();
+ };
+
+ let Some(entries) = value.get("usb_storage_quirk").and_then(Value::as_array) else {
+ return Vec::new();
+ };
+
+ entries.iter().filter_map(parse_runtime_quirk_entry).collect()
+}
+
+fn parse_runtime_quirk_entry(value: &Value) -> Option<RuntimeQuirkEntry> {
+ let table = value.as_table()?;
+ let vendor = u16::try_from(table.get("vendor")?.as_integer()?).ok()?;
+ let product = u16::try_from(table.get("product")?.as_integer()?).ok()?;
+ let flags = parse_flag_list(table.get("flags")?.as_array()?);
+
+ (!flags.is_empty()).then_some(RuntimeQuirkEntry { vendor, product, flags })
+}
+
+fn parse_flag_list(values: &[Value]) -> UsbStorageQuirkFlags {
+ let mut flags = UsbStorageQuirkFlags::empty();
+
+ for value in values {
+ if let Some(name) = value.as_str().and_then(parse_flag_name) {
+ flags |= name;
+ }
+ }
+
+ flags
+}
+
+fn parse_flag_name(name: &str) -> Option<UsbStorageQuirkFlags> {
+ Some(match name {
+ "ignore_residue" => UsbStorageQuirkFlags::IGNORE_RESIDUE,
+ "fix_capacity" => UsbStorageQuirkFlags::FIX_CAPACITY,
+ "single_lun" => UsbStorageQuirkFlags::SINGLE_LUN,
+ "max_sectors_64" => UsbStorageQuirkFlags::MAX_SECTORS_64,
+ "initial_read10" => UsbStorageQuirkFlags::INITIAL_READ10,
+ "fix_inquiry" => UsbStorageQuirkFlags::FIX_INQUIRY,
+ "not_lockable" => UsbStorageQuirkFlags::NOT_LOCKABLE,
+ "scm_mult_targ" => UsbStorageQuirkFlags::SCM_MULT_TARG,
+ "sane_sense" => UsbStorageQuirkFlags::SANE_SENSE,
+ "bulk_ignore_tag" => UsbStorageQuirkFlags::BULK_IGNORE_TAG,
+ "needs_sync_cache" => UsbStorageQuirkFlags::NEEDS_SYNC_CACHE,
+ "no_wp_detect" => UsbStorageQuirkFlags::NO_WP_DETECT,
+ "no_read_cap16" => UsbStorageQuirkFlags::NO_READ_CAP16,
+ "ignore_device" => UsbStorageQuirkFlags::IGNORE_DEVICE,
+ _ => return None,
+ })
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn compiled_fallback_lookup_returns_expected_flags() {
+ let flags = lookup_usb_storage_quirks(0x090C, 0x6000);
+ assert!(flags.contains(UsbStorageQuirkFlags::INITIAL_READ10));
+ }
+
+ #[test]
+ fn runtime_toml_parser_keeps_supported_flags_and_skips_unknown_ones() {
+ let entries = parse_runtime_quirks_from_toml(
+ r#"
+ [[usb_storage_quirk]]
+ vendor = 0x1234
+ product = 0x5678
+ flags = ["ignore_residue", "unknown_flag", "fix_capacity"]
+ "#,
+ );
+
+ assert_eq!(entries.len(), 1);
+ assert!(entries[0].flags.contains(UsbStorageQuirkFlags::IGNORE_RESIDUE));
+ assert!(entries[0].flags.contains(UsbStorageQuirkFlags::FIX_CAPACITY));
+ assert!(!entries[0].flags.contains(UsbStorageQuirkFlags::SINGLE_LUN));
+ }
+}
diff --git a/drivers/storage/usbscsid/src/main.rs b/drivers/storage/usbscsid/src/main.rs
index 5382d118..dca7762c 100644
--- a/drivers/storage/usbscsid/src/main.rs
+++ b/drivers/storage/usbscsid/src/main.rs
@@ -1,53 +1,67 @@
use std::collections::BTreeMap;
use std::env;
+use std::error::Error as StdError;
+use std::io;
use driver_block::{Disk, DiskScheme, ExecutorTrait};
use syscall::{Error, EIO};
use xhcid_interface::{ConfigureEndpointsReq, PortId, XhciClientHandle};
pub mod protocol;
+pub mod quirks;
pub mod scsi;
use crate::protocol::Protocol;
use crate::scsi::Scsi;
+type Result<T> = std::result::Result<T, Box<dyn StdError>>;
+
+const USAGE: &str = "usbscsid <scheme> <port> <protocol>";
+
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 <scheme> <port> <protocol>";
+ 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::<PortId>()
- .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::<u8>()
- .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<Item = String>, name: &str) -> io::Result<String> {
+ args.next()
+ .ok_or_else(|| usage_error(format!("missing {name} argument")))
+}
+
+fn usage_error(message: impl Into<String>) -> io::Error {
+ let message = message.into();
+ io::Error::new(
+ io::ErrorKind::InvalidInput,
+ format!("{message} (usage: {USAGE})"),
+ )
+}
+
+fn runtime_error(message: impl Into<String>) -> 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<Self, ProtocolError> {
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::<CommandBlockWrapper>(&mut cbw_bytes).unwrap();
- *cbw = CommandBlockWrapper::new(tag, data.len() as u32, data.direction().into(), 0, cb)?;
+ let cbw = plain::from_mut_bytes::<CommandBlockWrapper>(&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<NonZeroU32> = 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::<CommandStatusWrapper>(&csw_buffer).unwrap();
+ let csw = plain::from_bytes::<CommandStatusWrapper>(&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<Box<dyn Protocol + 'a>> {
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<u8>,
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<T, E = ScsiError> = std::result::Result<T, E>;
@@ -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::<T>();
+ 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::<T>();
+ 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<Self> {
- assert_eq!(std::mem::size_of::<StandardInquiryData>(), 96);
+ pub fn new(protocol: &mut dyn Protocol, quirks: UsbStorageQuirkFlags) -> Result<Self> {
+ let inquiry_size = std::mem::size_of::<StandardInquiryData>();
+ 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<u16> {
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<Item = cmds::AnyModePage<'_>>,
)> {
let initial_alloc_len = mem::size_of::<cmds::ModeParamHeader10>() 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::<cmds::ModeParamHeader10>(), 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::<cmds::ModeParamHeader6>();
- 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<BlkDescSlice<'_>> {
+ let header = self.res_mode_param_header10()?;
let descs_start = mem::size_of::<cmds::ModeParamHeader10>();
+ 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<Item = cmds::AnyModePage<'_>> {
- let header = self.res_mode_param_header10();
+ pub fn res_mode_pages10(&self) -> Result<impl Iterator<Item = cmds::AnyModePage<'_>> + '_> {
+ let header = self.res_mode_param_header10()?;
let descs_start = mem::size_of::<cmds::ModeParamHeader10>();
- 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::<cmds::ReadCapacity16ParamData>(), 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<u32> {
- 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<u32> {
- let blocks_to_write = buffer.len() as u64 / u64::from(self.block_size);
- let bytes_to_write = blocks_to_write as usize * self.block_size as usize;
- let transfer_len = u32::try_from(blocks_to_write).or(Err(ScsiError::Overflow(
- "number of blocks to write couldn't fit inside a u32",
- )))?;
+ let mut blocks_to_write = buffer.len() as u64 / u64::from(self.block_size);
+
+ if self.quirks.contains(UsbStorageQuirkFlags::MAX_SECTORS_64)
+ && blocks_to_write > MAX_SECTORS_64_LIMIT
{
- let read = self.cmd_write16();
- *read = cmds::Write16::new(lba, transfer_len, 0);
+ blocks_to_write = MAX_SECTORS_64_LIMIT;
+ }
+
+ let bytes_to_write = blocks_to_write as usize * self.block_size as usize;
+
+ if self.quirks.contains(UsbStorageQuirkFlags::INITIAL_READ10) {
+ let transfer_len = u16::try_from(blocks_to_write).or(Err(ScsiError::Overflow(
+ "number of blocks to write couldn't fit inside a u16 for WRITE(10)",
+ )))?;
+ {
+ let write = self.cmd_write10()?;
+ *write = cmds::Write10::new(lba, transfer_len, 0);
+ }
+ self.data_buffer.resize(bytes_to_write, 0u8);
+ self.data_buffer[..bytes_to_write].copy_from_slice(&buffer[..bytes_to_write]);
+ let status = protocol.send_command(
+ &self.command_buffer[..10],
+ DeviceReqData::Out(&buffer[..bytes_to_write]),
+ )?;
+ Ok(status.bytes_transferred(bytes_to_write as u32))
+ } else {
+ let transfer_len = u32::try_from(blocks_to_write).or(Err(ScsiError::Overflow(
+ "number of blocks to write couldn't fit inside a u32",
+ )))?;
+ {
+ let write = self.cmd_write16()?;
+ *write = cmds::Write16::new(lba, transfer_len, 0);
+ }
+ self.data_buffer.resize(bytes_to_write, 0u8);
+ self.data_buffer[..bytes_to_write].copy_from_slice(&buffer[..bytes_to_write]);
+ let status = protocol.send_command(
+ &self.command_buffer[..16],
+ DeviceReqData::Out(&buffer[..bytes_to_write]),
+ )?;
+ Ok(status.bytes_transferred(bytes_to_write as u32))
}
- // TODO: Use the to-be-written TransferReadStream instead of relying on everything being
- // able to fit within a single buffer.
- self.data_buffer.resize(bytes_to_write, 0u8);
- self.data_buffer[..bytes_to_write].copy_from_slice(&buffer[..bytes_to_write]);
- let status = protocol.send_command(
- &self.command_buffer[..16],
- DeviceReqData::Out(&buffer[..bytes_to_write]),
- )?;
- Ok(status.bytes_transferred(bytes_to_write as u32))
}
}
#[derive(Debug)]
diff --git a/drivers/usb/usbhubd/src/main.rs b/drivers/usb/usbhubd/src/main.rs
index 0e58542d..b13bb58a 100644
--- a/drivers/usb/usbhubd/src/main.rs
+++ b/drivers/usb/usbhubd/src/main.rs
@@ -84,7 +84,7 @@ fn main() -> Result<(), Box<dyn Error>> {
})?;
// Read hub descriptor
- let (ports, usb_3) = if desc.major_version() >= 3 {
+ let (ports, usb_3, hub_think_time) = if desc.major_version() >= 3 {
// USB 3.0 hubs
let mut hub_desc = usb::HubDescriptorV3::default();
handle
@@ -101,7 +101,7 @@ fn main() -> Result<(), Box<dyn Error>> {
"Failed to read USB 3 hub descriptor for port {port_id}: {err}"
))
})?;
- (hub_desc.ports, true)
+ (hub_desc.ports, true, None)
} else {
// USB 2.0 and earlier hubs
let mut hub_desc = usb::HubDescriptorV2::default();
@@ -119,7 +119,7 @@ fn main() -> Result<(), Box<dyn Error>> {
"Failed to read USB 2 hub descriptor for port {port_id}: {err}"
))
})?;
- (hub_desc.ports, false)
+ (hub_desc.ports, false, hub_desc.tt_think_time(desc.protocol))
};
@@ -128,6 +128,7 @@ fn main() -> Result<(), Box<dyn Error>> {
interface_desc: None, //TODO: stalls on USB 3 hub: Some(interface_num),
alternate_setting: None, //TODO: stalls on USB 3 hub: Some(if_desc.alternate_setting),
hub_ports: Some(ports),
+ hub_think_time,
})
.map_err(|err| {
other_error(format!(
diff --git a/drivers/usb/xhcid/src/usb/hub.rs b/drivers/usb/xhcid/src/usb/hub.rs
index 2d278320..fb02b17b 100644
--- a/drivers/usb/xhcid/src/usb/hub.rs
+++ b/drivers/usb/xhcid/src/usb/hub.rs
@@ -17,6 +17,23 @@ unsafe impl plain::Plain for HubDescriptorV2 {}
impl HubDescriptorV2 {
pub const DESCRIPTOR_KIND: u8 = 0x29;
+
+ pub fn tt_think_time(self, device_protocol: u8) -> Option<u8> {
+ const HUB_CHAR_TTTT: u16 = 0x0060;
+ const HUB_TTTT_8_BITS: u16 = 0x0000;
+ const HUB_TTTT_16_BITS: u16 = 0x0020;
+ const HUB_TTTT_24_BITS: u16 = 0x0040;
+ const HUB_TTTT_32_BITS: u16 = 0x0060;
+
+ match self.characteristics & HUB_CHAR_TTTT {
+ HUB_TTTT_8_BITS if device_protocol != 0 => Some(0),
+ HUB_TTTT_16_BITS => Some(1),
+ HUB_TTTT_24_BITS => Some(2),
+ HUB_TTTT_32_BITS => Some(3),
+ _ => None,
+ }
+ }
}
@@ -196,3 +213,23 @@ impl HubPortStatus {
}
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::HubDescriptorV2;
+
+ #[test]
+ fn usb2_hub_tt_think_time_decodes_linux_compatible_values() {
+ let mut hub = HubDescriptorV2::default();
+
+ hub.characteristics = 0x0000;
+ assert_eq!(hub.tt_think_time(0), None);
+ assert_eq!(hub.tt_think_time(1), Some(0));
+
+ hub.characteristics = 0x0020;
+ assert_eq!(hub.tt_think_time(0), Some(1));
+
+ hub.characteristics = 0x0040;
+ assert_eq!(hub.tt_think_time(0), Some(2));
+
+ hub.characteristics = 0x0060;
+ assert_eq!(hub.tt_think_time(0), Some(3));
+ }
+}
diff --git a/drivers/usb/xhcid/src/xhci/scheme.rs b/drivers/usb/xhcid/src/xhci/scheme.rs
index 627d33a7..7eb553ae 100644
--- a/drivers/usb/xhcid/src/xhci/scheme.rs
+++ b/drivers/usb/xhcid/src/xhci/scheme.rs
@@ -1196,11 +1196,8 @@ impl<const N: usize> Xhci<N> {
// Set hub data
current_slot_a &= !(1 << 26);
current_slot_b &= !HUB_PORTS_MASK;
- current_slot_c &= !TT_THINK_TIME_MASK;
if let Some(hub_ports) = req.hub_ports {
current_slot_a |= 1 << 26;
current_slot_b |= (u32::from(hub_ports) << HUB_PORTS_SHIFT) & HUB_PORTS_MASK;
- if let Some(hub_think_time) = req.hub_think_time {
- current_slot_c |= (u32::from(hub_think_time) << TT_THINK_TIME_SHIFT) & TT_THINK_TIME_MASK;
- }
}
current_slot_c = apply_hub_tt_info(current_slot_c, req);
@@ -3250,6 +3247,21 @@ fn resolve_active_alternates(
active
}
+fn apply_hub_tt_info(current_slot_c: u32, req: &ConfigureEndpointsReq) -> u32 {
+ const TT_THINK_TIME_MASK: u32 = 0x0003_0000;
+ const TT_THINK_TIME_SHIFT: u8 = 16;
+
+ let mut slot_c = current_slot_c & !TT_THINK_TIME_MASK;
+ if req.hub_ports.is_some() {
+ if let Some(hub_think_time) = req.hub_think_time {
+ slot_c |= (u32::from(hub_think_time) << TT_THINK_TIME_SHIFT) & TT_THINK_TIME_MASK;
+ }
+ }
+ slot_c
+}
+
use lazy_static::lazy_static;
use std::ops::{Add, Div, Rem};
@@ -3283,4 +3295,18 @@ mod tests {
assert_eq!(resolved.get(&0), Some(&1));
assert_eq!(resolved.get(&1), Some(&2));
}
+
+ #[test]
+ fn apply_hub_tt_info_only_sets_bits_for_hub_requests() {
+ let req = ConfigureEndpointsReq {
+ config_desc: 1,
+ interface_desc: None,
+ alternate_setting: None,
+ hub_ports: Some(4),
+ hub_think_time: Some(3),
+ };
+ assert_eq!(apply_hub_tt_info(0, &req), 0x0003_0000);
+
+ let no_hub = ConfigureEndpointsReq { hub_ports: None, ..req.clone() };
+ assert_eq!(apply_hub_tt_info(0x0003_0000, &no_hub), 0);
+ }
}
diff --git a/drivers/usb/usbhubd/src/main.rs b/drivers/usb/usbhubd/src/main.rs
index 0e58542d..b13bb58a 100644
--- a/drivers/usb/usbhubd/src/main.rs
+++ b/drivers/usb/usbhubd/src/main.rs
@@ -84,7 +84,7 @@ fn main() -> Result<(), Box<dyn Error>> {
})?;
// Read hub descriptor
- let (ports, usb_3) = if desc.major_version() >= 3 {
+ let (ports, usb_3, hub_think_time) = if desc.major_version() >= 3 {
// USB 3.0 hubs
let mut hub_desc = usb::HubDescriptorV3::default();
handle
@@ -101,7 +101,7 @@ fn main() -> Result<(), Box<dyn Error>> {
"Failed to read USB 3 hub descriptor for port {port_id}: {err}"
))
})?;
- (hub_desc.ports, true)
+ (hub_desc.ports, true, None)
} else {
// USB 2.0 and earlier hubs
let mut hub_desc = usb::HubDescriptorV2::default();
@@ -119,7 +119,7 @@ fn main() -> Result<(), Box<dyn Error>> {
"Failed to read USB 2 hub descriptor for port {port_id}: {err}"
))
})?;
- (hub_desc.ports, false)
+ (hub_desc.ports, false, hub_desc.tt_think_time(desc.protocol))
};
@@ -128,6 +128,7 @@ fn main() -> Result<(), Box<dyn Error>> {
interface_desc: None, //TODO: stalls on USB 3 hub: Some(interface_num),
alternate_setting: None, //TODO: stalls on USB 3 hub: Some(if_desc.alternate_setting),
hub_ports: Some(ports),
+ hub_think_time,
})
.map_err(|err| {
other_error(format!(
diff --git a/drivers/usb/xhcid/src/usb/hub.rs b/drivers/usb/xhcid/src/usb/hub.rs
index b7dc4d54..2d278320 100644
--- a/drivers/usb/xhcid/src/usb/hub.rs
+++ b/drivers/usb/xhcid/src/usb/hub.rs
@@ -17,6 +17,23 @@ unsafe impl plain::Plain for HubDescriptorV2 {}
impl HubDescriptorV2 {
pub const DESCRIPTOR_KIND: u8 = 0x29;
+
+ pub fn tt_think_time(self, device_protocol: u8) -> Option<u8> {
+ const HUB_CHAR_TTTT: u16 = 0x0060;
+ const HUB_TTTT_8_BITS: u16 = 0x0000;
+ const HUB_TTTT_16_BITS: u16 = 0x0020;
+ const HUB_TTTT_24_BITS: u16 = 0x0040;
+ const HUB_TTTT_32_BITS: u16 = 0x0060;
+
+ match self.characteristics & HUB_CHAR_TTTT {
+ HUB_TTTT_8_BITS if device_protocol != 0 => Some(0),
+ HUB_TTTT_16_BITS => Some(1),
+ HUB_TTTT_24_BITS => Some(2),
+ HUB_TTTT_32_BITS => Some(3),
+ _ => None,
+ }
+ }
}
@@ -196,3 +213,23 @@ impl HubPortStatus {
}
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::HubDescriptorV2;
+
+ #[test]
+ fn usb2_hub_tt_think_time_decodes_linux_compatible_values() {
+ let mut hub = HubDescriptorV2::default();
+
+ hub.characteristics = 0x0000;
+ assert_eq!(hub.tt_think_time(0), None);
+ assert_eq!(hub.tt_think_time(1), Some(0));
+
+ hub.characteristics = 0x0020;
+ assert_eq!(hub.tt_think_time(0), Some(1));
+
+ hub.characteristics = 0x0040;
+ assert_eq!(hub.tt_think_time(0), Some(2));
+
+ hub.characteristics = 0x0060;
+ assert_eq!(hub.tt_think_time(0), Some(3));
+ }
+}
diff --git a/drivers/usb/xhcid/src/xhci/scheme.rs b/drivers/usb/xhcid/src/xhci/scheme.rs
index d5266ca0..627d33a7 100644
--- a/drivers/usb/xhcid/src/xhci/scheme.rs
+++ b/drivers/usb/xhcid/src/xhci/scheme.rs
@@ -1196,11 +1196,8 @@ impl<const N: usize> Xhci<N> {
// Set hub data
current_slot_a &= !(1 << 26);
current_slot_b &= !HUB_PORTS_MASK;
- current_slot_c &= !TT_THINK_TIME_MASK;
if let Some(hub_ports) = req.hub_ports {
current_slot_a |= 1 << 26;
current_slot_b |= (u32::from(hub_ports) << HUB_PORTS_SHIFT) & HUB_PORTS_MASK;
- if let Some(hub_think_time) = req.hub_think_time {
- current_slot_c |= (u32::from(hub_think_time) << TT_THINK_TIME_SHIFT) & TT_THINK_TIME_MASK;
- }
}
+ current_slot_c = apply_hub_tt_info(current_slot_c, req);
input_context.device.slot.a.write(current_slot_a);
input_context.device.slot.b.write(current_slot_b);
@@ -3250,6 +3247,21 @@ fn resolve_active_alternates(
active
}
+fn apply_hub_tt_info(current_slot_c: u32, req: &ConfigureEndpointsReq) -> u32 {
+ const TT_THINK_TIME_MASK: u32 = 0x0003_0000;
+ const TT_THINK_TIME_SHIFT: u8 = 16;
+
+ let mut slot_c = current_slot_c & !TT_THINK_TIME_MASK;
+ if req.hub_ports.is_some() {
+ if let Some(hub_think_time) = req.hub_think_time {
+ slot_c |= (u32::from(hub_think_time) << TT_THINK_TIME_SHIFT) & TT_THINK_TIME_MASK;
+ }
+ }
+ slot_c
+}
+
use lazy_static::lazy_static;
use std::ops::{Add, Div, Rem};
@@ -3283,4 +3295,18 @@ mod tests {
assert_eq!(resolved.get(&0), Some(&1));
assert_eq!(resolved.get(&1), Some(&2));
}
+
+ #[test]
+ fn apply_hub_tt_info_only_sets_bits_for_hub_requests() {
+ let req = ConfigureEndpointsReq {
+ config_desc: 1,
+ interface_desc: None,
+ alternate_setting: None,
+ hub_ports: Some(4),
+ hub_think_time: Some(3),
+ };
+ assert_eq!(apply_hub_tt_info(0, &req), 0x0003_0000);
+
+ let no_hub = ConfigureEndpointsReq { hub_ports: None, ..req.clone() };
+ assert_eq!(apply_hub_tt_info(0x0003_0000, &no_hub), 0);
+ }
}
diff --git a/drivers/usb/usbhubd/src/main.rs b/drivers/usb/usbhubd/src/main.rs
index 2c8b9876..68538b77 100644
--- a/drivers/usb/usbhubd/src/main.rs
+++ b/drivers/usb/usbhubd/src/main.rs
@@ -1,27 +1,41 @@
-use std::{env, thread, time};
+use std::{env, error::Error, io, thread, time};
use xhcid_interface::{
- plain, usb, ConfigureEndpointsReq, DevDesc, DeviceReqData, PortId, PortReqRecipient, PortReqTy,
- XhciClientHandle,
+ plain, usb, ConfigureEndpointsReq, DevDesc, DeviceReqData, EndpDirection, PortId,
+ PortReqRecipient, PortReqTy, XhciClientHandle, XhciEndpHandle,
};
-fn main() {
+fn invalid_input_error(message: impl Into<String>) -> Box<dyn Error> {
+ let message = message.into();
+ log::error!("{message}");
+ Box::new(io::Error::new(io::ErrorKind::InvalidInput, message))
+}
+
+fn other_error(message: impl Into<String>) -> Box<dyn Error> {
+ let message = message.into();
+ log::error!("{message}");
+ Box::new(io::Error::other(message))
+}
+
+fn main() -> Result<(), Box<dyn Error>> {
common::init();
let mut args = env::args().skip(1);
const USAGE: &'static str = "usbhubd <scheme> <port> <interface>";
- 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::<PortId>()
- .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::<u8>()
- .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<dyn Error>> {
+ 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<XhciEndpHandle> {
+ 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<DevDesc, XhciClientHandleError> {
let json = self.read("descriptors")?;
Ok(serde_json::from_slice(&json)?)
diff --git a/drivers/usb/xhcid/Cargo.toml b/drivers/usb/xhcid/Cargo.toml
index 778376b0..1651bcf5 100644
--- a/drivers/usb/xhcid/Cargo.toml
+++ b/drivers/usb/xhcid/Cargo.toml
@@ -32,6 +32,7 @@ common = { path = "../../common" }
daemon = { path = "../../../daemon" }
pcid = { path = "../../pcid" }
+redox-driver-sys = { path = "../../../../../../../local/recipes/drivers/redox-driver-sys/source" }
libredox.workspace = true
regex = "1.10.6"
diff --git a/drivers/usb/xhcid/src/usb_quirks.rs b/drivers/usb/xhcid/src/usb_quirks.rs
new file mode 100644
index 00000000..83ca324d
--- /dev/null
+++ b/drivers/usb/xhcid/src/usb_quirks.rs
@@ -0,0 +1,10 @@
+pub use redox_driver_sys::quirks::UsbQuirkFlags;
+
+use crate::driver_interface::PortId;
+
+pub fn lookup_usb_quirks(vendor: u16, product: u16) -> UsbQuirkFlags {
+ redox_driver_sys::quirks::lookup_usb_quirks(vendor, product)
+}
+
+pub fn lookup_usb_quirks_early(_port_id: PortId) -> UsbQuirkFlags {
+ UsbQuirkFlags::empty()
+}
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;
@@ -141,8 +142,19 @@ fn daemon_with_context_size<const N: usize>(
let address = unsafe { pcid_handle.map_bar(0) }.ptr.as_ptr() as usize;
- let (irq_file, interrupt_method) = (None, InterruptMethod::Polling); //get_int_method(&mut pcid_handle);
- //TODO: Fix interrupts.
+ let (irq_file, interrupt_method) = get_int_method(&mut pcid_handle);
+
+ match interrupt_method {
+ InterruptMethod::Msi => {
+ log::info!("xhcid: using MSI/MSI-X interrupt delivery");
+ }
+ InterruptMethod::Intx => {
+ log::info!("xhcid: using legacy INTx interrupt delivery");
+ }
+ InterruptMethod::Polling => {
+ log::warn!("xhcid: using polling event delivery");
+ }
+ }
log::info!("XHCI {}", pci_config.func.display());
diff --git a/drivers/usb/xhcid/src/usb/hub.rs b/drivers/usb/xhcid/src/usb/hub.rs
index 9dab55e8..fe6efdd2 100644
--- a/drivers/usb/xhcid/src/usb/hub.rs
+++ b/drivers/usb/xhcid/src/usb/hub.rs
@@ -86,8 +86,10 @@ pub enum HubPortFeature {
PortOverCurrent = 3,
PortReset = 4,
PortLinkState = 5,
+ PortSuspend = 7,
PortPower = 8,
CPortConnection = 16,
+ CPortSuspend = 18,
CPortOverCurrent = 19,
CPortReset = 20,
}
@@ -184,4 +186,11 @@ impl HubPortStatus {
Self::V3(x) => x.contains(HubPortStatusV3::ENABLE),
}
}
+
+ pub fn is_suspended(&self) -> bool {
+ match self {
+ Self::V2(x) => x.contains(HubPortStatusV2::SUSPEND),
+ Self::V3(_) => false,
+ }
+ }
}
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<const N: usize> DeviceEnumerator<N> {
};
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<const N: usize> DeviceEnumerator<N> {
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..d81648bf 100644
--- a/drivers/usb/xhcid/src/xhci/mod.rs
+++ b/drivers/usb/xhcid/src/xhci/mod.rs
@@ -11,12 +11,13 @@
//! documents are specified in the crate-level documentation.
use std::collections::BTreeMap;
use std::convert::TryFrom;
-use std::fs::File;
+use std::fs::{self, File};
+use std::time::Duration;
use std::sync::atomic::AtomicUsize;
-use std::sync::{Arc, Mutex};
+use std::sync::{Arc, Condvar, Mutex};
use std::{mem, process, slice, thread};
-use syscall::error::{Error, Result, EBADF, EBADMSG, EIO, ENOENT};
+use syscall::error::{Error, Result, EBADF, EBADMSG, EBUSY, EIO, ENOENT};
use syscall::{EAGAIN, PAGE_SIZE};
use chashmap::CHashMap;
@@ -77,7 +78,55 @@ pub enum InterruptMethod {
Msi,
}
+const XHCID_TEST_HOOK_PATH: &str = "/tmp/xhcid-test-hook";
+const XHCID_TEST_HOOK_MAX_DELAY_MS: u64 = 5_000;
+
impl<const N: usize> Xhci<N> {
+ fn read_test_hook_command_from_path(path: &str) -> Option<String> {
+ let contents = fs::read_to_string(path).ok()?;
+ contents
+ .lines()
+ .map(str::trim)
+ .find(|line| !line.is_empty() && !line.starts_with('#'))
+ .map(ToOwned::to_owned)
+ }
+
+ fn clear_test_hook_command_path(path: &str) {
+ if let Err(err) = fs::remove_file(path) {
+ if err.kind() != std::io::ErrorKind::NotFound {
+ warn!(
+ "failed to remove xhcid test hook file {}: {}",
+ path, err
+ );
+ }
+ }
+ }
+
+ fn consume_test_hook_from_path(path: &str, expected: &str) -> bool {
+ match Self::read_test_hook_command_from_path(path) {
+ Some(command) if command == expected => {
+ Self::clear_test_hook_command_path(path);
+ true
+ }
+ _ => false,
+ }
+ }
+
+ fn consume_test_hook_delay_ms_from_path(path: &str, prefix: &str) -> Option<u64> {
+ let command = Self::read_test_hook_command_from_path(path)?;
+ let delay_ms = command.strip_prefix(prefix)?.parse::<u64>().ok()?;
+ Self::clear_test_hook_command_path(path);
+ Some(delay_ms.min(XHCID_TEST_HOOK_MAX_DELAY_MS))
+ }
+
+ pub(crate) fn consume_test_hook(&self, expected: &str) -> bool {
+ Self::consume_test_hook_from_path(XHCID_TEST_HOOK_PATH, expected)
+ }
+
+ pub(crate) fn consume_test_hook_delay_ms(&self, prefix: &str) -> Option<u64> {
+ Self::consume_test_hook_delay_ms_from_path(XHCID_TEST_HOOK_PATH, prefix)
+ }
+
/// Gets descriptors, before the port state is initiated.
async fn get_desc_raw<T>(
&self,
@@ -104,7 +153,18 @@ impl<const N: usize> Xhci<N> {
);
let future = {
- let mut port_state = self.port_states.get_mut(&port).ok_or(Error::new(ENOENT))?;
+ let mut published_port_state = self.port_states.get_mut(&port);
+ let mut staged_port_state = if published_port_state.is_none() {
+ self.staged_port_states.get_mut(&port)
+ } else {
+ None
+ };
+
+ let port_state = published_port_state
+ .as_deref_mut()
+ .or_else(|| staged_port_state.as_deref_mut())
+ .ok_or(Error::new(ENOENT))?;
+
let ring = port_state
.endpoint_states
.get_mut(&0)
@@ -150,7 +210,7 @@ impl<const N: usize> Xhci<N> {
trace!("Handling the transfer event TRB!");
self::scheme::handle_transfer_event_trb("GET_DESC", &event_trb, &status_trb)?;
- //self.event_handler_finished();
+ self.event_handler_finished();
Ok(())
}
@@ -283,6 +343,7 @@ pub struct Xhci<const N: usize> {
handles: CHashMap<usize, scheme::Handle>,
next_handle: AtomicUsize,
port_states: CHashMap<PortId, PortState<N>>,
+ staged_port_states: CHashMap<PortId, PortState<N>>,
drivers: CHashMap<PortId, Vec<process::Child>>,
scheme_name: String,
@@ -311,6 +372,144 @@ struct PortState<const N: usize> {
input_context: Mutex<Dma<InputContext<N>>>,
dev_desc: Option<DevDesc>,
endpoint_states: BTreeMap<u8, EndpointState>,
+ quirks: crate::usb_quirks::UsbQuirkFlags,
+ pm_state: PortPmState,
+ lifecycle: Arc<PortLifecycle>,
+}
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub(crate) enum PortLifecycleState {
+ Attaching,
+ Attached,
+ Detaching,
+}
+
+struct PortLifecycleInner {
+ state: PortLifecycleState,
+ active_operations: usize,
+}
+
+pub(crate) struct PortLifecycle {
+ inner: Mutex<PortLifecycleInner>,
+ idle: Condvar,
+}
+
+impl PortLifecycle {
+ pub(crate) fn new_attaching() -> Self {
+ Self {
+ inner: Mutex::new(PortLifecycleInner {
+ state: PortLifecycleState::Attaching,
+ active_operations: 1,
+ }),
+ idle: Condvar::new(),
+ }
+ }
+
+ fn lock_inner(&self) -> std::sync::MutexGuard<'_, PortLifecycleInner> {
+ self.inner.lock().unwrap_or_else(|err| err.into_inner())
+ }
+
+ pub(crate) fn state(&self) -> PortLifecycleState {
+ self.lock_inner().state
+ }
+
+ pub(crate) fn begin_operation(&self, allow_attaching: bool) -> Result<()> {
+ let mut inner = self.lock_inner();
+
+ let allowed = match inner.state {
+ PortLifecycleState::Attached => true,
+ PortLifecycleState::Attaching => allow_attaching,
+ PortLifecycleState::Detaching => false,
+ };
+
+ if !allowed {
+ return Err(Error::new(EBUSY));
+ }
+
+ inner.active_operations += 1;
+ Ok(())
+ }
+
+ pub(crate) fn finish_operation(&self) {
+ let mut inner = self.lock_inner();
+
+ if inner.active_operations == 0 {
+ return;
+ }
+
+ inner.active_operations -= 1;
+ if inner.active_operations == 0 {
+ self.idle.notify_all();
+ }
+ }
+
+ pub(crate) fn finish_attach_success(&self) -> PortLifecycleState {
+ let mut inner = self.lock_inner();
+
+ if inner.state == PortLifecycleState::Attaching {
+ inner.state = PortLifecycleState::Attached;
+ }
+
+ if inner.active_operations != 0 {
+ inner.active_operations -= 1;
+ }
+ if inner.active_operations == 0 {
+ self.idle.notify_all();
+ }
+
+ inner.state
+ }
+
+ pub(crate) fn finish_attach_failure(&self) {
+ let mut inner = self.lock_inner();
+ inner.state = PortLifecycleState::Detaching;
+
+ if inner.active_operations != 0 {
+ inner.active_operations -= 1;
+ }
+ if inner.active_operations == 0 {
+ self.idle.notify_all();
+ }
+ }
+
+ pub(crate) fn begin_detaching(&self) {
+ let mut inner = self.lock_inner();
+ inner.state = PortLifecycleState::Detaching;
+
+ while inner.active_operations != 0 {
+ inner = self.idle.wait(inner).unwrap_or_else(|err| err.into_inner());
+ }
+ }
+}
+
+pub(crate) struct PortOperationGuard {
+ lifecycle: Arc<PortLifecycle>,
+}
+
+impl PortOperationGuard {
+ pub(crate) fn new(lifecycle: Arc<PortLifecycle>) -> Self {
+ Self { lifecycle }
+ }
+}
+
+impl Drop for PortOperationGuard {
+ fn drop(&mut self) {
+ self.lifecycle.finish_operation();
+ }
+}
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub(crate) enum PortPmState {
+ Active,
+ Suspended,
+}
+impl PortPmState {
+ pub fn as_str(&self) -> &'static str {
+ match self {
+ Self::Active => "active",
+ Self::Suspended => "suspended",
+ }
+ }
}
impl<const N: usize> PortState<N> {
@@ -463,6 +662,7 @@ impl<const N: usize> Xhci<N> {
handles: CHashMap::new(),
next_handle: AtomicUsize::new(0),
port_states: CHashMap::new(),
+ staged_port_states: CHashMap::new(),
drivers: CHashMap::new(),
scheme_name,
@@ -615,29 +815,24 @@ impl<const N: usize> Xhci<N> {
route_string: 0,
};
- //Get the CCS and CSC flags
- let (ccs, csc, flags) = {
+ // Only queue ports that are actually connected at startup. A stale CSC bit on an
+ // otherwise disconnected port should not trigger a full attach attempt.
+ let (ccs, flags) = {
let mut ports = self.ports.lock().unwrap();
let port = &mut ports[port_id.root_hub_port_index()];
let flags = port.flags();
let ccs = flags.contains(PortFlags::CCS);
- let csc = flags.contains(PortFlags::CSC);
- (ccs, csc, flags)
+ (ccs, flags)
};
debug!("Port {} has flags {:?}", port_id, flags);
- match (ccs, csc) {
- (false, false) => { // Nothing is connected, and there was no port status change
- //Do nothing
- }
- _ => {
- //Either something is connected, or nothing is connected and a port status change was asserted.
- self.device_enumerator_sender
- .send(DeviceEnumerationRequest { port_id })
- .expect("Failed to generate the port enumeration request!");
- }
+ if ccs {
+ info!("xhcid: queueing initial enumeration for port {} with flags {:?}", port_id, flags);
+ self.device_enumerator_sender
+ .send(DeviceEnumerationRequest { port_id })
+ .expect("Failed to generate the port enumeration request!");
}
}
}
@@ -757,7 +952,7 @@ impl<const N: usize> Xhci<N> {
trace!("Slot is enabled!");
self::scheme::handle_event_trb("ENABLE_SLOT", &event_trb, &command_trb)?;
- //self.event_handler_finished();
+ self.event_handler_finished();
Ok(event_trb.event_slot())
}
@@ -768,7 +963,7 @@ impl<const N: usize> Xhci<N> {
.await;
self::scheme::handle_event_trb("DISABLE_SLOT", &event_trb, &command_trb)?;
- //self.event_handler_finished();
+ self.event_handler_finished();
Ok(())
}
@@ -793,11 +988,13 @@ impl<const N: usize> Xhci<N> {
}
pub async fn attach_device(&self, port_id: PortId) -> syscall::Result<()> {
- if self.port_states.contains_key(&port_id) {
+ if self.port_states.contains_key(&port_id) || self.staged_port_states.contains_key(&port_id) {
debug!("Already contains port {}", port_id);
return Err(syscall::Error::new(EAGAIN));
}
+ info!("xhcid: begin attach for port {}", port_id);
+
let (data, state, speed, flags) = {
let port = &self.ports.lock().unwrap()[port_id.root_hub_port_index()];
(port.read(), port.state(), port.speed(), port.flags())
@@ -808,74 +1005,114 @@ impl<const N: usize> Xhci<N> {
port_id, data, state, speed, flags
);
- if flags.contains(port::PortFlags::CCS) {
- let slot_ty = match self.supported_protocol(port_id) {
- Some(protocol) => protocol.proto_slot_ty(),
- None => {
- warn!("Failed to find supported protocol information for port");
- 0
- }
- };
-
- debug!("Slot type: {}", slot_ty);
- debug!("Enabling slot.");
- let slot = match self.enable_port_slot(slot_ty).await {
- Ok(ok) => ok,
- Err(err) => {
- error!("Failed to enable slot for port {}: {}", port_id, err);
- return Err(err);
- }
- };
+ if !flags.contains(port::PortFlags::CCS) {
+ warn!("Attempted to attach a device that didnt have CCS=1");
+ return Ok(());
+ }
- debug!("Enabled port {}, which the xHC mapped to {}", port_id, slot);
+ let early_quirks = crate::usb_quirks::lookup_usb_quirks_early(port_id);
+ let slot_ty = match self.supported_protocol(port_id) {
+ Some(protocol) => protocol.proto_slot_ty(),
+ None => {
+ warn!("Failed to find supported protocol information for port {}", port_id);
+ 0
+ }
+ };
- //TODO: get correct speed for child devices
- let protocol_speed = self
- .lookup_psiv(port_id, speed)
- .expect("Failed to retrieve speed ID");
+ debug!("Slot type: {}", slot_ty);
+ debug!("Enabling slot.");
+ let slot = match self.enable_port_slot(slot_ty).await {
+ Ok(ok) => ok,
+ Err(err) => {
+ error!("Failed to enable slot for port {}: {}", port_id, err);
+ return Err(err);
+ }
+ };
- let mut input = unsafe { self.alloc_dma_zeroed::<InputContext<N>>()? };
+ debug!("Enabled port {}, which the xHC mapped to {}", port_id, slot);
+ info!("xhcid: enabled slot {} for port {}", slot, port_id);
- debug!("Attempting to address the device");
- let mut ring = match self
- .address_device(&mut input, port_id, slot_ty, slot, protocol_speed, speed)
- .await
- {
- Ok(device_ring) => device_ring,
- Err(err) => {
- error!("Failed to address device for port {}: `{}`", port_id, err);
- return Err(err);
+ let protocol_speed = match self.lookup_psiv(port_id, speed) {
+ Some(protocol_speed) => protocol_speed,
+ None => {
+ let err = Error::new(EIO);
+ error!("Failed to retrieve speed ID for port {}", port_id);
+ if let Err(disable_err) = self.disable_port_slot(slot).await {
+ warn!(
+ "Failed to disable slot {} after speed lookup failure on port {}: {}",
+ slot, port_id, disable_err
+ );
}
- };
-
- debug!("Addressed device");
+ return Err(err);
+ }
+ };
- // TODO: Should the descriptors be cached in PortState, or refetched?
+ let mut input = unsafe { self.alloc_dma_zeroed::<InputContext<N>>()? };
- let mut port_state = PortState {
+ debug!("Attempting to address the device");
+ let ring = match self
+ .address_device(
+ &mut input,
+ port_id,
+ slot_ty,
slot,
protocol_speed,
- input_context: Mutex::new(input),
- dev_desc: None,
- cfg_idx: None,
- endpoint_states: std::iter::once((
- 0,
- EndpointState {
- transfer: RingOrStreams::Ring(ring),
- driver_if_state: EndpIfState::Init,
- },
- ))
- .collect::<BTreeMap<_, _>>(),
- };
- self.port_states.insert(port_id, port_state);
- debug!("Got port states!");
+ speed,
+ early_quirks,
+ )
+ .await
+ {
+ Ok(device_ring) => device_ring,
+ Err(err) => {
+ error!("Failed to address device for port {}: `{}`", port_id, err);
+ if let Err(disable_err) = self.disable_port_slot(slot).await {
+ warn!(
+ "Failed to disable slot {} after address failure on port {}: {}",
+ slot, port_id, disable_err
+ );
+ }
+ return Err(err);
+ }
+ };
+
+ debug!("Addressed device");
+ info!("xhcid: addressed device on port {} slot {}", port_id, slot);
+
+ let lifecycle = Arc::new(PortLifecycle::new_attaching());
+ let port_state = PortState {
+ slot,
+ protocol_speed,
+ input_context: Mutex::new(input),
+ dev_desc: None,
+ cfg_idx: None,
+ endpoint_states: std::iter::once((
+ 0,
+ EndpointState {
+ transfer: RingOrStreams::Ring(ring),
+ driver_if_state: EndpIfState::Init,
+ },
+ ))
+ .collect::<BTreeMap<_, _>>(),
+ quirks: early_quirks,
+ pm_state: PortPmState::Active,
+ lifecycle: Arc::clone(&lifecycle),
+ };
+ self.staged_port_states.insert(port_id, port_state);
+ debug!("Got staged port state!");
- // Ensure correct packet size is used
+ let attach_result = async {
let dev_desc_8_byte = self.fetch_dev_desc_8_byte(port_id, slot).await?;
+ info!("xhcid: fetched 8-byte device descriptor for port {}", port_id);
{
- let mut port_state = self.port_states.get_mut(&port_id).unwrap();
+ let mut port_state = self
+ .staged_port_states
+ .get_mut(&port_id)
+ .ok_or(Error::new(ENOENT))?;
- let mut input = port_state.input_context.lock().unwrap();
+ let mut input = port_state
+ .input_context
+ .lock()
+ .unwrap_or_else(|err| err.into_inner());
self.update_max_packet_size(&mut *input, slot, dev_desc_8_byte)
.await?;
@@ -884,38 +1121,131 @@ impl<const N: usize> Xhci<N> {
debug!("Got the 8 byte dev descriptor: {:X?}", dev_desc_8_byte);
let dev_desc = self.get_desc(port_id, slot).await?;
+ info!(
+ "xhcid: got descriptors for port {} vendor {:04x} product {:04x}",
+ port_id,
+ dev_desc.vendor,
+ dev_desc.product
+ );
+ let quirks = early_quirks
+ | crate::usb_quirks::lookup_usb_quirks(dev_desc.vendor, dev_desc.product);
debug!("Got the full device descriptor!");
- self.port_states.get_mut(&port_id).unwrap().dev_desc = Some(dev_desc);
+ {
+ let mut port_state = self
+ .staged_port_states
+ .get_mut(&port_id)
+ .ok_or(Error::new(ENOENT))?;
+ port_state.quirks = quirks;
+ port_state.dev_desc = Some(dev_desc);
+ }
debug!("Got the port states again!");
{
- let mut port_state = self.port_states.get_mut(&port_id).unwrap();
-
- let mut input = port_state.input_context.lock().unwrap();
+ let mut port_state = self
+ .staged_port_states
+ .get_mut(&port_id)
+ .ok_or(Error::new(ENOENT))?;
+
+ let mut input = port_state
+ .input_context
+ .lock()
+ .unwrap_or_else(|err| err.into_inner());
debug!("Got the input context!");
- let dev_desc = port_state.dev_desc.as_ref().unwrap();
+ let dev_desc = port_state.dev_desc.as_ref().ok_or(Error::new(EIO))?;
self.update_default_control_pipe(&mut *input, slot, dev_desc)
.await?;
}
debug!("Updated the default control pipe");
+ Ok(())
+ }
+ .await;
+
+ match attach_result {
+ Ok(()) => {
+ if let Some(delay_ms) =
+ self.consume_test_hook_delay_ms("delay_before_attach_commit_ms=")
+ {
+ info!(
+ "xhcid: test hook delaying attach commit for port {} by {} ms",
+ port_id, delay_ms
+ );
+ thread::sleep(Duration::from_millis(delay_ms));
+ }
- match self.spawn_drivers(port_id) {
- Ok(()) => (),
- Err(err) => {
- error!("Failed to spawn driver for port {}: `{}`", port_id, err)
+ if lifecycle.finish_attach_success() != PortLifecycleState::Attached {
+ warn!(
+ "attach for port {} completed after detach already started; skipping publication",
+ port_id
+ );
+ return Err(Error::new(EBUSY));
}
+
+ let staged_port_state = self
+ .staged_port_states
+ .remove(&port_id)
+ .ok_or(Error::new(ENOENT))?;
+ self.port_states.insert(port_id, staged_port_state);
+
+ match self.spawn_drivers(port_id) {
+ Ok(()) => (),
+ Err(err) => {
+ error!("Failed to spawn driver for port {}: `{}`", port_id, err)
+ }
+ }
+ info!("xhcid: finished attach for port {}", port_id);
+ Ok(())
+ }
+ Err(err) => {
+ lifecycle.finish_attach_failure();
+ if let Err(detach_err) = self.detach_device(port_id).await {
+ warn!(
+ "failed to clean up attach failure on port {}: {}",
+ port_id, detach_err
+ );
+ }
+ Err(err)
}
- } else {
- warn!("Attempted to attach a device that didnt have CCS=1");
}
-
- Ok(())
}
pub async fn detach_device(&self, port_id: PortId) -> Result<bool> {
- if let Some(children) = self.drivers.remove(&port_id) {
+ let published_state = self.port_states.get(&port_id);
+ let staged_state = if published_state.is_none() {
+ self.staged_port_states.get(&port_id)
+ } else {
+ None
+ };
+
+ let (slot, lifecycle, was_published) = match published_state
+ .as_deref()
+ .or_else(|| staged_state.as_deref())
+ {
+ Some(state) => (state.slot, Arc::clone(&state.lifecycle), published_state.is_some()),
+ None => {
+ debug!(
+ "Attempted to detach from port {}, which wasn't previously attached.",
+ port_id
+ );
+ return Ok(false);
+ }
+ };
+
+ info!("xhcid: begin detach quiesce for port {}", port_id);
+ lifecycle.begin_detaching();
+ info!("xhcid: detach quiesce complete for port {}", port_id);
+
+ if let Some(delay_ms) = self.consume_test_hook_delay_ms("delay_before_detach_disable_ms=") {
+ info!(
+ "xhcid: test hook delaying detach disable for port {} by {} ms",
+ port_id, delay_ms
+ );
+ thread::sleep(Duration::from_millis(delay_ms));
+ }
+
+ if was_published {
+ if let Some(children) = self.drivers.remove(&port_id) {
for mut child in children {
info!("killing driver process {} for port {}", child.id(), port_id);
match child.kill() {
@@ -961,21 +1291,26 @@ impl<const N: usize> Xhci<N> {
}
}
}
+ }
- if let Some(state) = self.port_states.remove(&port_id) {
- debug!("disabling port slot {} for port {}", state.slot, port_id);
- let result = self.disable_port_slot(state.slot).await.and(Ok(true));
- debug!(
- "disabled port slot {} for port {} with result: {:?}",
- state.slot, port_id, result
- );
- result
- } else {
- debug!(
- "Attempted to detach from port {}, which wasn't previously attached.",
- port_id
- );
- Ok(false)
+ debug!("disabling port slot {} for port {}", slot, port_id);
+ match self.disable_port_slot(slot).await {
+ Ok(()) => {
+ if was_published {
+ let _ = self.port_states.remove(&port_id);
+ } else {
+ let _ = self.staged_port_states.remove(&port_id);
+ }
+ debug!("disabled port slot {} for port {}", slot, port_id);
+ Ok(true)
+ }
+ Err(err) => {
+ warn!(
+ "failed to disable port slot {} for port {}: {}",
+ slot, port_id, err
+ );
+ Err(err)
+ }
}
}
@@ -1004,7 +1339,7 @@ impl<const N: usize> Xhci<N> {
.await;
self::scheme::handle_event_trb("EVALUATE_CONTEXT", &event_trb, &command_trb)?;
- //self.event_handler_finished();
+ self.event_handler_finished();
Ok(())
}
@@ -1039,7 +1374,7 @@ impl<const N: usize> Xhci<N> {
debug!("Completed the command to update the default control pipe");
self::scheme::handle_event_trb("EVALUATE_CONTEXT", &event_trb, &command_trb)?;
- //self.event_handler_finished();
+ self.event_handler_finished();
Ok(())
}
@@ -1052,6 +1387,7 @@ impl<const N: usize> Xhci<N> {
slot: u8,
protocol_speed: &ProtocolSpeed,
speed: u8,
+ quirks: crate::usb_quirks::UsbQuirkFlags,
) -> Result<Ring> {
// Collect MTT, parent port number, parent slot ID
let mut mtt = false;
@@ -1162,11 +1498,16 @@ impl<const N: usize> Xhci<N> {
let input_context_physical = input_context.physical();
- let (event_trb, _) = self
- .execute_command(|trb, cycle| {
- trb.address_device(slot, input_context_physical, false, cycle)
- })
- .await;
+ let address_timeout = if quirks.contains(crate::usb_quirks::UsbQuirkFlags::SHORT_SET_ADDR_TIMEOUT)
+ {
+ Timeout::from_millis(100)
+ } else {
+ Timeout::from_secs(1)
+ };
+
+ let (event_trb, _) = self.execute_command_with_timeout(address_timeout, |trb, cycle| {
+ trb.address_device(slot, input_context_physical, false, cycle)
+ })?;
if event_trb.completion_code() != TrbCompletionCode::Success as u8 {
error!(
@@ -1175,10 +1516,10 @@ impl<const N: usize> Xhci<N> {
port,
event_trb.completion_code()
);
- //self.event_handler_finished();
+ self.event_handler_finished();
return Err(Error::new(EIO));
}
- //self.event_handler_finished();
+ self.event_handler_finished();
Ok(ring)
}
@@ -1281,6 +1622,12 @@ impl<const N: usize> Xhci<N> {
ifdesc.sub_class,
ifdesc.protocol,
);
+ match driver.name.as_str() {
+ "USB HID" => info!("USB HID driver spawned"),
+ "SCSI over USB" => info!("USB SCSI driver spawned"),
+ "USB HUB" => info!("USB HUB driver spawned"),
+ _ => {}
+ }
let (command, args) = driver.command.split_first().ok_or(Error::new(EBADMSG))?;
let command = if command.starts_with('/') {
@@ -1487,3 +1834,52 @@ lazy_static! {
toml::from_slice::<DriversConfig>(TOML).expect("Failed to parse internally embedded config file")
};
}
+
+#[cfg(test)]
+mod tests {
+ use super::{Xhci, XHCID_TEST_HOOK_MAX_DELAY_MS};
+ use std::fs;
+ use std::path::Path;
+ use std::time::{SystemTime, UNIX_EPOCH};
+
+ fn unique_test_hook_path() -> String {
+ let unique = SystemTime::now()
+ .duration_since(UNIX_EPOCH)
+ .unwrap()
+ .as_nanos();
+ format!("/tmp/xhcid-test-hook-{}", unique)
+ }
+
+ #[test]
+ fn consume_test_hook_only_clears_matching_command() {
+ let path = unique_test_hook_path();
+ fs::write(&path, "fail_after_set_configuration\n").unwrap();
+
+ assert!(!Xhci::<16>::consume_test_hook_from_path(
+ &path,
+ "fail_after_configure_endpoint"
+ ));
+ assert!(Path::new(&path).exists());
+
+ assert!(Xhci::<16>::consume_test_hook_from_path(
+ &path,
+ "fail_after_set_configuration"
+ ));
+ assert!(!Path::new(&path).exists());
+ }
+
+ #[test]
+ fn consume_test_hook_delay_clamps_and_clears() {
+ let path = unique_test_hook_path();
+ fs::write(&path, "delay_before_attach_commit_ms=999999\n").unwrap();
+
+ assert_eq!(
+ Xhci::<16>::consume_test_hook_delay_ms_from_path(
+ &path,
+ "delay_before_attach_commit_ms="
+ ),
+ Some(XHCID_TEST_HOOK_MAX_DELAY_MS)
+ );
+ assert!(!Path::new(&path).exists());
+ }
+}
diff --git a/drivers/usb/xhcid/src/xhci/scheme.rs b/drivers/usb/xhcid/src/xhci/scheme.rs
index f2d439a4..bc6d7fca 100644
--- a/drivers/usb/xhcid/src/xhci/scheme.rs
+++ b/drivers/usb/xhcid/src/xhci/scheme.rs
@@ -18,12 +18,15 @@
//! port<n>/endpoints/<n>/data
use std::convert::TryFrom;
use std::io::prelude::*;
+use std::io::Write;
use std::ops::Deref;
+use std::sync::Arc;
use std::sync::atomic;
use std::{cmp, fmt, io, mem, str};
use common::dma::Dma;
use futures::executor::block_on;
+use futures::FutureExt;
use log::{debug, error, info, trace, warn};
use redox_scheme::scheme::SchemeSync;
use smallvec::SmallVec;
@@ -32,16 +35,16 @@ use common::io::Io;
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};
use super::{EndpointState, PortId, Xhci};
use super::context::{
- SlotState, StreamContextArray, StreamContextType, CONTEXT_32, CONTEXT_64,
+ EndpointContext, SlotState, StreamContextArray, StreamContextType, CONTEXT_32, CONTEXT_64,
SLOT_CONTEXT_STATE_MASK, SLOT_CONTEXT_STATE_SHIFT,
};
use super::extended::ProtocolSpeed;
@@ -60,10 +63,16 @@ lazy_static! {
.expect("Failed to create the regex for the port<n>/attach scheme.");
static ref REGEX_PORT_DETACH: Regex = Regex::new(r"^port([\d\.]+)/detach$")
.expect("Failed to create the regex for the port<n>/detach scheme.");
+ static ref REGEX_PORT_SUSPEND: Regex = Regex::new(r"^port([\d\.]+)/suspend$")
+ .expect("Failed to create the regex for the port<n>/suspend scheme.");
+ static ref REGEX_PORT_RESUME: Regex = Regex::new(r"^port([\d\.]+)/resume$")
+ .expect("Failed to create the regex for the port<n>/resume scheme.");
static ref REGEX_PORT_DESCRIPTORS: Regex = Regex::new(r"^port([\d\.]+)/descriptors$")
.expect("Failed to create the regex for the port<n>/descriptors");
static ref REGEX_PORT_STATE: Regex = Regex::new(r"^port([\d\.]+)/state$")
.expect("Failed to create the regex for the port<n>/state scheme");
+ static ref REGEX_PORT_PM_STATE: Regex = Regex::new(r"^port([\d\.]+)/pm_state$")
+ .expect("Failed to create the regex for the port<n>/pm_state scheme");
static ref REGEX_PORT_REQUEST: Regex = Regex::new(r"^port([\d\.]+)/request$")
.expect("Failed to create the regex for the port<n>/request scheme");
static ref REGEX_PORT_ENDPOINTS: Regex = Regex::new(r"^port([\d\.]+)/endpoints$")
@@ -137,12 +146,15 @@ pub enum Handle {
Port(PortId, Vec<u8>), // port, contents
PortDesc(PortId, Vec<u8>), // port, contents
PortState(PortId), // port
+ PortPmState(PortId), // port
PortReq(PortId, PortReqState), // port, state
Endpoints(PortId, Vec<u8>), // port, contents
Endpoint(PortId, u8, EndpointHandleTy), // port, endpoint, state
ConfigureEndpoints(PortId), // port
AttachDevice(PortId), // port
DetachDevice(PortId), // port
+ SuspendDevice(PortId), // port
+ ResumeDevice(PortId), // port
SchemeRoot,
}
@@ -172,6 +184,8 @@ enum SchemeParameters {
PortDesc(PortId), // port number
/// /port<n>/state
PortState(PortId), // port number
+ /// /port<n>/pm_state
+ PortPmState(PortId), // port number
/// /port<n>/request
PortReq(PortId), // port number
/// /port<n>/endpoints
@@ -187,6 +201,10 @@ enum SchemeParameters {
AttachDevice(PortId), // port number
/// /port<n>/detach
DetachDevice(PortId), // port number
+ /// /port<n>/suspend
+ SuspendDevice(PortId), // port number
+ /// /port<n>/resume
+ ResumeDevice(PortId), // port number
}
impl Handle {
@@ -209,6 +227,9 @@ impl Handle {
Handle::PortState(port_num) => {
format!("port{}/state", port_num)
}
+ Handle::PortPmState(port_num) => {
+ format!("port{}/pm_state", port_num)
+ }
Handle::PortReq(port_num, _) => {
format!("port{}/request", port_num)
}
@@ -235,6 +256,12 @@ impl Handle {
Handle::DetachDevice(port_num) => {
format!("port{}/detach", port_num)
}
+ Handle::SuspendDevice(port_num) => {
+ format!("port{}/suspend", port_num)
+ }
+ Handle::ResumeDevice(port_num) => {
+ format!("port{}/resume", port_num)
+ }
Handle::SchemeRoot => String::from(""),
}
}
@@ -258,10 +285,13 @@ impl Handle {
&Handle::PortReq(_, PortReqState::Tmp) => unreachable!(),
&Handle::PortReq(_, PortReqState::TmpSetup(_)) => unreachable!(),
&Handle::PortState(_) => HandleType::Character,
+ &Handle::PortPmState(_) => HandleType::Character,
&Handle::PortReq(_, _) => HandleType::Character,
&Handle::ConfigureEndpoints(_) => HandleType::Character,
&Handle::AttachDevice(_) => HandleType::Character,
&Handle::DetachDevice(_) => HandleType::Character,
+ &Handle::SuspendDevice(_) => HandleType::Character,
+ &Handle::ResumeDevice(_) => HandleType::Character,
&Handle::Endpoint(_, _, ref st) => match st {
EndpointHandleTy::Data => HandleType::Character,
EndpointHandleTy::Ctl => HandleType::Character,
@@ -289,10 +319,13 @@ impl Handle {
&Handle::PortReq(_, PortReqState::Tmp) => None,
&Handle::PortReq(_, PortReqState::TmpSetup(_)) => None,
&Handle::PortState(_) => None,
+ &Handle::PortPmState(_) => None,
&Handle::PortReq(_, _) => None,
&Handle::ConfigureEndpoints(_) => None,
&Handle::AttachDevice(_) => None,
&Handle::DetachDevice(_) => None,
+ &Handle::SuspendDevice(_) => None,
+ &Handle::ResumeDevice(_) => None,
&Handle::Endpoint(_, _, ref st) => match st {
EndpointHandleTy::Data => None,
EndpointHandleTy::Ctl => None,
@@ -383,6 +416,14 @@ impl SchemeParameters {
let port_num = get_port_id_from_regex(&REGEX_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(&REGEX_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(&REGEX_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(&REGEX_PORT_DESCRIPTORS, scheme, 0)?;
@@ -391,6 +432,10 @@ impl SchemeParameters {
let port_num = get_port_id_from_regex(&REGEX_PORT_STATE, scheme, 0)?;
Ok(Self::PortState(port_num))
+ } else if REGEX_PORT_PM_STATE.is_match(scheme) {
+ let port_num = get_port_id_from_regex(&REGEX_PORT_PM_STATE, scheme, 0)?;
+
+ Ok(Self::PortPmState(port_num))
} else if REGEX_PORT_REQUEST.is_match(scheme) {
let port_num = get_port_id_from_regex(&REGEX_PORT_REQUEST, scheme, 0)?;
@@ -556,6 +601,47 @@ impl AnyDescriptor {
}
impl<const N: usize> Xhci<N> {
+ fn begin_port_operation(
+ &self,
+ port: PortId,
+ allow_attaching: bool,
+ require_active_pm: bool,
+ ) -> Result<super::PortOperationGuard> {
+ let lifecycle = {
+ let port_state = self.port_states.get(&port).ok_or(Error::new(EBADFD))?;
+ Arc::clone(&port_state.lifecycle)
+ };
+
+ lifecycle.begin_operation(allow_attaching)?;
+ let guard = super::PortOperationGuard::new(lifecycle);
+
+ if require_active_pm {
+ let pm_state = self
+ .port_states
+ .get(&port)
+ .ok_or(Error::new(EBADFD))?
+ .pm_state;
+ if pm_state != super::PortPmState::Active {
+ drop(guard);
+ return Err(Error::new(EBUSY));
+ }
+ }
+
+ Ok(guard)
+ }
+
+ fn begin_transfer_operation(&self, port: PortId) -> Result<super::PortOperationGuard> {
+ self.begin_port_operation(port, true, true)
+ }
+
+ fn begin_routable_operation(&self, port: PortId) -> Result<super::PortOperationGuard> {
+ self.begin_port_operation(port, false, true)
+ }
+
+ fn begin_attached_operation(&self, port: PortId) -> Result<super::PortOperationGuard> {
+ self.begin_port_operation(port, false, false)
+ }
+
async fn new_if_desc(
&self,
port_id: PortId,
@@ -564,15 +650,22 @@ impl<const N: usize> Xhci<N> {
endps: impl IntoIterator<Item = EndpDesc>,
hid_descs: impl IntoIterator<Item = HidDesc>,
lang_id: u16,
+ quirks: crate::usb_quirks::UsbQuirkFlags,
) -> Result<IfDesc> {
Ok(IfDesc {
alternate_setting: desc.alternate_setting,
class: desc.class,
interface_str: if desc.interface_str > 0 {
- Some(
+ if quirks.contains(crate::usb_quirks::UsbQuirkFlags::BAD_DESCRIPTOR) {
self.fetch_string_desc(port_id, slot, desc.interface_str, lang_id)
- .await?,
- )
+ .await
+ .ok()
+ } else {
+ Some(
+ self.fetch_string_desc(port_id, slot, desc.interface_str, lang_id)
+ .await?,
+ )
+ }
} else {
None
},
@@ -590,10 +683,9 @@ impl<const N: usize> Xhci<N> {
/// # Locking
/// This function will lock `Xhci::cmd` and `Xhci::dbs`.
pub async fn execute_command<F: FnOnce(&mut Trb, bool)>(&self, f: F) -> (Trb, Trb) {
- //TODO: find out why this bit is set earlier!
if self.interrupt_is_pending(0) {
debug!("The EHB bit is already set!");
- //self.force_clear_interrupt(0);
+ self.force_clear_interrupt(0);
}
let next_event = {
@@ -628,6 +720,54 @@ impl<const N: usize> Xhci<N> {
(event_trb, command_trb)
}
+ pub fn execute_command_with_timeout<F: FnOnce(&mut Trb, bool)>(
+ &self,
+ timeout: common::timeout::Timeout,
+ f: F,
+ ) -> Result<(Trb, Trb)> {
+ if self.interrupt_is_pending(0) {
+ debug!("The EHB bit is already set!");
+ self.force_clear_interrupt(0);
+ }
+
+ let next_event = {
+ let mut command_ring = self.cmd.lock().unwrap();
+ let (cmd_index, cycle) = (command_ring.next_index(), command_ring.cycle);
+
+ debug!("Sending command with cycle bit {}", cycle as u8);
+
+ {
+ let command_trb = &mut command_ring.trbs[cmd_index];
+ f(command_trb, cycle);
+ }
+
+ let command_trb = &command_ring.trbs[cmd_index];
+ self.next_command_completion_event_trb(
+ &*command_ring,
+ command_trb,
+ EventDoorbell::new(self, 0, 0),
+ )
+ };
+
+ let mut next_event = Box::pin(next_event);
+
+ loop {
+ if let Some(trbs) = next_event.as_mut().now_or_never() {
+ let event_trb = trbs.event_trb;
+ let command_trb = trbs.src_trb.ok_or(Error::new(EIO))?;
+
+ assert_eq!(
+ event_trb.trb_type(),
+ TrbType::CommandCompletion as u8,
+ "The IRQ reactor (or the xHC) gave an invalid event TRB"
+ );
+
+ return Ok((event_trb, command_trb));
+ }
+
+ timeout.run().map_err(|()| Error::new(EIO))?;
+ }
+ }
pub async fn execute_control_transfer<D>(
&self,
port_num: PortId,
@@ -639,6 +779,9 @@ impl<const N: usize> Xhci<N> {
where
D: FnMut(&mut Trb, bool) -> ControlFlow,
{
+ let _op = self.begin_transfer_operation(port_num)?;
+ self.ensure_port_active(port_num)?;
+
let future = {
let mut port_state = self.port_state_mut(port_num)?;
let slot = port_state.slot;
@@ -690,7 +833,21 @@ impl<const N: usize> Xhci<N> {
handle_transfer_event_trb("CONTROL_TRANSFER", &event_trb, &status_trb)?;
- //self.event_handler_finished();
+ let delay_ctrl_msg = self
+ .port_states
+ .get(&port_num)
+ .map(|port_state| {
+ port_state
+ .quirks
+ .contains(crate::usb_quirks::UsbQuirkFlags::DELAY_CTRL_MSG)
+ })
+ .unwrap_or(false);
+
+ if delay_ctrl_msg {
+ std::thread::sleep(std::time::Duration::from_millis(20));
+ }
+
+ self.event_handler_finished();
Ok(event_trb)
}
@@ -709,6 +866,9 @@ impl<const N: usize> Xhci<N> {
where
D: FnMut(&mut Trb, bool) -> ControlFlow,
{
+ let _op = self.begin_transfer_operation(port_num)?;
+ self.ensure_port_active(port_num)?;
+
let endp_idx = endp_num.checked_sub(1).ok_or(Error::new(EIO))?;
let mut port_state = self.port_state_mut(port_num)?;
@@ -785,7 +945,31 @@ impl<const N: usize> Xhci<N> {
let event_trb = trbs.event_trb;
let transfer_trb = trbs.src_trb.ok_or(Error::new(EIO))?;
- handle_transfer_event_trb("EXECUTE_TRANSFER", &event_trb, &transfer_trb)?;
+ if let Err(err) = handle_transfer_event_trb("EXECUTE_TRANSFER", &event_trb, &transfer_trb)
+ {
+ let need_reset = self
+ .port_states
+ .get(&port_num)
+ .map(|port_state| {
+ port_state
+ .quirks
+ .contains(crate::usb_quirks::UsbQuirkFlags::NEED_RESET)
+ })
+ .unwrap_or(false);
+
+ if need_reset {
+ if let Err(reset_err) = self.reset_device_slot(port_num).await {
+ error!(
+ "EXECUTE_TRANSFER reset recovery failed for port {}: {}",
+ port_num, reset_err
+ );
+ }
+ }
+
+ self.event_handler_finished();
+
+ return Err(err);
+ }
// FIXME: EDTLA if event data was set
if event_trb.completion_code() != TrbCompletionCode::ShortPacket as u8
@@ -798,6 +982,8 @@ impl<const N: usize> Xhci<N> {
// TODO: Handle event data
trace!("EVENT DATA: {:?}", event_trb.event_data());
+ self.event_handler_finished();
+
Ok(event_trb)
}
async fn device_req_no_data(&self, port: PortId, req: usb::Setup) -> Result<()> {
@@ -857,10 +1043,27 @@ impl<const N: usize> Xhci<N> {
trb.reset_endpoint(slot, endp_num_xhc, tsp, cycle);
})
.await;
- //self.event_handler_finished();
+ self.event_handler_finished();
handle_event_trb("RESET_ENDPOINT", &event_trb, &command_trb)
}
+ async fn reset_device_slot(&self, port_num: PortId) -> Result<()> {
+ let slot = self
+ .port_states
+ .get(&port_num)
+ .ok_or(Error::new(EBADF))?
+ .slot;
+
+ let (event_trb, command_trb) = self
+ .execute_command(|trb, cycle| {
+ trb.reset_device(slot, cycle);
+ })
+ .await;
+
+ self.event_handler_finished();
+
+ handle_event_trb("RESET_DEVICE", &event_trb, &command_trb)
+ }
fn endp_ctx_interval(speed_id: &ProtocolSpeed, endp_desc: &EndpDesc) -> u8 {
/// Logarithmic (base 2) 125 µs periods per millisecond.
@@ -949,35 +1152,106 @@ impl<const N: usize> Xhci<N> {
self.port_states.get_mut(&port).ok_or(Error::new(EBADF))
}
+ fn restore_configure_input_context(
+ &self,
+ port: PortId,
+ snapshot: ConfigureContextSnapshot,
+ endpoint_snapshots: &[(usize, EndpointContextSnapshot)],
+ ) -> Result<usize> {
+ let port_state = self.port_states.get(&port).ok_or(Error::new(EBADFD))?;
+ let mut input_context = port_state
+ .input_context
+ .lock()
+ .unwrap_or_else(|err| err.into_inner());
+
+ input_context.add_context.write(snapshot.add_context);
+ input_context.drop_context.write(snapshot.drop_context);
+ input_context.control.write(snapshot.control);
+ input_context.device.slot.a.write(snapshot.slot_a);
+ input_context.device.slot.b.write(snapshot.slot_b);
+ input_context.device.slot.c.write(snapshot.slot_c);
+
+ for (endp_i, endp_snapshot) in endpoint_snapshots {
+ input_context.device.endpoints[*endp_i].a.write(endp_snapshot.a);
+ input_context.device.endpoints[*endp_i].b.write(endp_snapshot.b);
+ input_context.device.endpoints[*endp_i].trl.write(endp_snapshot.trl);
+ input_context.device.endpoints[*endp_i].trh.write(endp_snapshot.trh);
+ input_context.device.endpoints[*endp_i].c.write(endp_snapshot.c);
+ }
+
+ Ok(input_context.physical())
+ }
+
+ async fn rollback_configure_attempt(
+ &self,
+ port: PortId,
+ slot: u8,
+ configure_snapshot: ConfigureContextSnapshot,
+ endpoint_snapshots: &[(usize, EndpointContextSnapshot)],
+ stage: &str,
+ ) {
+ let rollback_input_context_physical = match self.restore_configure_input_context(
+ port,
+ configure_snapshot,
+ endpoint_snapshots,
+ ) {
+ Ok(physical) => physical,
+ Err(restore_err) => {
+ warn!(
+ "failed to restore configure input context after {}: {:?}",
+ stage, restore_err
+ );
+ return;
+ }
+ };
+
+ let (rollback_event_trb, rollback_command_trb) = self
+ .execute_command(|trb, cycle| {
+ trb.configure_endpoint(slot, rollback_input_context_physical, cycle)
+ })
+ .await;
+
+ self.event_handler_finished();
+
+ if let Err(rollback_err) =
+ handle_event_trb("CONFIGURE_ENDPOINT_ROLLBACK", &rollback_event_trb, &rollback_command_trb)
+ {
+ warn!(
+ "failed to roll back CONFIGURE_ENDPOINT after {}: {:?}",
+ stage, rollback_err
+ );
+ }
+ }
+
async fn configure_endpoints_once(
&self,
port: PortId,
req: &ConfigureEndpointsReq,
) -> Result<()> {
- let (endp_desc_count, new_context_entries, configuration_value) = {
- let mut port_state = self.port_states.get_mut(&port).ok_or(Error::new(EBADFD))?;
-
- port_state.cfg_idx = Some(req.config_desc);
+ let (dev_desc, endpoint_descs, new_context_entries, configuration_value) = {
+ let port_state = self.port_states.get(&port).ok_or(Error::new(EBADFD))?;
+ let dev_desc = port_state.dev_desc.as_ref().ok_or(Error::new(EBADFD))?.clone();
- let config_desc = port_state
- .dev_desc
- .as_ref()
- .unwrap()
+ let config_desc = dev_desc
.config_descs
.iter()
.find(|desc| desc.configuration_value == req.config_desc)
.ok_or(Error::new(EBADFD))?;
+ let configuration_value = config_desc.configuration_value;
- //TODO: USE ENDPOINTS FROM ALL INTERFACES
- let mut endp_desc_count = 0;
- let mut new_context_entries = 1;
- for if_desc in config_desc.interface_descs.iter() {
- for endpoint in if_desc.endpoints.iter() {
- endp_desc_count += 1;
- let entry = Self::endp_num_to_dci(endp_desc_count, endpoint);
- if entry > new_context_entries {
- new_context_entries = entry;
- }
+ let endpoint_descs = config_desc
+ .interface_descs
+ .iter()
+ .flat_map(|if_desc| if_desc.endpoints.iter().copied())
+ .collect::<Vec<_>>();
+
+ let endp_desc_count = endpoint_descs.len();
+ let mut new_context_entries = 1u8;
+ for (endp_idx, endpoint) in endpoint_descs.iter().enumerate() {
+ let endp_num = endp_idx as u8 + 1;
+ let entry = Self::endp_num_to_dci(endp_num, endpoint);
+ if entry > new_context_entries {
+ new_context_entries = entry;
}
}
new_context_entries += 1;
@@ -988,11 +1262,13 @@ impl<const N: usize> Xhci<N> {
}
(
- endp_desc_count,
+ dev_desc,
+ endpoint_descs,
new_context_entries,
- config_desc.configuration_value,
+ configuration_value,
)
};
+ let endp_desc_count = endpoint_descs.len();
let lec = self.cap.lec();
let log_max_psa_size = self.cap.max_psa_size();
@@ -1002,9 +1278,160 @@ impl<const N: usize> Xhci<N> {
Error::new(EIO)
})?;
+ let mut endpoint_programs = Vec::with_capacity(endp_desc_count as usize);
+ let mut staged_endpoint_states = Vec::with_capacity(endp_desc_count as usize);
+
{
+ for (endp_idx, endp_desc) in endpoint_descs.iter().enumerate() {
+ let endp_num = endp_idx as u8 + 1;
+
+ let endp_num_xhc = Self::endp_num_to_dci(endp_num, endp_desc);
+ let usb_log_max_streams = endp_desc.log_max_streams();
+
+ let primary_streams = if let Some(log_max_streams) = usb_log_max_streams {
+ if log_max_psa_size != 0 {
+ cmp::min(u8::from(log_max_streams), log_max_psa_size + 1) - 1
+ } else {
+ 0
+ }
+ } else {
+ 0
+ };
+ let linear_stream_array = primary_streams != 0;
+
+ let mult = endp_desc.isoch_mult(lec);
+
+ let max_packet_size = Self::endp_ctx_max_packet_size(endp_desc);
+ let max_burst_size = Self::endp_ctx_max_burst(speed_id, &dev_desc, endp_desc);
+
+ let max_esit_payload = Self::endp_ctx_max_esit_payload(
+ speed_id,
+ &dev_desc,
+ endp_desc,
+ max_packet_size,
+ max_burst_size,
+ );
+ let max_esit_payload_lo = max_esit_payload as u16;
+ let max_esit_payload_hi = ((max_esit_payload & 0x00FF_0000) >> 16) as u8;
+
+ let interval = Self::endp_ctx_interval(speed_id, endp_desc);
+
+ let max_error_count = 3;
+ let ep_ty = endp_desc.xhci_ep_type()?;
+ let host_initiate_disable = false;
+
+ let avg_trb_len: u16 = match endp_desc.ty() {
+ EndpointTy::Ctrl => {
+ warn!("trying to use control endpoint");
+ return Err(Error::new(EIO));
+ }
+ EndpointTy::Bulk | EndpointTy::Isoch => 3072,
+ EndpointTy::Interrupt => 1024,
+ };
+
+ assert_eq!(ep_ty & 0x7, ep_ty);
+ assert_eq!(mult & 0x3, mult);
+ assert_eq!(max_error_count & 0x3, max_error_count);
+ assert_ne!(ep_ty, 0);
+
+ let ring_ptr = if usb_log_max_streams.is_some() {
+ let mut array =
+ StreamContextArray::new::<N>(self.cap.ac64(), 1 << (primary_streams + 1))?;
+
+ array.add_ring::<N>(self.cap.ac64(), 1, true)?;
+ let array_ptr = array.register();
+
+ assert_eq!(
+ array_ptr & 0xFFFF_FFFF_FFFF_FF81,
+ array_ptr,
+ "stream ctx ptr not aligned to 16 bytes"
+ );
+
+ staged_endpoint_states.push((
+ endp_num,
+ EndpointState {
+ transfer: super::RingOrStreams::Streams(array),
+ driver_if_state: EndpIfState::Init,
+ },
+ ));
+
+ array_ptr
+ } else {
+ let ring = Ring::new::<N>(self.cap.ac64(), 16, true)?;
+ let ring_ptr = ring.register();
+
+ assert_eq!(
+ ring_ptr & 0xFFFF_FFFF_FFFF_FF81,
+ ring_ptr,
+ "ring pointer not aligned to 16 bytes"
+ );
+
+ staged_endpoint_states.push((
+ endp_num,
+ EndpointState {
+ transfer: super::RingOrStreams::Ring(ring),
+ driver_if_state: EndpIfState::Init,
+ },
+ ));
+
+ ring_ptr
+ };
+ assert_eq!(primary_streams & 0x1F, primary_streams);
+
+ endpoint_programs.push(EndpointProgram {
+ endp_num,
+ endp_num_xhc,
+ a: u32::from(mult) << 8
+ | u32::from(primary_streams) << 10
+ | u32::from(linear_stream_array) << 15
+ | u32::from(interval) << 16
+ | u32::from(max_esit_payload_hi) << 24,
+ b: max_error_count << 1
+ | u32::from(ep_ty) << 3
+ | u32::from(host_initiate_disable) << 7
+ | u32::from(max_burst_size) << 8
+ | u32::from(max_packet_size) << 16,
+ trl: ring_ptr as u32,
+ trh: (ring_ptr >> 32) as u32,
+ c: u32::from(avg_trb_len) | (u32::from(max_esit_payload_lo) << 16),
+ });
+
+ log::debug!("staged endpoint {}", endp_num);
+ }
+ }
+
+ let (configure_snapshot, endpoint_snapshots, input_context_physical) = {
let port_state = self.port_states.get(&port).ok_or(Error::new(EBADFD))?;
- let mut input_context = port_state.input_context.lock().unwrap();
+ let mut input_context = port_state
+ .input_context
+ .lock()
+ .unwrap_or_else(|err| err.into_inner());
+
+ let configure_snapshot = ConfigureContextSnapshot {
+ add_context: input_context.add_context.read(),
+ drop_context: input_context.drop_context.read(),
+ control: input_context.control.read(),
+ slot_a: input_context.device.slot.a.read(),
+ slot_b: input_context.device.slot.b.read(),
+ slot_c: input_context.device.slot.c.read(),
+ };
+
+ let endpoint_snapshots = endpoint_programs
+ .iter()
+ .map(|program| {
+ let endp_i = program.endp_num_xhc as usize - 1;
+ (
+ endp_i,
+ EndpointContextSnapshot::capture_values(
+ input_context.device.endpoints[endp_i].a.read(),
+ input_context.device.endpoints[endp_i].b.read(),
+ input_context.device.endpoints[endp_i].trl.read(),
+ input_context.device.endpoints[endp_i].trh.read(),
+ input_context.device.endpoints[endp_i].c.read(),
+ ),
+ )
+ })
+ .collect::<Vec<_>>();
// Configure the slot context as well, which holds the last index of the endp descs.
input_context.add_context.write(1);
@@ -1015,25 +1442,26 @@ impl<const N: usize> Xhci<N> {
const HUB_PORTS_MASK: u32 = 0xFF00_0000;
const HUB_PORTS_SHIFT: u8 = 24;
+ let mut current_slot_c = input_context.device.slot.c.read();
let mut current_slot_a = input_context.device.slot.a.read();
let mut current_slot_b = input_context.device.slot.b.read();
- // Set context entries
current_slot_a &= !CONTEXT_ENTRIES_MASK;
current_slot_a |=
(u32::from(new_context_entries) << CONTEXT_ENTRIES_SHIFT) & CONTEXT_ENTRIES_MASK;
- // Set hub data
current_slot_a &= !(1 << 26);
current_slot_b &= !HUB_PORTS_MASK;
if let Some(hub_ports) = req.hub_ports {
current_slot_a |= 1 << 26;
current_slot_b |= (u32::from(hub_ports) << HUB_PORTS_SHIFT) & HUB_PORTS_MASK;
}
+ current_slot_c = apply_hub_tt_info(current_slot_c, req);
input_context.device.slot.a.write(current_slot_a);
input_context.device.slot.b.write(current_slot_b);
+ input_context.device.slot.c.write(current_slot_c);
let control = if self.op.lock().unwrap().cie() {
(u32::from(req.alternate_setting.unwrap_or(0)) << 16)
@@ -1043,174 +1471,138 @@ impl<const N: usize> Xhci<N> {
0
};
input_context.control.write(control);
- }
- for endp_idx in 0..endp_desc_count as u8 {
- let endp_num = endp_idx + 1;
-
- let mut port_state = self.port_states.get_mut(&port).ok_or(Error::new(EBADFD))?;
- let dev_desc = port_state.dev_desc.as_ref().unwrap();
- let endp_desc = port_state.get_endp_desc(endp_idx).ok_or_else(|| {
- warn!("failed to find endpoint {}", endp_idx);
- Error::new(EIO)
- })?;
-
- let endp_num_xhc = Self::endp_num_to_dci(endp_num, endp_desc);
-
- let usb_log_max_streams = endp_desc.log_max_streams();
-
- // TODO: Secondary streams.
- let primary_streams = if let Some(log_max_streams) = usb_log_max_streams {
- // TODO: Can streams-capable be configured to not use streams?
- if log_max_psa_size != 0 {
- cmp::min(u8::from(log_max_streams), log_max_psa_size + 1) - 1
- } else {
- 0
- }
- } else {
- 0
- };
- let linear_stream_array = if primary_streams != 0 { true } else { false };
+ for program in &endpoint_programs {
+ let endp_i = program.endp_num_xhc as usize - 1;
+ input_context.add_context.writef(1 << program.endp_num_xhc, true);
+ input_context.device.endpoints[endp_i].a.write(program.a);
+ input_context.device.endpoints[endp_i].b.write(program.b);
+ input_context.device.endpoints[endp_i].trl.write(program.trl);
+ input_context.device.endpoints[endp_i].trh.write(program.trh);
+ input_context.device.endpoints[endp_i].c.write(program.c);
+ }
- // TODO: Interval related fields
- // TODO: Max ESIT payload size.
+ (configure_snapshot, endpoint_snapshots, input_context.physical())
+ };
- let mult = endp_desc.isoch_mult(lec);
+ let port_state = self.port_states.get(&port).ok_or(Error::new(EBADFD))?;
+ let slot = port_state.slot;
- let max_packet_size = Self::endp_ctx_max_packet_size(endp_desc);
- let max_burst_size = Self::endp_ctx_max_burst(speed_id, dev_desc, endp_desc);
+ let (event_trb, command_trb) = self
+ .execute_command(|trb, cycle| trb.configure_endpoint(slot, input_context_physical, cycle))
+ .await;
- let max_esit_payload = Self::endp_ctx_max_esit_payload(
- speed_id,
- dev_desc,
- endp_desc,
- max_packet_size,
- max_burst_size,
- );
- let max_esit_payload_lo = max_esit_payload as u16;
- let max_esit_payload_hi = ((max_esit_payload & 0x00FF_0000) >> 16) as u8;
-
- let interval = Self::endp_ctx_interval(speed_id, endp_desc);
-
- let max_error_count = 3;
- let ep_ty = endp_desc.xhci_ep_type()?;
- let host_initiate_disable = false;
-
- // TODO: Maybe this value is out of scope for xhcid, because the actual usb device
- // driver probably knows better. The spec says that the initial value should be 8 bytes
- // for control, 1KiB for interrupt and 3KiB for bulk and isoch.
- let avg_trb_len: u16 = match endp_desc.ty() {
- EndpointTy::Ctrl => {
- warn!("trying to use control endpoint");
- return Err(Error::new(EIO)); // only endpoint zero is of type control, and is configured separately with the address device command.
+ self.event_handler_finished();
+
+ if let Err(err) = handle_event_trb("CONFIGURE_ENDPOINT", &event_trb, &command_trb) {
+ let rollback_input_context_physical = match self.restore_configure_input_context(
+ port,
+ configure_snapshot,
+ &endpoint_snapshots,
+ ) {
+ Ok(physical) => physical,
+ Err(restore_err) => {
+ warn!(
+ "failed to restore configure input context after CONFIGURE_ENDPOINT failure: {:?}",
+ restore_err
+ );
+ return Err(err);
}
- EndpointTy::Bulk | EndpointTy::Isoch => 3072, // 3 KiB
- EndpointTy::Interrupt => 1024, // 1 KiB
};
- assert_eq!(ep_ty & 0x7, ep_ty);
- assert_eq!(mult & 0x3, mult);
- assert_eq!(max_error_count & 0x3, max_error_count);
- assert_ne!(ep_ty, 0); // 0 means invalid.
-
- let ring_ptr = if usb_log_max_streams.is_some() {
- let mut array =
- StreamContextArray::new::<N>(self.cap.ac64(), 1 << (primary_streams + 1))?;
+ let (rollback_event_trb, rollback_command_trb) = self
+ .execute_command(|trb, cycle| {
+ trb.configure_endpoint(slot, rollback_input_context_physical, cycle)
+ })
+ .await;
- // TODO: Use as many stream rings as needed.
- array.add_ring::<N>(self.cap.ac64(), 1, true)?;
- let array_ptr = array.register();
+ self.event_handler_finished();
- assert_eq!(
- array_ptr & 0xFFFF_FFFF_FFFF_FF81,
- array_ptr,
- "stream ctx ptr not aligned to 16 bytes"
- );
- port_state.endpoint_states.insert(
- endp_num,
- EndpointState {
- transfer: super::RingOrStreams::Streams(array),
- driver_if_state: EndpIfState::Init,
- },
+ if let Err(rollback_err) =
+ handle_event_trb("CONFIGURE_ENDPOINT_ROLLBACK", &rollback_event_trb, &rollback_command_trb)
+ {
+ warn!(
+ "failed to roll back CONFIGURE_ENDPOINT after failure {:?}: {:?}",
+ err,
+ rollback_err
);
+ }
- array_ptr
- } else {
- let ring = Ring::new::<N>(self.cap.ac64(), 16, true)?;
- let ring_ptr = ring.register();
+ return Err(err);
+ }
- assert_eq!(
- ring_ptr & 0xFFFF_FFFF_FFFF_FF81,
- ring_ptr,
- "ring pointer not aligned to 16 bytes"
- );
- port_state.endpoint_states.insert(
- endp_num,
- EndpointState {
- transfer: super::RingOrStreams::Ring(ring),
- driver_if_state: EndpIfState::Init,
- },
- );
- ring_ptr
- };
- assert_eq!(primary_streams & 0x1F, primary_streams);
-
- let mut input_context = port_state.input_context.lock().unwrap();
- input_context.add_context.writef(1 << endp_num_xhc, true);
-
- let endp_i = endp_num_xhc as usize - 1;
- input_context.device.endpoints[endp_i].a.write(
- u32::from(mult) << 8
- | u32::from(primary_streams) << 10
- | u32::from(linear_stream_array) << 15
- | u32::from(interval) << 16
- | u32::from(max_esit_payload_hi) << 24,
+ if self.consume_test_hook("fail_after_configure_endpoint") {
+ info!(
+ "xhcid: test hook injecting failure after CONFIGURE_ENDPOINT for port {}",
+ port
);
- input_context.device.endpoints[endp_i].b.write(
- max_error_count << 1
- | u32::from(ep_ty) << 3
- | u32::from(host_initiate_disable) << 7
- | u32::from(max_burst_size) << 8
- | u32::from(max_packet_size) << 16,
- );
-
- input_context.device.endpoints[endp_i]
- .trl
- .write(ring_ptr as u32);
- input_context.device.endpoints[endp_i]
- .trh
- .write((ring_ptr >> 32) as u32);
-
- input_context.device.endpoints[endp_i]
- .c
- .write(u32::from(avg_trb_len) | (u32::from(max_esit_payload_lo) << 16));
-
- log::debug!("initialized endpoint {}", endp_num);
+ self.rollback_configure_attempt(
+ port,
+ slot,
+ configure_snapshot,
+ &endpoint_snapshots,
+ "test hook fail_after_configure_endpoint",
+ )
+ .await;
+ return Err(Error::new(EIO));
}
- {
- let port_state = self.port_states.get(&port).ok_or(Error::new(EBADFD))?;
- let slot = port_state.slot;
- let input_context_physical = port_state.input_context.lock().unwrap().physical();
+ // Tell the device about this configuration.
+ let skip_set_configuration = self
+ .port_states
+ .get(&port)
+ .map(|port_state| {
+ port_state
+ .quirks
+ .contains(crate::usb_quirks::UsbQuirkFlags::NO_SET_CONFIG)
+ })
+ .unwrap_or(false);
- let (event_trb, command_trb) = self
- .execute_command(|trb, cycle| {
- trb.configure_endpoint(slot, input_context_physical, cycle)
- })
+ if !skip_set_configuration {
+ if let Err(err) = self.set_configuration(port, configuration_value).await {
+ self.rollback_configure_attempt(
+ port,
+ slot,
+ configure_snapshot,
+ &endpoint_snapshots,
+ "set_configuration failure",
+ )
.await;
- //self.event_handler_finished();
+ return Err(err);
+ }
- handle_event_trb("CONFIGURE_ENDPOINT", &event_trb, &command_trb)?;
+ if self.consume_test_hook("fail_after_set_configuration") {
+ info!(
+ "xhcid: test hook injecting failure after SET_CONFIGURATION for port {}",
+ port
+ );
+ self.rollback_configure_attempt(
+ port,
+ slot,
+ configure_snapshot,
+ &endpoint_snapshots,
+ "test hook fail_after_set_configuration",
+ )
+ .await;
+ return Err(Error::new(EIO));
+ }
}
- // Tell the device about this configuration.
- self.set_configuration(port, configuration_value).await?;
+ {
+ let mut port_state = self.port_states.get_mut(&port).ok_or(Error::new(EBADFD))?;
+ port_state.cfg_idx = Some(configuration_value);
+ port_state.endpoint_states.retain(|endp_num, _| *endp_num == 0);
+ for (endp_num, endpoint_state) in staged_endpoint_states {
+ port_state.endpoint_states.insert(endp_num, endpoint_state);
+ }
+ }
Ok(())
}
async fn configure_endpoints(&self, port: PortId, json_buf: &[u8]) -> Result<()> {
+ let _op = self.begin_routable_operation(port)?;
let mut req: ConfigureEndpointsReq =
serde_json::from_slice(json_buf).or(Err(Error::new(EBADMSG)))?;
@@ -1234,8 +1626,20 @@ impl<const N: usize> Xhci<N> {
if let Some(interface_num) = req.interface_desc {
if let Some(alternate_setting) = req.alternate_setting {
- self.set_interface(port, interface_num, alternate_setting)
- .await?;
+ let skip_set_interface = self
+ .port_states
+ .get(&port)
+ .map(|port_state| {
+ port_state
+ .quirks
+ .contains(crate::usb_quirks::UsbQuirkFlags::NO_SET_INTF)
+ })
+ .unwrap_or(false);
+
+ if !skip_set_interface {
+ self.set_interface(port, interface_num, alternate_setting)
+ .await?;
+ }
}
}
@@ -1432,7 +1836,7 @@ impl<const N: usize> Xhci<N> {
},
)
.await?;
- //self.event_handler_finished();
+ self.event_handler_finished();
let bytes_transferred = dma_buf
.as_ref()
@@ -1453,52 +1857,109 @@ impl<const N: usize> Xhci<N> {
let raw_dd = self.fetch_dev_desc(port_id, slot).await?;
log::debug!("port {} slot {} desc {:X?}", port_id, slot, raw_dd);
+ let vendor = raw_dd.vendor;
+ let product = raw_dd.product;
+ let quirks = crate::usb_quirks::lookup_usb_quirks(vendor, product);
+ if !quirks.is_empty() {
+ log::info!(
+ "port {}: USB quirks for {:04x}:{:04x}: {:?}",
+ port_id, vendor, product, quirks
+ );
+ }
+
// Only fetch language IDs if we need to. Some devices will fail to return this descriptor
//TODO: also check configurations and interfaces for defined strings?
+ let bad_descriptor = quirks.contains(crate::usb_quirks::UsbQuirkFlags::BAD_DESCRIPTOR);
+
let lang_id =
- if raw_dd.manufacturer_str > 0 || raw_dd.product_str > 0 || raw_dd.serial_str > 0 {
- let lang_ids = self.fetch_lang_ids_desc(port_id, slot).await?;
- // Prefer US English, but fall back to first language ID, or zero
- let en_us_id = 0x409;
- if lang_ids.contains(&en_us_id) {
- en_us_id
- } else {
- match lang_ids.first() {
- Some(some) => *some,
- None => 0,
+ if !quirks.contains(crate::usb_quirks::UsbQuirkFlags::NO_STRING_FETCH)
+ && (raw_dd.manufacturer_str > 0
+ || raw_dd.product_str > 0
+ || raw_dd.serial_str > 0)
+ {
+ match self.fetch_lang_ids_desc(port_id, slot).await {
+ Ok(lang_ids) => {
+ // Prefer US English, but fall back to first language ID, or zero
+ let en_us_id = 0x409;
+ if lang_ids.contains(&en_us_id) {
+ en_us_id
+ } else {
+ match lang_ids.first() {
+ Some(some) => *some,
+ None => 0,
+ }
+ }
+ }
+ Err(err) if bad_descriptor => {
+ log::warn!(
+ "port {} slot {}: failed to fetch language IDs with BAD_DESCRIPTOR set: {}",
+ port_id,
+ slot,
+ err
+ );
+ 0
}
+ Err(err) => return Err(err),
}
} else {
0
};
log::debug!("port {} using language ID 0x{:04x}", port_id, lang_id);
- let (manufacturer_str, product_str, serial_str) = (
- if raw_dd.manufacturer_str > 0 {
- Some(
- self.fetch_string_desc(port_id, slot, raw_dd.manufacturer_str, lang_id)
- .await?,
- )
- } else {
- None
- },
- if raw_dd.product_str > 0 {
- Some(
- self.fetch_string_desc(port_id, slot, raw_dd.product_str, lang_id)
- .await?,
- )
+ let (manufacturer_str, product_str, serial_str) =
+ if quirks.contains(crate::usb_quirks::UsbQuirkFlags::NO_STRING_FETCH) {
+ (None, None, None)
} else {
- None
- },
- if raw_dd.serial_str > 0 {
- Some(
- self.fetch_string_desc(port_id, slot, raw_dd.serial_str, lang_id)
- .await?,
+ (
+ if raw_dd.manufacturer_str > 0 {
+ if bad_descriptor {
+ self.fetch_string_desc(port_id, slot, raw_dd.manufacturer_str, lang_id)
+ .await
+ .ok()
+ } else {
+ Some(
+ self.fetch_string_desc(
+ port_id,
+ slot,
+ raw_dd.manufacturer_str,
+ lang_id,
+ )
+ .await?,
+ )
+ }
+ } else {
+ None
+ },
+ if raw_dd.product_str > 0 {
+ if bad_descriptor {
+ self.fetch_string_desc(port_id, slot, raw_dd.product_str, lang_id)
+ .await
+ .ok()
+ } else {
+ Some(
+ self.fetch_string_desc(port_id, slot, raw_dd.product_str, lang_id)
+ .await?,
+ )
+ }
+ } else {
+ None
+ },
+ if raw_dd.serial_str > 0 {
+ if bad_descriptor {
+ self.fetch_string_desc(port_id, slot, raw_dd.serial_str, lang_id)
+ .await
+ .ok()
+ } else {
+ Some(
+ self.fetch_string_desc(port_id, slot, raw_dd.serial_str, lang_id)
+ .await?,
+ )
+ }
+ } else {
+ None
+ },
)
- } else {
- None
- },
- );
+ };
log::debug!(
"manufacturer {:?} product {:?} serial {:?}",
manufacturer_str,
@@ -1508,14 +1969,39 @@ impl<const N: usize> Xhci<N> {
//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<u8> =
+ if quirks.contains(crate::usb_quirks::UsbQuirkFlags::FORCE_ONE_CONFIG) {
+ vec![0]
+ } else {
+ (0..raw_dd.configurations).collect()
+ };
+
+ for index in configuration_indices {
debug!("Fetching the config descriptor at index {}", index);
let (desc, data) = self.fetch_config_desc(port_id, slot, index).await?;
log::debug!(
@@ -1541,6 +2027,12 @@ impl<const N: usize> Xhci<N> {
let mut iter = descriptors.into_iter().peekable();
while let Some(item) = iter.next() {
+ if quirks.contains(crate::usb_quirks::UsbQuirkFlags::HONOR_BNUMINTERFACES)
+ && interface_descs.len() >= desc.interfaces as usize
+ {
+ break;
+ }
+
if let AnyDescriptor::Interface(idesc) = item {
let mut endpoints = SmallVec::<[EndpDesc; 4]>::new();
let mut hid_descs = SmallVec::<[HidDesc; 1]>::new();
@@ -1554,6 +2046,9 @@ impl<const N: usize> Xhci<N> {
}
Some(unexpected) => {
log::warn!("expected endpoint, got {:X?}", unexpected);
+ if bad_descriptor {
+ continue;
+ }
break;
}
None => break,
@@ -1578,8 +2073,16 @@ impl<const N: usize> Xhci<N> {
}
interface_descs.push(
- self.new_if_desc(port_id, slot, idesc, endpoints, hid_descs, lang_id)
- .await?,
+ self.new_if_desc(
+ port_id,
+ slot,
+ idesc,
+ endpoints,
+ hid_descs,
+ lang_id,
+ quirks,
+ )
+ .await?,
);
} else {
log::warn!("expected interface, got {:?}", item);
@@ -1590,11 +2093,20 @@ impl<const N: usize> Xhci<N> {
config_descs.push(ConfDesc {
kind: desc.kind,
- configuration: if desc.configuration_str > 0 {
- Some(
+ configuration: if quirks.contains(crate::usb_quirks::UsbQuirkFlags::NO_STRING_FETCH)
+ {
+ None
+ } else if desc.configuration_str > 0 {
+ if bad_descriptor {
self.fetch_string_desc(port_id, slot, desc.configuration_str, lang_id)
- .await?,
- )
+ .await
+ .ok()
+ } else {
+ Some(
+ self.fetch_string_desc(port_id, slot, desc.configuration_str, lang_id)
+ .await?,
+ )
+ }
} else {
None
},
@@ -1856,7 +2368,7 @@ impl<const N: usize> Xhci<N> {
if (flags & O_DIRECTORY != 0) || (flags & O_STAT != 0) {
let mut contents = Vec::new();
- write!(contents, "descriptors\nendpoints\n").unwrap();
+ write!(contents, "descriptors\nendpoints\npm_state\nsuspend\nresume\n").unwrap();
if self.slot_state(
self.port_states
@@ -1893,6 +2405,14 @@ impl<const N: usize> Xhci<N> {
Ok(Handle::PortState(port_num))
}
+ fn open_handle_port_pm_state(&self, port_num: PortId, flags: usize) -> Result<Handle> {
+ if flags & O_DIRECTORY != 0 && flags & O_STAT == 0 {
+ return Err(Error::new(ENOTDIR));
+ }
+
+ Ok(Handle::PortPmState(port_num))
+ }
+
/// implements open() for /port<n>/endpoints
///
/// # Arguments
@@ -2087,6 +2607,30 @@ impl<const N: usize> Xhci<N> {
Ok(Handle::DetachDevice(port_num))
}
+ fn open_handle_suspend_device(&self, port_num: PortId, flags: usize) -> Result<Handle> {
+ 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<Handle> {
+ 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<n>/request
///
/// # Arguments
@@ -2155,6 +2699,9 @@ impl<const N: usize> SchemeSync for &Xhci<N> {
SchemeParameters::PortState(port_number) => {
self.open_handle_port_state(port_number, flags)?
}
+ SchemeParameters::PortPmState(port_number) => {
+ self.open_handle_port_pm_state(port_number, flags)?
+ }
SchemeParameters::PortReq(port_number) => {
self.open_handle_port_request(port_number, flags)?
}
@@ -2173,6 +2720,12 @@ impl<const N: usize> SchemeSync for &Xhci<N> {
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 +2756,11 @@ impl<const N: usize> SchemeSync for &Xhci<N> {
//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 +2820,8 @@ impl<const N: usize> SchemeSync for &Xhci<N> {
Handle::ConfigureEndpoints(_) => Err(Error::new(EBADF)),
Handle::AttachDevice(_) => Err(Error::new(EBADF)),
Handle::DetachDevice(_) => Err(Error::new(EBADF)),
+ Handle::SuspendDevice(_) => Err(Error::new(EBADF)),
+ Handle::ResumeDevice(_) => Err(Error::new(EBADF)),
Handle::SchemeRoot => Err(Error::new(EBADF)),
&mut Handle::Endpoint(port_num, endp_num, ref mut st) => match st {
@@ -2294,6 +2853,10 @@ impl<const N: usize> SchemeSync for &Xhci<N> {
Ok(Xhci::<N>::write_dyn_string(string, buf, offset))
}
+ &mut Handle::PortPmState(port_num) => {
+ let ps = self.port_states.get(&port_num).ok_or(Error::new(EBADF))?;
+ Ok(Xhci::<N>::write_dyn_string(ps.pm_state.as_str().as_bytes(), buf, offset))
+ }
&mut Handle::PortReq(port_num, ref mut st) => {
let state = std::mem::replace(st, PortReqState::Tmp);
drop(guard); // release the lock
@@ -2333,6 +2896,14 @@ impl<const N: usize> SchemeSync for &Xhci<N> {
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 +2927,59 @@ impl<const N: usize> Xhci<N> {
self.handles.remove(&fd);
}
+ fn ensure_port_active(&self, port_num: PortId) -> Result<()> {
+ let port_state = self.port_states.get(&port_num).ok_or(Error::new(EBADFD))?;
+ if port_state.lifecycle.state() == super::PortLifecycleState::Detaching {
+ return Err(Error::new(EBUSY));
+ }
+
+ let pm_state = port_state.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 _op = self.begin_attached_operation(port_num)?;
+ let mut port_state = self.port_states.get_mut(&port_num).ok_or(Error::new(EBADFD))?;
+
+ if port_state
+ .quirks
+ .contains(crate::usb_quirks::UsbQuirkFlags::NO_SUSPEND)
+ {
+ return Err(Error::new(EOPNOTSUPP));
+ }
+
+ if port_state.pm_state != super::PortPmState::Active {
+ return Err(Error::new(EBUSY));
+ }
+
+ port_state.pm_state = super::PortPmState::Suspended;
+ Ok(())
+ }
+
+ pub async fn resume_device(&self, port_num: PortId) -> Result<()> {
+ let _op = self.begin_attached_operation(port_num)?;
+ let mut port_state = self.port_states.get_mut(&port_num).ok_or(Error::new(EBADFD))?;
+
+ if port_state.pm_state == super::PortPmState::Active {
+ return Ok(());
+ }
+
+ let slot_state = self.slot_state(port_state.slot as usize);
+ if slot_state != SlotState::Addressed as u8 && slot_state != SlotState::Configured as u8 {
+ warn!(
+ "refusing to resume port {} while slot {} is in controller state {}",
+ port_num, port_state.slot, slot_state
+ );
+ return Err(Error::new(EIO));
+ }
+
+ port_state.pm_state = super::PortPmState::Active;
+ Ok(())
+ }
+
pub fn get_endp_status(&self, port_num: PortId, endp_num: u8) -> Result<EndpointStatus> {
let port_state = self.port_states.get(&port_num).ok_or(Error::new(EBADFD))?;
@@ -2406,6 +3030,8 @@ impl<const N: usize> Xhci<N> {
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));
}
@@ -2531,7 +3157,7 @@ impl<const N: usize> Xhci<N> {
)
})
.await;
- //self.event_handler_finished();
+ self.event_handler_finished();
handle_event_trb("SET_TR_DEQUEUE_PTR", &event_trb, &command_trb)
}
@@ -2541,10 +3167,14 @@ impl<const N: usize> Xhci<N> {
endp_num: u8,
buf: &[u8],
) -> Result<usize> {
+ let _op = self.begin_routable_operation(port_num)?;
let mut port_state = self
.port_states
.get_mut(&port_num)
.ok_or(Error::new(EBADF))?;
+ if port_state.pm_state != super::PortPmState::Active {
+ return Err(Error::new(EBUSY));
+ }
let ep_if_state = &mut port_state
.endpoint_states
@@ -2562,6 +3192,7 @@ impl<const N: usize> Xhci<N> {
},
XhciEndpCtlReq::Reset { no_clear_feature } => match ep_if_state {
EndpIfState::Init => {
+ drop(port_state);
self.on_req_reset_device(port_num, endp_num, !no_clear_feature)
.await?
}
@@ -2631,6 +3262,9 @@ impl<const N: usize> Xhci<N> {
endp_num: u8,
buf: &[u8],
) -> Result<usize> {
+ let _op = self.begin_routable_operation(port_num)?;
+ self.ensure_port_active(port_num)?;
+
let mut port_state = self
.port_states
.get_mut(&port_num)
@@ -2732,6 +3366,9 @@ impl<const N: usize> Xhci<N> {
endp_num: u8,
buf: &mut [u8],
) -> Result<usize> {
+ let _op = self.begin_routable_operation(port_num)?;
+ self.ensure_port_active(port_num)?;
+
let mut port_state = self
.port_states
.get_mut(&port_num)
@@ -2832,6 +3469,64 @@ pub fn handle_transfer_event_trb(name: &str, event_trb: &Trb, transfer_trb: &Trb
Err(Error::new(EIO))
}
}
+
+fn apply_hub_tt_info(current_slot_c: u32, req: &ConfigureEndpointsReq) -> u32 {
+ const TT_THINK_TIME_MASK: u32 = 0x0003_0000;
+ const TT_THINK_TIME_SHIFT: u8 = 16;
+
+ let mut slot_c = current_slot_c & !TT_THINK_TIME_MASK;
+ if req.hub_ports.is_some() {
+ if let Some(hub_think_time) = req.hub_think_time {
+ slot_c |= (u32::from(hub_think_time) << TT_THINK_TIME_SHIFT) & TT_THINK_TIME_MASK;
+ }
+ }
+ slot_c
+}
+
+#[derive(Clone, Copy)]
+struct ConfigureContextSnapshot {
+ add_context: u32,
+ drop_context: u32,
+ control: u32,
+ slot_a: u32,
+ slot_b: u32,
+ slot_c: u32,
+}
+
+#[derive(Clone, Copy)]
+struct EndpointContextSnapshot {
+ a: u32,
+ b: u32,
+ trl: u32,
+ trh: u32,
+ c: u32,
+}
+
+impl EndpointContextSnapshot {
+ fn capture_values(a: u32, b: u32, trl: u32, trh: u32, c: u32) -> Self {
+ Self { a, b, trl, trh, c }
+ }
+
+ fn restore<const N: usize>(&self, ctx: &mut EndpointContext<N>) {
+ ctx.a.write(self.a);
+ ctx.b.write(self.b);
+ ctx.trl.write(self.trl);
+ ctx.trh.write(self.trh);
+ ctx.c.write(self.c);
+ }
+}
+
+#[derive(Clone, Copy)]
+struct EndpointProgram {
+ endp_num: u8,
+ endp_num_xhc: u8,
+ a: u32,
+ b: u32,
+ trl: u32,
+ trh: u32,
+ c: u32,
+}
+
use lazy_static::lazy_static;
use std::ops::{Add, Div, Rem};
@@ -2845,3 +3540,26 @@ where
a / b
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::{apply_hub_tt_info, ConfigureEndpointsReq};
+
+ #[test]
+ fn apply_hub_tt_info_only_sets_bits_for_hub_requests() {
+ let req = ConfigureEndpointsReq {
+ config_desc: 1,
+ interface_desc: None,
+ alternate_setting: None,
+ hub_ports: Some(4),
+ hub_think_time: Some(3),
+ };
+ assert_eq!(apply_hub_tt_info(0, &req), 0x0003_0000);
+
+ let no_hub = ConfigureEndpointsReq {
+ hub_ports: None,
+ ..req.clone()
+ };
+ assert_eq!(apply_hub_tt_info(0x0003_0000, &no_hub), 0);
+ }
+}
diff --git a/init.initfs.d/40_ps2d.service b/init.initfs.d/40_ps2d.service
index 881e75ea..bbee2699 100644
--- a/init.initfs.d/40_ps2d.service
+++ b/init.initfs.d/40_ps2d.service
@@ -5,4 +5,4 @@ condition_architecture = ["x86", "x86_64"]
[service]
cmd = "ps2d"
-type = "notify"
+type = "oneshot_async"
diff --git a/init/src/scheduler.rs b/init/src/scheduler.rs
index d42a4e57..f8ac5cd3 100644
--- a/init/src/scheduler.rs
+++ b/init/src/scheduler.rs
@@ -1,7 +1,8 @@
use std::collections::VecDeque;
-use crate::InitConfig;
+use crate::service::ServiceType;
use crate::unit::{Unit, UnitId, UnitKind, UnitStore};
+use crate::InitConfig;
pub struct Scheduler {
pending: VecDeque<Job>,
@@ -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('$') {