Files
RedBear-OS/local/patches/base/redox.patch
T
vasilito 5f44bbab0c Add Intel Alder Lake/Raptor Lake/Meteor Lake/Arrow Lake GPU IDs to ihdgd
Regenerate aggregate base patch to include ihdgd device ID additions
for modern Intel integrated GPUs (12th-14th Gen, Core Ultra, Arrow Lake).
This allows pcid-spawner to match and load ihdgd on current Intel laptops.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-18 07:06:00 +01:00

5818 lines
208 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
@@ -8,7 +8,7 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
-acpi = { git = "https://github.com/jackpot51/acpi.git" }
+acpi = { path = "../acpi" }
arrayvec = "0.7.6"
log.workspace = true
num-derive = "0.3"
@@ -21,6 +21,7 @@ rustc-hash = "1.1.0"
thiserror.workspace = true
ron.workspace = true
serde.workspace = true
+toml.workspace = true
amlserde = { path = "../amlserde" }
common = { path = "../common" }
diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs
index 94a1eb17..24799ae2 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
@@ -269,7 +731,7 @@ impl AmlSymbols {
unsafe { AcpiTables::from_rsdp(handler.clone(), rsdp_address).map_err(format_err)? };
let platform = AcpiPlatform::new(tables, handler).map_err(format_err)?;
let interpreter = Interpreter::new_from_platform(&platform).map_err(format_err)?;
- for (region, handler) in self.aml_region_handlers.drain(..) {
+ for (region, handler) in default_aml_region_handlers() {
interpreter.install_region_handler(region, handler);
}
self.aml_context = Some(interpreter);
@@ -356,6 +818,21 @@ impl AmlSymbols {
self.symbol_cache = symbol_cache;
}
+
+ pub fn reset(&mut self) {
+ self.aml_context = None;
+ self.symbol_cache = FxHashMap::default();
+ *self.page_cache.lock().unwrap() = AmlPageCache::default();
+ }
+}
+
+fn default_aml_region_handlers() -> Vec<(RegionSpace, Box<dyn RegionHandler>)> {
+ let mut handlers: Vec<(RegionSpace, Box<dyn RegionHandler>)> = Vec::new();
+
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ handlers.push((RegionSpace::EmbeddedControl, Box::new(crate::ec::Ec::new())));
+
+ handlers
}
#[derive(Debug, Error)]
@@ -368,6 +845,8 @@ pub enum AmlEvalError {
DeserializationError,
#[error("AML not initialized")]
NotInitialized,
+ #[error("PCI registration not ready")]
+ PciNotReady,
}
impl From<AmlError> for AmlEvalError {
fn from(value: AmlError) -> Self {
@@ -375,10 +854,122 @@ impl From<AmlError> for AmlEvalError {
}
}
+#[derive(Clone, Debug)]
+pub struct AcpiPowerAdapter {
+ pub id: String,
+ pub path: String,
+ pub online: bool,
+}
+
+#[derive(Clone, Debug)]
+pub struct AcpiBattery {
+ pub id: String,
+ pub path: String,
+ pub state: u64,
+ pub present_rate: Option<u64>,
+ pub remaining_capacity: Option<u64>,
+ pub present_voltage: Option<u64>,
+ pub power_unit: Option<String>,
+ pub design_capacity: Option<u64>,
+ pub last_full_capacity: Option<u64>,
+ pub design_voltage: Option<u64>,
+ pub technology: Option<String>,
+ pub model: Option<String>,
+ pub serial: Option<String>,
+ pub battery_type: Option<String>,
+ pub oem_info: Option<String>,
+ pub percentage: Option<f64>,
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct AcpiPowerSnapshot {
+ pub adapters: Vec<AcpiPowerAdapter>,
+ pub batteries: Vec<AcpiBattery>,
+}
+
+impl AcpiPowerSnapshot {
+ pub fn on_battery(&self) -> bool {
+ if self.adapters.iter().any(|adapter| adapter.online) {
+ return false;
+ }
+
+ self.batteries
+ .iter()
+ .any(|battery| battery.state & 0x1 != 0)
+ }
+}
+
+fn sanitize_power_id(path: &str) -> String {
+ let sanitized = path
+ .chars()
+ .map(|ch| if ch.is_ascii_alphanumeric() { ch } else { '_' })
+ .collect::<String>()
+ .trim_matches('_')
+ .to_string();
+
+ if sanitized.is_empty() {
+ String::from("device")
+ } else {
+ sanitized
+ }
+}
+
+fn aml_value_as_integer(value: &AmlSerdeValue) -> Option<u64> {
+ match value {
+ AmlSerdeValue::Integer(value) => Some(*value),
+ _ => None,
+ }
+}
+
+fn aml_value_as_string(value: &AmlSerdeValue) -> Option<String> {
+ match value {
+ AmlSerdeValue::String(value) => {
+ let trimmed = value.trim();
+ if trimmed.is_empty() {
+ None
+ } else {
+ Some(trimmed.to_string())
+ }
+ }
+ _ => None,
+ }
+}
+
+fn power_unit_name(value: u64) -> Option<&'static str> {
+ match value {
+ 0 => Some("mWh"),
+ 1 => Some("mAh"),
+ _ => None,
+ }
+}
+
+fn battery_technology_name(value: u64) -> Option<&'static str> {
+ match value {
+ 0 => Some("non-rechargeable"),
+ 1 => Some("rechargeable"),
+ _ => None,
+ }
+}
+
+fn battery_percentage(remaining_capacity: u64, full_capacity: u64) -> Option<f64> {
+ if full_capacity == 0 {
+ return None;
+ }
+
+ Some((remaining_capacity as f64 * 100.0 / full_capacity as f64).clamp(0.0, 100.0))
+}
+
pub struct AcpiContext {
tables: Vec<Sdt>,
dsdt: Option<Dsdt>,
fadt: Option<Fadt>,
+ pm1a_cnt_blk: u64,
+ pm1b_cnt_blk: u64,
+ s5_values: RwLock<Option<(u8, u8)>>,
+ reset_reg: Option<GenericAddress>,
+ reset_value: u8,
+
+ pci_fd: RwLock<Option<libredox::Fd>>,
aml_symbols: RwLock<AmlSymbols>,
@@ -388,16 +979,190 @@ pub struct AcpiContext {
sdt_order: RwLock<Vec<Option<SdtSignature>>>,
pub next_ctx: RwLock<u64>,
+ dmi_info: Option<DmiInfo>,
}
impl AcpiContext {
+ fn evaluate_acpi_object(
+ &self,
+ path: &str,
+ object: &str,
+ args: &[u64],
+ ) -> Result<AmlSerdeValue, AmlEvalError> {
+ let full_path = format!("{path}.{object}");
+ let aml_name =
+ AmlName::from_str(&full_path).map_err(|_| AmlEvalError::DeserializationError)?;
+ let args = args
+ .iter()
+ .copied()
+ .map(AmlSerdeValue::Integer)
+ .collect::<Vec<_>>();
+
+ self.aml_eval(aml_name, args)
+ }
+
+ fn try_evaluate_acpi_object(
+ &self,
+ path: &str,
+ object: &str,
+ args: &[u64],
+ ) -> Option<AmlSerdeValue> {
+ match self.evaluate_acpi_object(path, object, args) {
+ Ok(value) => Some(value),
+ Err(error) => {
+ log::debug!("Failed to evaluate {}.{}: {:?}", path, object, error);
+ None
+ }
+ }
+ }
+
+ fn power_device_paths(&self) -> Result<Vec<String>, AmlEvalError> {
+ let mut symbols = self.aml_symbols.write();
+ let pci_fd = self.pci_fd.read();
+ let interpreter = symbols.aml_context_mut(pci_fd.as_ref())?;
+
+ let mut names = Vec::with_capacity(512);
+ {
+ let mut namespace = interpreter.namespace.lock();
+ namespace
+ .traverse(|level_aml_name, level| {
+ for (child_seg, _) in level.values.iter() {
+ if let Ok(aml_name) =
+ AmlName::from_name_seg(child_seg.to_owned()).resolve(level_aml_name)
+ {
+ names.push(aml_name);
+ }
+ }
+ Ok(true)
+ })
+ .map_err(AmlEvalError::from)?;
+ }
+
+ let mut namespace = interpreter.namespace.lock();
+ let mut devices = Vec::new();
+
+ for name in names {
+ let Ok(object) = namespace.get(name.clone()) else {
+ continue;
+ };
+
+ if matches!(object.deref(), Object::Device) {
+ devices.push(name.to_string());
+ }
+ }
+
+ Ok(devices)
+ }
+
+ fn power_device_present(&self, path: &str) -> bool {
+ match self.try_evaluate_acpi_object(path, "_STA", &[]) {
+ Some(AmlSerdeValue::Integer(status)) => status & 0x1 != 0,
+ Some(_) => false,
+ None => true,
+ }
+ }
+
+ fn power_adapter_from_path(&self, path: &str) -> Option<AcpiPowerAdapter> {
+ let online = match self.try_evaluate_acpi_object(path, "_PSR", &[])? {
+ AmlSerdeValue::Integer(value) => value != 0,
+ _ => return None,
+ };
+
+ Some(AcpiPowerAdapter {
+ id: sanitize_power_id(path),
+ path: path.to_string(),
+ online,
+ })
+ }
+
+ fn power_battery_from_path(&self, path: &str) -> Option<AcpiBattery> {
+ let bif_contents = match self.try_evaluate_acpi_object(path, "_BIF", &[])? {
+ AmlSerdeValue::Package { contents } => contents,
+ _ => return None,
+ };
+ let bst_contents = match self.try_evaluate_acpi_object(path, "_BST", &[])? {
+ AmlSerdeValue::Package { contents } => contents,
+ _ => return None,
+ };
+
+ if bif_contents.len() < 13 || bst_contents.len() < 4 {
+ return None;
+ }
+
+ let state = aml_value_as_integer(&bst_contents[0])?;
+ let present_rate = aml_value_as_integer(&bst_contents[1]);
+ let remaining_capacity = aml_value_as_integer(&bst_contents[2]);
+ let present_voltage = aml_value_as_integer(&bst_contents[3]);
+
+ let design_capacity = aml_value_as_integer(&bif_contents[1]);
+ let last_full_capacity = aml_value_as_integer(&bif_contents[2]);
+ let design_voltage = aml_value_as_integer(&bif_contents[4]);
+ let percentage = remaining_capacity.and_then(|remaining| {
+ let full_capacity = last_full_capacity.or(design_capacity)?;
+ battery_percentage(remaining, full_capacity)
+ });
+
+ Some(AcpiBattery {
+ id: sanitize_power_id(path),
+ path: path.to_string(),
+ state,
+ present_rate,
+ remaining_capacity,
+ present_voltage,
+ power_unit: aml_value_as_integer(&bif_contents[0])
+ .and_then(power_unit_name)
+ .map(str::to_string),
+ design_capacity,
+ last_full_capacity,
+ design_voltage,
+ technology: aml_value_as_integer(&bif_contents[3])
+ .and_then(battery_technology_name)
+ .map(str::to_string),
+ model: aml_value_as_string(&bif_contents[9]),
+ serial: aml_value_as_string(&bif_contents[10]),
+ battery_type: aml_value_as_string(&bif_contents[11]),
+ oem_info: aml_value_as_string(&bif_contents[12]),
+ percentage,
+ })
+ }
+
+ pub fn power_snapshot(&self) -> Result<AcpiPowerSnapshot, AmlEvalError> {
+ let mut adapters = Vec::new();
+ let mut batteries = Vec::new();
+
+ for device_path in self.power_device_paths()? {
+ if !self.power_device_present(&device_path) {
+ continue;
+ }
+
+ if let Some(adapter) = self.power_adapter_from_path(&device_path) {
+ adapters.push(adapter);
+ }
+ if let Some(battery) = self.power_battery_from_path(&device_path) {
+ batteries.push(battery);
+ }
+ }
+
+ adapters.sort_by(|left, right| left.id.cmp(&right.id));
+ batteries.sort_by(|left, right| left.id.cmp(&right.id));
+
+ Ok(AcpiPowerSnapshot {
+ adapters,
+ batteries,
+ })
+ }
+
pub fn aml_eval(
&self,
symbol: AmlName,
args: Vec<AmlSerdeValue>,
) -> Result<AmlSerdeValue, AmlEvalError> {
+ if !self.pci_ready() {
+ return Err(AmlEvalError::PciNotReady);
+ }
let mut symbols = self.aml_symbols.write();
- let interpreter = symbols.aml_context_mut(None)?;
+ let pci_fd = self.pci_fd.read();
+ let interpreter = symbols.aml_context_mut(pci_fd.as_ref())?;
interpreter.acquire_global_lock(16)?;
let args = args
@@ -424,10 +1189,55 @@ impl AcpiContext {
.flatten()
}
- pub fn init(
- rxsdt_physaddrs: impl Iterator<Item = u64>,
- ec: Vec<(RegionSpace, Box<dyn RegionHandler>)>,
- ) -> Self {
+ pub fn evaluate_acpi_method(
+ &self,
+ path: &str,
+ method: &str,
+ args: &[u64],
+ ) -> Result<Vec<u64>, AmlEvalError> {
+ match self.evaluate_acpi_object(path, method, args)? {
+ AmlSerdeValue::Integer(value) => Ok(vec![value]),
+ AmlSerdeValue::Package { contents } => contents
+ .into_iter()
+ .map(|value| match value {
+ AmlSerdeValue::Integer(value) => Ok(value),
+ _ => Err(AmlEvalError::DeserializationError),
+ })
+ .collect(),
+ _ => Err(AmlEvalError::DeserializationError),
+ }
+ }
+
+ pub fn device_power_on(&self, device_path: &str) {
+ match self.evaluate_acpi_method(device_path, "_PS0", &[]) {
+ Ok(values) => {
+ log::debug!("{}._PS0 => {:?}", device_path, values);
+ }
+ Err(error) => {
+ log::warn!("Failed to power on {} with _PS0: {:?}", device_path, error);
+ }
+ }
+ }
+
+ pub fn device_power_off(&self, device_path: &str) {
+ match self.evaluate_acpi_method(device_path, "_PS3", &[]) {
+ Ok(values) => {
+ log::debug!("{}._PS3 => {:?}", device_path, values);
+ }
+ Err(error) => {
+ log::warn!("Failed to power off {} with _PS3: {:?}", device_path, error);
+ }
+ }
+ }
+
+ pub fn device_get_performance(&self, device_path: &str) -> Result<u64, AmlEvalError> {
+ self.evaluate_acpi_method(device_path, "_PPC", &[])?
+ .into_iter()
+ .next()
+ .ok_or(AmlEvalError::DeserializationError)
+ }
+
+ pub fn init(rxsdt_physaddrs: impl Iterator<Item = u64>) -> Self {
let tables = rxsdt_physaddrs
.map(|physaddr| {
let physaddr: usize = physaddr
@@ -440,17 +1250,28 @@ impl AcpiContext {
})
.collect::<Vec<Sdt>>();
+ let dmi_info = load_dmi_info();
+ let tables = apply_acpi_table_quirks(tables, dmi_info.as_ref());
+
let mut this = Self {
tables,
dsdt: None,
fadt: None,
+ pm1a_cnt_blk: 0,
+ pm1b_cnt_blk: 0,
+ s5_values: RwLock::new(None),
+ reset_reg: None,
+ reset_value: 0,
+
+ pci_fd: RwLock::new(None),
// Temporary values
- aml_symbols: RwLock::new(AmlSymbols::new(ec)),
+ aml_symbols: RwLock::new(AmlSymbols::new()),
next_ctx: RwLock::new(0),
sdt_order: RwLock::new(Vec::new()),
+ dmi_info,
};
for table in &this.tables {
@@ -458,7 +1279,10 @@ impl AcpiContext {
}
Fadt::init(&mut this);
- //TODO (hangs on real hardware): Dmar::init(&this);
+ // DMAR (Intel VT-d) init — previously disabled due to iterator bug (type_bytes copied
+ // instead of len_bytes in DmarRawIter). Safe to call now: on AMD systems, no DMAR table
+ // exists and this returns early with a warning.
+ Dmar::init(&this);
this
}
@@ -521,22 +1345,28 @@ impl AcpiContext {
pub fn tables(&self) -> &[Sdt] {
&self.tables
}
+ pub fn dmi_info(&self) -> Option<&DmiInfo> {
+ self.dmi_info.as_ref()
+ }
pub fn new_index(&self, signature: &SdtSignature) {
self.sdt_order.write().push(Some(*signature));
}
pub fn aml_lookup(&self, symbol: &str) -> Option<String> {
- if let Ok(aml_symbols) = self.aml_symbols(None) {
+ if !self.pci_ready() {
+ return None;
+ }
+ if let Ok(aml_symbols) = self.aml_symbols() {
aml_symbols.lookup(symbol)
} else {
None
}
}
- pub fn aml_symbols(
- &self,
- pci_fd: Option<&libredox::Fd>,
- ) -> Result<RwLockReadGuard<'_, AmlSymbols>, AmlError> {
+ pub fn aml_symbols(&self) -> Result<RwLockReadGuard<'_, AmlSymbols>, AmlError> {
+ if !self.pci_ready() {
+ return Err(AmlError::NoHandlerForRegionAccess(RegionSpace::PciConfig));
+ }
// return the cached value if it exists
let symbols = self.aml_symbols.read();
if !symbols.symbols_cache().is_empty() {
@@ -549,8 +1379,13 @@ impl AcpiContext {
log::trace!("Creating symbols list");
let mut aml_symbols = self.aml_symbols.write();
+ let pci_fd = self.pci_fd.read();
- aml_symbols.build_cache(pci_fd);
+ aml_symbols.build_cache(pci_fd.as_ref());
+
+ if aml_symbols.symbols_cache().is_empty() {
+ return Err(AmlError::NoHandlerForRegionAccess(RegionSpace::PciConfig));
+ }
// return the cached value
Ok(RwLockWriteGuard::downgrade(aml_symbols))
@@ -559,99 +1394,164 @@ impl AcpiContext {
/// Discard any cached symbols list. To be called if the AML namespace changes.
pub fn aml_symbols_reset(&self) {
let mut aml_symbols = self.aml_symbols.write();
- aml_symbols.symbol_cache = FxHashMap::default();
+ aml_symbols.reset();
}
- /// Set Power State
- /// See https://uefi.org/sites/default/files/resources/ACPI_6_1.pdf
- /// - search for PM1a
- /// See https://forum.osdev.org/viewtopic.php?t=16990 for practical details
- pub fn set_global_s_state(&self, state: u8) {
- if state != 5 {
- return;
- }
- let fadt = match self.fadt() {
- Some(fadt) => fadt,
- None => {
- log::error!("Cannot set global S-state due to missing FADT.");
- return;
- }
- };
+ pub fn pci_ready(&self) -> bool {
+ self.pci_fd.read().is_some()
+ }
- let port = fadt.pm1a_control_block as u16;
- let mut val = 1 << 13;
+ pub fn register_pci_fd(&self, pci_fd: libredox::Fd) -> Result<(), libredox::Fd> {
+ let mut aml_symbols = self.aml_symbols.write();
+ let mut registered_pci_fd = self.pci_fd.write();
- let aml_symbols = self.aml_symbols.read();
+ if registered_pci_fd.is_some() {
+ return Err(pci_fd);
+ }
- let s5_aml_name = match acpi::aml::namespace::AmlName::from_str("\\_S5") {
- Ok(aml_name) => aml_name,
- Err(error) => {
- log::error!("Could not build AmlName for \\_S5, {:?}", error);
- return;
- }
- };
+ *registered_pci_fd = Some(pci_fd);
- let s5 = match &aml_symbols.aml_context {
- Some(aml_context) => match aml_context.namespace.lock().get(s5_aml_name) {
- Ok(s5) => s5,
- Err(error) => {
- log::error!("Cannot set S-state, missing \\_S5, {:?}", error);
- return;
- }
- },
- None => {
- log::error!("Cannot set S-state, AML context not initialized");
- return;
- }
- };
+ if aml_symbols.aml_context.is_some() || !aml_symbols.symbol_cache.is_empty() {
+ log::warn!("PCI registration arrived after AML init; rebuilding AML interpreter state");
+ aml_symbols.reset();
+ *self.s5_values.write() = None;
+ }
- let package = match s5.deref() {
- acpi::aml::object::Object::Package(package) => package,
- _ => {
- log::error!("Cannot set S-state, \\_S5 is not a package");
- return;
- }
- };
+ Ok(())
+ }
- let slp_typa = match package[0].deref() {
- acpi::aml::object::Object::Integer(i) => i.to_owned(),
- _ => {
- log::error!("typa is not an Integer");
- return;
- }
- };
- let slp_typb = match package[1].deref() {
- acpi::aml::object::Object::Integer(i) => i.to_owned(),
- _ => {
- log::error!("typb is not an Integer");
- return;
- }
+ pub fn acpi_shutdown(&self) {
+ let Some((slp_typa_s5, slp_typb_s5)) = self.ensure_s5_values() else {
+ log::error!("Cannot shut down with ACPI: failed to resolve \\_S5 sleep type values");
+ return;
};
- log::trace!("Shutdown SLP_TYPa {:X}, SLP_TYPb {:X}", slp_typa, slp_typb);
- val |= slp_typa as u16;
+ let pm1a_value = (u16::from(slp_typa_s5) << 10) | 0x2000;
+ let pm1b_value = (u16::from(slp_typb_s5) << 10) | 0x2000;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
- log::warn!("Shutdown with ACPI outw(0x{:X}, 0x{:X})", port, val);
- Pio::<u16>::new(port).write(val);
- }
+ let Ok(pm1a_port) = u16::try_from(self.pm1a_cnt_blk) else {
+ log::error!("PM1a_CNT_BLK address is invalid: {:#X}", self.pm1a_cnt_blk);
+ return;
+ };
- // TODO: Handle SLP_TYPb
+ log::warn!(
+ "Shutdown with ACPI PM1a_CNT outw(0x{:X}, 0x{:X})",
+ pm1a_port,
+ pm1a_value
+ );
+ Pio::<u16>::new(pm1a_port).write(pm1a_value);
+
+ if self.pm1b_cnt_blk != 0 {
+ match u16::try_from(self.pm1b_cnt_blk) {
+ Ok(pm1b_port) => {
+ log::warn!(
+ "Shutdown with ACPI PM1b_CNT outw(0x{:X}, 0x{:X})",
+ pm1b_port,
+ pm1b_value
+ );
+ Pio::<u16>::new(pm1b_port).write(pm1b_value);
+ }
+ Err(_) => {
+ log::error!("PM1b_CNT_BLK address is invalid: {:#X}", self.pm1b_cnt_blk);
+ }
+ }
+ }
+ }
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
{
log::error!(
- "Cannot shutdown with ACPI outw(0x{:X}, 0x{:X}) on this architecture",
- port,
- val
+ "Cannot shutdown with ACPI PM1_CNT writes on this architecture (PM1a={:#X}, PM1b={:#X})",
+ self.pm1a_cnt_blk,
+ self.pm1b_cnt_blk
);
}
+ }
+
+ pub fn acpi_reboot(&self) {
+ match self.reset_reg {
+ Some(reset_reg) => {
+ log::warn!(
+ "Reboot with ACPI reset register {:?} value {:#X}",
+ reset_reg,
+ self.reset_value
+ );
+ reset_reg.write_u8(self.reset_value);
+ }
+ None => {
+ log::error!("Cannot reboot with ACPI: no reset register present in FADT");
+ }
+ }
+ }
+
+ /// Set Power State
+ /// See https://uefi.org/sites/default/files/resources/ACPI_6_1.pdf
+ /// - search for PM1a
+ /// See https://forum.osdev.org/viewtopic.php?t=16990 for practical details
+ pub fn set_global_s_state(&self, state: u8) {
+ if state != 5 {
+ return;
+ }
+
+ if self.fadt().is_none() {
+ log::error!("Cannot set global S-state due to missing FADT.");
+ return;
+ }
+
+ self.acpi_shutdown();
loop {
core::hint::spin_loop();
}
}
+
+ fn evaluate_s5_values(&self) -> Option<(u8, u8)> {
+ match AmlName::from_str("\\_S5") {
+ Ok(s5_name) => match self.aml_eval(s5_name, Vec::new()) {
+ Ok(AmlSerdeValue::Package { contents }) => match (contents.get(0), contents.get(1))
+ {
+ (
+ Some(AmlSerdeValue::Integer(slp_typa)),
+ Some(AmlSerdeValue::Integer(slp_typb)),
+ ) => match (u8::try_from(*slp_typa), u8::try_from(*slp_typb)) {
+ (Ok(slp_typa_s5), Ok(slp_typb_s5)) => Some((slp_typa_s5, slp_typb_s5)),
+ _ => {
+ log::warn!("\\_S5 values do not fit in u8: {:?}", contents);
+ None
+ }
+ },
+ _ => {
+ log::warn!("\\_S5 package did not contain two integers: {:?}", contents);
+ None
+ }
+ },
+ Ok(value) => {
+ log::warn!("\\_S5 returned unexpected AML value: {:?}", value);
+ None
+ }
+ Err(error) => {
+ log::warn!("Failed to evaluate \\_S5: {:?}", error);
+ None
+ }
+ },
+ Err(error) => {
+ log::warn!("Could not build AmlName for \\_S5: {:?}", error);
+ None
+ }
+ }
+ }
+
+ fn ensure_s5_values(&self) -> Option<(u8, u8)> {
+ if let Some(values) = *self.s5_values.read() {
+ return Some(values);
+ }
+
+ let values = self.evaluate_s5_values()?;
+ *self.s5_values.write() = Some(values);
+ Some(values)
+ }
}
#[repr(C, packed)]
@@ -707,7 +1607,7 @@ unsafe impl plain::Plain for FadtStruct {}
#[repr(C, packed)]
#[derive(Clone, Copy, Debug, Default)]
-pub struct GenericAddressStructure {
+pub struct GenericAddress {
address_space: u8,
bit_width: u8,
bit_offset: u8,
@@ -715,11 +1615,77 @@ pub struct GenericAddressStructure {
address: u64,
}
+impl GenericAddress {
+ pub fn is_empty(&self) -> bool {
+ self.address == 0
+ }
+
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ pub fn write_u8(&self, value: u8) {
+ match self.address_space {
+ 0 => {
+ let raw_address = self.address;
+ let Ok(address) = usize::try_from(raw_address) else {
+ log::error!(
+ "Reset register physical address is invalid: {:#X}",
+ raw_address
+ );
+ return;
+ };
+ let page = address / PAGE_SIZE * PAGE_SIZE;
+ let offset = address % PAGE_SIZE;
+ let virt = unsafe {
+ common::physmap(
+ page,
+ PAGE_SIZE,
+ common::Prot::RW,
+ common::MemoryType::default(),
+ )
+ };
+
+ match virt {
+ Ok(virt) => unsafe {
+ (virt as *mut u8).add(offset).write_volatile(value);
+ let _ = libredox::call::munmap(virt, PAGE_SIZE);
+ },
+ Err(error) => {
+ log::error!("Failed to map ACPI reset register: {}", error);
+ }
+ }
+ }
+ 1 => match u16::try_from(self.address) {
+ Ok(port) => {
+ Pio::<u8>::new(port).write(value);
+ }
+ Err(_) => {
+ let raw_address = self.address;
+ log::error!("Reset register I/O port is invalid: {:#X}", raw_address);
+ }
+ },
+ address_space => {
+ log::warn!(
+ "Unsupported ACPI reset register address space {} for {:?}",
+ address_space,
+ self
+ );
+ }
+ }
+ }
+
+ #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
+ pub fn write_u8(&self, _value: u8) {
+ log::error!(
+ "Cannot access ACPI reset register {:?} on this architecture",
+ self
+ );
+ }
+}
+
#[repr(C, packed)]
#[derive(Clone, Copy, Debug)]
pub struct FadtAcpi2Struct {
// 12 byte structure; see below for details
- pub reset_reg: GenericAddressStructure,
+ pub reset_reg: GenericAddress,
pub reset_value: u8,
reserved3: [u8; 3],
@@ -728,14 +1694,14 @@ pub struct FadtAcpi2Struct {
pub x_firmware_control: u64,
pub x_dsdt: u64,
- pub x_pm1a_event_block: GenericAddressStructure,
- pub x_pm1b_event_block: GenericAddressStructure,
- pub x_pm1a_control_block: GenericAddressStructure,
- pub x_pm1b_control_block: GenericAddressStructure,
- pub x_pm2_control_block: GenericAddressStructure,
- pub x_pm_timer_block: GenericAddressStructure,
- pub x_gpe0_block: GenericAddressStructure,
- pub x_gpe1_block: GenericAddressStructure,
+ pub x_pm1a_event_block: GenericAddress,
+ pub x_pm1b_event_block: GenericAddress,
+ pub x_pm1a_control_block: GenericAddress,
+ pub x_pm1b_control_block: GenericAddress,
+ pub x_pm2_control_block: GenericAddress,
+ pub x_pm_timer_block: GenericAddress,
+ pub x_gpe0_block: GenericAddress,
+ pub x_gpe1_block: GenericAddress,
}
unsafe impl plain::Plain for FadtAcpi2Struct {}
@@ -793,9 +1759,27 @@ impl Fadt {
None => usize::try_from(fadt.dsdt).expect("expected any given u32 to fit within usize"),
};
- log::debug!("FACP at {:X}", { dsdt_ptr });
+ let pm1a_evt_blk = u64::from(fadt.pm1a_event_block);
+ let pm1b_evt_blk = u64::from(fadt.pm1b_event_block);
+ let pm1a_cnt_blk = u64::from(fadt.pm1a_control_block);
+ let pm1b_cnt_blk = u64::from(fadt.pm1b_control_block);
+ let (reset_reg, reset_value) = match fadt.acpi_2_struct() {
+ Some(fadt2) if !fadt2.reset_reg.is_empty() => {
+ (Some(fadt2.reset_reg), fadt2.reset_value)
+ }
+ _ => (None, 0),
+ };
- let dsdt_sdt = match Sdt::load_from_physical(fadt.dsdt as usize) {
+ log::debug!("FACP at {:X}", { dsdt_ptr });
+ log::debug!(
+ "FADT power blocks: PM1a_EVT={:#X}, PM1b_EVT={:#X}, PM1a_CNT={:#X}, PM1b_CNT={:#X}",
+ pm1a_evt_blk,
+ pm1b_evt_blk,
+ pm1a_cnt_blk,
+ pm1b_cnt_blk
+ );
+
+ let dsdt_sdt = match Sdt::load_from_physical(dsdt_ptr) {
Ok(dsdt) => dsdt,
Err(error) => {
log::error!("Failed to load DSDT: {}", error);
@@ -805,6 +1789,10 @@ impl Fadt {
context.fadt = Some(fadt.clone());
context.dsdt = Some(Dsdt(dsdt_sdt.clone()));
+ context.pm1a_cnt_blk = pm1a_cnt_blk;
+ context.pm1b_cnt_blk = pm1b_cnt_blk;
+ context.reset_reg = reset_reg;
+ context.reset_value = reset_value;
context.tables.push(dsdt_sdt);
}
diff --git a/drivers/acpid/src/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/amlserde/Cargo.toml b/drivers/amlserde/Cargo.toml
index ea76f6b6..ae63aea8 100644
--- a/drivers/amlserde/Cargo.toml
+++ b/drivers/amlserde/Cargo.toml
@@ -9,6 +9,6 @@ license = "MIT/Apache-2.0"
edition = "2021"
[dependencies]
-acpi = { git = "https://github.com/jackpot51/acpi.git" }
+acpi = { path = "../acpi" }
serde.workspace = true
toml.workspace = true
diff --git a/drivers/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
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/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 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/src/main.rs b/drivers/usb/xhcid/src/main.rs
index 25b2fdd6..d5dea9b2 100644
--- a/drivers/usb/xhcid/src/main.rs
+++ b/drivers/usb/xhcid/src/main.rs
@@ -49,6 +49,7 @@ use crate::xhci::{InterruptMethod, Xhci};
// mean anything.
pub mod driver_interface;
+mod usb_quirks;
mod usb;
mod xhci;
diff --git a/drivers/usb/xhcid/src/usb/hub.rs b/drivers/usb/xhcid/src/usb/hub.rs
index 9dab55e8..fe6efdd2 100644
--- a/drivers/usb/xhcid/src/usb/hub.rs
+++ b/drivers/usb/xhcid/src/usb/hub.rs
@@ -86,8 +86,10 @@ pub enum HubPortFeature {
PortOverCurrent = 3,
PortReset = 4,
PortLinkState = 5,
+ PortSuspend = 7,
PortPower = 8,
CPortConnection = 16,
+ CPortSuspend = 18,
CPortOverCurrent = 19,
CPortReset = 20,
}
@@ -184,4 +186,11 @@ impl HubPortStatus {
Self::V3(x) => x.contains(HubPortStatusV3::ENABLE),
}
}
+
+ pub fn is_suspended(&self) -> bool {
+ match self {
+ Self::V2(x) => x.contains(HubPortStatusV2::SUSPEND),
+ Self::V3(_) => false,
+ }
+ }
}
diff --git a/drivers/usb/xhcid/src/xhci/device_enumerator.rs b/drivers/usb/xhcid/src/xhci/device_enumerator.rs
index 74b9f732..1f144ac9 100644
--- a/drivers/usb/xhcid/src/xhci/device_enumerator.rs
+++ b/drivers/usb/xhcid/src/xhci/device_enumerator.rs
@@ -54,6 +54,7 @@ impl<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..c53cb59f 100644
--- a/drivers/usb/xhcid/src/xhci/mod.rs
+++ b/drivers/usb/xhcid/src/xhci/mod.rs
@@ -311,6 +311,14 @@ 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,
+}
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub(crate) enum PortPmState {
+ Active,
+ Suspended,
}
impl<const N: usize> PortState<N> {
@@ -809,6 +817,7 @@ impl<const N: usize> Xhci<N> {
);
if flags.contains(port::PortFlags::CCS) {
+ let early_quirks = crate::usb_quirks::lookup_usb_quirks_early(port_id);
let slot_ty = match self.supported_protocol(port_id) {
Some(protocol) => protocol.proto_slot_ty(),
None => {
@@ -838,7 +847,15 @@ impl<const N: usize> Xhci<N> {
debug!("Attempting to address the device");
let mut ring = match self
- .address_device(&mut input, port_id, slot_ty, slot, protocol_speed, speed)
+ .address_device(
+ &mut input,
+ port_id,
+ slot_ty,
+ slot,
+ protocol_speed,
+ speed,
+ early_quirks,
+ )
.await
{
Ok(device_ring) => device_ring,
@@ -866,6 +883,8 @@ impl<const N: usize> Xhci<N> {
},
))
.collect::<BTreeMap<_, _>>(),
+ quirks: early_quirks,
+ pm_state: PortPmState::Active,
};
self.port_states.insert(port_id, port_state);
debug!("Got port states!");
@@ -884,8 +903,14 @@ 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?;
+ let quirks = early_quirks
+ | crate::usb_quirks::lookup_usb_quirks(dev_desc.vendor, dev_desc.product);
debug!("Got the full device descriptor!");
- self.port_states.get_mut(&port_id).unwrap().dev_desc = Some(dev_desc);
+ {
+ let mut port_state = self.port_states.get_mut(&port_id).unwrap();
+ port_state.quirks = quirks;
+ port_state.dev_desc = Some(dev_desc);
+ }
debug!("Got the port states again!");
{
@@ -1052,6 +1077,7 @@ impl<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 +1188,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!(
diff --git a/drivers/usb/xhcid/src/xhci/scheme.rs b/drivers/usb/xhcid/src/xhci/scheme.rs
index f2d439a4..b0fb9b85 100644
--- a/drivers/usb/xhcid/src/xhci/scheme.rs
+++ b/drivers/usb/xhcid/src/xhci/scheme.rs
@@ -24,6 +24,7 @@ use std::{cmp, fmt, io, mem, str};
use common::dma::Dma;
use futures::executor::block_on;
+use futures::FutureExt;
use log::{debug, error, info, trace, warn};
use redox_scheme::scheme::SchemeSync;
use smallvec::SmallVec;
@@ -32,9 +33,9 @@ use common::io::Io;
use redox_scheme::{CallerCtx, OpenResult};
use syscall::schemev2::NewFdFlags;
use syscall::{
- Error, Result, Stat, EACCES, EBADF, EBADFD, EBADMSG, EINVAL, EIO, EISDIR, ENOENT, ENOSYS,
- ENOTDIR, EOPNOTSUPP, EPROTO, ESPIPE, MODE_CHR, MODE_DIR, MODE_FILE, O_DIRECTORY, O_RDWR,
- O_STAT, O_WRONLY, SEEK_CUR, SEEK_END, SEEK_SET,
+ Error, Result, Stat, EACCES, EBADF, EBADFD, EBADMSG, EBUSY, EINVAL, EIO, EISDIR, ENOENT,
+ ENOSYS, ENOTDIR, EOPNOTSUPP, EPROTO, ESPIPE, MODE_CHR, MODE_DIR, MODE_FILE, O_DIRECTORY,
+ O_RDWR, O_STAT, O_WRONLY, SEEK_CUR, SEEK_END, SEEK_SET,
};
use super::{port, usb};
@@ -60,6 +61,10 @@ lazy_static! {
.expect("Failed to create the regex for the port<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$")
@@ -143,6 +148,8 @@ pub enum Handle {
ConfigureEndpoints(PortId), // port
AttachDevice(PortId), // port
DetachDevice(PortId), // port
+ SuspendDevice(PortId), // port
+ ResumeDevice(PortId), // port
SchemeRoot,
}
@@ -187,6 +194,10 @@ enum SchemeParameters {
AttachDevice(PortId), // port number
/// /port<n>/detach
DetachDevice(PortId), // port number
+ /// /port<n>/suspend
+ SuspendDevice(PortId), // port number
+ /// /port<n>/resume
+ ResumeDevice(PortId), // port number
}
impl Handle {
@@ -235,6 +246,12 @@ impl Handle {
Handle::DetachDevice(port_num) => {
format!("port{}/detach", port_num)
}
+ Handle::SuspendDevice(port_num) => {
+ format!("port{}/suspend", port_num)
+ }
+ Handle::ResumeDevice(port_num) => {
+ format!("port{}/resume", port_num)
+ }
Handle::SchemeRoot => String::from(""),
}
}
@@ -262,6 +279,8 @@ impl Handle {
&Handle::ConfigureEndpoints(_) => HandleType::Character,
&Handle::AttachDevice(_) => HandleType::Character,
&Handle::DetachDevice(_) => HandleType::Character,
+ &Handle::SuspendDevice(_) => HandleType::Character,
+ &Handle::ResumeDevice(_) => HandleType::Character,
&Handle::Endpoint(_, _, ref st) => match st {
EndpointHandleTy::Data => HandleType::Character,
EndpointHandleTy::Ctl => HandleType::Character,
@@ -293,6 +312,8 @@ impl Handle {
&Handle::ConfigureEndpoints(_) => None,
&Handle::AttachDevice(_) => None,
&Handle::DetachDevice(_) => None,
+ &Handle::SuspendDevice(_) => None,
+ &Handle::ResumeDevice(_) => None,
&Handle::Endpoint(_, _, ref st) => match st {
EndpointHandleTy::Data => None,
EndpointHandleTy::Ctl => None,
@@ -383,6 +404,14 @@ impl SchemeParameters {
let port_num = get_port_id_from_regex(&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)?;
@@ -564,15 +593,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
},
@@ -628,6 +664,53 @@ 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!");
+ }
+
+ 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 +722,8 @@ impl<const N: usize> Xhci<N> {
where
D: FnMut(&mut Trb, bool) -> ControlFlow,
{
+ self.ensure_port_active(port_num)?;
+
let future = {
let mut port_state = self.port_state_mut(port_num)?;
let slot = port_state.slot;
@@ -690,6 +775,20 @@ impl<const N: usize> Xhci<N> {
handle_transfer_event_trb("CONTROL_TRANSFER", &event_trb, &status_trb)?;
+ let delay_ctrl_msg = self
+ .port_states
+ .get(&port_num)
+ .map(|port_state| {
+ port_state
+ .quirks
+ .contains(crate::usb_quirks::UsbQuirkFlags::DELAY_CTRL_MSG)
+ })
+ .unwrap_or(false);
+
+ if delay_ctrl_msg {
+ std::thread::sleep(std::time::Duration::from_millis(20));
+ }
+
//self.event_handler_finished();
Ok(event_trb)
@@ -709,6 +808,8 @@ impl<const N: usize> Xhci<N> {
where
D: FnMut(&mut Trb, bool) -> ControlFlow,
{
+ self.ensure_port_active(port_num)?;
+
let endp_idx = endp_num.checked_sub(1).ok_or(Error::new(EIO))?;
let mut port_state = self.port_state_mut(port_num)?;
@@ -785,7 +886,29 @@ impl<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
+ );
+ }
+ }
+
+ return Err(err);
+ }
// FIXME: EDTLA if event data was set
if event_trb.completion_code() != TrbCompletionCode::ShortPacket as u8
@@ -861,6 +984,21 @@ impl<const N: usize> Xhci<N> {
handle_event_trb("RESET_ENDPOINT", &event_trb, &command_trb)
}
+ async fn reset_device_slot(&self, port_num: PortId) -> Result<()> {
+ let slot = self
+ .port_states
+ .get(&port_num)
+ .ok_or(Error::new(EBADF))?
+ .slot;
+
+ let (event_trb, command_trb) = self
+ .execute_command(|trb, cycle| {
+ trb.reset_device(slot, cycle);
+ })
+ .await;
+
+ handle_event_trb("RESET_DEVICE", &event_trb, &command_trb)
+ }
fn endp_ctx_interval(speed_id: &ProtocolSpeed, endp_desc: &EndpDesc) -> u8 {
/// Logarithmic (base 2) 125 µs periods per millisecond.
@@ -1205,7 +1343,19 @@ impl<const N: usize> Xhci<N> {
}
// Tell the device about this configuration.
- self.set_configuration(port, configuration_value).await?;
+ let skip_set_configuration = self
+ .port_states
+ .get(&port)
+ .map(|port_state| {
+ port_state
+ .quirks
+ .contains(crate::usb_quirks::UsbQuirkFlags::NO_SET_CONFIG)
+ })
+ .unwrap_or(false);
+
+ if !skip_set_configuration {
+ self.set_configuration(port, configuration_value).await?;
+ }
Ok(())
}
@@ -1234,8 +1384,20 @@ impl<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?;
+ }
}
}
@@ -1453,52 +1615,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 +1727,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 +1785,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 +1804,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 +1831,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 +1851,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 +2126,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\nsuspend\nresume\n").unwrap();
if self.slot_state(
self.port_states
@@ -2087,6 +2357,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
@@ -2173,6 +2467,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 +2503,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 +2567,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 {
@@ -2333,6 +2639,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 +2670,38 @@ impl<const N: usize> Xhci<N> {
self.handles.remove(&fd);
}
+ fn ensure_port_active(&self, port_num: PortId) -> Result<()> {
+ let pm_state = self
+ .port_states
+ .get(&port_num)
+ .ok_or(Error::new(EBADFD))?
+ .pm_state;
+ match pm_state {
+ super::PortPmState::Active => Ok(()),
+ super::PortPmState::Suspended => Err(Error::new(EBUSY)),
+ }
+ }
+
+ pub async fn suspend_device(&self, port_num: PortId) -> Result<()> {
+ let mut port_state = self.port_states.get_mut(&port_num).ok_or(Error::new(EBADFD))?;
+
+ if port_state
+ .quirks
+ .contains(crate::usb_quirks::UsbQuirkFlags::NO_SUSPEND)
+ {
+ return Err(Error::new(EOPNOTSUPP));
+ }
+
+ port_state.pm_state = super::PortPmState::Suspended;
+ Ok(())
+ }
+
+ pub async fn resume_device(&self, port_num: PortId) -> Result<()> {
+ let mut port_state = self.port_states.get_mut(&port_num).ok_or(Error::new(EBADFD))?;
+ port_state.pm_state = super::PortPmState::Active;
+ Ok(())
+ }
+
pub fn get_endp_status(&self, port_num: PortId, endp_num: u8) -> Result<EndpointStatus> {
let port_state = self.port_states.get(&port_num).ok_or(Error::new(EBADFD))?;
@@ -2406,6 +2752,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));
}
@@ -2562,6 +2910,7 @@ impl<const N: usize> Xhci<N> {
},
XhciEndpCtlReq::Reset { no_clear_feature } => match ep_if_state {
EndpIfState::Init => {
+ self.ensure_port_active(port_num)?;
self.on_req_reset_device(port_num, endp_num, !no_clear_feature)
.await?
}
@@ -2571,6 +2920,7 @@ impl<const N: usize> Xhci<N> {
},
XhciEndpCtlReq::Transfer { direction, count } => match ep_if_state {
state @ EndpIfState::Init => {
+ self.ensure_port_active(port_num)?;
if direction == XhciEndpCtlDirection::NoData {
// Yield the result directly because no bytes have to be sent or received
// beforehand.
@@ -2631,6 +2981,8 @@ impl<const N: usize> Xhci<N> {
endp_num: u8,
buf: &[u8],
) -> Result<usize> {
+ self.ensure_port_active(port_num)?;
+
let mut port_state = self
.port_states
.get_mut(&port_num)
@@ -2732,6 +3084,8 @@ impl<const N: usize> Xhci<N> {
endp_num: u8,
buf: &mut [u8],
) -> Result<usize> {
+ self.ensure_port_active(port_num)?;
+
let mut port_state = self
.port_states
.get_mut(&port_num)
diff --git a/init.initfs.d/40_ps2d.service b/init.initfs.d/40_ps2d.service
index 881e75ea..bbee2699 100644
--- a/init.initfs.d/40_ps2d.service
+++ b/init.initfs.d/40_ps2d.service
@@ -5,4 +5,4 @@ condition_architecture = ["x86", "x86_64"]
[service]
cmd = "ps2d"
-type = "notify"
+type = "oneshot_async"
diff --git a/init/src/scheduler.rs b/init/src/scheduler.rs
index d42a4e57..f8ac5cd3 100644
--- a/init/src/scheduler.rs
+++ b/init/src/scheduler.rs
@@ -1,7 +1,8 @@
use std::collections::VecDeque;
-use crate::InitConfig;
+use crate::service::ServiceType;
use crate::unit::{Unit, UnitId, UnitKind, UnitStore};
+use crate::InitConfig;
pub struct Scheduler {
pending: VecDeque<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('$') {