1ae63502c1
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
6429 lines
235 KiB
Diff
6429 lines
235 KiB
Diff
diff --git a/Cargo.lock b/Cargo.lock
|
|
index 91185a1f..01150fb3 100644
|
|
--- a/Cargo.lock
|
|
+++ b/Cargo.lock
|
|
@@ -21,7 +21,6 @@ dependencies = [
|
|
[[package]]
|
|
name = "acpi"
|
|
version = "6.1.1"
|
|
-source = "git+https://github.com/jackpot51/acpi.git#3dc8a2d98a7a164cbf87e7a86855c4d3bed4de75"
|
|
dependencies = [
|
|
"bit_field",
|
|
"bitflags 2.11.0",
|
|
@@ -54,6 +53,7 @@ dependencies = [
|
|
"scheme-utils",
|
|
"serde",
|
|
"thiserror 2.0.18",
|
|
+ "toml 1.0.6+spec-1.1.0",
|
|
]
|
|
|
|
[[package]]
|
|
@@ -86,7 +86,7 @@ version = "0.0.1"
|
|
dependencies = [
|
|
"acpi",
|
|
"serde",
|
|
- "toml",
|
|
+ "toml 1.0.6+spec-1.1.0",
|
|
]
|
|
|
|
[[package]]
|
|
@@ -1109,7 +1109,7 @@ dependencies = [
|
|
"redox_syscall 0.7.3",
|
|
"serde",
|
|
"serde_json",
|
|
- "toml",
|
|
+ "toml 1.0.6+spec-1.1.0",
|
|
]
|
|
|
|
[[package]]
|
|
@@ -1505,9 +1505,10 @@ dependencies = [
|
|
"log",
|
|
"pcid",
|
|
"pico-args",
|
|
+ "redox-driver-sys",
|
|
"redox_syscall 0.7.3",
|
|
"serde",
|
|
- "toml",
|
|
+ "toml 1.0.6+spec-1.1.0",
|
|
]
|
|
|
|
[[package]]
|
|
@@ -1779,6 +1780,20 @@ dependencies = [
|
|
"rand_core 0.3.1",
|
|
]
|
|
|
|
+[[package]]
|
|
+name = "redox-driver-sys"
|
|
+version = "0.1.0"
|
|
+dependencies = [
|
|
+ "bincode",
|
|
+ "bitflags 2.11.0",
|
|
+ "libredox",
|
|
+ "log",
|
|
+ "redox_syscall 0.7.3",
|
|
+ "serde",
|
|
+ "thiserror 2.0.18",
|
|
+ "toml 0.8.23",
|
|
+]
|
|
+
|
|
[[package]]
|
|
name = "redox-initfs"
|
|
version = "0.2.0"
|
|
@@ -2130,6 +2145,15 @@ dependencies = [
|
|
"zmij",
|
|
]
|
|
|
|
+[[package]]
|
|
+name = "serde_spanned"
|
|
+version = "0.6.9"
|
|
+source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
+checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
|
|
+dependencies = [
|
|
+ "serde",
|
|
+]
|
|
+
|
|
[[package]]
|
|
name = "serde_spanned"
|
|
version = "1.0.4"
|
|
@@ -2331,6 +2355,18 @@ dependencies = [
|
|
"syn 2.0.117",
|
|
]
|
|
|
|
+[[package]]
|
|
+name = "toml"
|
|
+version = "0.8.23"
|
|
+source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
+checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
|
|
+dependencies = [
|
|
+ "serde",
|
|
+ "serde_spanned 0.6.9",
|
|
+ "toml_datetime 0.6.11",
|
|
+ "toml_edit",
|
|
+]
|
|
+
|
|
[[package]]
|
|
name = "toml"
|
|
version = "1.0.6+spec-1.1.0"
|
|
@@ -2339,13 +2375,22 @@ checksum = "399b1124a3c9e16766831c6bba21e50192572cdd98706ea114f9502509686ffc"
|
|
dependencies = [
|
|
"indexmap",
|
|
"serde_core",
|
|
- "serde_spanned",
|
|
- "toml_datetime",
|
|
+ "serde_spanned 1.0.4",
|
|
+ "toml_datetime 1.0.0+spec-1.1.0",
|
|
"toml_parser",
|
|
"toml_writer",
|
|
"winnow",
|
|
]
|
|
|
|
+[[package]]
|
|
+name = "toml_datetime"
|
|
+version = "0.6.11"
|
|
+source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
+checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
|
|
+dependencies = [
|
|
+ "serde",
|
|
+]
|
|
+
|
|
[[package]]
|
|
name = "toml_datetime"
|
|
version = "1.0.0+spec-1.1.0"
|
|
@@ -2355,6 +2400,20 @@ dependencies = [
|
|
"serde_core",
|
|
]
|
|
|
|
+[[package]]
|
|
+name = "toml_edit"
|
|
+version = "0.22.27"
|
|
+source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
+checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
|
+dependencies = [
|
|
+ "indexmap",
|
|
+ "serde",
|
|
+ "serde_spanned 0.6.9",
|
|
+ "toml_datetime 0.6.11",
|
|
+ "toml_write",
|
|
+ "winnow",
|
|
+]
|
|
+
|
|
[[package]]
|
|
name = "toml_parser"
|
|
version = "1.0.9+spec-1.1.0"
|
|
@@ -2364,6 +2423,12 @@ dependencies = [
|
|
"winnow",
|
|
]
|
|
|
|
+[[package]]
|
|
+name = "toml_write"
|
|
+version = "0.1.2"
|
|
+source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
+checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
|
+
|
|
[[package]]
|
|
name = "toml_writer"
|
|
version = "1.0.6+spec-1.1.0"
|
|
@@ -2438,6 +2503,7 @@ name = "usbscsid"
|
|
version = "0.1.0"
|
|
dependencies = [
|
|
"base64 0.11.0",
|
|
+ "bitflags 2.11.0",
|
|
"daemon",
|
|
"driver-block",
|
|
"libredox",
|
|
@@ -2445,6 +2511,7 @@ dependencies = [
|
|
"redox_event",
|
|
"redox_syscall 0.7.3",
|
|
"thiserror 2.0.18",
|
|
+ "toml 1.0.6+spec-1.1.0",
|
|
"xhcid",
|
|
]
|
|
|
|
@@ -2801,6 +2868,9 @@ name = "winnow"
|
|
version = "0.7.15"
|
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
|
|
+dependencies = [
|
|
+ "memchr 2.8.0",
|
|
+]
|
|
|
|
[[package]]
|
|
name = "wit-bindgen"
|
|
@@ -2913,7 +2983,7 @@ dependencies = [
|
|
"serde_json",
|
|
"smallvec 1.15.1",
|
|
"thiserror 2.0.18",
|
|
- "toml",
|
|
+ "toml 1.0.6+spec-1.1.0",
|
|
]
|
|
|
|
[[package]]
|
|
diff --git a/drivers/acpid/Cargo.toml b/drivers/acpid/Cargo.toml
|
|
index 2d22a8f9..fea105c8 100644
|
|
--- a/drivers/acpid/Cargo.toml
|
|
+++ b/drivers/acpid/Cargo.toml
|
|
@@ -8,7 +8,7 @@ edition = "2018"
|
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
|
|
[dependencies]
|
|
-acpi = { git = "https://github.com/jackpot51/acpi.git" }
|
|
+acpi = { path = "../acpi" }
|
|
arrayvec = "0.7.6"
|
|
log.workspace = true
|
|
num-derive = "0.3"
|
|
@@ -21,6 +21,7 @@ rustc-hash = "1.1.0"
|
|
thiserror.workspace = true
|
|
ron.workspace = true
|
|
serde.workspace = true
|
|
+toml.workspace = true
|
|
|
|
amlserde = { path = "../amlserde" }
|
|
common = { path = "../common" }
|
|
diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs
|
|
index 94a1eb17..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/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/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(®EX_PORT_DETACH, scheme, 0)?;
|
|
|
|
Ok(Self::DetachDevice(port_num))
|
|
+ } else if REGEX_PORT_SUSPEND.is_match(scheme) {
|
|
+ let port_num = get_port_id_from_regex(®EX_PORT_SUSPEND, scheme, 0)?;
|
|
+
|
|
+ Ok(Self::SuspendDevice(port_num))
|
|
+ } else if REGEX_PORT_RESUME.is_match(scheme) {
|
|
+ let port_num = get_port_id_from_regex(®EX_PORT_RESUME, scheme, 0)?;
|
|
+
|
|
+ Ok(Self::ResumeDevice(port_num))
|
|
} else if REGEX_PORT_DESCRIPTORS.is_match(scheme) {
|
|
let port_num = get_port_id_from_regex(®EX_PORT_DESCRIPTORS, scheme, 0)?;
|
|
|
|
@@ -564,15 +593,22 @@ impl<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/drivers/usb/xhcid/src/xhci/mod.rs b/drivers/usb/xhcid/src/xhci/mod.rs
|
|
index f2143676..74126f67 100644
|
|
--- a/drivers/usb/xhcid/src/xhci/mod.rs
|
|
+++ b/drivers/usb/xhcid/src/xhci/mod.rs
|
|
@@ -311,6 +311,22 @@ struct PortState<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 PortPmState {
|
|
+ pub fn as_str(&self) -> &'static str {
|
|
+ match self {
|
|
+ Self::Active => "active",
|
|
+ Self::Suspended => "suspended",
|
|
+ }
|
|
+ }
|
|
}
|
|
|
|
impl<const N: usize> PortState<N> {
|
|
@@ -809,6 +825,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 +855,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 +891,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 +911,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 +1085,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 +1196,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..dfe9fdec 100644
|
|
--- a/drivers/usb/xhcid/src/xhci/scheme.rs
|
|
+++ b/drivers/usb/xhcid/src/xhci/scheme.rs
|
|
@@ -24,6 +24,7 @@ use std::{cmp, fmt, io, mem, str};
|
|
|
|
use common::dma::Dma;
|
|
use futures::executor::block_on;
|
|
+use futures::FutureExt;
|
|
use log::{debug, error, info, trace, warn};
|
|
use redox_scheme::scheme::SchemeSync;
|
|
use smallvec::SmallVec;
|
|
@@ -32,9 +33,9 @@ use common::io::Io;
|
|
use redox_scheme::{CallerCtx, OpenResult};
|
|
use syscall::schemev2::NewFdFlags;
|
|
use syscall::{
|
|
- Error, Result, Stat, EACCES, EBADF, EBADFD, EBADMSG, EINVAL, EIO, EISDIR, ENOENT, ENOSYS,
|
|
- ENOTDIR, EOPNOTSUPP, EPROTO, ESPIPE, MODE_CHR, MODE_DIR, MODE_FILE, O_DIRECTORY, O_RDWR,
|
|
- O_STAT, O_WRONLY, SEEK_CUR, SEEK_END, SEEK_SET,
|
|
+ Error, Result, Stat, EACCES, EBADF, EBADFD, EBADMSG, EBUSY, EINVAL, EIO, EISDIR, ENOENT,
|
|
+ ENOSYS, ENOTDIR, EOPNOTSUPP, EPROTO, ESPIPE, MODE_CHR, MODE_DIR, MODE_FILE, O_DIRECTORY,
|
|
+ O_RDWR, O_STAT, O_WRONLY, SEEK_CUR, SEEK_END, SEEK_SET,
|
|
};
|
|
|
|
use super::{port, usb};
|
|
@@ -60,10 +61,16 @@ lazy_static! {
|
|
.expect("Failed to create the regex for the port<n>/attach scheme.");
|
|
static ref REGEX_PORT_DETACH: Regex = Regex::new(r"^port([\d\.]+)/detach$")
|
|
.expect("Failed to create the regex for the port<n>/detach scheme.");
|
|
+ static ref REGEX_PORT_SUSPEND: Regex = Regex::new(r"^port([\d\.]+)/suspend$")
|
|
+ .expect("Failed to create the regex for the port<n>/suspend scheme.");
|
|
+ static ref REGEX_PORT_RESUME: Regex = Regex::new(r"^port([\d\.]+)/resume$")
|
|
+ .expect("Failed to create the regex for the port<n>/resume scheme.");
|
|
static ref REGEX_PORT_DESCRIPTORS: Regex = Regex::new(r"^port([\d\.]+)/descriptors$")
|
|
.expect("Failed to create the regex for the port<n>/descriptors");
|
|
static ref REGEX_PORT_STATE: Regex = Regex::new(r"^port([\d\.]+)/state$")
|
|
.expect("Failed to create the regex for the port<n>/state scheme");
|
|
+ static ref REGEX_PORT_PM_STATE: Regex = Regex::new(r"^port([\d\.]+)/pm_state$")
|
|
+ .expect("Failed to create the regex for the port<n>/pm_state scheme");
|
|
static ref REGEX_PORT_REQUEST: Regex = Regex::new(r"^port([\d\.]+)/request$")
|
|
.expect("Failed to create the regex for the port<n>/request scheme");
|
|
static ref REGEX_PORT_ENDPOINTS: Regex = Regex::new(r"^port([\d\.]+)/endpoints$")
|
|
@@ -137,12 +144,15 @@ pub enum Handle {
|
|
Port(PortId, Vec<u8>), // port, contents
|
|
PortDesc(PortId, Vec<u8>), // port, contents
|
|
PortState(PortId), // port
|
|
+ PortPmState(PortId), // port
|
|
PortReq(PortId, PortReqState), // port, state
|
|
Endpoints(PortId, Vec<u8>), // port, contents
|
|
Endpoint(PortId, u8, EndpointHandleTy), // port, endpoint, state
|
|
ConfigureEndpoints(PortId), // port
|
|
AttachDevice(PortId), // port
|
|
DetachDevice(PortId), // port
|
|
+ SuspendDevice(PortId), // port
|
|
+ ResumeDevice(PortId), // port
|
|
SchemeRoot,
|
|
}
|
|
|
|
@@ -172,6 +182,8 @@ enum SchemeParameters {
|
|
PortDesc(PortId), // port number
|
|
/// /port<n>/state
|
|
PortState(PortId), // port number
|
|
+ /// /port<n>/pm_state
|
|
+ PortPmState(PortId), // port number
|
|
/// /port<n>/request
|
|
PortReq(PortId), // port number
|
|
/// /port<n>/endpoints
|
|
@@ -187,6 +199,10 @@ enum SchemeParameters {
|
|
AttachDevice(PortId), // port number
|
|
/// /port<n>/detach
|
|
DetachDevice(PortId), // port number
|
|
+ /// /port<n>/suspend
|
|
+ SuspendDevice(PortId), // port number
|
|
+ /// /port<n>/resume
|
|
+ ResumeDevice(PortId), // port number
|
|
}
|
|
|
|
impl Handle {
|
|
@@ -209,6 +225,9 @@ impl Handle {
|
|
Handle::PortState(port_num) => {
|
|
format!("port{}/state", port_num)
|
|
}
|
|
+ Handle::PortPmState(port_num) => {
|
|
+ format!("port{}/pm_state", port_num)
|
|
+ }
|
|
Handle::PortReq(port_num, _) => {
|
|
format!("port{}/request", port_num)
|
|
}
|
|
@@ -235,6 +254,12 @@ impl Handle {
|
|
Handle::DetachDevice(port_num) => {
|
|
format!("port{}/detach", port_num)
|
|
}
|
|
+ Handle::SuspendDevice(port_num) => {
|
|
+ format!("port{}/suspend", port_num)
|
|
+ }
|
|
+ Handle::ResumeDevice(port_num) => {
|
|
+ format!("port{}/resume", port_num)
|
|
+ }
|
|
Handle::SchemeRoot => String::from(""),
|
|
}
|
|
}
|
|
@@ -258,10 +283,13 @@ impl Handle {
|
|
&Handle::PortReq(_, PortReqState::Tmp) => unreachable!(),
|
|
&Handle::PortReq(_, PortReqState::TmpSetup(_)) => unreachable!(),
|
|
&Handle::PortState(_) => HandleType::Character,
|
|
+ &Handle::PortPmState(_) => HandleType::Character,
|
|
&Handle::PortReq(_, _) => HandleType::Character,
|
|
&Handle::ConfigureEndpoints(_) => HandleType::Character,
|
|
&Handle::AttachDevice(_) => HandleType::Character,
|
|
&Handle::DetachDevice(_) => HandleType::Character,
|
|
+ &Handle::SuspendDevice(_) => HandleType::Character,
|
|
+ &Handle::ResumeDevice(_) => HandleType::Character,
|
|
&Handle::Endpoint(_, _, ref st) => match st {
|
|
EndpointHandleTy::Data => HandleType::Character,
|
|
EndpointHandleTy::Ctl => HandleType::Character,
|
|
@@ -289,10 +317,13 @@ impl Handle {
|
|
&Handle::PortReq(_, PortReqState::Tmp) => None,
|
|
&Handle::PortReq(_, PortReqState::TmpSetup(_)) => None,
|
|
&Handle::PortState(_) => None,
|
|
+ &Handle::PortPmState(_) => None,
|
|
&Handle::PortReq(_, _) => None,
|
|
&Handle::ConfigureEndpoints(_) => None,
|
|
&Handle::AttachDevice(_) => None,
|
|
&Handle::DetachDevice(_) => None,
|
|
+ &Handle::SuspendDevice(_) => None,
|
|
+ &Handle::ResumeDevice(_) => None,
|
|
&Handle::Endpoint(_, _, ref st) => match st {
|
|
EndpointHandleTy::Data => None,
|
|
EndpointHandleTy::Ctl => None,
|
|
@@ -383,6 +414,14 @@ impl SchemeParameters {
|
|
let port_num = get_port_id_from_regex(®EX_PORT_DETACH, scheme, 0)?;
|
|
|
|
Ok(Self::DetachDevice(port_num))
|
|
+ } else if REGEX_PORT_SUSPEND.is_match(scheme) {
|
|
+ let port_num = get_port_id_from_regex(®EX_PORT_SUSPEND, scheme, 0)?;
|
|
+
|
|
+ Ok(Self::SuspendDevice(port_num))
|
|
+ } else if REGEX_PORT_RESUME.is_match(scheme) {
|
|
+ let port_num = get_port_id_from_regex(®EX_PORT_RESUME, scheme, 0)?;
|
|
+
|
|
+ Ok(Self::ResumeDevice(port_num))
|
|
} else if REGEX_PORT_DESCRIPTORS.is_match(scheme) {
|
|
let port_num = get_port_id_from_regex(®EX_PORT_DESCRIPTORS, scheme, 0)?;
|
|
|
|
@@ -391,6 +430,10 @@ impl SchemeParameters {
|
|
let port_num = get_port_id_from_regex(®EX_PORT_STATE, scheme, 0)?;
|
|
|
|
Ok(Self::PortState(port_num))
|
|
+ } else if REGEX_PORT_PM_STATE.is_match(scheme) {
|
|
+ let port_num = get_port_id_from_regex(®EX_PORT_PM_STATE, scheme, 0)?;
|
|
+
|
|
+ Ok(Self::PortPmState(port_num))
|
|
} else if REGEX_PORT_REQUEST.is_match(scheme) {
|
|
let port_num = get_port_id_from_regex(®EX_PORT_REQUEST, scheme, 0)?;
|
|
|
|
@@ -564,15 +607,22 @@ impl<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 +678,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 +736,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 +789,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 +822,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 +900,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 +998,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 +1357,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 +1398,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 +1629,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 +1741,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 +1799,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 +1818,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 +1845,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 +1865,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 +2140,7 @@ impl<const N: usize> Xhci<N> {
|
|
if (flags & O_DIRECTORY != 0) || (flags & O_STAT != 0) {
|
|
let mut contents = Vec::new();
|
|
|
|
- write!(contents, "descriptors\nendpoints\n").unwrap();
|
|
+ write!(contents, "descriptors\nendpoints\npm_state\nsuspend\nresume\n").unwrap();
|
|
|
|
if self.slot_state(
|
|
self.port_states
|
|
@@ -1893,6 +2177,14 @@ impl<const N: usize> Xhci<N> {
|
|
Ok(Handle::PortState(port_num))
|
|
}
|
|
|
|
+ fn open_handle_port_pm_state(&self, port_num: PortId, flags: usize) -> Result<Handle> {
|
|
+ if flags & O_DIRECTORY != 0 && flags & O_STAT == 0 {
|
|
+ return Err(Error::new(ENOTDIR));
|
|
+ }
|
|
+
|
|
+ Ok(Handle::PortPmState(port_num))
|
|
+ }
|
|
+
|
|
/// implements open() for /port<n>/endpoints
|
|
///
|
|
/// # Arguments
|
|
@@ -2087,6 +2379,30 @@ impl<const N: usize> Xhci<N> {
|
|
Ok(Handle::DetachDevice(port_num))
|
|
}
|
|
|
|
+ fn open_handle_suspend_device(&self, port_num: PortId, flags: usize) -> Result<Handle> {
|
|
+ if flags & O_DIRECTORY != 0 && flags & O_STAT == 0 {
|
|
+ return Err(Error::new(ENOTDIR));
|
|
+ }
|
|
+
|
|
+ if flags & O_RDWR != O_WRONLY && flags & O_STAT == 0 {
|
|
+ return Err(Error::new(EACCES));
|
|
+ }
|
|
+
|
|
+ Ok(Handle::SuspendDevice(port_num))
|
|
+ }
|
|
+
|
|
+ fn open_handle_resume_device(&self, port_num: PortId, flags: usize) -> Result<Handle> {
|
|
+ if flags & O_DIRECTORY != 0 && flags & O_STAT == 0 {
|
|
+ return Err(Error::new(ENOTDIR));
|
|
+ }
|
|
+
|
|
+ if flags & O_RDWR != O_WRONLY && flags & O_STAT == 0 {
|
|
+ return Err(Error::new(EACCES));
|
|
+ }
|
|
+
|
|
+ Ok(Handle::ResumeDevice(port_num))
|
|
+ }
|
|
+
|
|
/// implements open() for /port<n>/request
|
|
///
|
|
/// # Arguments
|
|
@@ -2155,6 +2471,9 @@ impl<const N: usize> SchemeSync for &Xhci<N> {
|
|
SchemeParameters::PortState(port_number) => {
|
|
self.open_handle_port_state(port_number, flags)?
|
|
}
|
|
+ SchemeParameters::PortPmState(port_number) => {
|
|
+ self.open_handle_port_pm_state(port_number, flags)?
|
|
+ }
|
|
SchemeParameters::PortReq(port_number) => {
|
|
self.open_handle_port_request(port_number, flags)?
|
|
}
|
|
@@ -2173,6 +2492,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 +2528,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 +2592,8 @@ impl<const N: usize> SchemeSync for &Xhci<N> {
|
|
Handle::ConfigureEndpoints(_) => Err(Error::new(EBADF)),
|
|
Handle::AttachDevice(_) => Err(Error::new(EBADF)),
|
|
Handle::DetachDevice(_) => Err(Error::new(EBADF)),
|
|
+ Handle::SuspendDevice(_) => Err(Error::new(EBADF)),
|
|
+ Handle::ResumeDevice(_) => Err(Error::new(EBADF)),
|
|
Handle::SchemeRoot => Err(Error::new(EBADF)),
|
|
|
|
&mut Handle::Endpoint(port_num, endp_num, ref mut st) => match st {
|
|
@@ -2294,6 +2625,10 @@ impl<const N: usize> SchemeSync for &Xhci<N> {
|
|
|
|
Ok(Xhci::<N>::write_dyn_string(string, buf, offset))
|
|
}
|
|
+ &mut Handle::PortPmState(port_num) => {
|
|
+ let ps = self.port_states.get(&port_num).ok_or(Error::new(EBADF))?;
|
|
+ Ok(Xhci::<N>::write_dyn_string(ps.pm_state.as_str().as_bytes(), buf, offset))
|
|
+ }
|
|
&mut Handle::PortReq(port_num, ref mut st) => {
|
|
let state = std::mem::replace(st, PortReqState::Tmp);
|
|
drop(guard); // release the lock
|
|
@@ -2333,6 +2668,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 +2699,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 +2781,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 +2939,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 +2949,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 +3010,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 +3113,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)
|