Files
RedBear-OS/local/patches/base/redox.patch
T

8520 lines
312 KiB
Diff

diff --git a/Cargo.lock b/Cargo.lock
index 9934cd8f..b3923c52 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -54,6 +54,7 @@ dependencies = [
"scheme-utils",
"serde",
"thiserror 2.0.18",
+ "toml",
]
[[package]]
diff --git a/daemon/src/lib.rs b/daemon/src/lib.rs
index 9f507221..f74fe715 100644
--- a/daemon/src/lib.rs
+++ b/daemon/src/lib.rs
@@ -57,25 +57,28 @@ impl Daemon {
/// Executes `Command` as a child process.
// FIXME remove once the service spawning of hwd and pcid-spawner is moved to init
#[deprecated]
- pub fn spawn(mut cmd: Command) {
+ pub fn spawn(mut cmd: Command) -> io::Result<()> {
let (mut read_pipe, write_pipe) = io::pipe().unwrap();
unsafe { pass_fd(&mut cmd, "INIT_NOTIFY", write_pipe.into()) };
- if let Err(err) = cmd.spawn() {
- eprintln!("daemon: failed to execute {cmd:?}: {err}");
- return;
- }
+ cmd.spawn().map_err(|err| {
+ io::Error::new(err.kind(), format!("failed to execute {cmd:?}: {err}"))
+ })?;
let mut data = [0];
match read_pipe.read_exact(&mut data) {
- Ok(()) => {}
+ Ok(()) => Ok(()),
Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => {
- eprintln!("daemon: {cmd:?} exited without notifying readiness");
- }
- Err(err) => {
- eprintln!("daemon: failed to wait for {cmd:?}: {err}");
+ Err(io::Error::new(
+ io::ErrorKind::UnexpectedEof,
+ format!("{cmd:?} exited without notifying readiness"),
+ ))
}
+ Err(err) => Err(io::Error::new(
+ err.kind(),
+ format!("failed to wait for {cmd:?}: {err}"),
+ )),
}
}
}
diff --git a/drivers/acpid/Cargo.toml b/drivers/acpid/Cargo.toml
index 2d22a8f9..f03a4ccb 100644
--- a/drivers/acpid/Cargo.toml
+++ b/drivers/acpid/Cargo.toml
@@ -21,6 +21,7 @@ rustc-hash = "1.1.0"
thiserror.workspace = true
ron.workspace = true
serde.workspace = true
+toml.workspace = true
amlserde = { path = "../amlserde" }
common = { path = "../common" }
diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs
index 94a1eb17..dad44d1d 100644
--- a/drivers/acpid/src/acpi.rs
+++ b/drivers/acpid/src/acpi.rs
@@ -1,13 +1,15 @@
use acpi::aml::object::{Object, WrappedObject};
-use acpi::aml::op_region::{RegionHandler, RegionSpace};
use rustc_hash::FxHashMap;
+use std::any::Any;
use std::convert::{TryFrom, TryInto};
use std::error::Error;
use std::ops::Deref;
+use std::panic::{catch_unwind, AssertUnwindSafe};
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};
@@ -16,16 +18,17 @@ use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use thiserror::Error;
use acpi::{
- aml::{namespace::AmlName, AmlError, Interpreter},
+ aml::{namespace::AmlName, op_region::RegionSpace, AmlError, Interpreter},
platform::AcpiPlatform,
AcpiTables,
};
use amlserde::aml_serde_name::aml_to_symbol;
use amlserde::{AmlSerde, AmlSerdeValue};
-#[cfg(target_arch = "x86_64")]
-pub mod dmar;
use crate::aml_physmem::{AmlPageCache, AmlPhysMemHandler};
+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+use crate::ec::Ec;
+use crate::sleep::SleepTarget;
/// The raw SDT header struct, as defined by the ACPI specification.
#[derive(Copy, Clone, Debug)]
@@ -206,6 +209,615 @@ 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::{
+ compute_battery_percentage, fill_bif_fields, fill_bix_fields, parse_acpi_signature,
+ parse_acpi_table_quirks, parse_sleep_package, parse_bst_package, smbios_string,
+ AcpiBattery, AcpiTableMatchRule, AmlSerdeValue, DmiInfo, SleepStateValuesError,
+ };
+ use crate::sleep::SleepTarget;
+ use std::iter::FromIterator;
+ use toml::Value;
+
+ #[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);
+ }
+
+ #[test]
+ fn parse_sleep_package_accepts_two_integers() {
+ let package = AmlSerdeValue::Package {
+ contents: vec![AmlSerdeValue::Integer(3), AmlSerdeValue::Integer(5)],
+ };
+
+ assert_eq!(parse_sleep_package(SleepTarget::S5, package).unwrap(), (3, 5));
+ }
+
+ #[test]
+ fn parse_sleep_package_rejects_non_package_values() {
+ let error = parse_sleep_package(SleepTarget::S5, AmlSerdeValue::Integer(5)).unwrap_err();
+ assert!(matches!(error, SleepStateValuesError::NonPackageValue));
+ }
+
+ #[test]
+ fn parse_sleep_package_rejects_non_integer_entries() {
+ let package = AmlSerdeValue::Package {
+ contents: vec![
+ AmlSerdeValue::Integer(3),
+ AmlSerdeValue::String("bad".to_string()),
+ ],
+ };
+
+ let error = parse_sleep_package(SleepTarget::S5, package).unwrap_err();
+ assert!(matches!(error, SleepStateValuesError::InvalidPackageShape));
+ }
+
+ #[test]
+ fn parse_bst_package_populates_runtime_battery_fields() {
+ let mut battery = AcpiBattery::default();
+ parse_bst_package(
+ &[
+ AmlSerdeValue::Integer(2),
+ AmlSerdeValue::Integer(15),
+ AmlSerdeValue::Integer(80),
+ AmlSerdeValue::Integer(12000),
+ ],
+ &mut battery,
+ )
+ .unwrap();
+
+ assert_eq!(battery.state, 2);
+ assert_eq!(battery.present_rate, Some(15));
+ assert_eq!(battery.remaining_capacity, Some(80));
+ assert_eq!(battery.present_voltage, Some(12000));
+ }
+
+ #[test]
+ fn bif_and_bix_metadata_fill_percentage_inputs() {
+ let mut bif_battery = AcpiBattery::default();
+ fill_bif_fields(
+ &[
+ AmlSerdeValue::Integer(0),
+ AmlSerdeValue::Integer(100),
+ AmlSerdeValue::Integer(90),
+ AmlSerdeValue::Integer(1),
+ AmlSerdeValue::Integer(12000),
+ AmlSerdeValue::Integer(0),
+ AmlSerdeValue::Integer(0),
+ AmlSerdeValue::Integer(0),
+ AmlSerdeValue::Integer(0),
+ AmlSerdeValue::String("Li-ion".to_string()),
+ AmlSerdeValue::String("Red Bear".to_string()),
+ AmlSerdeValue::String("RB-1".to_string()),
+ AmlSerdeValue::String("123".to_string()),
+ ],
+ &mut bif_battery,
+ )
+ .unwrap();
+ bif_battery.remaining_capacity = Some(45);
+ assert_eq!(compute_battery_percentage(&bif_battery), Some(50.0));
+
+ let mut bix_battery = AcpiBattery::default();
+ fill_bix_fields(
+ &[
+ AmlSerdeValue::Integer(0),
+ AmlSerdeValue::Integer(100),
+ AmlSerdeValue::Integer(90),
+ AmlSerdeValue::Integer(1),
+ AmlSerdeValue::Integer(0),
+ AmlSerdeValue::Integer(12000),
+ AmlSerdeValue::Integer(0),
+ AmlSerdeValue::Integer(0),
+ AmlSerdeValue::Integer(0),
+ AmlSerdeValue::Integer(0),
+ AmlSerdeValue::Integer(0),
+ AmlSerdeValue::Integer(0),
+ AmlSerdeValue::Integer(0),
+ AmlSerdeValue::String("RB-2".to_string()),
+ AmlSerdeValue::String("456".to_string()),
+ AmlSerdeValue::String("Li-ion".to_string()),
+ AmlSerdeValue::String("Red Bear".to_string()),
+ ],
+ &mut bix_battery,
+ )
+ .unwrap();
+ bix_battery.remaining_capacity = Some(45);
+ assert_eq!(compute_battery_percentage(&bix_battery), Some(50.0));
+ }
+
+ #[test]
+ fn parse_acpi_signature_requires_exactly_four_bytes() {
+ assert_eq!(parse_acpi_signature("DSDT"), Some(*b"DSDT"));
+ assert_eq!(parse_acpi_signature("SSDTX"), None);
+ assert_eq!(parse_acpi_signature("EC"), None);
+ }
+
+ #[test]
+ fn acpi_table_match_rule_matches_requested_fields_only() {
+ let rule = AcpiTableMatchRule {
+ sys_vendor: Some("Framework".to_string()),
+ product_name: Some("Laptop 16".to_string()),
+ ..AcpiTableMatchRule::default()
+ };
+ 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 mismatch = DmiInfo {
+ product_name: Some("Laptop 13".to_string()),
+ ..info.clone()
+ };
+
+ assert!(rule.matches(&info));
+ assert!(!rule.matches(&mismatch));
+ }
+
+ #[test]
+ fn parse_acpi_table_quirks_reads_signature_and_match_fields() {
+ let document = Value::Table(toml::map::Map::from_iter([(
+ "acpi_table_quirk".to_string(),
+ Value::Array(vec![Value::Table(toml::map::Map::from_iter([
+ ("signature".to_string(), Value::String("SSDT".to_string())),
+ (
+ "match".to_string(),
+ Value::Table(toml::map::Map::from_iter([
+ (
+ "sys_vendor".to_string(),
+ Value::String("Framework".to_string()),
+ ),
+ (
+ "product_name".to_string(),
+ Value::String("Laptop 16".to_string()),
+ ),
+ ])),
+ ),
+ ]))]),
+ )]));
+
+ let rules = parse_acpi_table_quirks(&document, "test.toml");
+ assert_eq!(rules.len(), 1);
+ assert_eq!(rules[0].signature, *b"SSDT");
+ assert!(rules[0].dmi_match.matches(&DmiInfo {
+ sys_vendor: Some("Framework".to_string()),
+ product_name: Some("Laptop 16".to_string()),
+ ..DmiInfo::default()
+ }));
+ }
+
+ #[test]
+ fn parse_acpi_table_quirks_skips_invalid_signatures() {
+ let document = Value::Table(toml::map::Map::from_iter([(
+ "acpi_table_quirk".to_string(),
+ Value::Array(vec![Value::Table(toml::map::Map::from_iter([(
+ "signature".to_string(),
+ Value::String("BAD!!".to_string()),
+ )]))]),
+ )]));
+
+ let rules = parse_acpi_table_quirks(&document, "bad.toml");
+ assert!(rules.is_empty());
+ }
+
+ // 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 +856,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 +871,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,9 +882,8 @@ 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(..) {
- interpreter.install_region_handler(region, handler);
- }
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ interpreter.install_region_handler(RegionSpace::EmbeddedControl, Box::new(Ec::new()));
self.aml_context = Some(interpreter);
Ok(())
}
@@ -316,7 +928,7 @@ impl AmlSymbols {
.namespace
.lock()
.traverse(|level_aml_name, level| {
- for (child_seg, handle) in level.values.iter() {
+ for (child_seg, _handle) in level.values.iter() {
if let Ok(aml_name) =
AmlName::from_name_seg(child_seg.to_owned()).resolve(level_aml_name)
{
@@ -343,7 +955,18 @@ impl AmlSymbols {
for (aml_name, name) in &symbol_list {
// create an empty entry, in case something goes wrong with serialization
symbol_cache.insert(name.to_owned(), "".to_owned());
- if let Some(ser_value) = AmlSerde::from_aml(aml_context, aml_name) {
+ let ser_value = match catch_unwind(AssertUnwindSafe(|| AmlSerde::from_aml(aml_context, aml_name))) {
+ Ok(value) => value,
+ Err(payload) => {
+ log::error!(
+ "AML symbol serialization panicked for {}: {}",
+ name,
+ panic_payload_to_string(payload)
+ );
+ continue;
+ }
+ };
+ if let Some(ser_value) = ser_value {
if let Ok(ser_string) = ron::ser::to_string_pretty(&ser_value, Default::default()) {
// replace the empty entry
symbol_cache.insert(name.to_owned(), ser_string);
@@ -368,6 +991,10 @@ pub enum AmlEvalError {
DeserializationError,
#[error("AML not initialized")]
NotInitialized,
+ #[error("AML host fault: {0}")]
+ HostFault(String),
+ #[error("{0}")]
+ Unsupported(&'static str),
}
impl From<AmlError> for AmlEvalError {
fn from(value: AmlError) -> Self {
@@ -375,10 +1002,169 @@ impl From<AmlError> for AmlEvalError {
}
}
+fn panic_payload_to_string(payload: Box<dyn Any + Send>) -> String {
+ if let Some(message) = payload.downcast_ref::<&'static str>() {
+ (*message).to_string()
+ } else if let Some(message) = payload.downcast_ref::<String>() {
+ message.clone()
+ } else {
+ "non-string panic payload".to_string()
+ }
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct AcpiPowerAdapter {
+ pub id: String,
+ pub path: String,
+ pub online: bool,
+}
+
+#[derive(Clone, Debug, Default)]
+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 {
+ !self.adapters.is_empty() && self.adapters.iter().all(|adapter| !adapter.online)
+ }
+}
+
+fn symbol_parent_path(symbol: &str, suffix: &str) -> Option<String> {
+ symbol
+ .strip_suffix(suffix)
+ .map(str::to_string)
+ .filter(|path| !path.is_empty())
+}
+
+fn symbol_leaf_id(path: &str) -> String {
+ path.rsplit('.').next().unwrap_or(path).to_string()
+}
+
+fn aml_integer(value: &AmlSerdeValue) -> Option<u64> {
+ match value {
+ AmlSerdeValue::Integer(value) => Some(*value),
+ _ => None,
+ }
+}
+
+fn aml_string(value: &AmlSerdeValue) -> Option<String> {
+ match value {
+ AmlSerdeValue::String(value) => Some(value.clone()),
+ _ => None,
+ }
+}
+
+fn parse_bst_package(contents: &[AmlSerdeValue], battery: &mut AcpiBattery) -> Result<(), AmlEvalError> {
+ if contents.len() < 4 {
+ return Err(AmlEvalError::DeserializationError);
+ }
+
+ battery.state = aml_integer(&contents[0]).ok_or(AmlEvalError::DeserializationError)?;
+ battery.present_rate = aml_integer(&contents[1]);
+ battery.remaining_capacity = aml_integer(&contents[2]);
+ battery.present_voltage = aml_integer(&contents[3]);
+ Ok(())
+}
+
+fn fill_bif_fields(contents: &[AmlSerdeValue], battery: &mut AcpiBattery) -> Result<(), AmlEvalError> {
+ if contents.len() < 13 {
+ return Err(AmlEvalError::DeserializationError);
+ }
+
+ battery.power_unit = Some(
+ match aml_integer(&contents[0]).ok_or(AmlEvalError::DeserializationError)? {
+ 0 => "mWh",
+ 1 => "mAh",
+ _ => "unknown",
+ }
+ .to_string(),
+ );
+ battery.design_capacity = aml_integer(&contents[1]);
+ battery.last_full_capacity = aml_integer(&contents[2]);
+ battery.technology = aml_integer(&contents[3]).map(|value| match value {
+ 0 => "primary".to_string(),
+ 1 => "rechargeable".to_string(),
+ _ => format!("unknown({value})"),
+ });
+ battery.design_voltage = aml_integer(&contents[4]);
+ battery.battery_type = aml_string(&contents[9]);
+ battery.oem_info = aml_string(&contents[10]);
+ battery.model = aml_string(&contents[11]);
+ battery.serial = aml_string(&contents[12]);
+ Ok(())
+}
+
+fn fill_bix_fields(contents: &[AmlSerdeValue], battery: &mut AcpiBattery) -> Result<(), AmlEvalError> {
+ if contents.len() < 16 {
+ return Err(AmlEvalError::DeserializationError);
+ }
+
+ battery.power_unit = Some(
+ match aml_integer(&contents[0]).ok_or(AmlEvalError::DeserializationError)? {
+ 0 => "mWh",
+ 1 => "mAh",
+ _ => "unknown",
+ }
+ .to_string(),
+ );
+ battery.design_capacity = aml_integer(&contents[1]);
+ battery.last_full_capacity = aml_integer(&contents[2]);
+ battery.technology = aml_integer(&contents[3]).map(|value| match value {
+ 0 => "primary".to_string(),
+ 1 => "rechargeable".to_string(),
+ _ => format!("unknown({value})"),
+ });
+ battery.design_voltage = aml_integer(&contents[5]);
+ battery.model = aml_string(&contents[13]);
+ battery.serial = aml_string(&contents[14]);
+ battery.battery_type = aml_string(&contents[15]);
+ battery.oem_info = contents.get(16).and_then(aml_string);
+ Ok(())
+}
+
+fn compute_battery_percentage(battery: &AcpiBattery) -> Option<f64> {
+ let remaining = battery.remaining_capacity? as f64;
+ let full = battery.last_full_capacity.or(battery.design_capacity)? as f64;
+ if full <= 0.0 {
+ None
+ } else {
+ Some((remaining / full * 100.0).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,
+ slp_s5_values: RwLock<Option<(u8, u8)>>,
+ reset_reg: Option<GenericAddress>,
+ reset_value: u8,
+ dmi_info: Option<DmiInfo>,
+ pci_fd: RwLock<Option<libredox::Fd>>,
aml_symbols: RwLock<AmlSymbols>,
@@ -397,7 +1183,8 @@ impl AcpiContext {
args: Vec<AmlSerdeValue>,
) -> Result<AmlSerdeValue, AmlEvalError> {
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
@@ -410,43 +1197,120 @@ impl AcpiContext {
})
.collect::<Result<Vec<WrappedObject>, AmlEvalError>>()?;
- let result = interpreter.evaluate(symbol, args);
- interpreter
- .release_global_lock()
- .expect("Failed to release GIL!"); //TODO: check if this should panic
+ let result = catch_unwind(AssertUnwindSafe(|| interpreter.evaluate(symbol, args)))
+ .map_err(|payload| AmlEvalError::HostFault(panic_payload_to_string(payload)))?;
+ if let Err(error) = interpreter.release_global_lock() {
+ log::error!("Failed to release GIL: {:?}", error);
+ }
result
.map_err(AmlEvalError::from)
- .map(|object| {
- AmlSerdeValue::from_aml_value(object.deref())
+ .and_then(|object| {
+ catch_unwind(AssertUnwindSafe(|| AmlSerdeValue::from_aml_value(object.deref())))
+ .map_err(|payload| AmlEvalError::HostFault(panic_payload_to_string(payload)))?
.ok_or(AmlEvalError::SerializationError)
})
- .flatten()
}
- pub fn init(
- rxsdt_physaddrs: impl Iterator<Item = u64>,
- ec: Vec<(RegionSpace, Box<dyn RegionHandler>)>,
- ) -> Self {
- let tables = rxsdt_physaddrs
- .map(|physaddr| {
- let physaddr: usize = physaddr
- .try_into()
- .expect("expected ACPI addresses to be compatible with the current word size");
+ pub fn evaluate_acpi_method(
+ &mut self,
+ path: &str,
+ method: &str,
+ args: &[u64],
+ ) -> Result<Vec<u64>, AmlEvalError> {
+ let full_path = format!("{path}.{method}");
+ let aml_name = AmlName::from_str(&full_path).map_err(|_| AmlEvalError::DeserializationError)?;
+ let args = args
+ .iter()
+ .copied()
+ .map(AmlSerdeValue::Integer)
+ .collect::<Vec<_>>();
+
+ match self.aml_eval(aml_name, 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(&mut 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(&mut 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(&mut 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 dmi_info = load_dmi_info();
+ let tables = apply_acpi_table_quirks(
+ rxsdt_physaddrs
+ .filter_map(|physaddr| {
+ let physaddr: usize = match physaddr.try_into() {
+ Ok(physaddr) => physaddr,
+ Err(_) => {
+ log::error!(
+ "Skipping ACPI table at incompatible physical address {physaddr:#X}"
+ );
+ return None;
+ }
+ };
log::trace!("TABLE AT {:#>08X}", physaddr);
- Sdt::load_from_physical(physaddr).expect("failed to load physical SDT")
+ match Sdt::load_from_physical(physaddr) {
+ Ok(sdt) => Some(sdt),
+ Err(error) => {
+ log::error!("Skipping unreadable ACPI table at {physaddr:#X}: {error}");
+ None
+ }
+ }
})
- .collect::<Vec<Sdt>>();
+ .collect::<Vec<Sdt>>(),
+ dmi_info.as_ref(),
+ );
let mut this = Self {
tables,
dsdt: None,
fadt: None,
+ pm1a_cnt_blk: 0,
+ pm1b_cnt_blk: 0,
+ slp_s5_values: RwLock::new(None),
+ reset_reg: None,
+ reset_value: 0,
+ dmi_info,
+ 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),
@@ -458,7 +1322,8 @@ impl AcpiContext {
}
Fadt::init(&mut this);
- //TODO (hangs on real hardware): Dmar::init(&this);
+ // Intel DMAR runtime ownership is intentionally deferred out of acpid until a real
+ // replacement owner is ready. Do not resurrect the old acpid DMAR path piecemeal.
this
}
@@ -525,18 +1390,143 @@ impl AcpiContext {
self.sdt_order.write().push(Some(*signature));
}
+ pub fn dmi_info(&self) -> Option<&DmiInfo> {
+ self.dmi_info.as_ref()
+ }
+
+ pub fn pci_ready(&self) -> bool {
+ self.pci_fd.read().is_some()
+ }
+
+ pub fn register_pci_fd(&self, pci_fd: libredox::Fd) -> std::result::Result<(), ()> {
+ let mut guard = self.pci_fd.write();
+ if guard.is_some() {
+ return Err(());
+ }
+ *guard = Some(pci_fd);
+ drop(guard);
+ self.aml_symbols_reset();
+ if let Err(error) = self.refresh_s5_values() {
+ log::warn!("Failed to refresh \\_S5 after PCI registration: {error}");
+ }
+ Ok(())
+ }
+
+ pub fn power_snapshot(&self) -> std::result::Result<AcpiPowerSnapshot, AmlEvalError> {
+ let symbols = self.aml_symbols()?;
+ let symbol_names = symbols
+ .symbols_cache()
+ .keys()
+ .cloned()
+ .collect::<Vec<_>>();
+ drop(symbols);
+
+ let mut adapter_paths = symbol_names
+ .iter()
+ .filter_map(|symbol| symbol_parent_path(symbol, "._PSR"))
+ .collect::<Vec<_>>();
+ adapter_paths.sort();
+ adapter_paths.dedup();
+
+ let mut battery_paths = symbol_names
+ .iter()
+ .filter_map(|symbol| symbol_parent_path(symbol, "._BST"))
+ .collect::<Vec<_>>();
+ battery_paths.sort();
+ battery_paths.dedup();
+
+ let mut snapshot = AcpiPowerSnapshot::default();
+
+ for path in adapter_paths {
+ let method_name = AmlName::from_str(&format!("\\{}.{}", path, "_PSR"))
+ .map_err(|_| AmlEvalError::DeserializationError)?;
+ match self.aml_eval(method_name, Vec::new()) {
+ Ok(AmlSerdeValue::Integer(state)) => {
+ snapshot.adapters.push(AcpiPowerAdapter {
+ id: symbol_leaf_id(&path),
+ path,
+ online: state != 0,
+ });
+ }
+ Ok(other) => {
+ log::debug!("Skipping AC adapter {} due to unexpected _PSR value: {:?}", path, other);
+ }
+ Err(error) => {
+ log::debug!("Skipping AC adapter power source {}: {:?}", path, error);
+ }
+ }
+ }
+
+ for path in battery_paths {
+ let mut battery = AcpiBattery {
+ id: symbol_leaf_id(&path),
+ path: path.clone(),
+ ..AcpiBattery::default()
+ };
+
+ match self.aml_eval(
+ AmlName::from_str(&format!("\\{}.{}", path, "_BST"))
+ .map_err(|_| AmlEvalError::DeserializationError)?,
+ Vec::new(),
+ ) {
+ Ok(AmlSerdeValue::Package { contents }) => {
+ if let Err(error) = parse_bst_package(&contents, &mut battery) {
+ log::debug!("Skipping battery {} due to malformed _BST: {:?}", path, error);
+ continue;
+ }
+ }
+ Ok(other) => {
+ log::debug!("Skipping battery {} due to unexpected _BST value: {:?}", path, other);
+ continue;
+ }
+ Err(error) => {
+ log::debug!("Skipping battery {} due to _BST eval failure: {:?}", path, error);
+ continue;
+ }
+ }
+
+ for method in ["_BIX", "_BIF"] {
+ let method_name = AmlName::from_str(&format!("\\{}.{}", path, method))
+ .map_err(|_| AmlEvalError::DeserializationError)?;
+ match self.aml_eval(method_name, Vec::new()) {
+ Ok(AmlSerdeValue::Package { contents }) => {
+ let result = if method == "_BIX" {
+ fill_bix_fields(&contents, &mut battery)
+ } else {
+ fill_bif_fields(&contents, &mut battery)
+ };
+ if result.is_ok() {
+ break;
+ }
+ }
+ Ok(_) => {}
+ Err(_) => {}
+ }
+ }
+
+ battery.percentage = compute_battery_percentage(&battery);
+ snapshot.batteries.push(battery);
+ }
+
+ if snapshot.adapters.is_empty() && snapshot.batteries.is_empty() {
+ Err(AmlEvalError::Unsupported(
+ "ACPI power devices were not discoverable from AML",
+ ))
+ } else {
+ Ok(snapshot)
+ }
+ }
+
pub fn aml_lookup(&self, symbol: &str) -> Option<String> {
- if let Ok(aml_symbols) = self.aml_symbols(None) {
+ if let Ok(aml_symbols) = self.aml_symbols() {
aml_symbols.lookup(symbol)
} else {
None
}
}
- pub fn aml_symbols(
- &self,
- pci_fd: Option<&libredox::Fd>,
- ) -> Result<RwLockReadGuard<'_, AmlSymbols>, AmlError> {
+ pub fn aml_symbols(&self) -> Result<RwLockReadGuard<'_, AmlSymbols>, AmlError> {
+ let pci_fd = self.pci_fd.read();
// return the cached value if it exists
let symbols = self.aml_symbols.read();
if !symbols.symbols_cache().is_empty() {
@@ -550,7 +1540,7 @@ impl AcpiContext {
let mut aml_symbols = self.aml_symbols.write();
- aml_symbols.build_cache(pci_fd);
+ aml_symbols.build_cache(pci_fd.as_ref());
// return the cached value
Ok(RwLockWriteGuard::downgrade(aml_symbols))
@@ -562,95 +1552,223 @@ impl AcpiContext {
aml_symbols.symbol_cache = FxHashMap::default();
}
- /// 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;
+ pub fn sleep_values_for_target(
+ &self,
+ target: SleepTarget,
+ ) -> Result<(u8, u8), SleepStateValuesError> {
+ let aml_name = AmlName::from_str(&format!("\\{}", target.aml_method_name()))
+ .map_err(SleepStateValuesError::InvalidName)?;
+ let values = parse_sleep_package(target, self.aml_eval(aml_name, Vec::new())?)?;
+ if target.is_soft_off() {
+ *self.slp_s5_values.write() = Some(values);
}
- let fadt = match self.fadt() {
- Some(fadt) => fadt,
- None => {
- log::error!("Cannot set global S-state due to missing FADT.");
- return;
- }
- };
+ Ok(values)
+ }
- let port = fadt.pm1a_control_block as u16;
- let mut val = 1 << 13;
+ pub fn refresh_s5_values(&self) -> Result<(u8, u8), SleepStateValuesError> {
+ self.sleep_values_for_target(SleepTarget::S5)
+ }
- let aml_symbols = self.aml_symbols.read();
+ pub fn acpi_shutdown(&self, slp_typa_s5: u8, slp_typb_s5: u8) -> Result<(), PowerTransitionError> {
+ let pm1a_value = (u16::from(slp_typa_s5) << 10) | 0x2000;
+ let pm1b_value = (u16::from(slp_typb_s5) << 10) | 0x2000;
- 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;
- }
- };
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ {
+ let Ok(pm1a_port) = u16::try_from(self.pm1a_cnt_blk) else {
+ return Err(PowerTransitionError::InvalidPm1aControlBlock(self.pm1a_cnt_blk));
+ };
- 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;
+ 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(_) => {
+ return Err(PowerTransitionError::InvalidPm1bControlBlock(
+ self.pm1b_cnt_blk,
+ ));
+ }
}
- },
- None => {
- log::error!("Cannot set S-state, AML context not initialized");
- return;
}
- };
- let package = match s5.deref() {
- acpi::aml::object::Object::Package(package) => package,
- _ => {
- log::error!("Cannot set S-state, \\_S5 is not a package");
- return;
+ Ok(())
+ }
+
+ #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
+ {
+ Err(PowerTransitionError::UnsupportedArchitecture {
+ pm1a_cnt_blk: self.pm1a_cnt_blk,
+ pm1b_cnt_blk: self.pm1b_cnt_blk,
+ })
+ }
+ }
+
+ pub fn acpi_reboot(&self) -> Result<(), PowerTransitionError> {
+ 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);
+ Ok(())
}
- };
+ None => {
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ {
+ const I8042_COMMAND_PORT: u16 = 0x64;
+ const I8042_PULSE_RESET: u8 = 0xFE;
+
+ log::warn!(
+ "Reboot with keyboard-controller fallback outb(0x{:X}, 0x{:X})",
+ I8042_COMMAND_PORT,
+ I8042_PULSE_RESET
+ );
+ Pio::<u8>::new(I8042_COMMAND_PORT).write(I8042_PULSE_RESET);
+ 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;
+ #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
+ {
+ Err(PowerTransitionError::MissingResetRegister)
+ }
}
- };
- let slp_typb = match package[1].deref() {
- acpi::aml::object::Object::Integer(i) => i.to_owned(),
- _ => {
- log::error!("typb is not an Integer");
- return;
+ }
+ }
+
+ /// 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) -> Result<(), GlobalSleepStateError> {
+ let target = SleepTarget::try_from(state)
+ .map_err(|_| GlobalSleepStateError::UnknownSleepState(state))?;
+ if !target.is_soft_off() {
+ return Err(GlobalSleepStateError::UnsupportedTarget(target));
+ }
+
+ if self.fadt().is_none() {
+ return Err(GlobalSleepStateError::MissingFadt);
+ }
+
+ let cached_s5 = *self.slp_s5_values.read();
+ let (slp_typa, slp_typb) = match self.sleep_values_for_target(SleepTarget::S5) {
+ Ok(values) => values,
+ Err(error) => match cached_s5 {
+ Some(values) => {
+ log::warn!(
+ "Using cached {} values after refresh failure: {error}",
+ SleepTarget::S5.aml_method_name()
+ );
+ values
+ }
+ None => {
+ return Err(GlobalSleepStateError::MissingSleepValues {
+ target: SleepTarget::S5,
+ source: error,
+ })
+ }
}
};
- log::trace!("Shutdown SLP_TYPa {:X}, SLP_TYPb {:X}", slp_typa, slp_typb);
- val |= slp_typa as u16;
+ self.acpi_shutdown(slp_typa, slp_typb)
+ .map_err(GlobalSleepStateError::PowerTransitionFailed)?;
- #[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);
- }
+ Err(GlobalSleepStateError::TransitionDidNotComplete(target))
+ }
+}
- // TODO: Handle SLP_TYPb
+#[derive(Debug, Error)]
+pub enum SleepStateValuesError {
+ #[error("failed to build AML name for sleep-state method: {0:?}")]
+ InvalidName(AmlError),
- #[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
- );
- }
+ #[error("failed to evaluate sleep-state package: {0}")]
+ Evaluation(#[from] AmlEvalError),
- loop {
- core::hint::spin_loop();
- }
+ #[error("sleep-state method returned a non-package AML value")]
+ NonPackageValue,
+
+ #[error("sleep-state package did not contain two integer sleep-type entries")]
+ InvalidPackageShape,
+
+ #[error("sleep-state values did not fit in u8")]
+ ValueOutOfRange,
+}
+
+#[derive(Debug, Error)]
+pub enum PowerTransitionError {
+ #[error("PM1a control block address is invalid: {0:#X}")]
+ InvalidPm1aControlBlock(u64),
+
+ #[error("PM1b control block address is invalid: {0:#X}")]
+ InvalidPm1bControlBlock(u64),
+
+ #[error(
+ "cannot issue ACPI PM1 control writes on this architecture (PM1a={pm1a_cnt_blk:#X}, PM1b={pm1b_cnt_blk:#X})"
+ )]
+ UnsupportedArchitecture {
+ pm1a_cnt_blk: u64,
+ pm1b_cnt_blk: u64,
+ },
+
+ #[error("cannot reboot with ACPI: no reset register present in FADT")]
+ MissingResetRegister,
+}
+
+#[derive(Debug, Error)]
+pub enum GlobalSleepStateError {
+ #[error("unknown global sleep state S{0}")]
+ UnknownSleepState(u8),
+
+ #[error("sleep target {:?} remains groundwork-only until full sleep lifecycle support lands", .0)]
+ UnsupportedTarget(SleepTarget),
+
+ #[error("cannot set global S-state due to missing FADT")]
+ MissingFadt,
+
+ #[error("failed to derive usable {} values: {source}", target.aml_method_name())]
+ MissingSleepValues {
+ target: SleepTarget,
+ source: SleepStateValuesError,
+ },
+
+ #[error("ACPI power transition failed: {0}")]
+ PowerTransitionFailed(#[from] PowerTransitionError),
+
+ #[error("ACPI transition to {:?} returned without completing", .0)]
+ TransitionDidNotComplete(SleepTarget),
+}
+
+fn parse_sleep_package(
+ _target: SleepTarget,
+ value: AmlSerdeValue,
+) -> Result<(u8, u8), SleepStateValuesError> {
+ match value {
+ AmlSerdeValue::Package { contents } => match (contents.first(), 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)) => Ok((slp_typa_s5, slp_typb_s5)),
+ _ => Err(SleepStateValuesError::ValueOutOfRange),
+ }
+ }
+ _ => Err(SleepStateValuesError::InvalidPackageShape),
+ },
+ _ => Err(SleepStateValuesError::NonPackageValue),
}
}
@@ -707,7 +1825,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 +1833,68 @@ 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) {
+ let address = self.address;
+ match self.address_space {
+ 0 => {
+ let Ok(address) = usize::try_from(address) else {
+ log::error!("Reset register physical address is invalid: {:#X}", 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(address) {
+ Ok(port) => {
+ Pio::<u8>::new(port).write(value);
+ }
+ Err(_) => {
+ log::error!("Reset register I/O port is invalid: {:#X}", 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 +1903,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 {}
@@ -774,9 +1949,10 @@ impl Fadt {
}
pub fn init(context: &mut AcpiContext) {
- let fadt_sdt = context
- .take_single_sdt(*b"FACP")
- .expect("expected ACPI to always have a FADT");
+ let Some(fadt_sdt) = context.take_single_sdt(*b"FACP") else {
+ log::error!("Failed to find FADT");
+ return;
+ };
let fadt = match Fadt::new(fadt_sdt) {
Some(fadt) => fadt,
@@ -793,9 +1969,25 @@ 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,8 +1997,16 @@ 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);
+
+ if let Err(error) = context.refresh_s5_values() {
+ log::warn!("Failed to evaluate \\_S5 during FADT init: {error}");
+ }
}
}
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/aml_physmem.rs b/drivers/acpid/src/aml_physmem.rs
index 2bdd667b..69b8c48b 100644
--- a/drivers/acpid/src/aml_physmem.rs
+++ b/drivers/acpid/src/aml_physmem.rs
@@ -6,7 +6,10 @@ use rustc_hash::FxHashMap;
use std::fmt::LowerHex;
use std::mem::size_of;
use std::ptr::NonNull;
-use std::sync::{Arc, Mutex};
+use std::sync::atomic::{AtomicU32, Ordering};
+use std::sync::{Arc, Condvar, Mutex};
+use std::thread::ThreadId;
+use std::time::{Duration, Instant};
use syscall::PAGE_SIZE;
const PAGE_MASK: usize = !(PAGE_SIZE - 1);
@@ -141,6 +144,20 @@ impl AmlPageCache {
pub struct AmlPhysMemHandler {
page_cache: Arc<Mutex<AmlPageCache>>,
pci_fd: Arc<Option<libredox::Fd>>,
+ aml_mutexes: Arc<Mutex<FxHashMap<u32, Arc<AmlMutex>>>>,
+ next_mutex_handle: Arc<AtomicU32>,
+}
+
+#[derive(Debug, Default)]
+struct AmlMutexState {
+ owner: Option<ThreadId>,
+ depth: u32,
+}
+
+#[derive(Debug, Default)]
+struct AmlMutex {
+ state: Mutex<AmlMutexState>,
+ condvar: Condvar,
}
/// Read from a physical address.
@@ -156,6 +173,30 @@ impl AmlPhysMemHandler {
Self {
page_cache,
pci_fd: Arc::new(pci_fd),
+ aml_mutexes: Arc::new(Mutex::new(FxHashMap::default())),
+ next_mutex_handle: Arc::new(AtomicU32::new(1)),
+ }
+ }
+
+ fn aml_mutex(&self, handle: Handle) -> Option<Arc<AmlMutex>> {
+ self.aml_mutexes
+ .lock()
+ .unwrap_or_else(|poisoned| poisoned.into_inner())
+ .get(&handle.0)
+ .cloned()
+ }
+
+ fn read_phys_or_fault<T>(&self, address: usize) -> T
+ where
+ T: PrimInt + LowerHex,
+ {
+ let mut page_cache = self
+ .page_cache
+ .lock()
+ .unwrap_or_else(|poisoned| poisoned.into_inner());
+ match page_cache.read_from_phys::<T>(address) {
+ Ok(value) => value,
+ Err(error) => panic!("AML physmem read failed at {:#x}: {}", address, error),
}
}
@@ -240,43 +281,19 @@ impl acpi::Handler for AmlPhysMemHandler {
fn read_u8(&self, address: usize) -> u8 {
log::trace!("read u8 {:X}", address);
- if let Ok(mut page_cache) = self.page_cache.lock() {
- if let Ok(value) = page_cache.read_from_phys::<u8>(address) {
- return value;
- }
- }
- log::error!("failed to read u8 {:#x}", address);
- 0
+ self.read_phys_or_fault::<u8>(address)
}
fn read_u16(&self, address: usize) -> u16 {
log::trace!("read u16 {:X}", address);
- if let Ok(mut page_cache) = self.page_cache.lock() {
- if let Ok(value) = page_cache.read_from_phys::<u16>(address) {
- return value;
- }
- }
- log::error!("failed to read u16 {:#x}", address);
- 0
+ self.read_phys_or_fault::<u16>(address)
}
fn read_u32(&self, address: usize) -> u32 {
log::trace!("read u32 {:X}", address);
- if let Ok(mut page_cache) = self.page_cache.lock() {
- if let Ok(value) = page_cache.read_from_phys::<u32>(address) {
- return value;
- }
- }
- log::error!("failed to read u32 {:#x}", address);
- 0
+ self.read_phys_or_fault::<u32>(address)
}
fn read_u64(&self, address: usize) -> u64 {
log::trace!("read u64 {:X}", address);
- if let Ok(mut page_cache) = self.page_cache.lock() {
- if let Ok(value) = page_cache.read_from_phys::<u64>(address) {
- return value;
- }
- }
- log::error!("failed to read u64 {:#x}", address);
- 0
+ self.read_phys_or_fault::<u64>(address)
}
fn write_u8(&self, address: usize, value: u8) {
@@ -415,16 +432,102 @@ impl acpi::Handler for AmlPhysMemHandler {
}
fn create_mutex(&self) -> Handle {
- log::debug!("TODO: Handler::create_mutex");
- Handle(0)
+ let handle = self.next_mutex_handle.fetch_add(1, Ordering::Relaxed);
+ self.aml_mutexes
+ .lock()
+ .unwrap_or_else(|poisoned| poisoned.into_inner())
+ .insert(handle, Arc::new(AmlMutex::default()));
+ log::trace!("created AML mutex handle {handle}");
+ Handle(handle)
}
fn acquire(&self, mutex: Handle, timeout: u16) -> Result<(), AmlError> {
- log::debug!("TODO: Handler::acquire");
- Ok(())
+ let Some(aml_mutex) = self.aml_mutex(mutex) else {
+ log::error!("attempted to acquire unknown AML mutex handle {}", mutex.0);
+ return Err(AmlError::MutexAcquireTimeout);
+ };
+
+ let current_thread = std::thread::current().id();
+ let deadline = (timeout != 0xffff).then(|| Instant::now() + Duration::from_millis(timeout.into()));
+
+ let mut state = aml_mutex
+ .state
+ .lock()
+ .unwrap_or_else(|poisoned| poisoned.into_inner());
+
+ loop {
+ match state.owner {
+ None => {
+ state.owner = Some(current_thread);
+ state.depth = 1;
+ return Ok(());
+ }
+ Some(owner) if owner == current_thread => {
+ state.depth = state.depth.saturating_add(1);
+ return Ok(());
+ }
+ Some(_) if timeout == 0 => return Err(AmlError::MutexAcquireTimeout),
+ Some(_) if timeout == 0xffff => {
+ state = aml_mutex
+ .condvar
+ .wait(state)
+ .unwrap_or_else(|poisoned| poisoned.into_inner());
+ }
+ Some(_) => {
+ let Some(deadline) = deadline else {
+ return Err(AmlError::MutexAcquireTimeout);
+ };
+ let now = Instant::now();
+ if now >= deadline {
+ return Err(AmlError::MutexAcquireTimeout);
+ }
+
+ let remaining = deadline.saturating_duration_since(now);
+ let (next_state, wait_result) = aml_mutex
+ .condvar
+ .wait_timeout(state, remaining)
+ .unwrap_or_else(|poisoned| poisoned.into_inner());
+ state = next_state;
+
+ if wait_result.timed_out() && state.owner != Some(current_thread) {
+ return Err(AmlError::MutexAcquireTimeout);
+ }
+ }
+ }
+ }
}
fn release(&self, mutex: Handle) {
- log::debug!("TODO: Handler::release");
+ let Some(aml_mutex) = self.aml_mutex(mutex) else {
+ log::error!("attempted to release unknown AML mutex handle {}", mutex.0);
+ return;
+ };
+
+ let current_thread = std::thread::current().id();
+ let mut state = aml_mutex
+ .state
+ .lock()
+ .unwrap_or_else(|poisoned| poisoned.into_inner());
+
+ match state.owner {
+ Some(owner) if owner == current_thread => {
+ if state.depth > 1 {
+ state.depth -= 1;
+ } else {
+ state.owner = None;
+ state.depth = 0;
+ aml_mutex.condvar.notify_one();
+ }
+ }
+ Some(_) => {
+ log::warn!(
+ "ignoring AML mutex release for handle {} from non-owner thread",
+ mutex.0
+ );
+ }
+ None => {
+ log::warn!("ignoring AML mutex release for unlocked handle {}", mutex.0);
+ }
+ }
}
}
diff --git a/drivers/acpid/src/ec.rs b/drivers/acpid/src/ec.rs
index c322790a..99842586 100644
--- a/drivers/acpid/src/ec.rs
+++ b/drivers/acpid/src/ec.rs
@@ -1,3 +1,4 @@
+use std::convert::TryFrom;
use std::time::Duration;
use acpi::aml::{
@@ -30,6 +31,28 @@ const BURST_ACK: u8 = 0x90;
pub const DEFAULT_EC_TIMEOUT: Duration = Duration::from_millis(10);
+#[derive(Debug, Clone, Copy)]
+enum EcError {
+ Timeout,
+ OffsetOutOfRange,
+}
+
+impl EcError {
+ fn as_aml_error(self) -> AmlError {
+ match self {
+ EcError::Timeout | EcError::OffsetOutOfRange => {
+ AmlError::NoHandlerForRegionAccess(RegionSpace::EmbeddedControl)
+ }
+ }
+ }
+}
+
+impl From<EcError> for AmlError {
+ fn from(value: EcError) -> Self {
+ value.as_aml_error()
+ }
+}
+
#[repr(transparent)]
pub struct ScBits(u8);
#[allow(dead_code)]
@@ -90,28 +113,33 @@ impl Ec {
Pio::<u8>::new(self.data).write(value);
}
#[inline]
- fn wait_for_write_ready(&self) -> Option<()> {
+ fn wait_for_write_ready(&self) -> Result<(), EcError> {
let timeout = Timeout::new(self.timeout);
loop {
if !self.read_reg_sc().ibf() {
- return Some(());
+ return Ok(());
}
- timeout.run().ok()?;
+ timeout.run().map_err(|_| EcError::Timeout)?;
}
}
#[inline]
- fn wait_for_read_ready(&self) -> Option<()> {
+ fn wait_for_read_ready(&self) -> Result<(), EcError> {
let timeout = Timeout::new(self.timeout);
loop {
if self.read_reg_sc().obf() {
- return Some(());
+ return Ok(());
}
- timeout.run().ok()?;
+ timeout.run().map_err(|_| EcError::Timeout)?;
}
}
+ #[inline]
+ fn checked_address(offset: usize, byte_index: usize) -> Result<u8, EcError> {
+ u8::try_from(offset + byte_index).map_err(|_| EcError::OffsetOutOfRange)
+ }
+
//https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/12_ACPI_Embedded_Controller_Interface_Specification/embedded-controller-command-set.html
- pub fn read(&self, address: u8) -> Option<u8> {
+ fn read(&self, address: u8) -> Result<u8, EcError> {
trace!("ec read addr: {:x}", address);
self.wait_for_write_ready()?;
@@ -125,9 +153,9 @@ impl Ec {
let val = self.read_reg_data();
trace!("got: {:x}", val);
- Some(val)
+ Ok(val)
}
- pub fn write(&self, address: u8, value: u8) -> Option<()> {
+ fn write(&self, address: u8, value: u8) -> Result<(), EcError> {
trace!("ec write addr: {:x}, with: {:x}", address, value);
self.wait_for_write_ready()?;
@@ -141,7 +169,22 @@ impl Ec {
self.write_reg_data(value);
trace!("done");
- Some(())
+ Ok(())
+ }
+
+ fn read_bytes<const N: usize>(&self, offset: usize) -> Result<[u8; N], EcError> {
+ let mut bytes = [0u8; N];
+ for (index, byte) in bytes.iter_mut().enumerate() {
+ *byte = self.read(Self::checked_address(offset, index)?)?;
+ }
+ Ok(bytes)
+ }
+
+ fn write_bytes<const N: usize>(&self, offset: usize, bytes: [u8; N]) -> Result<(), EcError> {
+ for (index, byte) in IntoIterator::into_iter(bytes).enumerate() {
+ self.write(Self::checked_address(offset, index)?, byte)?;
+ }
+ Ok(())
}
// disabled if not met
// First Access - 400 microseconds
@@ -151,11 +194,11 @@ impl Ec {
#[allow(dead_code)]
fn enable_burst(&self) -> bool {
trace!("ec burst enable");
- self.wait_for_write_ready();
+ let _ = self.wait_for_write_ready();
self.write_reg_sc(BE_EC);
- self.wait_for_read_ready();
+ let _ = self.wait_for_read_ready();
let res = self.read_reg_data() == BURST_ACK;
trace!("success: {}", res);
@@ -164,7 +207,7 @@ impl Ec {
#[allow(dead_code)]
fn disable_burst(&self) {
trace!("ec burst disable");
- self.wait_for_write_ready();
+ let _ = self.wait_for_write_ready();
self.write_reg_sc(BD_EC);
trace!("done");
}
@@ -172,11 +215,11 @@ impl Ec {
#[allow(dead_code)]
fn queue_query(&mut self) -> u8 {
trace!("ec query");
- self.wait_for_write_ready();
+ let _ = self.wait_for_write_ready();
self.write_reg_sc(QR_EC);
- self.wait_for_read_ready();
+ let _ = self.wait_for_read_ready();
let val = self.read_reg_data();
trace!("got: {}", val);
@@ -190,7 +233,10 @@ impl RegionHandler for Ec {
offset: usize,
) -> Result<u8, acpi::aml::AmlError> {
assert_eq!(region.space, RegionSpace::EmbeddedControl);
- self.read(offset as u8).ok_or(AmlError::MutexAcquireTimeout) // TODO proper error type
+ self.read(Self::checked_address(offset, 0)?).map_err(|error| {
+ warn!("EC read_u8 failed at offset {offset:#x}: {error:?}");
+ error.as_aml_error()
+ })
}
fn write_u8(
&self,
@@ -199,58 +245,73 @@ impl RegionHandler for Ec {
value: u8,
) -> Result<(), acpi::aml::AmlError> {
assert_eq!(region.space, RegionSpace::EmbeddedControl);
- self.write(offset as u8, value)
- .ok_or(AmlError::MutexAcquireTimeout) // TODO proper error type
- }
- fn read_u16(&self, _region: &OpRegion, _offset: usize) -> Result<u16, acpi::aml::AmlError> {
- warn!("Got u16 EC read from AML!");
- Err(acpi::aml::AmlError::NoHandlerForRegionAccess(
- RegionSpace::EmbeddedControl,
- )) // TODO proper error type
- }
- fn read_u32(&self, _region: &OpRegion, _offset: usize) -> Result<u32, acpi::aml::AmlError> {
- warn!("Got u32 EC read from AML!");
- Err(acpi::aml::AmlError::NoHandlerForRegionAccess(
- RegionSpace::EmbeddedControl,
- )) // TODO proper error type
- }
- fn read_u64(&self, _region: &OpRegion, _offset: usize) -> Result<u64, acpi::aml::AmlError> {
- warn!("Got u64 EC read from AML!");
- Err(acpi::aml::AmlError::NoHandlerForRegionAccess(
- RegionSpace::EmbeddedControl,
- )) // TODO proper error type
+ self.write(Self::checked_address(offset, 0)?, value)
+ .map_err(|error| {
+ warn!("EC write_u8 failed at offset {offset:#x}: {error:?}");
+ error.as_aml_error()
+ })
+ }
+ fn read_u16(&self, region: &OpRegion, offset: usize) -> Result<u16, acpi::aml::AmlError> {
+ assert_eq!(region.space, RegionSpace::EmbeddedControl);
+ self.read_bytes::<2>(offset)
+ .map(u16::from_le_bytes)
+ .map_err(|error| {
+ warn!("EC read_u16 failed at offset {offset:#x}: {error:?}");
+ error.as_aml_error()
+ })
+ }
+ fn read_u32(&self, region: &OpRegion, offset: usize) -> Result<u32, acpi::aml::AmlError> {
+ assert_eq!(region.space, RegionSpace::EmbeddedControl);
+ self.read_bytes::<4>(offset)
+ .map(u32::from_le_bytes)
+ .map_err(|error| {
+ warn!("EC read_u32 failed at offset {offset:#x}: {error:?}");
+ error.as_aml_error()
+ })
+ }
+ fn read_u64(&self, region: &OpRegion, offset: usize) -> Result<u64, acpi::aml::AmlError> {
+ assert_eq!(region.space, RegionSpace::EmbeddedControl);
+ self.read_bytes::<8>(offset)
+ .map(u64::from_le_bytes)
+ .map_err(|error| {
+ warn!("EC read_u64 failed at offset {offset:#x}: {error:?}");
+ error.as_aml_error()
+ })
}
fn write_u16(
&self,
- _region: &OpRegion,
- _offset: usize,
- _value: u16,
+ region: &OpRegion,
+ offset: usize,
+ value: u16,
) -> Result<(), acpi::aml::AmlError> {
- warn!("Got u16 EC write from AML!");
- Err(acpi::aml::AmlError::NoHandlerForRegionAccess(
- RegionSpace::EmbeddedControl,
- )) // TODO proper error type
+ assert_eq!(region.space, RegionSpace::EmbeddedControl);
+ self.write_bytes(offset, value.to_le_bytes()).map_err(|error| {
+ warn!("EC write_u16 failed at offset {offset:#x}: {error:?}");
+ error.as_aml_error()
+ })
}
fn write_u32(
&self,
- _region: &OpRegion,
- _offset: usize,
- _value: u32,
+ region: &OpRegion,
+ offset: usize,
+ value: u32,
) -> Result<(), acpi::aml::AmlError> {
- warn!("Got u32 EC write from AML!");
- Err(acpi::aml::AmlError::NoHandlerForRegionAccess(
- RegionSpace::EmbeddedControl,
- )) // TODO proper error type
+ assert_eq!(region.space, RegionSpace::EmbeddedControl);
+ self.write_bytes(offset, value.to_le_bytes()).map_err(|error| {
+ warn!("EC write_u32 failed at offset {offset:#x}: {error:?}");
+ error.as_aml_error()
+ })
}
fn write_u64(
&self,
- _region: &OpRegion,
- _offset: usize,
- _value: u64,
+ region: &OpRegion,
+ offset: usize,
+ value: u64,
) -> Result<(), acpi::aml::AmlError> {
- warn!("Got u64 EC write from AML!");
- Err(acpi::aml::AmlError::NoHandlerForRegionAccess(
- RegionSpace::EmbeddedControl,
- )) // TODO proper error type
+ assert_eq!(region.space, RegionSpace::EmbeddedControl);
+ self.write_bytes(offset, value.to_le_bytes()).map_err(|error| {
+ warn!("EC write_u64 failed at offset {offset:#x}: {error:?}");
+ error.as_aml_error()
+ })
}
}
diff --git a/drivers/acpid/src/main.rs b/drivers/acpid/src/main.rs
index 059254b3..e388cd46 100644
--- a/drivers/acpid/src/main.rs
+++ b/drivers/acpid/src/main.rs
@@ -5,117 +5,189 @@ use std::ops::ControlFlow;
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, Socket};
use scheme_utils::Blocking;
+use thiserror::Error;
mod acpi;
mod aml_physmem;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
mod ec;
+mod sleep;
mod scheme;
-fn daemon(daemon: daemon::Daemon) -> ! {
- common::setup_logging(
- "misc",
- "acpi",
- "acpid",
- common::output_level(),
- common::file_level(),
- );
+#[derive(Debug, Error)]
+enum StartupError {
+ #[error("failed to read `/scheme/kernel.acpi/rxsdt`: {0}")]
+ ReadRootTable(std::io::Error),
- log::info!("acpid start");
+ #[error("failed to parse [R/X]SDT from kernel: {0}")]
+ ParseRootTable(self::acpi::InvalidSdtError),
- let rxsdt_raw_data: Arc<[u8]> = std::fs::read("/scheme/kernel.acpi/rxsdt")
- .expect("acpid: failed to read `/scheme/kernel.acpi/rxsdt`")
- .into();
+ #[error("kernel returned unsupported root table signature `{signature}`")]
+ UnsupportedRootSignature { signature: String },
- if rxsdt_raw_data.is_empty() {
- log::info!("System doesn't use ACPI");
- daemon.ready();
- std::process::exit(0);
+ #[error(
+ "root table `{signature}` payload length {payload_len} is not divisible by entry size {entry_size}"
+ )]
+ MisalignedRootEntries {
+ signature: String,
+ payload_len: usize,
+ entry_size: usize,
+ },
+
+ #[error("{context}: {message}")]
+ Runtime {
+ context: &'static str,
+ message: String,
+ },
+}
+
+impl StartupError {
+ fn runtime(context: &'static str, message: impl Into<String>) -> Self {
+ Self::Runtime {
+ context,
+ message: message.into(),
+ }
}
+}
- let sdt = self::acpi::Sdt::new(rxsdt_raw_data).expect("acpid: failed to parse [RX]SDT");
+fn parse_root_sdt(raw_data: Arc<[u8]>) -> Result<Option<self::acpi::Sdt>, StartupError> {
+ if raw_data.is_empty() {
+ return Ok(None);
+ }
- let mut thirty_two_bit;
- let mut sixty_four_bit;
+ self::acpi::Sdt::new(raw_data)
+ .map(Some)
+ .map_err(StartupError::ParseRootTable)
+}
+
+fn root_table_physaddrs(sdt: &self::acpi::Sdt) -> Result<Vec<u64>, StartupError> {
+ let signature = String::from_utf8_lossy(&sdt.signature).into_owned();
+ let data = sdt.data();
- let physaddrs_iter = match &sdt.signature {
+ match &sdt.signature {
b"RSDT" => {
- thirty_two_bit = sdt
- .data()
- .chunks(mem::size_of::<u32>())
- // TODO: With const generics, the compiler has some way of doing this for static sizes.
- .map(|chunk| <[u8; mem::size_of::<u32>()]>::try_from(chunk).unwrap())
- .map(|chunk| u32::from_le_bytes(chunk))
- .map(u64::from);
+ let entry_size = mem::size_of::<u32>();
+ if data.len() % entry_size != 0 {
+ return Err(StartupError::MisalignedRootEntries {
+ signature,
+ payload_len: data.len(),
+ entry_size,
+ });
+ }
- &mut thirty_two_bit as &mut dyn Iterator<Item = u64>
+ Ok(data
+ .chunks_exact(entry_size)
+ .map(|chunk| <[u8; mem::size_of::<u32>()]>::try_from(chunk).unwrap())
+ .map(u32::from_le_bytes)
+ .map(u64::from)
+ .collect())
}
b"XSDT" => {
- sixty_four_bit = sdt
- .data()
- .chunks(mem::size_of::<u64>())
- .map(|chunk| <[u8; mem::size_of::<u64>()]>::try_from(chunk).unwrap())
- .map(|chunk| u64::from_le_bytes(chunk));
+ let entry_size = mem::size_of::<u64>();
+ if data.len() % entry_size != 0 {
+ return Err(StartupError::MisalignedRootEntries {
+ signature,
+ payload_len: data.len(),
+ entry_size,
+ });
+ }
- &mut sixty_four_bit as &mut dyn Iterator<Item = u64>
+ Ok(data
+ .chunks_exact(entry_size)
+ .map(|chunk| <[u8; mem::size_of::<u64>()]>::try_from(chunk).unwrap())
+ .map(u64::from_le_bytes)
+ .collect())
}
- _ => panic!("acpid: expected [RX]SDT from kernel to be either of those"),
+ _ => Err(StartupError::UnsupportedRootSignature { signature }),
+ }
+}
+
+fn run_acpid(daemon: daemon::Daemon) -> Result<(), StartupError> {
+ let rxsdt_raw_data: Arc<[u8]> = std::fs::read("/scheme/kernel.acpi/rxsdt")
+ .map(Arc::<[u8]>::from)
+ .map_err(StartupError::ReadRootTable)?;
+
+ let Some(sdt) = parse_root_sdt(rxsdt_raw_data)? else {
+ log::info!("System doesn't use ACPI");
+ daemon.ready();
+ std::process::exit(0);
};
- 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 physaddrs = root_table_physaddrs(&sdt)?;
+ let acpi_context = self::acpi::AcpiContext::init(physaddrs.into_iter());
- // TODO: I/O permission bitmap?
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
- common::acquire_port_io_rights().expect("acpid: failed to set I/O privilege level to Ring 3");
+ common::acquire_port_io_rights().map_err(|error| {
+ StartupError::runtime(
+ "failed to set I/O privilege level to Ring 3",
+ format!("{error}"),
+ )
+ })?;
- let shutdown_pipe = File::open("/scheme/kernel.acpi/kstop")
- .expect("acpid: failed to open `/scheme/kernel.acpi/kstop`");
+ let shutdown_pipe = File::open("/scheme/kernel.acpi/kstop").map_err(|error| {
+ StartupError::runtime(
+ "failed to open `/scheme/kernel.acpi/kstop`",
+ error.to_string(),
+ )
+ })?;
- let mut event_queue = RawEventQueue::new().expect("acpid: failed to create event queue");
- let socket = Socket::nonblock().expect("acpid: failed to create disk scheme");
+ let mut event_queue = RawEventQueue::new().map_err(|error| {
+ StartupError::runtime("failed to create event queue", error.to_string())
+ })?;
+ let socket = Socket::nonblock().map_err(|error| {
+ StartupError::runtime("failed to create acpi scheme socket", error.to_string())
+ })?;
let mut scheme = self::scheme::AcpiScheme::new(&acpi_context, &socket);
let mut handler = Blocking::new(&socket, 16);
event_queue
.subscribe(shutdown_pipe.as_raw_fd() as usize, 0, EventFlags::READ)
- .expect("acpid: failed to register shutdown pipe for event queue");
+ .map_err(|error| {
+ StartupError::runtime(
+ "failed to register shutdown pipe for event queue",
+ error.to_string(),
+ )
+ })?;
event_queue
.subscribe(socket.inner().raw(), 1, EventFlags::READ)
- .expect("acpid: failed to register scheme socket for event queue");
+ .map_err(|error| {
+ StartupError::runtime(
+ "failed to register scheme socket for event queue",
+ error.to_string(),
+ )
+ })?;
- register_sync_scheme(&socket, "acpi", &mut scheme)
- .expect("acpid: failed to register acpi scheme to namespace");
+ register_sync_scheme(&socket, "acpi", &mut scheme).map_err(|error| {
+ StartupError::runtime(
+ "failed to register acpi scheme to namespace",
+ error.to_string(),
+ )
+ })?;
- daemon.ready();
+ libredox::call::setrens(0, 0).map_err(|error| {
+ StartupError::runtime("failed to enter null namespace", error.to_string())
+ })?;
- libredox::call::setrens(0, 0).expect("acpid: failed to enter null namespace");
+ daemon.ready();
let mut mounted = true;
while mounted {
- let Some(event) = event_queue
- .next()
- .transpose()
- .expect("acpid: failed to read event file")
- else {
+ let Some(event) = event_queue.next().transpose().map_err(|error| {
+ StartupError::runtime("failed to read event file", error.to_string())
+ })? else {
break;
};
if event.fd == socket.inner().raw() {
loop {
- match handler
- .process_requests_nonblocking(&mut scheme)
- .expect("acpid: failed to process requests")
- {
+ match handler.process_requests_nonblocking(&mut scheme).map_err(|error| {
+ StartupError::runtime("failed to process requests", error.to_string())
+ })? {
ControlFlow::Continue(()) => {}
ControlFlow::Break(()) => break,
}
@@ -125,19 +197,136 @@ fn daemon(daemon: daemon::Daemon) -> ! {
mounted = false;
} else {
log::debug!("Received request to unknown fd: {}", event.fd);
- continue;
}
}
drop(shutdown_pipe);
drop(event_queue);
- acpi_context.set_global_s_state(5);
+ acpi_context.set_global_s_state(5).map_err(|error| {
+ StartupError::runtime("failed to shut down after kernel request", error.to_string())
+ })
+}
+
+fn daemon(daemon: daemon::Daemon) -> ! {
+ common::setup_logging(
+ "misc",
+ "acpi",
+ "acpid",
+ common::output_level(),
+ common::file_level(),
+ );
- unreachable!("System should have shut down before this is entered");
+ log::info!("acpid start");
+
+ if let Err(error) = run_acpid(daemon) {
+ log::error!("acpid startup/runtime failure: {error}");
+ std::process::exit(1);
+ }
+
+ unreachable!("acpid returned from run_acpid without exiting or shutting down");
}
fn main() {
common::init();
daemon::Daemon::new(daemon);
}
+
+#[cfg(test)]
+mod tests {
+ use super::{parse_root_sdt, root_table_physaddrs, StartupError};
+ use crate::acpi::SdtHeader;
+ use std::sync::Arc;
+
+ fn make_sdt(signature: [u8; 4], payload: &[u8]) -> Arc<[u8]> {
+ let length = (std::mem::size_of::<SdtHeader>() + payload.len()) as u32;
+ let header = SdtHeader {
+ signature,
+ length,
+ revision: 1,
+ checksum: 0,
+ oem_id: *b"REDBAR",
+ oem_table_id: *b"ACPITEST",
+ oem_revision: 1,
+ creator_id: 1,
+ creator_revision: 1,
+ };
+
+ let mut bytes = unsafe { plain::as_bytes(&header) }.to_vec();
+ bytes.extend_from_slice(payload);
+
+ let checksum = bytes
+ .iter()
+ .copied()
+ .fold(0u8, |sum, byte| sum.wrapping_add(byte));
+ bytes[9] = 0u8.wrapping_sub(checksum);
+
+ bytes.into()
+ }
+
+ #[test]
+ fn empty_root_table_means_no_acpi() {
+ let parsed = parse_root_sdt(Arc::<[u8]>::from(Vec::<u8>::new())).unwrap();
+ assert!(parsed.is_none());
+ }
+
+ #[test]
+ fn rsdt_physaddrs_parse_without_panic() {
+ let payload = [
+ 0x78, 0x56, 0x34, 0x12, // 0x12345678
+ 0xF0, 0xDE, 0xBC, 0x9A, // 0x9ABCDEF0
+ ];
+ let sdt = parse_root_sdt(make_sdt(*b"RSDT", &payload))
+ .unwrap()
+ .unwrap();
+
+ assert_eq!(
+ root_table_physaddrs(&sdt).unwrap(),
+ vec![0x1234_5678, 0x9ABC_DEF0]
+ );
+ }
+
+ #[test]
+ fn xsdt_physaddrs_parse_without_panic() {
+ let payload = [
+ 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11,
+ 0x00, 0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0x99,
+ ];
+ let sdt = parse_root_sdt(make_sdt(*b"XSDT", &payload))
+ .unwrap()
+ .unwrap();
+
+ assert_eq!(
+ root_table_physaddrs(&sdt).unwrap(),
+ vec![0x1122_3344_5566_7788, 0x99AA_BBCC_DDEE_FF00]
+ );
+ }
+
+ #[test]
+ fn invalid_root_signature_is_explicit() {
+ let sdt = parse_root_sdt(make_sdt(*b"FADT", &[])).unwrap().unwrap();
+
+ let error = root_table_physaddrs(&sdt).unwrap_err();
+ assert!(matches!(
+ error,
+ StartupError::UnsupportedRootSignature { .. }
+ ));
+ }
+
+ #[test]
+ fn misaligned_rsdt_entries_are_rejected() {
+ let sdt = parse_root_sdt(make_sdt(*b"RSDT", &[1, 2, 3]))
+ .unwrap()
+ .unwrap();
+
+ let error = root_table_physaddrs(&sdt).unwrap_err();
+ assert!(matches!(
+ error,
+ StartupError::MisalignedRootEntries {
+ entry_size: 4,
+ payload_len: 3,
+ ..
+ }
+ ));
+ }
+}
diff --git a/drivers/acpid/src/scheme.rs b/drivers/acpid/src/scheme.rs
index 5a5040c3..7070e8b9 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, EOPNOTSUPP};
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,170 @@ enum HandleKind<'a> {
Table(SdtSignature),
Symbols(RwLockReadGuard<'a, AmlSymbols>),
Symbol { name: String, description: String },
+ Reboot,
+ 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
+}
+
+fn top_level_entries(power_available: bool) -> Vec<(&'static str, DirentKind)> {
+ let mut entries = vec![
+ ("tables", DirentKind::Directory),
+ ("symbols", DirentKind::Directory),
+ ("dmi", DirentKind::Directory),
+ ("reboot", DirentKind::Regular),
+ ];
+ if power_available {
+ entries.push(("power", DirentKind::Directory));
+ }
+ entries
+}
+
impl HandleKind<'_> {
fn is_dir(&self) -> bool {
match self {
@@ -53,6 +214,15 @@ impl HandleKind<'_> {
Self::Table(_) => false,
Self::Symbols(_) => true,
Self::Symbol { .. } => false,
+ Self::Reboot => 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 +235,19 @@ impl HandleKind<'_> {
.ok_or(Error::new(EBADFD))?
.length(),
Self::Symbol { description, .. } => description.len(),
+ Self::Reboot => 0,
+ 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 +258,111 @@ 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| match error {
+ crate::acpi::AmlEvalError::NotInitialized => Error::new(EAGAIN),
+ crate::acpi::AmlEvalError::Unsupported(message) => {
+ log::warn!("ACPI power surface unavailable: {message}");
+ Error::new(EOPNOTSUPP)
+ }
+ other => {
+ log::warn!("Failed to build ACPI power snapshot: {:?}", other);
+ Error::new(EIO)
+ }
+ })
+ }
+
+ fn power_available(&self) -> bool {
+ matches!(self.ctx.power_snapshot(), Ok(_))
+ }
+
+ fn power_handle(&self, path: &str) -> Result<HandleKind<'acpi>> {
+ let normalized = path.trim_matches('/');
+ self.power_snapshot()?;
+
+ 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 +466,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 +477,25 @@ impl SchemeSync for AcpiScheme<'_, '_> {
match &*components {
[""] => HandleKind::TopLevel,
+ ["reboot"] => HandleKind::Reboot,
+ ["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 +505,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 +517,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 +536,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 +555,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 +633,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 +646,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,13 +667,82 @@ 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"];
+ for (idx, (name, kind)) in top_level_entries(self.power_available())
+ .iter()
+ .enumerate()
+ .skip(opaque_offset as usize)
+ {
+ buf.entry(DirEntry {
+ inode: 0,
+ next_opaque_id: idx as u64 + 1,
+ name,
+ kind: *kind,
+ })?;
+ }
+ }
+ 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 TOPLEVEL_ENTRIES
+ for (idx, name) in power_adapter_entry_names()
.iter()
.enumerate()
.skip(opaque_offset as usize)
@@ -343,10 +751,44 @@ impl SchemeSync for AcpiScheme<'_, '_> {
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()
@@ -444,6 +886,38 @@ impl SchemeSync for AcpiScheme<'_, '_> {
Ok(result_len)
}
+ fn write(
+ &mut self,
+ id: usize,
+ buf: &[u8],
+ _offset: u64,
+ _flags: u32,
+ _ctx: &CallerCtx,
+ ) -> Result<usize> {
+ let handle = self.handles.get_mut(id)?;
+
+ if handle.stat {
+ return Err(Error::new(EBADF));
+ }
+ if !handle.allowed_to_eval {
+ return Err(Error::new(EPERM));
+ }
+
+ match handle.kind {
+ HandleKind::Reboot => {
+ if buf.is_empty() {
+ return Err(Error::new(EINVAL));
+ }
+ self.ctx.acpi_reboot().map_err(|error| {
+ log::error!("ACPI reboot failed: {error}");
+ Error::new(EIO)
+ })?;
+ Ok(buf.len())
+ }
+ _ => Err(Error::new(EBADF)),
+ }
+ }
+
fn on_sendfd(&mut self, sendfd_request: &SendFdRequest) -> Result<usize> {
let id = sendfd_request.id();
let num_fds = sendfd_request.num_fds();
@@ -470,10 +944,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 +955,58 @@ impl SchemeSync for AcpiScheme<'_, '_> {
self.handles.remove(id);
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::{dmi_contents, top_level_entries};
+ use crate::acpi::DmiInfo;
+ use syscall::dirent::DirentKind;
+
+ #[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);
+ }
+
+ #[test]
+ fn top_level_entries_always_include_reboot() {
+ let entries = top_level_entries(false);
+ assert!(entries.iter().any(|(name, kind)| {
+ *name == "reboot" && matches!(kind, DirentKind::Regular)
+ }));
+ }
+
+ #[test]
+ fn top_level_entries_only_include_power_when_available() {
+ let hidden = top_level_entries(false);
+ let visible = top_level_entries(true);
+
+ assert!(!hidden.iter().any(|(name, _)| *name == "power"));
+ assert!(visible.iter().any(|(name, kind)| {
+ *name == "power" && matches!(kind, DirentKind::Directory)
+ }));
+ }
+}
diff --git a/drivers/audio/ac97d/src/main.rs b/drivers/audio/ac97d/src/main.rs
index ffa8a94b..e4dbf930 100644
--- a/drivers/audio/ac97d/src/main.rs
+++ b/drivers/audio/ac97d/src/main.rs
@@ -3,6 +3,7 @@ use std::os::unix::io::AsRawFd;
use std::usize;
use event::{user_data, EventQueue};
+use log::error;
use pcid_interface::PciFunctionHandle;
use redox_scheme::scheme::register_sync_scheme;
use redox_scheme::Socket;
@@ -22,13 +23,28 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
let mut name = pci_config.func.name();
name.push_str("_ac97");
- let bar0 = pci_config.func.bars[0].expect_port();
- let bar1 = pci_config.func.bars[1].expect_port();
+ let bar0 = match pci_config.func.bars[0].try_port() {
+ Ok(port) => port,
+ Err(err) => {
+ error!("ac97d: invalid BAR0: {err}");
+ std::process::exit(1);
+ }
+ };
+ let bar1 = match pci_config.func.bars[1].try_port() {
+ Ok(port) => port,
+ Err(err) => {
+ error!("ac97d: invalid BAR1: {err}");
+ std::process::exit(1);
+ }
+ };
let irq = pci_config
.func
.legacy_interrupt_line
- .expect("ac97d: no legacy interrupts supported");
+ .unwrap_or_else(|| {
+ error!("ac97d: no legacy interrupts supported");
+ std::process::exit(1);
+ });
println!(" + ac97 {}", pci_config.func.display());
@@ -40,13 +56,35 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
common::file_level(),
);
- common::acquire_port_io_rights().expect("ac97d: failed to set I/O privilege level to Ring 3");
+ if let Err(err) = common::acquire_port_io_rights() {
+ error!("ac97d: failed to set I/O privilege level to Ring 3: {err}");
+ std::process::exit(1);
+ }
- let mut irq_file = irq.irq_handle("ac97d");
+ let mut irq_file = match irq.try_irq_handle("ac97d") {
+ Ok(file) => file,
+ Err(err) => {
+ error!("ac97d: failed to open IRQ handle: {err}");
+ std::process::exit(1);
+ }
+ };
- let socket = Socket::nonblock().expect("ac97d: failed to create socket");
- let mut device =
- unsafe { device::Ac97::new(bar0, bar1).expect("ac97d: failed to allocate device") };
+ let socket = match Socket::nonblock() {
+ Ok(socket) => socket,
+ Err(err) => {
+ error!("ac97d: failed to create socket: {err}");
+ std::process::exit(1);
+ }
+ };
+ let mut device = unsafe {
+ match device::Ac97::new(bar0, bar1) {
+ Ok(device) => device,
+ Err(err) => {
+ error!("ac97d: failed to allocate device: {err}");
+ std::process::exit(1);
+ }
+ }
+ };
let mut readiness_based = ReadinessBased::new(&socket, 16);
user_data! {
@@ -56,49 +94,81 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
}
}
- let event_queue = EventQueue::<Source>::new().expect("ac97d: Could not create event queue.");
+ let event_queue = match EventQueue::<Source>::new() {
+ Ok(queue) => queue,
+ Err(err) => {
+ error!("ac97d: could not create event queue: {err}");
+ std::process::exit(1);
+ }
+ };
event_queue
.subscribe(
irq_file.as_raw_fd() as usize,
Source::Irq,
event::EventFlags::READ,
)
- .unwrap();
+ .unwrap_or_else(|err| {
+ error!("ac97d: failed to subscribe IRQ fd: {err}");
+ std::process::exit(1);
+ });
event_queue
.subscribe(
socket.inner().raw(),
Source::Scheme,
event::EventFlags::READ,
)
- .unwrap();
-
- register_sync_scheme(&socket, "audiohw", &mut device)
- .expect("ac97d: failed to register audiohw scheme to namespace");
+ .unwrap_or_else(|err| {
+ error!("ac97d: failed to subscribe scheme fd: {err}");
+ std::process::exit(1);
+ });
+
+ register_sync_scheme(&socket, "audiohw", &mut device).unwrap_or_else(|err| {
+ error!("ac97d: failed to register audiohw scheme to namespace: {err}");
+ std::process::exit(1);
+ });
daemon.ready();
- libredox::call::setrens(0, 0).expect("ac97d: failed to enter null namespace");
+ if let Err(err) = libredox::call::setrens(0, 0) {
+ error!("ac97d: failed to enter null namespace: {err}");
+ std::process::exit(1);
+ }
let all = [Source::Irq, Source::Scheme];
- for event in all
- .into_iter()
- .chain(event_queue.map(|e| e.expect("ac97d: failed to get next event").user_data))
- {
+ for event in all.into_iter().chain(event_queue.map(|e| match e {
+ Ok(event) => event.user_data,
+ Err(err) => {
+ error!("ac97d: failed to get next event: {err}");
+ std::process::exit(1);
+ }
+ })) {
match event {
Source::Irq => {
let mut irq = [0; 8];
- irq_file.read(&mut irq).unwrap();
+ if let Err(err) = irq_file.read(&mut irq) {
+ error!("ac97d: failed to read IRQ file: {err}");
+ std::process::exit(1);
+ }
if !device.irq() {
continue;
}
- irq_file.write(&mut irq).unwrap();
+ if let Err(err) = irq_file.write(&mut irq) {
+ error!("ac97d: failed to acknowledge IRQ: {err}");
+ std::process::exit(1);
+ }
readiness_based
.poll_all_requests(&mut device)
- .expect("ac97d: failed to poll requests");
+ .unwrap_or_else(|err| {
+ error!("ac97d: failed to poll requests: {err}");
+ std::process::exit(1);
+ });
readiness_based
.write_responses()
- .expect("ac97d: failed to write to socket");
+ .unwrap_or_else(|err| {
+ error!("ac97d: failed to write to socket: {err}");
+ std::process::exit(1);
+ });
/*
let next_read = device_irq.next_read();
@@ -110,10 +180,16 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
Source::Scheme => {
readiness_based
.read_and_process_requests(&mut device)
- .expect("ac97d: failed to read from socket");
+ .unwrap_or_else(|err| {
+ error!("ac97d: failed to read from socket: {err}");
+ std::process::exit(1);
+ });
readiness_based
.write_responses()
- .expect("ac97d: failed to write to socket");
+ .unwrap_or_else(|err| {
+ error!("ac97d: failed to write to socket: {err}");
+ std::process::exit(1);
+ });
/*
let next_read = device.borrow().next_read();
@@ -125,7 +201,7 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
}
}
- std::process::exit(0);
+ std::process::exit(1);
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
diff --git a/drivers/audio/ihdad/src/main.rs b/drivers/audio/ihdad/src/main.rs
index 31a2add7..11d80133 100755
--- a/drivers/audio/ihdad/src/main.rs
+++ b/drivers/audio/ihdad/src/main.rs
@@ -6,7 +6,7 @@ use std::os::unix::io::AsRawFd;
use std::usize;
use event::{user_data, EventQueue};
-use pcid_interface::irq_helpers::pci_allocate_interrupt_vector;
+use pcid_interface::irq_helpers::try_pci_allocate_interrupt_vector;
use pcid_interface::PciFunctionHandle;
pub mod hda;
@@ -38,9 +38,19 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
log::info!("IHDA {}", pci_config.func.display());
+ if let Err(err) = pci_config.func.bars[0].try_mem() {
+ log::error!("ihdad: invalid BAR0: {err}");
+ std::process::exit(1);
+ }
let address = unsafe { pcid_handle.map_bar(0) }.ptr.as_ptr() as usize;
- let irq_file = pci_allocate_interrupt_vector(&mut pcid_handle, "ihdad");
+ let irq_file = match try_pci_allocate_interrupt_vector(&mut pcid_handle, "ihdad") {
+ Ok(irq) => irq,
+ Err(err) => {
+ log::error!("ihdad: failed to allocate interrupt vector: {err}");
+ std::process::exit(1);
+ }
+ };
{
let vend_prod: u32 = ((pci_config.func.full_device_id.vendor_id as u32) << 16)
@@ -53,11 +63,28 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
}
}
- let event_queue =
- EventQueue::<Source>::new().expect("ihdad: Could not create event queue.");
- let socket = Socket::nonblock().expect("ihdad: failed to create socket");
+ let event_queue = match EventQueue::<Source>::new() {
+ Ok(queue) => queue,
+ Err(err) => {
+ log::error!("ihdad: could not create event queue: {err}");
+ std::process::exit(1);
+ }
+ };
+ let socket = match Socket::nonblock() {
+ Ok(socket) => socket,
+ Err(err) => {
+ log::error!("ihdad: failed to create socket: {err}");
+ std::process::exit(1);
+ }
+ };
let mut device = unsafe {
- hda::IntelHDA::new(address, vend_prod).expect("ihdad: failed to allocate device")
+ match hda::IntelHDA::new(address, vend_prod) {
+ Ok(device) => device,
+ Err(err) => {
+ log::error!("ihdad: failed to allocate device: {err}");
+ std::process::exit(1);
+ }
+ }
};
let mut readiness_based = ReadinessBased::new(&socket, 16);
diff --git a/drivers/graphics/ihdgd/config.toml b/drivers/graphics/ihdgd/config.toml
index acbb4e78..210731ae 100644
--- a/drivers/graphics/ihdgd/config.toml
+++ b/drivers/graphics/ihdgd/config.toml
@@ -51,5 +51,26 @@ ids = { 0x8086 = [
0x56B3, # Pro A60
0x56C0, # GPU Flex 170
0x56C1, # GPU Flex 140
+ # Alder Lake-S Desktop
+ 0x4680, 0x4682, 0x4688, 0x468A, 0x468B,
+ 0x4690, 0x4692, 0x4693,
+ # Alder Lake-P Mobile
+ 0x46A0, 0x46A1, 0x46A2, 0x46A3, 0x46A6,
+ 0x46A8, 0x46AA, 0x462A, 0x4626, 0x4628,
+ 0x46B0, 0x46B1, 0x46B2, 0x46B3,
+ 0x46C0, 0x46C1, 0x46C2, 0x46C3,
+ # Alder Lake-N Low-Power
+ 0x46D0, 0x46D1, 0x46D2, 0x46D3, 0x46D4,
+ # Raptor Lake-S Desktop
+ 0xA780, 0xA781, 0xA782, 0xA783,
+ 0xA788, 0xA789, 0xA78A, 0xA78B,
+ # Raptor Lake-P Mobile
+ 0xA720, 0xA7A0, 0xA7A8, 0xA7AA, 0xA7AB,
+ # Raptor Lake-U Mobile
+ 0xA721, 0xA7A1, 0xA7A9, 0xA7AC, 0xA7AD,
+ # Meteor Lake
+ 0x7D40, 0x7D45, 0x7D55, 0x7D60, 0x7DD5,
+ # Arrow Lake-H
+ 0x7D51, 0x7DD1,
] }
command = ["ihdgd"]
diff --git a/drivers/graphics/ihdgd/src/device/mod.rs b/drivers/graphics/ihdgd/src/device/mod.rs
index ced9dd56..0dc2e659 100644
--- a/drivers/graphics/ihdgd/src/device/mod.rs
+++ b/drivers/graphics/ihdgd/src/device/mod.rs
@@ -246,7 +246,9 @@ impl Device {
};
let gttmm = {
- let (phys, size) = func.bars[0].expect_mem();
+ let (phys, size) = func.bars[0]
+ .try_mem()
+ .map_err(|_| Error::new(ENODEV))?;
Arc::new(MmioRegion::new(
phys,
size,
@@ -255,7 +257,9 @@ impl Device {
};
log::info!("GTTMM {:X?}", gttmm);
let gm = {
- let (phys, size) = func.bars[2].expect_mem();
+ let (phys, size) = func.bars[2]
+ .try_mem()
+ .map_err(|_| Error::new(ENODEV))?;
MmioRegion::new(phys, size, common::MemoryType::WriteCombining)?
};
log::info!("GM {:X?}", gm);
diff --git a/drivers/graphics/ihdgd/src/main.rs b/drivers/graphics/ihdgd/src/main.rs
index a8b6cc60..e468aca7 100644
--- a/drivers/graphics/ihdgd/src/main.rs
+++ b/drivers/graphics/ihdgd/src/main.rs
@@ -1,6 +1,6 @@
use driver_graphics::GraphicsScheme;
use event::{user_data, EventQueue};
-use pcid_interface::{irq_helpers::pci_allocate_interrupt_vector, PciFunctionHandle};
+use pcid_interface::{irq_helpers::try_pci_allocate_interrupt_vector, PciFunctionHandle};
use std::{
io::{Read, Write},
os::fd::AsRawFd,
@@ -29,10 +29,21 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
log::info!("IHDG {}", pci_config.func.display());
- let device = Device::new(&mut pcid_handle, &pci_config.func)
- .expect("ihdgd: failed to initialize device");
+ let device = match Device::new(&mut pcid_handle, &pci_config.func) {
+ Ok(device) => device,
+ Err(err) => {
+ log::error!("ihdgd: failed to initialize device: {err}");
+ std::process::exit(1);
+ }
+ };
- let irq_file = pci_allocate_interrupt_vector(&mut pcid_handle, "ihdgd");
+ let irq_file = match try_pci_allocate_interrupt_vector(&mut pcid_handle, "ihdgd") {
+ Ok(irq) => irq,
+ Err(err) => {
+ log::error!("ihdgd: failed to allocate interrupt vector: {err}");
+ std::process::exit(1);
+ }
+ };
// Needs to be before GraphicsScheme::new to avoid a deadlock due to initnsmgr blocking on
// /scheme/event as it is already blocked on opening /scheme/display.ihdg.*.
diff --git a/drivers/graphics/virtio-gpud/src/main.rs b/drivers/graphics/virtio-gpud/src/main.rs
index b27f4c56..5e9d810d 100644
--- a/drivers/graphics/virtio-gpud/src/main.rs
+++ b/drivers/graphics/virtio-gpud/src/main.rs
@@ -482,7 +482,10 @@ fn main() {
}
fn daemon_runner(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
- deamon(daemon, pcid_handle).unwrap();
+ if let Err(err) = deamon(daemon, pcid_handle) {
+ log::error!("virtio-gpud: startup failed: {err}");
+ std::process::exit(1);
+ }
unreachable!();
}
@@ -500,7 +503,12 @@ fn deamon(deamon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> anyhow:
// 0x1050 - virtio-gpu
let pci_config = pcid_handle.config();
- assert_eq!(pci_config.func.full_device_id.device_id, 0x1050);
+ if pci_config.func.full_device_id.device_id != 0x1050 {
+ return Err(anyhow::anyhow!(
+ "unexpected virtio-gpu device id: {:04x}",
+ pci_config.func.full_device_id.device_id
+ ));
+ }
log::info!("virtio-gpu: initiating startup sequence :^)");
let device = DEVICE.try_call_once(|| virtio_core::probe_device(&mut pcid_handle))?;
@@ -530,8 +538,8 @@ fn deamon(deamon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> anyhow:
// Needs to be before GpuScheme::new to avoid a deadlock due to initnsmgr blocking on
// /scheme/event as it is already blocked on opening /scheme/display.virtio-gpu.
// FIXME change the initnsmgr to not block on openat for the target scheme.
- let event_queue: EventQueue<Source> =
- EventQueue::new().expect("virtio-gpud: failed to create event queue");
+ let event_queue: EventQueue<Source> = EventQueue::new()
+ .map_err(|err| anyhow::anyhow!("failed to create event queue: {err}"))?;
let mut scheme = scheme::GpuScheme::new(
config,
diff --git a/drivers/hwd/src/backend/acpi.rs b/drivers/hwd/src/backend/acpi.rs
index 3da41d63..c24dfc4b 100644
--- a/drivers/hwd/src/backend/acpi.rs
+++ b/drivers/hwd/src/backend/acpi.rs
@@ -14,7 +14,7 @@ impl Backend for AcpiBackend {
// Spawn acpid
//TODO: pass rxsdt data to acpid?
#[allow(deprecated, reason = "we can't yet move this to init")]
- daemon::Daemon::spawn(Command::new("acpid"));
+ let _ = daemon::Daemon::spawn(Command::new("acpid"));
Ok(Self { rxsdt })
}
diff --git a/drivers/hwd/src/main.rs b/drivers/hwd/src/main.rs
index 79360e34..4a2b9469 100644
--- a/drivers/hwd/src/main.rs
+++ b/drivers/hwd/src/main.rs
@@ -38,7 +38,7 @@ fn daemon(daemon: daemon::Daemon) -> ! {
//TODO: launch pcid based on backend information?
// Must launch after acpid but before probe calls /scheme/acpi/symbols
#[allow(deprecated, reason = "we can't yet move this to init")]
- daemon::Daemon::spawn(process::Command::new("pcid"));
+ let _ = daemon::Daemon::spawn(process::Command::new("pcid"));
daemon.ready();
diff --git a/drivers/net/e1000d/src/main.rs b/drivers/net/e1000d/src/main.rs
index 373ea9b3..d971c0a1 100644
--- a/drivers/net/e1000d/src/main.rs
+++ b/drivers/net/e1000d/src/main.rs
@@ -28,17 +28,38 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
let irq = pci_config
.func
.legacy_interrupt_line
- .expect("e1000d: no legacy interrupts supported");
+ .unwrap_or_else(|| {
+ log::error!("e1000d: no legacy interrupts supported");
+ std::process::exit(1);
+ });
log::info!("E1000 {}", pci_config.func.display());
- let mut irq_file = irq.irq_handle("e1000d");
+ let mut irq_file = match irq.try_irq_handle("e1000d") {
+ Ok(file) => file,
+ Err(err) => {
+ log::error!("e1000d: failed to open IRQ handle: {err}");
+ std::process::exit(1);
+ }
+ };
- let address = unsafe { pcid_handle.map_bar(0) }.ptr.as_ptr() as usize;
+ let address = match unsafe { pcid_handle.try_map_bar(0) } {
+ Ok(bar) => bar.ptr.as_ptr() as usize,
+ Err(err) => {
+ log::error!("e1000d: failed to map BAR0: {err}");
+ std::process::exit(1);
+ }
+ };
let mut scheme = NetworkScheme::new(
move || unsafe {
- device::Intel8254x::new(address).expect("e1000d: failed to allocate device")
+ match device::Intel8254x::new(address) {
+ Ok(device) => device,
+ Err(err) => {
+ log::error!("e1000d: failed to allocate device: {err}");
+ std::process::exit(1);
+ }
+ }
},
daemon,
format!("network.{name}"),
@@ -51,7 +72,13 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
}
}
- let event_queue = EventQueue::<Source>::new().expect("e1000d: failed to create event queue");
+ let event_queue = match EventQueue::<Source>::new() {
+ Ok(queue) => queue,
+ Err(err) => {
+ log::error!("e1000d: failed to create event queue: {err}");
+ std::process::exit(1);
+ }
+ };
event_queue
.subscribe(
@@ -59,32 +86,65 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
Source::Irq,
event::EventFlags::READ,
)
- .expect("e1000d: failed to subscribe to IRQ fd");
+ .unwrap_or_else(|err| {
+ log::error!("e1000d: failed to subscribe to IRQ fd: {err}");
+ std::process::exit(1);
+ });
event_queue
.subscribe(
scheme.event_handle().raw(),
Source::Scheme,
event::EventFlags::READ,
)
- .expect("e1000d: failed to subscribe to scheme fd");
-
- libredox::call::setrens(0, 0).expect("e1000d: failed to enter null namespace");
+ .unwrap_or_else(|err| {
+ log::error!("e1000d: failed to subscribe to scheme fd: {err}");
+ std::process::exit(1);
+ });
+
+ if let Err(err) = libredox::call::setrens(0, 0) {
+ log::error!("e1000d: failed to enter null namespace: {err}");
+ std::process::exit(1);
+ }
- scheme.tick().unwrap();
+ if let Err(err) = scheme.tick() {
+ log::error!("e1000d: failed initial scheme tick: {err}");
+ std::process::exit(1);
+ }
- for event in event_queue.map(|e| e.expect("e1000d: failed to get event")) {
+ for event in event_queue {
+ let event = match event {
+ Ok(event) => event,
+ Err(err) => {
+ log::error!("e1000d: failed to get event: {err}");
+ break;
+ }
+ };
match event.user_data {
Source::Irq => {
let mut irq = [0; 8];
- irq_file.read(&mut irq).unwrap();
+ if let Err(err) = irq_file.read(&mut irq) {
+ log::error!("e1000d: failed to read IRQ file: {err}");
+ break;
+ }
if unsafe { scheme.adapter().irq() } {
- irq_file.write(&mut irq).unwrap();
-
- scheme.tick().expect("e1000d: failed to handle IRQ")
+ if let Err(err) = irq_file.write(&mut irq) {
+ log::error!("e1000d: failed to acknowledge IRQ: {err}");
+ break;
+ }
+
+ if let Err(err) = scheme.tick() {
+ log::error!("e1000d: failed to handle IRQ: {err}");
+ break;
+ }
+ }
+ }
+ Source::Scheme => {
+ if let Err(err) = scheme.tick() {
+ log::error!("e1000d: failed to handle scheme op: {err}");
+ break;
}
}
- Source::Scheme => scheme.tick().expect("e1000d: failed to handle scheme op"),
}
}
- unreachable!()
+ std::process::exit(1)
}
diff --git a/drivers/net/ixgbed/src/main.rs b/drivers/net/ixgbed/src/main.rs
index 4a6ce74d..a9b6dd82 100644
--- a/drivers/net/ixgbed/src/main.rs
+++ b/drivers/net/ixgbed/src/main.rs
@@ -22,20 +22,44 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
let irq = pci_config
.func
.legacy_interrupt_line
- .expect("ixgbed: no legacy interrupts supported");
+ .unwrap_or_else(|| {
+ eprintln!("ixgbed: no legacy interrupts supported");
+ std::process::exit(1);
+ });
println!(" + IXGBE {}", pci_config.func.display());
- let mut irq_file = irq.irq_handle("ixgbed");
+ let mut irq_file = match irq.try_irq_handle("ixgbed") {
+ Ok(file) => file,
+ Err(err) => {
+ eprintln!("ixgbed: failed to open IRQ handle: {err}");
+ std::process::exit(1);
+ }
+ };
- let mapped_bar = unsafe { pcid_handle.map_bar(0) };
+ if let Err(err) = pci_config.func.bars[0].try_mem() {
+ eprintln!("ixgbed: invalid BAR0: {err}");
+ std::process::exit(1);
+ }
+ let mapped_bar = match unsafe { pcid_handle.try_map_bar(0) } {
+ Ok(bar) => bar,
+ Err(err) => {
+ eprintln!("ixgbed: failed to map BAR0: {err}");
+ std::process::exit(1);
+ }
+ };
let address = mapped_bar.ptr.as_ptr();
let size = mapped_bar.bar_size;
let mut scheme = NetworkScheme::new(
move || {
- device::Intel8259x::new(address as usize, size)
- .expect("ixgbed: failed to allocate device")
+ match device::Intel8259x::new(address as usize, size) {
+ Ok(device) => device,
+ Err(err) => {
+ eprintln!("ixgbed: failed to allocate device: {err}");
+ std::process::exit(1);
+ }
+ }
},
daemon,
format!("network.{name}"),
@@ -48,41 +72,78 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
}
}
- let event_queue = EventQueue::<Source>::new().expect("ixgbed: Could not create event queue.");
+ let event_queue = match EventQueue::<Source>::new() {
+ Ok(queue) => queue,
+ Err(err) => {
+ eprintln!("ixgbed: could not create event queue: {err}");
+ std::process::exit(1);
+ }
+ };
event_queue
.subscribe(
irq_file.as_raw_fd() as usize,
Source::Irq,
event::EventFlags::READ,
)
- .unwrap();
+ .unwrap_or_else(|err| {
+ eprintln!("ixgbed: failed to subscribe IRQ fd: {err}");
+ std::process::exit(1);
+ });
event_queue
.subscribe(
scheme.event_handle().raw(),
Source::Scheme,
event::EventFlags::READ,
)
- .unwrap();
-
- libredox::call::setrens(0, 0).expect("ixgbed: failed to enter null namespace");
+ .unwrap_or_else(|err| {
+ eprintln!("ixgbed: failed to subscribe scheme fd: {err}");
+ std::process::exit(1);
+ });
+
+ if let Err(err) = libredox::call::setrens(0, 0) {
+ eprintln!("ixgbed: failed to enter null namespace: {err}");
+ std::process::exit(1);
+ }
- scheme.tick().unwrap();
+ if let Err(err) = scheme.tick() {
+ eprintln!("ixgbed: failed initial scheme tick: {err}");
+ std::process::exit(1);
+ }
- for event in event_queue.map(|e| e.expect("ixgbed: failed to get next event")) {
+ for event in event_queue {
+ let event = match event {
+ Ok(event) => event,
+ Err(err) => {
+ eprintln!("ixgbed: failed to get next event: {err}");
+ break;
+ }
+ };
match event.user_data {
Source::Irq => {
let mut irq = [0; 8];
- irq_file.read(&mut irq).unwrap();
+ if let Err(err) = irq_file.read(&mut irq) {
+ eprintln!("ixgbed: failed to read IRQ file: {err}");
+ break;
+ }
if scheme.adapter().irq() {
- irq_file.write(&mut irq).unwrap();
-
- scheme.tick().unwrap();
+ if let Err(err) = irq_file.write(&mut irq) {
+ eprintln!("ixgbed: failed to acknowledge IRQ: {err}");
+ break;
+ }
+
+ if let Err(err) = scheme.tick() {
+ eprintln!("ixgbed: failed to handle IRQ: {err}");
+ break;
+ }
}
}
Source::Scheme => {
- scheme.tick().unwrap();
+ if let Err(err) = scheme.tick() {
+ eprintln!("ixgbed: failed to handle scheme op: {err}");
+ break;
+ }
}
}
}
- unreachable!()
+ std::process::exit(0)
}
diff --git a/drivers/net/rtl8139d/src/main.rs b/drivers/net/rtl8139d/src/main.rs
index d470e814..aa377446 100644
--- a/drivers/net/rtl8139d/src/main.rs
+++ b/drivers/net/rtl8139d/src/main.rs
@@ -3,7 +3,7 @@ use std::os::unix::io::AsRawFd;
use driver_network::NetworkScheme;
use event::{user_data, EventQueue};
-use pcid_interface::irq_helpers::pci_allocate_interrupt_vector;
+use pcid_interface::irq_helpers::try_pci_allocate_interrupt_vector;
use pcid_interface::PciFunctionHandle;
pub mod device;
@@ -20,19 +20,19 @@ where
}
}
-fn map_bar(pcid_handle: &mut PciFunctionHandle) -> *mut u8 {
+fn map_bar(pcid_handle: &mut PciFunctionHandle) -> Result<*mut u8, &'static str> {
let config = pcid_handle.config();
// RTL8139 uses BAR2, RTL8169 uses BAR1, search in that order
for &barnum in &[2, 1] {
match config.func.bars[usize::from(barnum)] {
pcid_interface::PciBar::Memory32 { .. } | pcid_interface::PciBar::Memory64 { .. } => unsafe {
- return pcid_handle.map_bar(barnum).ptr.as_ptr();
+ return Ok(pcid_handle.map_bar(barnum).ptr.as_ptr());
},
other => log::warn!("BAR {} is {:?} instead of memory BAR", barnum, other),
}
}
- panic!("rtl8139d: failed to find BAR");
+ Err("failed to find a usable MMIO BAR")
}
fn main() {
@@ -55,13 +55,31 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
log::info!(" + RTL8139 {}", pci_config.func.display());
- let bar = map_bar(&mut pcid_handle);
+ let bar = match map_bar(&mut pcid_handle) {
+ Ok(bar) => bar,
+ Err(err) => {
+ log::error!("rtl8139d: {err}");
+ std::process::exit(1);
+ }
+ };
- let irq_file = pci_allocate_interrupt_vector(&mut pcid_handle, "rtl8139d");
+ let irq_file = match try_pci_allocate_interrupt_vector(&mut pcid_handle, "rtl8139d") {
+ Ok(irq) => irq,
+ Err(err) => {
+ log::error!("rtl8139d: failed to allocate interrupt vector: {err}");
+ std::process::exit(1);
+ }
+ };
let mut scheme = NetworkScheme::new(
move || unsafe {
- device::Rtl8139::new(bar as usize).expect("rtl8139d: failed to allocate device")
+ match device::Rtl8139::new(bar as usize) {
+ Ok(device) => device,
+ Err(err) => {
+ log::error!("rtl8139d: failed to allocate device: {err}");
+ std::process::exit(1);
+ }
+ }
},
daemon,
format!("network.{name}"),
diff --git a/drivers/net/rtl8168d/src/main.rs b/drivers/net/rtl8168d/src/main.rs
index 1d9963a3..c6e21bd9 100644
--- a/drivers/net/rtl8168d/src/main.rs
+++ b/drivers/net/rtl8168d/src/main.rs
@@ -3,7 +3,7 @@ use std::os::unix::io::AsRawFd;
use driver_network::NetworkScheme;
use event::{user_data, EventQueue};
-use pcid_interface::irq_helpers::pci_allocate_interrupt_vector;
+use pcid_interface::irq_helpers::try_pci_allocate_interrupt_vector;
use pcid_interface::PciFunctionHandle;
pub mod device;
@@ -20,19 +20,19 @@ where
}
}
-fn map_bar(pcid_handle: &mut PciFunctionHandle) -> *mut u8 {
+fn map_bar(pcid_handle: &mut PciFunctionHandle) -> Result<*mut u8, &'static str> {
let config = pcid_handle.config();
// RTL8168 uses BAR2, RTL8169 uses BAR1, search in that order
for &barnum in &[2, 1] {
match config.func.bars[usize::from(barnum)] {
pcid_interface::PciBar::Memory32 { .. } | pcid_interface::PciBar::Memory64 { .. } => unsafe {
- return pcid_handle.map_bar(barnum).ptr.as_ptr();
+ return Ok(pcid_handle.map_bar(barnum).ptr.as_ptr());
},
other => log::warn!("BAR {} is {:?} instead of memory BAR", barnum, other),
}
}
- panic!("rtl8168d: failed to find BAR");
+ Err("failed to find a usable MMIO BAR")
}
fn main() {
@@ -55,13 +55,31 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
log::info!("RTL8168 {}", pci_config.func.display());
- let bar = map_bar(&mut pcid_handle);
+ let bar = match map_bar(&mut pcid_handle) {
+ Ok(bar) => bar,
+ Err(err) => {
+ log::error!("rtl8168d: {err}");
+ std::process::exit(1);
+ }
+ };
- let irq_file = pci_allocate_interrupt_vector(&mut pcid_handle, "rtl8168d");
+ let irq_file = match try_pci_allocate_interrupt_vector(&mut pcid_handle, "rtl8168d") {
+ Ok(irq) => irq,
+ Err(err) => {
+ log::error!("rtl8168d: failed to allocate interrupt vector: {err}");
+ std::process::exit(1);
+ }
+ };
let mut scheme = NetworkScheme::new(
move || unsafe {
- device::Rtl8168::new(bar as usize).expect("rtl8168d: failed to allocate device")
+ match device::Rtl8168::new(bar as usize) {
+ Ok(device) => device,
+ Err(err) => {
+ log::error!("rtl8168d: failed to allocate device: {err}");
+ std::process::exit(1);
+ }
+ }
},
daemon,
format!("network.{name}"),
diff --git a/drivers/net/virtio-netd/src/main.rs b/drivers/net/virtio-netd/src/main.rs
index 17d168ef..56f2c045 100644
--- a/drivers/net/virtio-netd/src/main.rs
+++ b/drivers/net/virtio-netd/src/main.rs
@@ -31,7 +31,10 @@ fn main() {
}
fn daemon_runner(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
- deamon(daemon, pcid_handle).unwrap();
+ if let Err(err) = deamon(daemon, pcid_handle) {
+ log::error!("virtio-netd: startup failed: {err}");
+ std::process::exit(1);
+ }
unreachable!();
}
@@ -52,7 +55,13 @@ fn deamon(
// 0x1000 - virtio-net
let pci_config = pcid_handle.config();
- assert_eq!(pci_config.func.full_device_id.device_id, 0x1000);
+ if pci_config.func.full_device_id.device_id != 0x1000 {
+ return Err(format!(
+ "unexpected virtio-net device id: {:04x}",
+ pci_config.func.full_device_id.device_id
+ )
+ .into());
+ }
log::info!("virtio-net: initiating startup sequence :^)");
let device = virtio_core::probe_device(&mut pcid_handle)?;
@@ -84,7 +93,7 @@ fn deamon(
device.transport.ack_driver_feature(VIRTIO_NET_F_MAC);
mac
} else {
- unimplemented!()
+ return Err("virtio-net: device does not expose VIRTIO_NET_F_MAC".into());
};
device.transport.finalize_features();
@@ -126,7 +135,7 @@ fn deamon(
data: 0,
})?;
- libredox::call::setrens(0, 0).expect("virtio-netd: failed to enter null namespace");
+ libredox::call::setrens(0, 0)?;
scheme.tick()?;
diff --git a/drivers/pcid-spawner/src/main.rs b/drivers/pcid-spawner/src/main.rs
index a968f4d4..e41caee0 100644
--- a/drivers/pcid-spawner/src/main.rs
+++ b/drivers/pcid-spawner/src/main.rs
@@ -55,10 +55,11 @@ fn main() -> Result<()> {
};
let full_device_id = handle.config().func.full_device_id;
+ let device_addr = handle.config().func.addr;
log::debug!(
"pcid-spawner enumerated: PCI {} {}",
- handle.config().func.addr,
+ device_addr,
full_device_id.display()
);
@@ -67,7 +68,7 @@ fn main() -> Result<()> {
.iter()
.find(|driver| driver.match_function(&full_device_id))
else {
- log::debug!("no driver for {}, continuing", handle.config().func.addr);
+ log::debug!("no driver for {}, continuing", device_addr);
continue;
};
@@ -85,16 +86,46 @@ fn main() -> Result<()> {
let mut command = Command::new(program);
command.args(args);
- log::info!("pcid-spawner: spawn {:?}", command);
-
- handle.enable_device();
+ log::info!(
+ "pcid-spawner: matched {} to driver {:?}",
+ device_addr,
+ driver.command
+ );
+ log::info!("pcid-spawner: enabling {} before spawn", device_addr);
+
+ if let Err(err) = handle.try_enable_device() {
+ log::error!(
+ "pcid-spawner: failed to enable {} before spawn: {}",
+ device_addr,
+ err
+ );
+ continue;
+ }
let channel_fd = handle.into_inner_fd();
command.env("PCID_CLIENT_CHANNEL", channel_fd.to_string());
+ log::info!("pcid-spawner: spawn {:?}", command);
#[allow(deprecated, reason = "we can't yet move this to init")]
- daemon::Daemon::spawn(command);
- syscall::close(channel_fd as usize).unwrap();
+ if let Err(err) = daemon::Daemon::spawn(command) {
+ log::error!(
+ "pcid-spawner: spawn/readiness failed for {}: {}",
+ device_addr,
+ err
+ );
+ log::error!(
+ "pcid-spawner: {} remains enabled without a confirmed ready driver",
+ device_addr
+ );
+ }
+ if let Err(err) = syscall::close(channel_fd as usize) {
+ log::error!(
+ "pcid-spawner: failed to close channel fd {} for {}: {}",
+ channel_fd,
+ device_addr,
+ err
+ );
+ }
}
Ok(())
diff --git a/drivers/pcid/src/driver_handler.rs b/drivers/pcid/src/driver_handler.rs
index f70a7f6d..bd0db746 100644
--- a/drivers/pcid/src/driver_handler.rs
+++ b/drivers/pcid/src/driver_handler.rs
@@ -48,8 +48,18 @@ impl<'a> DriverHandler<'a> {
self.capabilities
.iter()
.filter_map(|capability| match capability {
- PciCapability::Vendor(addr) => unsafe {
- Some(VendorSpecificCapability::parse(*addr, self.pcie))
+ PciCapability::Vendor(addr) => match unsafe {
+ VendorSpecificCapability::try_parse(*addr, self.pcie)
+ } {
+ Ok(capability) => Some(capability),
+ Err(err) => {
+ log::warn!(
+ "pcid: skipping malformed vendor capability at {:#x}: {}",
+ addr.offset,
+ err
+ );
+ None
+ }
},
_ => None,
})
@@ -266,7 +276,7 @@ impl<'a> DriverHandler<'a> {
);
}
}
- _ => unreachable!(),
+ _ => PcidClientResponse::Error(PcidServerResponseError::UnrecognizedRequest),
},
PcidClientRequest::ReadConfig(offset) => {
let value = unsafe { self.pcie.read(self.func.addr, offset) };
@@ -278,7 +288,7 @@ impl<'a> DriverHandler<'a> {
}
return PcidClientResponse::WriteConfig;
}
- _ => unreachable!(),
+ _ => PcidClientResponse::Error(PcidServerResponseError::UnrecognizedRequest),
}
}
}
diff --git a/drivers/pcid/src/driver_interface/bar.rs b/drivers/pcid/src/driver_interface/bar.rs
index b2c1d35b..7eaade51 100644
--- a/drivers/pcid/src/driver_interface/bar.rs
+++ b/drivers/pcid/src/driver_interface/bar.rs
@@ -1,7 +1,37 @@
use std::convert::TryInto;
+use std::fmt;
use serde::{Deserialize, Serialize};
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum PciBarError {
+ Missing,
+ ExpectedPortFoundMemory,
+ ExpectedMemoryFoundPort,
+ AddressTooLarge,
+ SizeTooLarge,
+}
+
+impl fmt::Display for PciBarError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ PciBarError::Missing => write!(f, "expected BAR to exist"),
+ PciBarError::ExpectedPortFoundMemory => {
+ write!(f, "expected port BAR, found memory BAR")
+ }
+ PciBarError::ExpectedMemoryFoundPort => {
+ write!(f, "expected memory BAR, found port BAR")
+ }
+ PciBarError::AddressTooLarge => {
+ write!(f, "conversion from 64-bit BAR address to usize failed")
+ }
+ PciBarError::SizeTooLarge => {
+ write!(f, "conversion from 64-bit BAR size to usize failed")
+ }
+ }
+ }
+}
+
// This type is used instead of [pci_types::Bar] in the driver interface as the
// latter can't be serialized and is missing the convenience functions of [PciBar].
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
@@ -30,26 +60,76 @@ impl PciBar {
}
pub fn expect_port(&self) -> u16 {
+ self.try_port().unwrap_or_else(|err| panic!("{err}"))
+ }
+
+ pub fn try_port(&self) -> Result<u16, PciBarError> {
match *self {
- PciBar::Port(port) => port,
+ PciBar::Port(port) => Ok(port),
PciBar::Memory32 { .. } | PciBar::Memory64 { .. } => {
- panic!("expected port BAR, found memory BAR");
+ Err(PciBarError::ExpectedPortFoundMemory)
}
- PciBar::None => panic!("expected BAR to exist"),
+ PciBar::None => Err(PciBarError::Missing),
}
}
pub fn expect_mem(&self) -> (usize, usize) {
+ self.try_mem().unwrap_or_else(|err| panic!("{err}"))
+ }
+
+ pub fn try_mem(&self) -> Result<(usize, usize), PciBarError> {
match *self {
- PciBar::Memory32 { addr, size } => (addr as usize, size as usize),
- PciBar::Memory64 { addr, size } => (
- addr.try_into()
- .expect("conversion from 64bit BAR to usize failed"),
- size.try_into()
- .expect("conversion from 64bit BAR size to usize failed"),
- ),
- PciBar::Port(_) => panic!("expected memory BAR, found port BAR"),
- PciBar::None => panic!("expected BAR to exist"),
+ PciBar::Memory32 { addr, size } => Ok((addr as usize, size as usize)),
+ PciBar::Memory64 { addr, size } => Ok((
+ addr.try_into().map_err(|_| PciBarError::AddressTooLarge)?,
+ size.try_into().map_err(|_| PciBarError::SizeTooLarge)?,
+ )),
+ PciBar::Port(_) => Err(PciBarError::ExpectedMemoryFoundPort),
+ PciBar::None => Err(PciBarError::Missing),
}
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::{PciBar, PciBarError};
+
+ #[test]
+ fn try_port_accepts_port_bar() {
+ assert_eq!(PciBar::Port(0x1234).try_port(), Ok(0x1234));
+ }
+
+ #[test]
+ fn try_port_rejects_non_port_bars() {
+ assert_eq!(
+ PciBar::Memory32 {
+ addr: 0x1000,
+ size: 0x100,
+ }
+ .try_port(),
+ Err(PciBarError::ExpectedPortFoundMemory)
+ );
+ assert_eq!(PciBar::None.try_port(), Err(PciBarError::Missing));
+ }
+
+ #[test]
+ fn try_mem_accepts_memory_bars() {
+ assert_eq!(
+ PciBar::Memory32 {
+ addr: 0x1000,
+ size: 0x200,
+ }
+ .try_mem(),
+ Ok((0x1000, 0x200))
+ );
+ }
+
+ #[test]
+ fn try_mem_rejects_non_memory_bars() {
+ assert_eq!(
+ PciBar::Port(0x1234).try_mem(),
+ Err(PciBarError::ExpectedMemoryFoundPort)
+ );
+ assert_eq!(PciBar::None.try_mem(), Err(PciBarError::Missing));
+ }
+}
diff --git a/drivers/pcid/src/driver_interface/cap.rs b/drivers/pcid/src/driver_interface/cap.rs
index 19521608..495aac61 100644
--- a/drivers/pcid/src/driver_interface/cap.rs
+++ b/drivers/pcid/src/driver_interface/cap.rs
@@ -1,14 +1,37 @@
use pci_types::capability::PciCapabilityAddress;
use pci_types::ConfigRegionAccess;
use serde::{Deserialize, Serialize};
+use std::fmt;
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct VendorSpecificCapability {
pub data: Vec<u8>,
}
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum VendorSpecificCapabilityError {
+ InvalidLength(u16),
+}
+
+impl fmt::Display for VendorSpecificCapabilityError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ VendorSpecificCapabilityError::InvalidLength(length) => {
+ write!(f, "invalid vendor capability length: {length}")
+ }
+ }
+ }
+}
+
impl VendorSpecificCapability {
pub unsafe fn parse(addr: PciCapabilityAddress, access: &dyn ConfigRegionAccess) -> Self {
+ Self::try_parse(addr, access).unwrap_or_else(|err| panic!("{err}"))
+ }
+
+ pub unsafe fn try_parse(
+ addr: PciCapabilityAddress,
+ access: &dyn ConfigRegionAccess,
+ ) -> Result<Self, VendorSpecificCapabilityError> {
let dword = access.read(addr.address, addr.offset);
let length = ((dword >> 16) & 0xFF) as u16;
// let next = (dword >> 8) & 0xFF;
@@ -17,11 +40,9 @@ impl VendorSpecificCapability {
// addr.offset
// );
let data = if length > 0 {
- assert!(
- length > 3 && length % 4 == 0,
- "invalid range length: {}",
- length
- );
+ if !(length > 3 && length % 4 == 0) {
+ return Err(VendorSpecificCapabilityError::InvalidLength(length));
+ }
let mut raw_data = {
(addr.offset..addr.offset + length)
.step_by(4)
@@ -33,6 +54,69 @@ impl VendorSpecificCapability {
log::warn!("Vendor specific capability is invalid");
Vec::new()
};
- VendorSpecificCapability { data }
+ Ok(VendorSpecificCapability { data })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{VendorSpecificCapability, VendorSpecificCapabilityError};
+ use pci_types::capability::PciCapabilityAddress;
+ use pci_types::{ConfigRegionAccess, PciAddress};
+ use std::collections::BTreeMap;
+ use std::sync::Mutex;
+
+ #[derive(Default)]
+ struct MockConfigRegionAccess {
+ values: Mutex<BTreeMap<(PciAddress, u16), u32>>,
+ }
+
+ impl MockConfigRegionAccess {
+ fn with_read(address: PciAddress, offset: u16, value: u32) -> Self {
+ let mut map = BTreeMap::new();
+ map.insert((address, offset), value);
+ Self {
+ values: Mutex::new(map),
+ }
+ }
+ }
+
+ impl ConfigRegionAccess for MockConfigRegionAccess {
+ unsafe fn read(&self, address: PciAddress, offset: u16) -> u32 {
+ self.values
+ .lock()
+ .unwrap()
+ .get(&(address, offset))
+ .copied()
+ .unwrap_or_default()
+ }
+
+ unsafe fn write(&self, _address: PciAddress, _offset: u16, _value: u32) {}
+ }
+
+ #[test]
+ fn try_parse_accepts_valid_vendor_capability() {
+ let address = PciAddress::new(0, 0, 1, 0);
+ let capability = PciCapabilityAddress {
+ address,
+ offset: 0x40,
+ };
+ let access = MockConfigRegionAccess::with_read(address, 0x40, 0x0010_0000);
+
+ let capability = unsafe { VendorSpecificCapability::try_parse(capability, &access) };
+ assert_eq!(capability.unwrap().data.len(), 13);
+ }
+
+ #[test]
+ fn try_parse_rejects_invalid_length() {
+ let address = PciAddress::new(0, 0, 1, 0);
+ let capability = PciCapabilityAddress {
+ address,
+ offset: 0x40,
+ };
+ let access = MockConfigRegionAccess::with_read(address, 0x40, 0x0005_0000);
+
+ let error = unsafe { VendorSpecificCapability::try_parse(capability, &access) }.unwrap_err();
+ assert_eq!(error, VendorSpecificCapabilityError::InvalidLength(5));
}
}
diff --git a/drivers/pcid/src/driver_interface/irq_helpers.rs b/drivers/pcid/src/driver_interface/irq_helpers.rs
index 28ca077a..b595d703 100644
--- a/drivers/pcid/src/driver_interface/irq_helpers.rs
+++ b/drivers/pcid/src/driver_interface/irq_helpers.rs
@@ -180,40 +180,51 @@ pub fn allocate_single_interrupt_vector(cpu_id: usize) -> io::Result<Option<(u8,
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
-pub fn allocate_single_interrupt_vector_for_msi(cpu_id: usize) -> (MsiAddrAndData, File) {
+pub fn try_allocate_single_interrupt_vector_for_msi(
+ cpu_id: usize,
+) -> io::Result<(MsiAddrAndData, File)> {
use crate::driver_interface::msi::x86 as x86_msix;
- // FIXME for cpu_id >255 we need to use the IOMMU to use IRQ remapping
- let lapic_id = u8::try_from(cpu_id).expect("CPU id couldn't fit inside u8");
+ let lapic_id = u8::try_from(cpu_id).map_err(|_| {
+ io::Error::new(
+ io::ErrorKind::InvalidInput,
+ format!("CPU id {cpu_id} could not fit inside u8"),
+ )
+ })?;
let rh = false;
let dm = false;
let addr = x86_msix::message_address(lapic_id, rh, dm);
- let (vector, interrupt_handle) = allocate_single_interrupt_vector(cpu_id)
- .expect("failed to allocate interrupt vector")
- .expect("no interrupt vectors left");
+ let (vector, interrupt_handle) = allocate_single_interrupt_vector(cpu_id)?
+ .ok_or_else(|| io::Error::other("no interrupt vectors left"))?;
let msg_data = x86_msix::message_data_edge_triggered(x86_msix::DeliveryMode::Fixed, vector);
- (
+ Ok((
MsiAddrAndData {
addr,
data: msg_data,
},
interrupt_handle,
- )
+ ))
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
-pub fn allocate_first_msi_interrupt_on_bsp(
+pub fn allocate_single_interrupt_vector_for_msi(cpu_id: usize) -> (MsiAddrAndData, File) {
+ try_allocate_single_interrupt_vector_for_msi(cpu_id)
+ .unwrap_or_else(|err| panic!("failed to allocate MSI interrupt vector: {err}"))
+}
+
+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+pub fn try_allocate_first_msi_interrupt_on_bsp(
pcid_handle: &mut crate::driver_interface::PciFunctionHandle,
-) -> File {
+) -> Result<File, InterruptVectorError> {
use crate::driver_interface::{MsiSetFeatureInfo, PciFeature, SetFeatureInfo};
- // TODO: Allow allocation of up to 32 vectors.
-
- let destination_id = read_bsp_apic_id().expect("failed to read BSP apic id");
- let (msg_addr_and_data, interrupt_handle) =
- allocate_single_interrupt_vector_for_msi(destination_id);
+ let destination_id = read_bsp_apic_id().map_err(InterruptVectorError::ApicId)?;
+ let (msg_addr_and_data, interrupt_handle) = try_allocate_single_interrupt_vector_for_msi(
+ destination_id,
+ )
+ .map_err(InterruptVectorError::Allocate)?;
let set_feature_info = MsiSetFeatureInfo {
multi_message_enable: Some(0),
@@ -222,10 +233,20 @@ pub fn allocate_first_msi_interrupt_on_bsp(
};
pcid_handle.set_feature_info(SetFeatureInfo::Msi(set_feature_info));
- pcid_handle.enable_feature(PciFeature::Msi);
+ pcid_handle
+ .try_enable_feature(PciFeature::Msi)
+ .map_err(InterruptVectorError::IrqHandle)?;
log::debug!("Enabled MSI");
- interrupt_handle
+ Ok(interrupt_handle)
+}
+
+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+pub fn allocate_first_msi_interrupt_on_bsp(
+ pcid_handle: &mut crate::driver_interface::PciFunctionHandle,
+) -> File {
+ try_allocate_first_msi_interrupt_on_bsp(pcid_handle)
+ .unwrap_or_else(|err| panic!("failed to allocate first MSI interrupt on BSP: {err}"))
}
pub struct InterruptVector {
@@ -234,6 +255,39 @@ pub struct InterruptVector {
kind: InterruptVectorKind,
}
+#[derive(Debug)]
+pub enum InterruptVectorError {
+ MissingMsixFeature,
+ MissingLegacyInterrupt,
+ ApicId(io::Error),
+ Allocate(io::Error),
+ IrqHandle(io::Error),
+ MsixMap(super::msi::MsixMapError),
+}
+
+impl std::fmt::Display for InterruptVectorError {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ InterruptVectorError::MissingMsixFeature => {
+ write!(f, "missing MSI-X feature information")
+ }
+ InterruptVectorError::MissingLegacyInterrupt => {
+ write!(f, "no interrupts supported at all")
+ }
+ InterruptVectorError::ApicId(err) => write!(f, "failed to read BSP APIC ID: {err}"),
+ InterruptVectorError::Allocate(err) => {
+ write!(f, "failed to allocate interrupt vector: {err}")
+ }
+ InterruptVectorError::IrqHandle(err) => {
+ write!(f, "failed to open IRQ handle: {err}")
+ }
+ InterruptVectorError::MsixMap(err) => {
+ write!(f, "failed to map MSI-X registers: {err}")
+ }
+ }
+ }
+}
+
enum InterruptVectorKind {
Legacy,
Msi,
@@ -266,10 +320,10 @@ impl InterruptVector {
// FIXME allow allocating multiple interrupt vectors
// FIXME move MSI-X IRQ allocation to pcid
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
-pub fn pci_allocate_interrupt_vector(
+pub fn try_pci_allocate_interrupt_vector(
pcid_handle: &mut crate::driver_interface::PciFunctionHandle,
driver: &str,
-) -> InterruptVector {
+) -> Result<InterruptVector, InterruptVectorError> {
let features = pcid_handle.fetch_all_features();
let has_msi = features.iter().any(|feature| feature.is_msi());
@@ -278,57 +332,79 @@ pub fn pci_allocate_interrupt_vector(
if has_msix {
let msix_info = match pcid_handle.feature_info(super::PciFeature::MsiX) {
super::PciFeatureInfo::MsiX(msix) => msix,
- _ => unreachable!(),
+ _ => return Err(InterruptVectorError::MissingMsixFeature),
};
- let mut info = unsafe { msix_info.map_and_mask_all(pcid_handle) };
+ let mut info = unsafe { msix_info.try_map_and_mask_all(pcid_handle) }
+ .map_err(InterruptVectorError::MsixMap)?;
pcid_handle.enable_feature(crate::driver_interface::PciFeature::MsiX);
let entry = info.table_entry_pointer(0);
- let bsp_cpu_id = read_bsp_apic_id()
- .unwrap_or_else(|err| panic!("{driver}: failed to read BSP APIC ID: {err}"));
- let (msg_addr_and_data, irq_handle) = allocate_single_interrupt_vector_for_msi(bsp_cpu_id);
+ let bsp_cpu_id = read_bsp_apic_id().map_err(InterruptVectorError::ApicId)?;
+ let (msg_addr_and_data, irq_handle) =
+ try_allocate_single_interrupt_vector_for_msi(bsp_cpu_id)
+ .map_err(InterruptVectorError::Allocate)?;
entry.write_addr_and_data(msg_addr_and_data);
entry.unmask();
- InterruptVector {
+ Ok(InterruptVector {
irq_handle,
vector: 0,
kind: InterruptVectorKind::MsiX { table_entry: entry },
- }
+ })
} else if has_msi {
- InterruptVector {
+ Ok(InterruptVector {
irq_handle: allocate_first_msi_interrupt_on_bsp(pcid_handle),
vector: 0,
kind: InterruptVectorKind::Msi,
- }
+ })
} else if let Some(irq) = pcid_handle.config().func.legacy_interrupt_line {
- // INTx# pin based interrupts.
- InterruptVector {
- irq_handle: irq.irq_handle(driver),
+ Ok(InterruptVector {
+ irq_handle: irq
+ .try_irq_handle(driver)
+ .map_err(InterruptVectorError::IrqHandle)?,
vector: 0,
kind: InterruptVectorKind::Legacy,
- }
+ })
} else {
- panic!("{driver}: no interrupts supported at all")
+ Err(InterruptVectorError::MissingLegacyInterrupt)
}
}
-// FIXME support MSI on non-x86 systems
-#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub fn pci_allocate_interrupt_vector(
pcid_handle: &mut crate::driver_interface::PciFunctionHandle,
driver: &str,
) -> InterruptVector {
+ try_pci_allocate_interrupt_vector(pcid_handle, driver)
+ .unwrap_or_else(|err| panic!("{driver}: {err}"))
+}
+
+// FIXME support MSI on non-x86 systems
+#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
+pub fn try_pci_allocate_interrupt_vector(
+ pcid_handle: &mut crate::driver_interface::PciFunctionHandle,
+ driver: &str,
+) -> Result<InterruptVector, InterruptVectorError> {
if let Some(irq) = pcid_handle.config().func.legacy_interrupt_line {
- // INTx# pin based interrupts.
- InterruptVector {
- irq_handle: irq.irq_handle(driver),
+ Ok(InterruptVector {
+ irq_handle: irq
+ .try_irq_handle(driver)
+ .map_err(InterruptVectorError::IrqHandle)?,
vector: 0,
kind: InterruptVectorKind::Legacy,
- }
+ })
} else {
- panic!("{driver}: no interrupts supported at all")
+ Err(InterruptVectorError::MissingLegacyInterrupt)
}
}
+
+#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
+pub fn pci_allocate_interrupt_vector(
+ pcid_handle: &mut crate::driver_interface::PciFunctionHandle,
+ driver: &str,
+) -> InterruptVector {
+ try_pci_allocate_interrupt_vector(pcid_handle, driver)
+ .unwrap_or_else(|err| panic!("{driver}: {err}"))
+}
diff --git a/drivers/pcid/src/driver_interface/mod.rs b/drivers/pcid/src/driver_interface/mod.rs
index bbc7304e..b0fb8aa8 100644
--- a/drivers/pcid/src/driver_interface/mod.rs
+++ b/drivers/pcid/src/driver_interface/mod.rs
@@ -30,7 +30,7 @@ pub struct LegacyInterruptLine {
impl LegacyInterruptLine {
/// Get an IRQ handle for this interrupt line.
- pub fn irq_handle(self, driver: &str) -> File {
+ pub fn try_irq_handle(self, _driver: &str) -> io::Result<File> {
if let Some((phandle, addr, cells)) = self.phandled {
let path = match cells {
1 => format!("/scheme/irq/phandle-{}/{}", phandle, addr[0]),
@@ -39,17 +39,25 @@ impl LegacyInterruptLine {
"/scheme/irq/phandle-{}/{},{},{}",
phandle, addr[0], addr[1], addr[2]
),
- _ => panic!(
- "unexpected number of IRQ description cells for phandle {phandle}: {cells}"
- ),
+ _ => {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidData,
+ format!(
+ "unexpected number of IRQ description cells for phandle {phandle}: {cells}"
+ ),
+ ))
+ }
};
File::create(path)
- .unwrap_or_else(|err| panic!("{driver}: failed to open IRQ file: {err}"))
} else {
File::open(format!("/scheme/irq/{}", self.irq))
- .unwrap_or_else(|err| panic!("{driver}: failed to open IRQ file: {err}"))
}
}
+
+ pub fn irq_handle(self, driver: &str) -> File {
+ self.try_irq_handle(driver)
+ .unwrap_or_else(|err| panic!("{driver}: failed to open IRQ file: {err}"))
+ }
}
impl fmt::Display for LegacyInterruptLine {
@@ -247,6 +255,7 @@ pub enum PcidClientRequest {
pub enum PcidServerResponseError {
NonexistentFeature(PciFeature),
InvalidBitPattern,
+ UnrecognizedRequest,
}
#[derive(Debug, Serialize, Deserialize)]
@@ -307,6 +316,38 @@ fn recv<T: DeserializeOwned>(r: &mut File) -> T {
bincode::deserialize_from(&data[..]).expect("couldn't deserialize pcid message")
}
+fn send_result<T: Serialize>(w: &mut File, message: &T) -> io::Result<()> {
+ let mut data = Vec::new();
+ bincode::serialize_into(&mut data, message)
+ .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
+
+ let len = w.write(&data)?;
+ if len == data.len() {
+ Ok(())
+ } else {
+ Err(io::Error::new(
+ io::ErrorKind::WriteZero,
+ format!("short pcid request write: wrote {len} of {} bytes", data.len()),
+ ))
+ }
+}
+
+fn recv_result<T: DeserializeOwned>(r: &mut File) -> io::Result<T> {
+ let mut length_bytes = [0u8; 8];
+ r.read_exact(&mut length_bytes)?;
+ let length = u64::from_le_bytes(length_bytes);
+ if length > 0x100_000 {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidData,
+ format!("pcid_interface: buffer too large ({length} bytes)"),
+ ));
+ }
+ let mut data = vec![0u8; length as usize];
+ r.read_exact(&mut data)?;
+ bincode::deserialize_from(&data[..])
+ .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))
+}
+
impl PciFunctionHandle {
fn connect_default() -> Self {
let channel_fd = match env::var("PCID_CLIENT_CHANNEL") {
@@ -369,55 +410,99 @@ impl PciFunctionHandle {
self.config.clone()
}
+ pub fn try_enable_device(&mut self) -> io::Result<()> {
+ send_result(&mut self.channel, &PcidClientRequest::EnableDevice)?;
+ match recv_result(&mut self.channel)? {
+ PcidClientResponse::EnabledDevice => Ok(()),
+ other => Err(io::Error::new(
+ io::ErrorKind::InvalidData,
+ format!("received wrong pcid response while enabling device: {other:?}"),
+ )),
+ }
+ }
+
pub fn enable_device(&mut self) {
- self.send(&PcidClientRequest::EnableDevice);
- match self.recv() {
- PcidClientResponse::EnabledDevice => {}
- other => {
- log::error!("received wrong pcid response: {other:?}");
- process::exit(1);
- }
+ if let Err(err) = self.try_enable_device() {
+ log::error!("failed to enable PCI device: {err}");
+ process::exit(1);
}
}
pub fn get_vendor_capabilities(&mut self) -> Vec<VendorSpecificCapability> {
- self.send(&PcidClientRequest::RequestVendorCapabilities);
- match self.recv() {
- PcidClientResponse::VendorCapabilities(a) => a,
- other => {
- log::error!("received wrong pcid response: {other:?}");
+ match self.try_get_vendor_capabilities() {
+ Ok(capabilities) => capabilities,
+ Err(err) => {
+ log::error!("failed to fetch vendor capabilities: {err}");
process::exit(1);
}
}
}
+ pub fn try_get_vendor_capabilities(&mut self) -> io::Result<Vec<VendorSpecificCapability>> {
+ send_result(&mut self.channel, &PcidClientRequest::RequestVendorCapabilities)?;
+ match recv_result(&mut self.channel)? {
+ PcidClientResponse::VendorCapabilities(capabilities) => Ok(capabilities),
+ other => Err(io::Error::new(
+ io::ErrorKind::InvalidData,
+ format!(
+ "received wrong pcid response while requesting vendor capabilities: {other:?}"
+ ),
+ )),
+ }
+ }
+
// FIXME turn into struct with bool fields
+ pub fn try_fetch_all_features(&mut self) -> io::Result<Vec<PciFeature>> {
+ send_result(&mut self.channel, &PcidClientRequest::RequestFeatures)?;
+ match recv_result(&mut self.channel)? {
+ PcidClientResponse::AllFeatures(features) => Ok(features),
+ other => Err(io::Error::new(
+ io::ErrorKind::InvalidData,
+ format!("received wrong pcid response while fetching features: {other:?}"),
+ )),
+ }
+ }
+
pub fn fetch_all_features(&mut self) -> Vec<PciFeature> {
- self.send(&PcidClientRequest::RequestFeatures);
- match self.recv() {
- PcidClientResponse::AllFeatures(a) => a,
- other => {
- log::error!("received wrong pcid response: {other:?}");
+ match self.try_fetch_all_features() {
+ Ok(features) => features,
+ Err(err) => {
+ log::error!("failed to fetch PCI features: {err}");
process::exit(1);
}
}
}
+ pub fn try_enable_feature(&mut self, feature: PciFeature) -> io::Result<()> {
+ send_result(&mut self.channel, &PcidClientRequest::EnableFeature(feature))?;
+ match recv_result(&mut self.channel)? {
+ PcidClientResponse::FeatureEnabled(feat) if feat == feature => Ok(()),
+ other => Err(io::Error::new(
+ io::ErrorKind::InvalidData,
+ format!("received wrong pcid response while enabling feature: {other:?}"),
+ )),
+ }
+ }
pub fn enable_feature(&mut self, feature: PciFeature) {
- self.send(&PcidClientRequest::EnableFeature(feature));
- match self.recv() {
- PcidClientResponse::FeatureEnabled(feat) if feat == feature => {}
- other => {
- log::error!("received wrong pcid response: {other:?}");
- process::exit(1);
- }
+ if let Err(err) = self.try_enable_feature(feature) {
+ log::error!("failed to enable PCI feature {feature:?}: {err}");
+ process::exit(1);
+ }
+ }
+ pub fn try_feature_info(&mut self, feature: PciFeature) -> io::Result<PciFeatureInfo> {
+ send_result(&mut self.channel, &PcidClientRequest::FeatureInfo(feature))?;
+ match recv_result(&mut self.channel)? {
+ PcidClientResponse::FeatureInfo(feat, info) if feat == feature => Ok(info),
+ other => Err(io::Error::new(
+ io::ErrorKind::InvalidData,
+ format!("received wrong pcid response while reading feature info: {other:?}"),
+ )),
}
}
pub fn feature_info(&mut self, feature: PciFeature) -> PciFeatureInfo {
- self.send(&PcidClientRequest::FeatureInfo(feature));
- match self.recv() {
- PcidClientResponse::FeatureInfo(feat, info) if feat == feature => info,
- other => {
- log::error!("received wrong pcid response: {other:?}");
+ match self.try_feature_info(feature) {
+ Ok(info) => info,
+ Err(err) => {
+ log::error!("failed to fetch PCI feature info for {feature:?}: {err}");
process::exit(1);
}
}
@@ -433,33 +518,50 @@ impl PciFunctionHandle {
}
}
pub unsafe fn read_config(&mut self, offset: u16) -> u32 {
- self.send(&PcidClientRequest::ReadConfig(offset));
- match self.recv() {
- PcidClientResponse::ReadConfig(value) => value,
- other => {
- log::error!("received wrong pcid response: {other:?}");
+ match self.try_read_config(offset) {
+ Ok(value) => value,
+ Err(err) => {
+ log::error!("failed to read PCI config dword at {offset:#x}: {err}");
process::exit(1);
}
}
}
+ pub unsafe fn try_read_config(&mut self, offset: u16) -> io::Result<u32> {
+ send_result(&mut self.channel, &PcidClientRequest::ReadConfig(offset))?;
+ match recv_result(&mut self.channel)? {
+ PcidClientResponse::ReadConfig(value) => Ok(value),
+ other => Err(io::Error::new(
+ io::ErrorKind::InvalidData,
+ format!("received wrong pcid response while reading config: {other:?}"),
+ )),
+ }
+ }
pub unsafe fn write_config(&mut self, offset: u16, value: u32) {
- self.send(&PcidClientRequest::WriteConfig(offset, value));
- match self.recv() {
- PcidClientResponse::WriteConfig => {}
- other => {
- log::error!("received wrong pcid response: {other:?}");
- process::exit(1);
- }
+ if let Err(err) = self.try_write_config(offset, value) {
+ log::error!("failed to write PCI config dword at {offset:#x}: {err}");
+ process::exit(1);
}
}
- pub unsafe fn map_bar(&mut self, bir: u8) -> &MappedBar {
+ pub unsafe fn try_write_config(&mut self, offset: u16, value: u32) -> io::Result<()> {
+ send_result(&mut self.channel, &PcidClientRequest::WriteConfig(offset, value))?;
+ match recv_result(&mut self.channel)? {
+ PcidClientResponse::WriteConfig => Ok(()),
+ other => Err(io::Error::new(
+ io::ErrorKind::InvalidData,
+ format!("received wrong pcid response while writing config: {other:?}"),
+ )),
+ }
+ }
+ pub unsafe fn try_map_bar(&mut self, bir: u8) -> io::Result<&MappedBar> {
let mapped_bar = &mut self.mapped_bars[bir as usize];
if let Some(mapped_bar) = mapped_bar {
- mapped_bar
+ Ok(mapped_bar)
} else {
- let (bar, bar_size) = self.config.func.bars[bir as usize].expect_mem();
+ let (bar, bar_size) = self.config.func.bars[bir as usize]
+ .try_mem()
+ .map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err.to_string()))?;
- let ptr = match unsafe {
+ let ptr = unsafe {
common::physmap(
bar,
bar_size,
@@ -467,18 +569,23 @@ impl PciFunctionHandle {
// FIXME once the kernel supports this use write-through for prefetchable BAR
common::MemoryType::Uncacheable,
)
- } {
- Ok(ptr) => ptr,
- Err(err) => {
- log::error!("failed to map BAR at {bar:016X}: {err}");
- process::exit(1);
- }
- };
+ }
+ .map_err(|err| io::Error::other(format!("failed to map BAR at {bar:016X}: {err}")))?;
- mapped_bar.insert(MappedBar {
+ Ok(mapped_bar.insert(MappedBar {
ptr: NonNull::new(ptr.cast::<u8>()).expect("Mapping a BAR resulted in a nullptr"),
bar_size,
- })
+ }))
+ }
+ }
+
+ pub unsafe fn map_bar(&mut self, bir: u8) -> &MappedBar {
+ match self.try_map_bar(bir) {
+ Ok(bar) => bar,
+ Err(err) => {
+ log::error!("failed to map BAR {bir}: {err}");
+ process::exit(1);
+ }
}
}
}
diff --git a/drivers/pcid/src/driver_interface/msi.rs b/drivers/pcid/src/driver_interface/msi.rs
index 0ca68ec5..6934ad49 100644
--- a/drivers/pcid/src/driver_interface/msi.rs
+++ b/drivers/pcid/src/driver_interface/msi.rs
@@ -1,6 +1,7 @@
use std::fmt;
use std::ptr::NonNull;
+use crate::driver_interface::bar::PciBarError;
use crate::driver_interface::PciBar;
use crate::PciFunctionHandle;
@@ -33,9 +34,65 @@ pub struct MsixInfo {
pub pba_offset: u32,
}
+#[derive(Debug)]
+pub enum MsixMapError {
+ ReservedBir(u8),
+ InvalidBar {
+ which: &'static str,
+ source: PciBarError,
+ },
+ TableOutsideBar {
+ offset: usize,
+ end: usize,
+ bar_size: usize,
+ },
+ PbaOutsideBar {
+ offset: usize,
+ end: usize,
+ bar_size: usize,
+ },
+}
+
+impl fmt::Display for MsixMapError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ MsixMapError::ReservedBir(bir) => {
+ write!(f, "MSI-X BIR contained a reserved value: {bir}")
+ }
+ MsixMapError::InvalidBar { which, source } => {
+ write!(f, "MSI-X {which} BAR is invalid: {source}")
+ }
+ MsixMapError::TableOutsideBar {
+ offset,
+ end,
+ bar_size,
+ } => write!(
+ f,
+ "MSI-X table {offset:#x}:{end:#x} outside BAR with length {bar_size:#x}"
+ ),
+ MsixMapError::PbaOutsideBar {
+ offset,
+ end,
+ bar_size,
+ } => write!(
+ f,
+ "MSI-X PBA {offset:#x}:{end:#x} outside BAR with length {bar_size:#x}"
+ ),
+ }
+ }
+}
+
impl MsixInfo {
pub unsafe fn map_and_mask_all(self, pcid_handle: &mut PciFunctionHandle) -> MappedMsixRegs {
- self.validate(pcid_handle.config().func.bars);
+ self.try_map_and_mask_all(pcid_handle)
+ .unwrap_or_else(|err| panic!("{err}"))
+ }
+
+ pub unsafe fn try_map_and_mask_all(
+ self,
+ pcid_handle: &mut PciFunctionHandle,
+ ) -> Result<MappedMsixRegs, MsixMapError> {
+ self.try_validate(pcid_handle.config().func.bars)?;
let virt_table_base = unsafe {
pcid_handle
@@ -46,7 +103,8 @@ impl MsixInfo {
};
let mut info = MappedMsixRegs {
- virt_table_base: NonNull::new(virt_table_base.cast::<MsixTableEntry>()).unwrap(),
+ virt_table_base: NonNull::new(virt_table_base.cast::<MsixTableEntry>())
+ .expect("MSI-X BAR mapping resulted in null pointer"),
info: self,
};
@@ -56,21 +114,15 @@ impl MsixInfo {
info.table_entry_pointer(i.into()).mask();
}
- info
+ Ok(info)
}
- fn validate(&self, bars: [PciBar; 6]) {
+ pub fn try_validate(&self, bars: [PciBar; 6]) -> Result<(), MsixMapError> {
if self.table_bar > 5 {
- panic!(
- "MSI-X Table BIR contained a reserved enum value: {}",
- self.table_bar
- );
+ return Err(MsixMapError::ReservedBir(self.table_bar));
}
if self.pba_bar > 5 {
- panic!(
- "MSI-X PBA BIR contained a reserved enum value: {}",
- self.pba_bar
- );
+ return Err(MsixMapError::ReservedBir(self.pba_bar));
}
let table_size = self.table_size;
@@ -80,28 +132,38 @@ impl MsixInfo {
let pba_offset = self.pba_offset as usize;
let pba_min_length = table_size.div_ceil(8);
- let (_, table_bar_size) = bars[self.table_bar as usize].expect_mem();
- let (_, pba_bar_size) = bars[self.pba_bar as usize].expect_mem();
+ let (_, table_bar_size) = bars[self.table_bar as usize]
+ .try_mem()
+ .map_err(|source| MsixMapError::InvalidBar {
+ which: "table",
+ source,
+ })?;
+ let (_, pba_bar_size) = bars[self.pba_bar as usize]
+ .try_mem()
+ .map_err(|source| MsixMapError::InvalidBar {
+ which: "PBA",
+ source,
+ })?;
// Ensure that the table and PBA are within the BAR.
if !(0..table_bar_size as u64).contains(&(table_offset as u64 + table_min_length as u64)) {
- panic!(
- "Table {:#x}:{:#x} outside of BAR with length {:#x}",
- table_offset,
- table_offset + table_min_length as usize,
- table_bar_size
- );
+ return Err(MsixMapError::TableOutsideBar {
+ offset: table_offset,
+ end: table_offset + table_min_length as usize,
+ bar_size: table_bar_size,
+ });
}
if !(0..pba_bar_size as u64).contains(&(pba_offset as u64 + pba_min_length as u64)) {
- panic!(
- "PBA {:#x}:{:#x} outside of BAR with length {:#x}",
- pba_offset,
- pba_offset + pba_min_length as usize,
- pba_bar_size
- );
+ return Err(MsixMapError::PbaOutsideBar {
+ offset: pba_offset,
+ end: pba_offset + pba_min_length as usize,
+ bar_size: pba_bar_size,
+ });
}
+
+ Ok(())
}
}
@@ -120,6 +182,68 @@ impl MappedMsixRegs {
}
}
+#[cfg(test)]
+mod tests {
+ use super::{MsixInfo, MsixMapError};
+ use crate::driver_interface::PciBar;
+
+ #[test]
+ fn try_validate_accepts_in_range_table_and_pba() {
+ let info = MsixInfo {
+ table_bar: 0,
+ table_offset: 0x100,
+ table_size: 4,
+ pba_bar: 1,
+ pba_offset: 0x80,
+ };
+ let mut bars = [PciBar::None; 6];
+ bars[0] = PciBar::Memory32 {
+ addr: 0x1000,
+ size: 0x400,
+ };
+ bars[1] = PciBar::Memory32 {
+ addr: 0x2000,
+ size: 0x200,
+ };
+
+ assert!(info.try_validate(bars).is_ok());
+ }
+
+ #[test]
+ fn try_validate_rejects_reserved_bir() {
+ let info = MsixInfo {
+ table_bar: 6,
+ table_offset: 0,
+ table_size: 1,
+ pba_bar: 0,
+ pba_offset: 0,
+ };
+
+ assert!(matches!(info.try_validate([PciBar::None; 6]), Err(MsixMapError::ReservedBir(6))));
+ }
+
+ #[test]
+ fn try_validate_rejects_out_of_range_table() {
+ let info = MsixInfo {
+ table_bar: 0,
+ table_offset: 0x100,
+ table_size: 16,
+ pba_bar: 0,
+ pba_offset: 0,
+ };
+ let mut bars = [PciBar::None; 6];
+ bars[0] = PciBar::Memory32 {
+ addr: 0x1000,
+ size: 0x80,
+ };
+
+ assert!(matches!(
+ info.try_validate(bars),
+ Err(MsixMapError::TableOutsideBar { .. })
+ ));
+ }
+}
+
#[repr(C, packed)]
pub struct MsixTableEntry {
pub addr_lo: Mmio<u32>,
diff --git a/drivers/pcid/src/scheme.rs b/drivers/pcid/src/scheme.rs
index bb9f39a3..df026ab4 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)
}
@@ -316,6 +352,10 @@ impl SchemeSync for PciScheme {
func.enabled = false;
}
}
+ Some(HandleWrapper {
+ inner: Handle::Config { .. },
+ ..
+ }) => {}
_ => {}
}
}
@@ -341,6 +381,7 @@ impl PciScheme {
let path = &after[1..];
match path {
+ "config" => Handle::Config { addr },
"channel" => {
if func.enabled {
return Err(Error::new(ENOLCK));
@@ -387,7 +428,7 @@ impl PciScheme {
match *state {
ChannelState::AwaitingResponseRead(_) => return Err(Error::new(EINVAL)),
ChannelState::AwaitingData => {
- let func = tree.get_mut(&addr).unwrap();
+ let func = tree.get_mut(&addr).ok_or(Error::new(ENOENT))?;
let request = bincode::deserialize_from(buf).map_err(|_| Error::new(EINVAL))?;
let response = crate::driver_handler::DriverHandler::new(
diff --git a/drivers/storage/ahcid/src/main.rs b/drivers/storage/ahcid/src/main.rs
index 1f130a29..059cdd4e 100644
--- a/drivers/storage/ahcid/src/main.rs
+++ b/drivers/storage/ahcid/src/main.rs
@@ -26,7 +26,10 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
let irq = pci_config
.func
.legacy_interrupt_line
- .expect("ahcid: no legacy interrupts supported");
+ .unwrap_or_else(|| {
+ error!("ahcid: no legacy interrupts supported");
+ std::process::exit(1);
+ });
common::setup_logging(
"disk",
@@ -38,6 +41,10 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
info!("AHCI {}", pci_config.func.display());
+ if let Err(err) = pci_config.func.bars[5].try_mem() {
+ error!("ahcid: invalid BAR5: {err}");
+ std::process::exit(1);
+ }
let address = unsafe { pcid_handle.map_bar(5) }.ptr.as_ptr() as usize;
{
let (hba_mem, disks) = ahci::disks(address as usize, &name);
@@ -54,31 +61,58 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
&FuturesExecutor,
);
- let mut irq_file = irq.irq_handle("ahcid");
+ let mut irq_file = match irq.try_irq_handle("ahcid") {
+ Ok(file) => file,
+ Err(err) => {
+ error!("ahcid: failed to open IRQ handle: {err}");
+ std::process::exit(1);
+ }
+ };
let irq_fd = irq_file.as_raw_fd() as usize;
- let event_queue = RawEventQueue::new().expect("ahcid: failed to create event queue");
+ let event_queue = match RawEventQueue::new() {
+ Ok(queue) => queue,
+ Err(err) => {
+ error!("ahcid: failed to create event queue: {err}");
+ std::process::exit(1);
+ }
+ };
- libredox::call::setrens(0, 0).expect("ahcid: failed to enter null namespace");
+ if let Err(err) = libredox::call::setrens(0, 0) {
+ error!("ahcid: failed to enter null namespace: {err}");
+ std::process::exit(1);
+ }
event_queue
.subscribe(scheme.event_handle().raw(), 1, EventFlags::READ)
- .expect("ahcid: failed to event scheme socket");
+ .unwrap_or_else(|err| {
+ error!("ahcid: failed to subscribe scheme socket: {err}");
+ std::process::exit(1);
+ });
event_queue
.subscribe(irq_fd, 1, EventFlags::READ)
- .expect("ahcid: failed to event irq scheme");
+ .unwrap_or_else(|err| {
+ error!("ahcid: failed to subscribe IRQ fd: {err}");
+ std::process::exit(1);
+ });
for event in event_queue {
- let event = event.unwrap();
+ let event = match event {
+ Ok(event) => event,
+ Err(err) => {
+ error!("ahcid: failed to read event queue: {err}");
+ break;
+ }
+ };
if event.fd == scheme.event_handle().raw() {
- FuturesExecutor.block_on(scheme.tick()).unwrap();
+ if let Err(err) = FuturesExecutor.block_on(scheme.tick()) {
+ error!("ahcid: failed to handle scheme op: {err}");
+ break;
+ }
} else if event.fd == irq_fd {
let mut irq = [0; 8];
- if irq_file
- .read(&mut irq)
- .expect("ahcid: failed to read irq file")
- >= irq.len()
- {
+ match irq_file.read(&mut irq) {
+ Ok(read) if read >= irq.len() => {
let is = hba_mem.is.read();
if is > 0 {
let pi = hba_mem.pi.read();
@@ -92,11 +126,21 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
}
hba_mem.is.write(is);
- irq_file
- .write(&irq)
- .expect("ahcid: failed to write irq file");
+ if let Err(err) = irq_file.write(&irq) {
+ error!("ahcid: failed to acknowledge IRQ: {err}");
+ break;
+ }
- FuturesExecutor.block_on(scheme.tick()).unwrap();
+ if let Err(err) = FuturesExecutor.block_on(scheme.tick()) {
+ error!("ahcid: failed to handle IRQ: {err}");
+ break;
+ }
+ }
+ }
+ Ok(_) => {}
+ Err(err) => {
+ error!("ahcid: failed to read IRQ file: {err}");
+ break;
}
}
} else {
@@ -105,5 +149,5 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
}
}
- std::process::exit(0);
+ std::process::exit(1);
}
diff --git a/drivers/storage/ided/src/main.rs b/drivers/storage/ided/src/main.rs
index 4197217d..6983912c 100644
--- a/drivers/storage/ided/src/main.rs
+++ b/drivers/storage/ided/src/main.rs
@@ -43,19 +43,42 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
// Get controller DMA capable
let dma = pci_config.func.full_device_id.interface & 0x80 != 0;
- let busmaster_base = pci_config.func.bars[4].expect_port();
+ let busmaster_base = match pci_config.func.bars[4].try_port() {
+ Ok(port) => port,
+ Err(err) => {
+ error!("ided: missing/invalid busmaster BAR: {err}");
+ std::process::exit(1);
+ }
+ };
let (primary, primary_irq) = if pci_config.func.full_device_id.interface & 1 != 0 {
- panic!("TODO: IDE primary channel is PCI native");
+ error!("ided: PCI native primary IDE channel is not supported yet");
+ std::process::exit(1);
} else {
- (Channel::primary_compat(busmaster_base).unwrap(), 14)
+ match Channel::primary_compat(busmaster_base) {
+ Ok(channel) => (channel, 14),
+ Err(err) => {
+ error!("ided: failed to initialize primary IDE channel: {err}");
+ std::process::exit(1);
+ }
+ }
};
let (secondary, secondary_irq) = if pci_config.func.full_device_id.interface & 1 != 0 {
- panic!("TODO: IDE secondary channel is PCI native");
+ error!("ided: PCI native secondary IDE channel is not supported yet");
+ std::process::exit(1);
} else {
- (Channel::secondary_compat(busmaster_base + 8).unwrap(), 15)
+ match Channel::secondary_compat(busmaster_base + 8) {
+ Ok(channel) => (channel, 15),
+ Err(err) => {
+ error!("ided: failed to initialize secondary IDE channel: {err}");
+ std::process::exit(1);
+ }
+ }
};
- common::acquire_port_io_rights().expect("ided: failed to get I/O privilege");
+ if let Err(err) = common::acquire_port_io_rights() {
+ error!("ided: failed to get I/O privilege: {err}");
+ std::process::exit(1);
+ }
//TODO: move this to ide.rs?
let chans = vec![
diff --git a/drivers/storage/nvmed/src/main.rs b/drivers/storage/nvmed/src/main.rs
index beb1b689..8c79ba5e 100644
--- a/drivers/storage/nvmed/src/main.rs
+++ b/drivers/storage/nvmed/src/main.rs
@@ -75,30 +75,62 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
log::debug!("NVME PCI CONFIG: {:?}", pci_config);
+ if let Err(err) = pci_config.func.bars[0].try_mem() {
+ log::error!("nvmed: invalid BAR0: {err}");
+ std::process::exit(1);
+ }
let address = unsafe { pcid_handle.map_bar(0).ptr };
- let interrupt_vector = irq_helpers::pci_allocate_interrupt_vector(&mut pcid_handle, "nvmed");
+ let interrupt_vector = match irq_helpers::try_pci_allocate_interrupt_vector(&mut pcid_handle, "nvmed") {
+ Ok(vector) => vector,
+ Err(err) => {
+ log::error!("nvmed: failed to allocate interrupt vector: {err}");
+ std::process::exit(1);
+ }
+ };
let iv = interrupt_vector.vector();
- let irq_handle = interrupt_vector.irq_handle().try_clone().unwrap();
+ let irq_handle = match interrupt_vector.irq_handle().try_clone() {
+ Ok(handle) => handle,
+ Err(err) => {
+ log::error!("nvmed: failed to clone IRQ handle: {err}");
+ std::process::exit(1);
+ }
+ };
- let mut nvme = Nvme::new(address.as_ptr() as usize, interrupt_vector, pcid_handle)
- .expect("nvmed: failed to allocate driver data");
+ let mut nvme = match Nvme::new(address.as_ptr() as usize, interrupt_vector, pcid_handle) {
+ Ok(nvme) => nvme,
+ Err(err) => {
+ log::error!("nvmed: failed to allocate driver data: {err}");
+ std::process::exit(1);
+ }
+ };
- unsafe { nvme.init().expect("nvmed: failed to init") }
+ if let Err(err) = unsafe { nvme.init() } {
+ log::error!("nvmed: failed to init: {err}");
+ std::process::exit(1);
+ }
log::debug!("Finished base initialization");
let nvme = Arc::new(nvme);
let executor = nvme::executor::init(Arc::clone(&nvme), iv, false /* FIXME */, irq_handle);
- let mut time_handle = File::open(&format!("/scheme/time/{}", libredox::flag::CLOCK_MONOTONIC))
- .expect("failed to open time handle");
+ let mut time_handle = match File::open(&format!("/scheme/time/{}", libredox::flag::CLOCK_MONOTONIC)) {
+ Ok(handle) => handle,
+ Err(err) => {
+ log::error!("nvmed: failed to open time handle: {err}");
+ std::process::exit(1);
+ }
+ };
let mut time_events = Box::pin(
executor.register_external_event(time_handle.as_raw_fd() as usize, event::EventFlags::READ),
);
// Try to init namespaces for 5 seconds
- time_arm(&mut time_handle, 5).expect("failed to arm timer");
+ if let Err(err) = time_arm(&mut time_handle, 5) {
+ log::error!("nvmed: failed to arm init timer: {err}");
+ std::process::exit(1);
+ }
let namespaces = executor.block_on(async {
let namespaces_future = nvme.init_with_queues();
let time_future = time_events.as_mut().next();
@@ -106,7 +138,10 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
futures::pin_mut!(time_future);
match futures::future::select(namespaces_future, time_future).await {
futures::future::Either::Left((namespaces, _)) => namespaces,
- futures::future::Either::Right(_) => panic!("timeout on init"),
+ futures::future::Either::Right(_) => {
+ log::error!("nvmed: timeout waiting for queue initialization");
+ std::process::exit(1);
+ }
}
});
log::debug!("Initialized!");
diff --git a/drivers/storage/usbscsid/src/main.rs b/drivers/storage/usbscsid/src/main.rs
index 5382d118..3a403bd3 100644
--- a/drivers/storage/usbscsid/src/main.rs
+++ b/drivers/storage/usbscsid/src/main.rs
@@ -3,7 +3,7 @@ use std::env;
use driver_block::{Disk, DiskScheme, ExecutorTrait};
use syscall::{Error, EIO};
-use xhcid_interface::{ConfigureEndpointsReq, PortId, XhciClientHandle};
+use xhcid_interface::{PortId, XhciClientHandle};
pub mod protocol;
pub mod scsi;
@@ -12,9 +12,9 @@ use crate::protocol::Protocol;
use crate::scsi::Scsi;
fn main() {
- daemon::Daemon::new(daemon);
+ run();
}
-fn daemon(daemon: daemon::Daemon) -> ! {
+fn run() -> ! {
let mut args = env::args().skip(1);
const USAGE: &'static str = "usbscsid <scheme> <port> <protocol>";
@@ -67,15 +67,6 @@ fn daemon(daemon: daemon::Daemon) -> ! {
})
.expect("Failed to find suitable configuration");
- handle
- .configure_endpoints(&ConfigureEndpointsReq {
- config_desc: configuration_value,
- interface_desc: Some(interface_num),
- 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");
@@ -108,9 +99,6 @@ fn daemon(daemon: daemon::Daemon) -> ! {
&driver_block::FuturesExecutor,
);
- // FIXME should this wait notifying readiness until the disk scheme is created?
- daemon.ready();
-
//libredox::call::setrens(0, 0).expect("nvmed: failed to enter null namespace");
event_queue
diff --git a/drivers/storage/usbscsid/src/protocol/bot.rs b/drivers/storage/usbscsid/src/protocol/bot.rs
index b751d51a..b5d43cba 100644
--- a/drivers/storage/usbscsid/src/protocol/bot.rs
+++ b/drivers/storage/usbscsid/src/protocol/bot.rs
@@ -103,16 +103,22 @@ impl<'a> BulkOnlyTransport<'a> {
) -> Result<Self, ProtocolError> {
let endpoints = &if_desc.endpoints;
- let bulk_in_num = (endpoints
+ let bulk_in_num = endpoints
.iter()
- .position(|endpoint| endpoint.direction() == EndpDirection::In)
- .unwrap()
- + 1) as u8;
- let bulk_out_num = (endpoints
+ .find(|endpoint| endpoint.direction() == EndpDirection::In)
+ .map(|endpoint| endpoint.address & 0x0F)
+ .filter(|num| *num != 0)
+ .ok_or(ProtocolError::ProtocolError(
+ "missing bulk-in endpoint descriptor",
+ ))?;
+ let bulk_out_num = endpoints
.iter()
- .position(|endpoint| endpoint.direction() == EndpDirection::Out)
- .unwrap()
- + 1) as u8;
+ .find(|endpoint| endpoint.direction() == EndpDirection::Out)
+ .map(|endpoint| endpoint.address & 0x0F)
+ .filter(|num| *num != 0)
+ .ok_or(ProtocolError::ProtocolError(
+ "missing bulk-out endpoint descriptor",
+ ))?;
let max_lun = get_max_lun(handle, 0)?;
println!("BOT_MAX_LUN {}", max_lun);
diff --git a/drivers/storage/usbscsid/src/protocol/mod.rs b/drivers/storage/usbscsid/src/protocol/mod.rs
index a580765f..62edac60 100644
--- a/drivers/storage/usbscsid/src/protocol/mod.rs
+++ b/drivers/storage/usbscsid/src/protocol/mod.rs
@@ -68,14 +68,14 @@ use bot::BulkOnlyTransport;
pub fn setup<'a>(
handle: &'a XhciClientHandle,
protocol: u8,
- dev_desc: &DevDesc,
+ _dev_desc: &DevDesc,
conf_desc: &ConfDesc,
if_desc: &IfDesc,
-) -> Option<Box<dyn Protocol + 'a>> {
+) -> Result<Box<dyn Protocol + 'a>, ProtocolError> {
match protocol {
- 0x50 => Some(Box::new(
- BulkOnlyTransport::init(handle, conf_desc, if_desc).unwrap(),
+ 0x50 => Ok(Box::new(BulkOnlyTransport::init(handle, conf_desc, if_desc)?)),
+ _ => Err(ProtocolError::ProtocolError(
+ "unsupported USB mass-storage transport protocol",
)),
- _ => None,
}
}
diff --git a/drivers/storage/virtio-blkd/src/main.rs b/drivers/storage/virtio-blkd/src/main.rs
index d21236b3..f66f725d 100644
--- a/drivers/storage/virtio-blkd/src/main.rs
+++ b/drivers/storage/virtio-blkd/src/main.rs
@@ -103,7 +103,10 @@ fn main() {
}
fn daemon_runner(redox_daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
- daemon(redox_daemon, pcid_handle).unwrap();
+ if let Err(err) = daemon(redox_daemon, pcid_handle) {
+ log::error!("virtio-blkd: startup failed: {err}");
+ std::process::exit(1);
+ }
unreachable!();
}
@@ -121,7 +124,12 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> anyhow:
// 0x1001 - virtio-blk
let pci_config = pcid_handle.config();
- assert_eq!(pci_config.func.full_device_id.device_id, 0x1001);
+ if pci_config.func.full_device_id.device_id != 0x1001 {
+ return Err(anyhow::anyhow!(
+ "unexpected virtio-blk device id: {:04x}",
+ pci_config.func.full_device_id.device_id
+ ));
+ }
log::info!("virtio-blk: initiating startup sequence :^)");
let device = virtio_core::probe_device(&mut pcid_handle)?;
diff --git a/drivers/usb/usbctl/src/main.rs b/drivers/usb/usbctl/src/main.rs
index 9b5773d9..232f7cfc 100644
--- a/drivers/usb/usbctl/src/main.rs
+++ b/drivers/usb/usbctl/src/main.rs
@@ -15,6 +15,9 @@ fn main() {
Command::new("port")
.arg(Arg::new("PORT").num_args(1).required(true))
.subcommand(Command::new("status"))
+ .subcommand(Command::new("pm-state"))
+ .subcommand(Command::new("suspend"))
+ .subcommand(Command::new("resume"))
.subcommand(
Command::new("endpoint")
.arg(Arg::new("ENDPOINT_NUM").num_args(1).required(true))
@@ -38,6 +41,15 @@ fn main() {
if let Some(_status_scmd_matches) = port_scmd_matches.subcommand_matches("status") {
let state = handle.port_state().expect("Failed to get port state");
println!("{}", state.as_str());
+ } else if let Some(_pm_state_scmd_matches) = port_scmd_matches.subcommand_matches("pm-state") {
+ let state = handle
+ .port_pm_state()
+ .expect("Failed to get port power-management state");
+ println!("{}", state.as_str());
+ } else if let Some(_suspend_scmd_matches) = port_scmd_matches.subcommand_matches("suspend") {
+ handle.suspend_device().expect("Failed to suspend device");
+ } else if let Some(_resume_scmd_matches) = port_scmd_matches.subcommand_matches("resume") {
+ handle.resume_device().expect("Failed to resume device");
} else if let Some(endp_scmd_matches) = port_scmd_matches.subcommand_matches("endpoint") {
let endp_num = endp_scmd_matches
.get_one::<String>("ENDPOINT_NUM")
diff --git a/drivers/usb/xhcid/drivers.toml b/drivers/usb/xhcid/drivers.toml
index 83c90e23..470ec063 100644
--- a/drivers/usb/xhcid/drivers.toml
+++ b/drivers/usb/xhcid/drivers.toml
@@ -1,9 +1,8 @@
-#TODO: causes XHCI errors
-#[[drivers]]
-#name = "SCSI over USB"
-#class = 8 # Mass Storage class
-#subclass = 6 # SCSI transparent command set
-#command = ["usbscsid", "$SCHEME", "$PORT", "$IF_PROTO"]
+[[drivers]]
+name = "SCSI over USB"
+class = 8 # Mass Storage class
+subclass = 6 # SCSI transparent command set
+command = ["usbscsid", "$SCHEME", "$PORT", "$IF_PROTO"]
[[drivers]]
name = "USB HUB"
diff --git a/drivers/usb/xhcid/src/driver_interface.rs b/drivers/usb/xhcid/src/driver_interface.rs
index 727f8d7e..82f839ae 100644
--- a/drivers/usb/xhcid/src/driver_interface.rs
+++ b/drivers/usb/xhcid/src/driver_interface.rs
@@ -444,6 +444,33 @@ impl str::FromStr for PortState {
}
}
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
+pub enum PortPmState {
+ Active,
+ Suspended,
+}
+impl PortPmState {
+ pub fn as_str(&self) -> &'static str {
+ match self {
+ Self::Active => "active",
+ Self::Suspended => "suspended",
+ }
+ }
+}
+
+impl str::FromStr for PortPmState {
+ type Err = Invalid;
+
+ fn from_str(s: &str) -> result::Result<Self, Self::Err> {
+ Ok(match s {
+ "active" => Self::Active,
+ "suspended" => Self::Suspended,
+ _ => return Err(Invalid("read reserved port PM state")),
+ })
+ }
+}
+
#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub enum EndpointStatus {
@@ -560,6 +587,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)?)
@@ -582,6 +619,10 @@ impl XhciClientHandle {
let string = self.read_to_string("state")?;
Ok(string.parse()?)
}
+ pub fn port_pm_state(&self) -> result::Result<PortPmState, XhciClientHandleError> {
+ let string = self.read_to_string("pm_state")?;
+ Ok(string.parse()?)
+ }
pub fn open_endpoint_ctl(&self, num: u8) -> result::Result<File, XhciClientHandleError> {
let path = format!("endpoints/{}/ctl", num);
let fd = self.fd.openat(&path, libredox::flag::O_RDWR, 0)?;
diff --git a/drivers/usb/xhcid/src/main.rs b/drivers/usb/xhcid/src/main.rs
index d345a52f..562c580a 100644
--- a/drivers/usb/xhcid/src/main.rs
+++ b/drivers/usb/xhcid/src/main.rs
@@ -33,7 +33,7 @@ use std::sync::Arc;
use pcid_interface::irq_helpers::read_bsp_apic_id;
#[cfg(target_arch = "x86_64")]
use pcid_interface::irq_helpers::{
- allocate_first_msi_interrupt_on_bsp, allocate_single_interrupt_vector_for_msi,
+ try_allocate_first_msi_interrupt_on_bsp, try_allocate_single_interrupt_vector_for_msi,
};
use pcid_interface::{PciFeature, PciFeatureInfo, PciFunctionHandle};
@@ -61,11 +61,24 @@ fn get_int_method(pcid_handle: &mut PciFunctionHandle) -> (Option<File>, Interru
let has_msix = all_pci_features.iter().any(|feature| feature.is_msix());
if has_msix {
- let msix_info = match pcid_handle.feature_info(PciFeature::MsiX) {
- PciFeatureInfo::Msi(_) => panic!(),
- PciFeatureInfo::MsiX(s) => s,
+ let msix_info = match pcid_handle.try_feature_info(PciFeature::MsiX) {
+ Ok(PciFeatureInfo::MsiX(s)) => s,
+ Ok(PciFeatureInfo::Msi(_)) => {
+ log::error!("xhcid: invalid MSI-X feature response payload");
+ return (None, InterruptMethod::Polling);
+ }
+ Err(err) => {
+ log::error!("xhcid: failed to fetch MSI-X feature info: {err}");
+ return (None, InterruptMethod::Polling);
+ }
+ };
+ let mut info = match unsafe { msix_info.try_map_and_mask_all(pcid_handle) } {
+ Ok(info) => info,
+ Err(err) => {
+ log::error!("xhcid: failed to map MSI-X registers: {err}");
+ return (None, InterruptMethod::Polling);
+ }
};
- let mut info = unsafe { msix_info.map_and_mask_all(pcid_handle) };
// Allocate one msi vector.
@@ -75,27 +88,53 @@ fn get_int_method(pcid_handle: &mut PciFunctionHandle) -> (Option<File>, Interru
let table_entry_pointer = info.table_entry_pointer(k);
- let destination_id = read_bsp_apic_id().expect("xhcid: failed to read BSP apic id");
+ let destination_id = match read_bsp_apic_id() {
+ Ok(id) => id,
+ Err(err) => {
+ log::error!("xhcid: failed to read BSP APIC ID: {err}");
+ return (None, InterruptMethod::Polling);
+ }
+ };
let (msg_addr_and_data, interrupt_handle) =
- allocate_single_interrupt_vector_for_msi(destination_id);
+ match try_allocate_single_interrupt_vector_for_msi(destination_id) {
+ Ok(result) => result,
+ Err(err) => {
+ log::error!("xhcid: failed to allocate MSI-X vector: {err}");
+ return (None, InterruptMethod::Polling);
+ }
+ };
table_entry_pointer.write_addr_and_data(msg_addr_and_data);
table_entry_pointer.unmask();
(Some(interrupt_handle), InterruptMethod::Msi)
};
- pcid_handle.enable_feature(PciFeature::MsiX);
+ if let Err(err) = pcid_handle.try_enable_feature(PciFeature::MsiX) {
+ log::error!("xhcid: failed to enable MSI-X: {err}");
+ return (None, InterruptMethod::Polling);
+ }
log::debug!("Enabled MSI-X");
method
} else if has_msi {
- let interrupt_handle = allocate_first_msi_interrupt_on_bsp(pcid_handle);
- (Some(interrupt_handle), InterruptMethod::Msi)
+ match try_allocate_first_msi_interrupt_on_bsp(pcid_handle) {
+ Ok(interrupt_handle) => (Some(interrupt_handle), InterruptMethod::Msi),
+ Err(err) => {
+ log::error!("xhcid: failed to allocate MSI interrupt: {err}");
+ (None, InterruptMethod::Polling)
+ }
+ }
} else if let Some(irq) = pci_config.func.legacy_interrupt_line {
log::debug!("Legacy IRQ {}", irq);
// legacy INTx# interrupt pins.
- (Some(irq.irq_handle("xhcid")), InterruptMethod::Intx)
+ match irq.try_irq_handle("xhcid") {
+ Ok(file) => (Some(file), InterruptMethod::Intx),
+ Err(err) => {
+ log::error!("xhcid: failed to open legacy IRQ handle: {err}");
+ (None, InterruptMethod::Polling)
+ }
+ }
} else {
// no interrupts at all
(None, InterruptMethod::Polling)
@@ -109,7 +148,13 @@ fn get_int_method(pcid_handle: &mut PciFunctionHandle) -> (Option<File>, Interru
if let Some(irq) = pci_config.func.legacy_interrupt_line {
// legacy INTx# interrupt pins.
- (Some(irq.irq_handle("xhcid")), InterruptMethod::Intx)
+ match irq.try_irq_handle("xhcid") {
+ Ok(file) => (Some(file), InterruptMethod::Intx),
+ Err(err) => {
+ log::error!("xhcid: failed to open legacy IRQ handle: {err}");
+ (None, InterruptMethod::Polling)
+ }
+ }
} else {
// no interrupts at all
(None, InterruptMethod::Polling)
@@ -136,23 +181,48 @@ fn daemon_with_context_size<const N: usize>(
log::debug!("XHCI PCI CONFIG: {:?}", pci_config);
- let address = unsafe { pcid_handle.map_bar(0) }.ptr.as_ptr() as usize;
+ let address = match unsafe { pcid_handle.try_map_bar(0) } {
+ Ok(bar) => bar.ptr.as_ptr() as usize,
+ Err(err) => {
+ log::error!("xhcid: failed to map BAR0: {err}");
+ std::process::exit(1);
+ }
+ };
+
+ let (irq_file, interrupt_method) = get_int_method(&mut pcid_handle);
- let (irq_file, interrupt_method) = (None, InterruptMethod::Polling); //get_int_method(&mut pcid_handle);
- //TODO: Fix interrupts.
+ match interrupt_method {
+ InterruptMethod::Msi => log::info!("xhcid: using MSI/MSI-X interrupt delivery"),
+ InterruptMethod::Intx => log::info!("xhcid: using legacy INTx interrupt delivery"),
+ InterruptMethod::Polling => log::warn!("xhcid: falling back to polling mode"),
+ }
log::info!("XHCI {}", pci_config.func.display());
let scheme_name = format!("usb.{}", name);
- let socket = Socket::create().expect("xhcid: failed to create usb scheme");
+ let socket = match Socket::create() {
+ Ok(socket) => socket,
+ Err(err) => {
+ log::error!("xhcid: failed to create usb scheme: {err}");
+ std::process::exit(1);
+ }
+ };
let handler = Blocking::new(&socket, 16);
let hci = Arc::new(
- Xhci::<N>::new(scheme_name.clone(), address, interrupt_method, pcid_handle)
- .expect("xhcid: failed to allocate device"),
+ match Xhci::<N>::new(scheme_name.clone(), address, interrupt_method, pcid_handle) {
+ Ok(hci) => hci,
+ Err(err) => {
+ log::error!("xhcid: failed to allocate device: {err}");
+ std::process::exit(1);
+ }
+ },
);
register_sync_scheme(&socket, &scheme_name, &mut &*hci)
- .expect("xhcid: failed to regsiter scheme to namespace");
+ .unwrap_or_else(|err| {
+ log::error!("xhcid: failed to register scheme to namespace: {err}");
+ std::process::exit(1);
+ });
daemon.ready();
@@ -163,7 +233,10 @@ fn daemon_with_context_size<const N: usize>(
handler
.process_requests_blocking(&*hci)
- .expect("xhcid: failed to process requests");
+ .unwrap_or_else(|err| {
+ log::error!("xhcid: failed to process requests: {err}");
+ std::process::exit(1);
+ });
}
fn main() {
@@ -171,7 +244,13 @@ fn main() {
}
fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
- let address = unsafe { pcid_handle.map_bar(0) }.ptr.as_ptr() as usize;
+ let address = match unsafe { pcid_handle.try_map_bar(0) } {
+ Ok(bar) => bar.ptr.as_ptr() as usize,
+ Err(err) => {
+ log::error!("xhcid: failed to map BAR0: {err}");
+ std::process::exit(1);
+ }
+ };
let cap = unsafe { &mut *(address as *mut xhci::CapabilityRegs) };
if cap.csz() {
daemon_with_context_size::<{ xhci::CONTEXT_64 }>(daemon, pcid_handle)
diff --git a/drivers/usb/xhcid/src/xhci/device_enumerator.rs b/drivers/usb/xhcid/src/xhci/device_enumerator.rs
index 74b9f732..493e79df 100644
--- a/drivers/usb/xhcid/src/xhci/device_enumerator.rs
+++ b/drivers/usb/xhcid/src/xhci/device_enumerator.rs
@@ -4,9 +4,11 @@ use common::io::Io;
use crossbeam_channel;
use log::{debug, info, warn};
use std::sync::Arc;
-use std::time::Duration;
+use std::time::{Duration, Instant};
use syscall::EAGAIN;
+const DEFAULT_PORT_RESET_SETTLE_MS: u64 = 16;
+
pub struct DeviceEnumerationRequest {
pub port_id: PortId,
}
@@ -28,7 +30,11 @@ impl<const N: usize> DeviceEnumerator<N> {
let request = match self.request_queue.recv() {
Ok(req) => req,
Err(err) => {
- panic!("Failed to received an enumeration request! error: {}", err)
+ warn!(
+ "device enumerator stopping after request queue closed: {}",
+ err
+ );
+ break;
}
};
@@ -38,7 +44,11 @@ impl<const N: usize> DeviceEnumerator<N> {
debug!("Device Enumerator request for port {}", port_id);
let (len, flags) = {
- let ports = self.hci.ports.lock().unwrap();
+ let ports = self
+ .hci
+ .ports
+ .lock()
+ .unwrap_or_else(|poisoned| poisoned.into_inner());
let len = ports.len();
@@ -62,43 +72,52 @@ impl<const N: usize> DeviceEnumerator<N> {
//A USB3 port won't generate a Connect Status Change until it's already enabled, so this check
//will always be skipped for USB3 ports
if !flags.contains(PortFlags::PED) {
- let disabled_state = flags.contains(PortFlags::PP)
- && flags.contains(PortFlags::CCS)
- && !flags.contains(PortFlags::PED)
- && !flags.contains(PortFlags::PR);
+ let disabled_state = Self::port_is_disabled(&flags);
if !disabled_state {
- panic!(
- "Port {} isn't in the disabled state! Current flags: {:?}",
+ warn!(
+ "Port {} never reached the disabled state before reset-driven enumeration; current flags: {:?}",
port_id, flags
);
+ continue;
} else {
debug!("Port {} has entered the disabled state.", port_id);
}
//THIS LOCKS THE PORTS. DO NOT LOCK PORTS BEFORE THIS POINT
debug!("Received a device connect on port {}, but it's not enabled. Resetting the port.", port_id);
- let _ = self.hci.reset_port(port_id);
+ if let Err(err) = self.hci.reset_port(port_id) {
+ warn!(
+ "failed to reset port {} before enumeration; skipping attach: {}",
+ port_id, err
+ );
+ continue;
+ }
- let mut ports = self.hci.ports.lock().unwrap();
+ let mut ports = self
+ .hci
+ .ports
+ .lock()
+ .unwrap_or_else(|poisoned| poisoned.into_inner());
let port = &mut ports[port_array_index];
port.clear_prc();
- std::thread::sleep(Duration::from_millis(16)); //Some controllers need some extra time to make the transition.
+ drop(ports);
- let flags = port.flags();
+ let flags = self.wait_for_port_enabled_state(
+ port_array_index,
+ Duration::from_millis(DEFAULT_PORT_RESET_SETTLE_MS),
+ );
- let enabled_state = flags.contains(PortFlags::PP)
- && flags.contains(PortFlags::CCS)
- && flags.contains(PortFlags::PED)
- && !flags.contains(PortFlags::PR);
+ let enabled_state = Self::port_is_enabled(&flags);
if !enabled_state {
warn!(
- "Port {} isn't in the enabled state! Current flags: {:?}",
+ "Port {} isn't in the enabled state after bounded reset settle; current flags: {:?}",
port_id, flags
);
+ continue;
} else {
debug!(
"Port {} is in the enabled state. Proceeding with enumeration",
@@ -131,13 +150,60 @@ impl<const N: usize> DeviceEnumerator<N> {
Ok(was_connected) => {
if was_connected {
info!("Device on port {} was detached", port_id);
+ } else {
+ debug!(
+ "Ignoring duplicate or out-of-order detach event for unattached port {}",
+ port_id
+ );
}
}
Err(err) => {
- warn!("processing of device attach request failed! Error: {}", err);
+ warn!("processing of device detach request failed! Error: {}", err);
}
}
}
}
}
+
+ fn port_is_disabled(flags: &PortFlags) -> bool {
+ flags.contains(PortFlags::PP)
+ && flags.contains(PortFlags::CCS)
+ && !flags.contains(PortFlags::PED)
+ && !flags.contains(PortFlags::PR)
+ }
+
+ fn port_is_enabled(flags: &PortFlags) -> bool {
+ flags.contains(PortFlags::PP)
+ && flags.contains(PortFlags::CCS)
+ && flags.contains(PortFlags::PED)
+ && !flags.contains(PortFlags::PR)
+ }
+
+ fn wait_for_port_enabled_state(
+ &self,
+ port_array_index: usize,
+ settle_timeout: Duration,
+ ) -> PortFlags {
+ let start = Instant::now();
+
+ loop {
+ let flags = {
+ let ports = self
+ .hci
+ .ports
+ .lock()
+ .unwrap_or_else(|poisoned| poisoned.into_inner());
+ ports[port_array_index].flags()
+ };
+
+ if Self::port_is_enabled(&flags)
+ || !flags.contains(PortFlags::PR)
+ || start.elapsed() >= settle_timeout
+ {
+ return flags;
+ }
+
+ std::thread::sleep(Duration::from_millis(1));
+ }
+ }
}
diff --git a/drivers/usb/xhcid/src/xhci/irq_reactor.rs b/drivers/usb/xhcid/src/xhci/irq_reactor.rs
index ac492d5b..310fe51f 100644
--- a/drivers/usb/xhcid/src/xhci/irq_reactor.rs
+++ b/drivers/usb/xhcid/src/xhci/irq_reactor.rs
@@ -633,7 +633,10 @@ impl<const N: usize> Xhci<N> {
pub fn with_ring<T, F: FnOnce(&Ring) -> T>(&self, id: RingId, function: F) -> Option<T> {
use super::RingOrStreams;
- let slot_state = self.port_states.get(&id.port)?;
+ let slot_state = self
+ .port_states
+ .get(&id.port)
+ .or_else(|| self.staged_port_states.get(&id.port))?;
let endpoint_state = slot_state.endpoint_states.get(&id.endpoint_num)?;
let ring_ref = match endpoint_state.transfer {
@@ -650,7 +653,10 @@ impl<const N: usize> Xhci<N> {
) -> Option<T> {
use super::RingOrStreams;
- let mut slot_state = self.port_states.get_mut(&id.port)?;
+ let mut slot_state = self
+ .port_states
+ .get_mut(&id.port)
+ .or_else(|| self.staged_port_states.get_mut(&id.port))?;
let mut endpoint_state = slot_state.endpoint_states.get_mut(&id.endpoint_num)?;
let ring_ref = match endpoint_state.transfer {
diff --git a/drivers/usb/xhcid/src/xhci/mod.rs b/drivers/usb/xhcid/src/xhci/mod.rs
index f2143676..9ce15161 100644
--- a/drivers/usb/xhcid/src/xhci/mod.rs
+++ b/drivers/usb/xhcid/src/xhci/mod.rs
@@ -11,12 +11,13 @@
//! documents are specified in the crate-level documentation.
use std::collections::BTreeMap;
use std::convert::TryFrom;
-use std::fs::File;
+use std::fs::{self, File};
use std::sync::atomic::AtomicUsize;
-use std::sync::{Arc, Mutex};
+use std::sync::{Arc, Condvar, Mutex};
+use std::time::Duration;
use std::{mem, process, slice, thread};
-use syscall::error::{Error, Result, EBADF, EBADMSG, EIO, ENOENT};
+use syscall::error::{Error, Result, EBADF, EBADMSG, EBUSY, EIO, ENOENT};
use syscall::{EAGAIN, PAGE_SIZE};
use chashmap::CHashMap;
@@ -77,7 +78,52 @@ pub enum InterruptMethod {
Msi,
}
+const XHCID_TEST_HOOK_PATH: &str = "/tmp/xhcid-test-hook";
+const XHCID_TEST_HOOK_MAX_DELAY_MS: u64 = 5_000;
+
impl<const N: usize> Xhci<N> {
+ fn read_test_hook_command_from_path(path: &str) -> Option<String> {
+ let contents = fs::read_to_string(path).ok()?;
+ contents
+ .lines()
+ .map(|line| line.trim())
+ .find(|line| !line.is_empty() && !line.starts_with('#'))
+ .map(|line| line.to_owned())
+ }
+
+ fn clear_test_hook_command_path(path: &str) {
+ if let Err(err) = fs::remove_file(path) {
+ if err.kind() != std::io::ErrorKind::NotFound {
+ warn!("failed to remove xhcid test hook file {}: {}", path, err);
+ }
+ }
+ }
+
+ fn consume_test_hook_from_path(path: &str, expected: &str) -> bool {
+ match Self::read_test_hook_command_from_path(path) {
+ Some(command) if command == expected => {
+ Self::clear_test_hook_command_path(path);
+ true
+ }
+ _ => false,
+ }
+ }
+
+ fn consume_test_hook_delay_ms_from_path(path: &str, prefix: &str) -> Option<u64> {
+ let command = Self::read_test_hook_command_from_path(path)?;
+ let delay_ms = command.strip_prefix(prefix)?.parse::<u64>().ok()?;
+ Self::clear_test_hook_command_path(path);
+ Some(delay_ms.min(XHCID_TEST_HOOK_MAX_DELAY_MS))
+ }
+
+ pub(crate) fn consume_test_hook(&self, expected: &str) -> bool {
+ Self::consume_test_hook_from_path(XHCID_TEST_HOOK_PATH, expected)
+ }
+
+ pub(crate) fn consume_test_hook_delay_ms(&self, prefix: &str) -> Option<u64> {
+ Self::consume_test_hook_delay_ms_from_path(XHCID_TEST_HOOK_PATH, prefix)
+ }
+
/// Gets descriptors, before the port state is initiated.
async fn get_desc_raw<T>(
&self,
@@ -104,7 +150,17 @@ impl<const N: usize> Xhci<N> {
);
let future = {
- let mut port_state = self.port_states.get_mut(&port).ok_or(Error::new(ENOENT))?;
+ let mut published_port_state = self.port_states.get_mut(&port);
+ let mut staged_port_state = if published_port_state.is_none() {
+ self.staged_port_states.get_mut(&port)
+ } else {
+ None
+ };
+
+ let port_state = published_port_state
+ .as_deref_mut()
+ .or_else(|| staged_port_state.as_deref_mut())
+ .ok_or(Error::new(ENOENT))?;
let ring = port_state
.endpoint_states
.get_mut(&0)
@@ -283,6 +339,7 @@ pub struct Xhci<const N: usize> {
handles: CHashMap<usize, scheme::Handle>,
next_handle: AtomicUsize,
port_states: CHashMap<PortId, PortState<N>>,
+ staged_port_states: CHashMap<PortId, PortState<N>>,
drivers: CHashMap<PortId, Vec<process::Child>>,
scheme_name: String,
@@ -311,6 +368,93 @@ struct PortState<const N: usize> {
input_context: Mutex<Dma<InputContext<N>>>,
dev_desc: Option<DevDesc>,
endpoint_states: BTreeMap<u8, EndpointState>,
+ lifecycle: Arc<PortLifecycle>,
+ pm_state: PortPmState,
+}
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub(crate) enum PortLifecycleState {
+ Attaching,
+ Attached,
+ Detaching,
+}
+
+struct PortLifecycleInner {
+ state: PortLifecycleState,
+ active_operations: usize,
+}
+
+pub(crate) struct PortLifecycle {
+ inner: Mutex<PortLifecycleInner>,
+ idle: Condvar,
+}
+
+impl PortLifecycle {
+ pub(crate) fn new_attaching() -> Self {
+ Self {
+ inner: Mutex::new(PortLifecycleInner {
+ state: PortLifecycleState::Attaching,
+ active_operations: 1,
+ }),
+ idle: Condvar::new(),
+ }
+ }
+
+ fn lock_inner(&self) -> std::sync::MutexGuard<'_, PortLifecycleInner> {
+ self.inner.lock().unwrap_or_else(|err| err.into_inner())
+ }
+
+ pub(crate) fn finish_attach_success(&self) -> PortLifecycleState {
+ let mut inner = self.lock_inner();
+
+ if inner.state == PortLifecycleState::Attaching {
+ inner.state = PortLifecycleState::Attached;
+ }
+
+ if inner.active_operations != 0 {
+ inner.active_operations -= 1;
+ }
+ if inner.active_operations == 0 {
+ self.idle.notify_all();
+ }
+
+ inner.state
+ }
+
+ pub(crate) fn finish_attach_failure(&self) {
+ let mut inner = self.lock_inner();
+ inner.state = PortLifecycleState::Detaching;
+
+ if inner.active_operations != 0 {
+ inner.active_operations -= 1;
+ }
+ if inner.active_operations == 0 {
+ self.idle.notify_all();
+ }
+ }
+
+ pub(crate) fn begin_detaching(&self) {
+ let mut inner = self.lock_inner();
+ inner.state = PortLifecycleState::Detaching;
+
+ while inner.active_operations != 0 {
+ inner = self.idle.wait(inner).unwrap_or_else(|err| err.into_inner());
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub(crate) enum PortPmState {
+ Active,
+ Suspended,
+}
+impl PortPmState {
+ pub fn as_str(&self) -> &'static str {
+ match self {
+ Self::Active => "active",
+ Self::Suspended => "suspended",
+ }
+ }
}
impl<const N: usize> PortState<N> {
@@ -463,6 +607,7 @@ impl<const N: usize> Xhci<N> {
handles: CHashMap::new(),
next_handle: AtomicUsize::new(0),
port_states: CHashMap::new(),
+ staged_port_states: CHashMap::new(),
drivers: CHashMap::new(),
scheme_name,
@@ -793,11 +938,14 @@ impl<const N: usize> Xhci<N> {
}
pub async fn attach_device(&self, port_id: PortId) -> syscall::Result<()> {
- if self.port_states.contains_key(&port_id) {
+ if self.port_states.contains_key(&port_id) || self.staged_port_states.contains_key(&port_id)
+ {
debug!("Already contains port {}", port_id);
return Err(syscall::Error::new(EAGAIN));
}
+ info!("xhcid: begin attach for port {}", port_id);
+
let (data, state, speed, flags) = {
let port = &self.ports.lock().unwrap()[port_id.root_hub_port_index()];
(port.read(), port.state(), port.speed(), port.flags())
@@ -808,74 +956,101 @@ impl<const N: usize> Xhci<N> {
port_id, data, state, speed, flags
);
- if flags.contains(port::PortFlags::CCS) {
- let slot_ty = match self.supported_protocol(port_id) {
- Some(protocol) => protocol.proto_slot_ty(),
- None => {
- warn!("Failed to find supported protocol information for port");
- 0
- }
- };
-
- debug!("Slot type: {}", slot_ty);
- debug!("Enabling slot.");
- let slot = match self.enable_port_slot(slot_ty).await {
- Ok(ok) => ok,
- Err(err) => {
- error!("Failed to enable slot for port {}: {}", port_id, err);
- return Err(err);
- }
- };
+ if !flags.contains(port::PortFlags::CCS) {
+ warn!("Attempted to attach a device that didnt have CCS=1");
+ return Ok(());
+ }
- debug!("Enabled port {}, which the xHC mapped to {}", port_id, slot);
+ let slot_ty = match self.supported_protocol(port_id) {
+ Some(protocol) => protocol.proto_slot_ty(),
+ None => {
+ warn!("Failed to find supported protocol information for port");
+ 0
+ }
+ };
- //TODO: get correct speed for child devices
- let protocol_speed = self
- .lookup_psiv(port_id, speed)
- .expect("Failed to retrieve speed ID");
+ debug!("Slot type: {}", slot_ty);
+ debug!("Enabling slot.");
+ let slot = match self.enable_port_slot(slot_ty).await {
+ Ok(ok) => ok,
+ Err(err) => {
+ error!("Failed to enable slot for port {}: {}", port_id, err);
+ return Err(err);
+ }
+ };
- let mut input = unsafe { self.alloc_dma_zeroed::<InputContext<N>>()? };
+ debug!("Enabled port {}, which the xHC mapped to {}", port_id, slot);
- debug!("Attempting to address the device");
- let mut ring = match self
- .address_device(&mut input, port_id, slot_ty, slot, protocol_speed, speed)
- .await
- {
- Ok(device_ring) => device_ring,
- Err(err) => {
- error!("Failed to address device for port {}: `{}`", port_id, err);
- return Err(err);
+ let protocol_speed = match self.lookup_psiv(port_id, speed) {
+ Some(protocol_speed) => protocol_speed,
+ None => {
+ let err = Error::new(EIO);
+ error!("Failed to retrieve speed ID for port {}", port_id);
+ if let Err(disable_err) = self.disable_port_slot(slot).await {
+ warn!(
+ "Failed to disable slot {} after speed lookup failure on port {}: {}",
+ slot, port_id, disable_err
+ );
}
- };
+ return Err(err);
+ }
+ };
- debug!("Addressed device");
+ let mut input = unsafe { self.alloc_dma_zeroed::<InputContext<N>>()? };
- // TODO: Should the descriptors be cached in PortState, or refetched?
+ debug!("Attempting to address the device");
+ let ring = match self
+ .address_device(&mut input, port_id, slot_ty, slot, protocol_speed, speed)
+ .await
+ {
+ Ok(device_ring) => device_ring,
+ Err(err) => {
+ error!("Failed to address device for port {}: `{}`", port_id, err);
+ if let Err(disable_err) = self.disable_port_slot(slot).await {
+ warn!(
+ "Failed to disable slot {} after address failure on port {}: {}",
+ slot, port_id, disable_err
+ );
+ }
+ return Err(err);
+ }
+ };
- let mut port_state = PortState {
- slot,
- protocol_speed,
- input_context: Mutex::new(input),
- dev_desc: None,
- cfg_idx: None,
- endpoint_states: std::iter::once((
- 0,
- EndpointState {
- transfer: RingOrStreams::Ring(ring),
- driver_if_state: EndpIfState::Init,
- },
- ))
- .collect::<BTreeMap<_, _>>(),
- };
- self.port_states.insert(port_id, port_state);
- debug!("Got port states!");
+ debug!("Addressed device");
- // Ensure correct packet size is used
+ let lifecycle = Arc::new(PortLifecycle::new_attaching());
+ let port_state = PortState {
+ slot,
+ protocol_speed,
+ input_context: Mutex::new(input),
+ dev_desc: None,
+ cfg_idx: None,
+ endpoint_states: std::iter::once((
+ 0,
+ EndpointState {
+ transfer: RingOrStreams::Ring(ring),
+ driver_if_state: EndpIfState::Init,
+ },
+ ))
+ .collect::<BTreeMap<_, _>>(),
+ lifecycle: Arc::clone(&lifecycle),
+ pm_state: PortPmState::Active,
+ };
+ self.staged_port_states.insert(port_id, port_state);
+ debug!("Got staged port state!");
+
+ let attach_result = async {
let dev_desc_8_byte = self.fetch_dev_desc_8_byte(port_id, slot).await?;
{
- let mut port_state = self.port_states.get_mut(&port_id).unwrap();
+ let mut port_state = self
+ .staged_port_states
+ .get_mut(&port_id)
+ .ok_or(Error::new(ENOENT))?;
- let mut input = port_state.input_context.lock().unwrap();
+ let mut input = port_state
+ .input_context
+ .lock()
+ .unwrap_or_else(|err| err.into_inner());
self.update_max_packet_size(&mut *input, slot, dev_desc_8_byte)
.await?;
@@ -885,97 +1060,175 @@ impl<const N: usize> Xhci<N> {
let dev_desc = self.get_desc(port_id, slot).await?;
debug!("Got the full device descriptor!");
- self.port_states.get_mut(&port_id).unwrap().dev_desc = Some(dev_desc);
+ self.staged_port_states
+ .get_mut(&port_id)
+ .ok_or(Error::new(ENOENT))?
+ .dev_desc = Some(dev_desc);
debug!("Got the port states again!");
{
- let mut port_state = self.port_states.get_mut(&port_id).unwrap();
-
- let mut input = port_state.input_context.lock().unwrap();
+ let mut port_state = self
+ .staged_port_states
+ .get_mut(&port_id)
+ .ok_or(Error::new(ENOENT))?;
+
+ let mut input = port_state
+ .input_context
+ .lock()
+ .unwrap_or_else(|err| err.into_inner());
debug!("Got the input context!");
- let dev_desc = port_state.dev_desc.as_ref().unwrap();
+ let dev_desc = port_state.dev_desc.as_ref().ok_or(Error::new(EIO))?;
self.update_default_control_pipe(&mut *input, slot, dev_desc)
.await?;
}
debug!("Updated the default control pipe");
+ Ok(())
+ }
+ .await;
+
+ match attach_result {
+ Ok(()) => {
+ if let Some(delay_ms) =
+ self.consume_test_hook_delay_ms("delay_before_attach_commit_ms=")
+ {
+ info!(
+ "xhcid: test hook delaying attach commit for port {} by {} ms",
+ port_id, delay_ms
+ );
+ thread::sleep(Duration::from_millis(delay_ms));
+ }
- match self.spawn_drivers(port_id) {
- Ok(()) => (),
- Err(err) => {
- error!("Failed to spawn driver for port {}: `{}`", port_id, err)
+ if lifecycle.finish_attach_success() != PortLifecycleState::Attached {
+ warn!(
+ "attach for port {} completed after detach already started; skipping publication",
+ port_id
+ );
+ return Err(Error::new(EBUSY));
}
+
+ let staged_port_state = self
+ .staged_port_states
+ .remove(&port_id)
+ .ok_or(Error::new(ENOENT))?;
+ self.port_states.insert(port_id, staged_port_state);
+
+ match self.spawn_drivers(port_id) {
+ Ok(()) => (),
+ Err(err) => {
+ error!("Failed to spawn driver for port {}: `{}`", port_id, err)
+ }
+ }
+
+ info!("xhcid: finished attach for port {}", port_id);
+ Ok(())
+ }
+ Err(err) => {
+ lifecycle.finish_attach_failure();
+ if let Err(detach_err) = self.detach_device(port_id).await {
+ warn!(
+ "failed to clean up attach failure on port {}: {}",
+ port_id, detach_err
+ );
+ }
+ Err(err)
}
- } else {
- warn!("Attempted to attach a device that didnt have CCS=1");
}
-
- Ok(())
}
pub async fn detach_device(&self, port_id: PortId) -> Result<bool> {
- if let Some(children) = self.drivers.remove(&port_id) {
- for mut child in children {
- info!("killing driver process {} for port {}", child.id(), port_id);
- match child.kill() {
- Ok(()) => {
- info!("killed driver process {} for port {}", child.id(), port_id);
- match child.try_wait() {
- Ok(status_opt) => match status_opt {
- Some(status) => {
- debug!(
- "driver process {} for port {} exited with status {}",
- child.id(),
- port_id,
- status
- );
- }
- None => {
- //TODO: kill harder
+ let published_state = self.port_states.get(&port_id);
+ let staged_state = if published_state.is_none() {
+ self.staged_port_states.get(&port_id)
+ } else {
+ None
+ };
+
+ let (slot, lifecycle, was_published) = match published_state
+ .as_deref()
+ .or_else(|| staged_state.as_deref())
+ {
+ Some(state) => (state.slot, Arc::clone(&state.lifecycle), published_state.is_some()),
+ None => {
+ debug!(
+ "Attempted to detach from port {}, which wasn't previously attached.",
+ port_id
+ );
+ return Ok(false);
+ }
+ };
+ drop(published_state);
+ drop(staged_state);
+
+ lifecycle.begin_detaching();
+
+ if was_published {
+ if let Some(children) = self.drivers.remove(&port_id) {
+ for mut child in children {
+ info!("killing driver process {} for port {}", child.id(), port_id);
+ match child.kill() {
+ Ok(()) => {
+ info!("killed driver process {} for port {}", child.id(), port_id);
+ match child.try_wait() {
+ Ok(status_opt) => match status_opt {
+ Some(status) => {
+ debug!(
+ "driver process {} for port {} exited with status {}",
+ child.id(),
+ port_id,
+ status
+ );
+ }
+ None => {
+ warn!(
+ "driver process {} for port {} still running",
+ child.id(),
+ port_id
+ );
+ }
+ },
+ Err(err) => {
warn!(
- "driver process {} for port {} still running",
+ "failed to wait for the driver process {} for port {}: {}",
child.id(),
- port_id
+ port_id,
+ err
);
}
- },
- Err(err) => {
- warn!(
- "failed to wait for the driver process {} for port {}: {}",
- child.id(),
- port_id,
- err
- );
}
}
- }
- Err(err) => {
- warn!(
- "failed to kill the driver process {} for port {}: {}",
- child.id(),
- port_id,
- err
- );
+ Err(err) => {
+ warn!(
+ "failed to kill the driver process {} for port {}: {}",
+ child.id(),
+ port_id,
+ err
+ );
+ }
}
}
}
}
- if let Some(state) = self.port_states.remove(&port_id) {
- debug!("disabling port slot {} for port {}", state.slot, port_id);
- let result = self.disable_port_slot(state.slot).await.and(Ok(true));
- debug!(
- "disabled port slot {} for port {} with result: {:?}",
- state.slot, port_id, result
- );
- result
- } else {
- debug!(
- "Attempted to detach from port {}, which wasn't previously attached.",
- port_id
- );
- Ok(false)
+ debug!("disabling port slot {} for port {}", slot, port_id);
+ match self.disable_port_slot(slot).await {
+ Ok(()) => {
+ if was_published {
+ let _ = self.port_states.remove(&port_id);
+ } else {
+ let _ = self.staged_port_states.remove(&port_id);
+ }
+ debug!("disabled port slot {} for port {}", slot, port_id);
+ Ok(true)
+ }
+ Err(err) => {
+ warn!(
+ "failed to disable port slot {} for port {}: {}",
+ slot, port_id, err
+ );
+ Err(err)
+ }
}
}
@@ -1458,6 +1711,53 @@ pub fn start_device_enumerator<const N: usize>(hci: &Arc<Xhci<N>>) {
}));
}
+#[cfg(test)]
+mod tests {
+ use std::fs;
+ use std::path::Path;
+ use std::time::{SystemTime, UNIX_EPOCH};
+
+ use super::{Xhci, XHCID_TEST_HOOK_MAX_DELAY_MS};
+
+ fn unique_test_hook_path() -> String {
+ let unique = SystemTime::now()
+ .duration_since(UNIX_EPOCH)
+ .unwrap()
+ .as_nanos();
+ format!("/tmp/xhcid-test-hook-{}", unique)
+ }
+
+ #[test]
+ fn consume_test_hook_only_clears_matching_command() {
+ let path = unique_test_hook_path();
+ fs::write(&path, "fail_after_set_configuration\n").unwrap();
+
+ assert!(!Xhci::<16>::consume_test_hook_from_path(
+ &path,
+ "fail_after_configure_endpoint"
+ ));
+ assert!(Path::new(&path).exists());
+
+ assert!(Xhci::<16>::consume_test_hook_from_path(
+ &path,
+ "fail_after_set_configuration"
+ ));
+ assert!(!Path::new(&path).exists());
+ }
+
+ #[test]
+ fn consume_test_hook_delay_clamps_and_clears() {
+ let path = unique_test_hook_path();
+ fs::write(&path, "delay_before_attach_commit_ms=999999\n").unwrap();
+
+ assert_eq!(
+ Xhci::<16>::consume_test_hook_delay_ms_from_path(&path, "delay_before_attach_commit_ms="),
+ Some(XHCID_TEST_HOOK_MAX_DELAY_MS)
+ );
+ assert!(!Path::new(&path).exists());
+ }
+}
+
#[derive(Deserialize)]
struct DriverConfig {
name: String,
diff --git a/drivers/usb/xhcid/src/xhci/scheme.rs b/drivers/usb/xhcid/src/xhci/scheme.rs
index b524f5d8..aac4fa82 100644
--- a/drivers/usb/xhcid/src/xhci/scheme.rs
+++ b/drivers/usb/xhcid/src/xhci/scheme.rs
@@ -20,6 +20,7 @@ use std::convert::TryFrom;
use std::io::prelude::*;
use std::ops::Deref;
use std::sync::atomic;
+use std::collections::BTreeMap;
use std::{cmp, fmt, io, mem, str};
use common::dma::Dma;
@@ -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(&REGEX_PORT_DETACH, scheme, 0)?;
Ok(Self::DetachDevice(port_num))
+ } else if REGEX_PORT_SUSPEND.is_match(scheme) {
+ let port_num = get_port_id_from_regex(&REGEX_PORT_SUSPEND, scheme, 0)?;
+
+ Ok(Self::SuspendDevice(port_num))
+ } else if REGEX_PORT_RESUME.is_match(scheme) {
+ let port_num = get_port_id_from_regex(&REGEX_PORT_RESUME, scheme, 0)?;
+
+ Ok(Self::ResumeDevice(port_num))
} else if REGEX_PORT_DESCRIPTORS.is_match(scheme) {
let port_num = get_port_id_from_regex(&REGEX_PORT_DESCRIPTORS, scheme, 0)?;
@@ -391,6 +430,10 @@ impl SchemeParameters {
let port_num = get_port_id_from_regex(&REGEX_PORT_STATE, scheme, 0)?;
Ok(Self::PortState(port_num))
+ } else if REGEX_PORT_PM_STATE.is_match(scheme) {
+ let port_num = get_port_id_from_regex(&REGEX_PORT_PM_STATE, scheme, 0)?;
+
+ Ok(Self::PortPmState(port_num))
} else if REGEX_PORT_REQUEST.is_match(scheme) {
let port_num = get_port_id_from_regex(&REGEX_PORT_REQUEST, scheme, 0)?;
@@ -523,6 +566,39 @@ pub enum AnyDescriptor {
SuperSpeedPlusCompanion(usb::SuperSpeedPlusIsochCmpDescriptor),
}
+#[derive(Clone, Copy)]
+struct ConfigureContextSnapshot {
+ add_context: u32,
+ drop_context: u32,
+ control: u32,
+ slot_a: u32,
+ slot_b: u32,
+}
+
+#[derive(Clone, Copy)]
+struct EndpointContextSnapshot {
+ a: u32,
+ b: u32,
+ trl: u32,
+ trh: u32,
+ c: u32,
+}
+
+impl EndpointContextSnapshot {
+ fn capture_values(a: u32, b: u32, trl: u32, trh: u32, c: u32) -> Self {
+ Self { a, b, trl, trh, c }
+ }
+}
+
+struct EndpointProgram {
+ endp_num_xhc: u8,
+ a: u32,
+ b: u32,
+ trl: u32,
+ trh: u32,
+ c: u32,
+}
+
impl AnyDescriptor {
fn parse(bytes: &[u8]) -> Option<(Self, usize)> {
if bytes.len() < 2 {
@@ -639,6 +715,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;
@@ -709,6 +787,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)?;
@@ -949,35 +1029,102 @@ impl<const N: usize> Xhci<N> {
self.port_states.get_mut(&port).ok_or(Error::new(EBADF))
}
+ fn restore_configure_input_context(
+ &self,
+ port: PortId,
+ snapshot: ConfigureContextSnapshot,
+ endpoint_snapshots: &[(usize, EndpointContextSnapshot)],
+ ) -> Result<usize> {
+ let port_state = self.port_states.get(&port).ok_or(Error::new(EBADFD))?;
+ let mut input_context = port_state.input_context.lock().unwrap();
+
+ input_context.add_context.write(snapshot.add_context);
+ input_context.drop_context.write(snapshot.drop_context);
+ input_context.control.write(snapshot.control);
+ input_context.device.slot.a.write(snapshot.slot_a);
+ input_context.device.slot.b.write(snapshot.slot_b);
+
+ for (endp_i, endp_snapshot) in endpoint_snapshots {
+ input_context.device.endpoints[*endp_i].a.write(endp_snapshot.a);
+ input_context.device.endpoints[*endp_i].b.write(endp_snapshot.b);
+ input_context.device.endpoints[*endp_i].trl.write(endp_snapshot.trl);
+ input_context.device.endpoints[*endp_i].trh.write(endp_snapshot.trh);
+ input_context.device.endpoints[*endp_i].c.write(endp_snapshot.c);
+ }
+
+ Ok(input_context.physical())
+ }
+
+ async fn rollback_configure_attempt(
+ &self,
+ port: PortId,
+ slot: u8,
+ configure_snapshot: ConfigureContextSnapshot,
+ endpoint_snapshots: &[(usize, EndpointContextSnapshot)],
+ stage: &str,
+ ) {
+ let rollback_input_context_physical = match self.restore_configure_input_context(
+ port,
+ configure_snapshot,
+ endpoint_snapshots,
+ ) {
+ Ok(physical) => physical,
+ Err(restore_err) => {
+ warn!(
+ "failed to restore configure input context after {}: {:?}",
+ stage, restore_err
+ );
+ return;
+ }
+ };
+
+ let (rollback_event_trb, rollback_command_trb) = self
+ .execute_command(|trb, cycle| {
+ trb.configure_endpoint(slot, rollback_input_context_physical, cycle)
+ })
+ .await;
+
+ if let Err(rollback_err) = handle_event_trb(
+ "CONFIGURE_ENDPOINT_ROLLBACK",
+ &rollback_event_trb,
+ &rollback_command_trb,
+ ) {
+ warn!(
+ "failed to roll back CONFIGURE_ENDPOINT after {}: {:?}",
+ stage, rollback_err
+ );
+ }
+ }
+
async fn configure_endpoints_once(
&self,
port: PortId,
req: &ConfigureEndpointsReq,
) -> Result<()> {
- let (endp_desc_count, new_context_entries, configuration_value) = {
- let mut port_state = self.port_states.get_mut(&port).ok_or(Error::new(EBADFD))?;
-
- port_state.cfg_idx = Some(req.config_desc);
+ let (dev_desc, endpoint_descs, new_context_entries, configuration_value, speed_id) = {
+ let port_state = self.port_states.get(&port).ok_or(Error::new(EBADFD))?;
+ let dev_desc = port_state.dev_desc.as_ref().ok_or(Error::new(EBADFD))?.clone();
+ let speed_id = port_state.protocol_speed;
- let config_desc = port_state
- .dev_desc
- .as_ref()
- .unwrap()
+ let config_desc = dev_desc
.config_descs
.iter()
.find(|desc| desc.configuration_value == req.config_desc)
.ok_or(Error::new(EBADFD))?;
- //TODO: USE ENDPOINTS FROM ALL INTERFACES
- let mut endp_desc_count = 0;
- let mut new_context_entries = 1;
- for if_desc in config_desc.interface_descs.iter() {
- for endpoint in if_desc.endpoints.iter() {
- endp_desc_count += 1;
- let entry = Self::endp_num_to_dci(endp_desc_count, endpoint);
- if entry > new_context_entries {
- new_context_entries = entry;
- }
+ let configuration_value = config_desc.configuration_value;
+ let endpoint_descs = config_desc
+ .interface_descs
+ .iter()
+ .flat_map(|if_desc| if_desc.endpoints.iter().copied())
+ .collect::<Vec<_>>();
+
+ let endp_desc_count = endpoint_descs.len();
+ let mut new_context_entries = 1u8;
+ for (endp_idx, endpoint) in endpoint_descs.iter().enumerate() {
+ let entry = Self::endp_num_to_dci(endp_idx as u8 + 1, endpoint);
+ if entry > new_context_entries {
+ new_context_entries = entry;
}
}
new_context_entries += 1;
@@ -988,74 +1135,22 @@ impl<const N: usize> Xhci<N> {
}
(
- endp_desc_count,
+ dev_desc,
+ endpoint_descs,
new_context_entries,
- config_desc.configuration_value,
+ configuration_value,
+ speed_id,
)
};
let lec = self.cap.lec();
let log_max_psa_size = self.cap.max_psa_size();
- let port_speed_id = self.ports.lock().unwrap()[port.root_hub_port_index()].speed();
- let speed_id: &ProtocolSpeed = self.lookup_psiv(port, port_speed_id).ok_or_else(|| {
- warn!("no speed_id");
- Error::new(EIO)
- })?;
-
- {
- let port_state = self.port_states.get(&port).ok_or(Error::new(EBADFD))?;
- let mut input_context = port_state.input_context.lock().unwrap();
-
- // Configure the slot context as well, which holds the last index of the endp descs.
- input_context.add_context.write(1);
- input_context.drop_context.write(0);
-
- const CONTEXT_ENTRIES_MASK: u32 = 0xF800_0000;
- const CONTEXT_ENTRIES_SHIFT: u8 = 27;
-
- const HUB_PORTS_MASK: u32 = 0xFF00_0000;
- const HUB_PORTS_SHIFT: u8 = 24;
-
- let mut current_slot_a = input_context.device.slot.a.read();
- let mut current_slot_b = input_context.device.slot.b.read();
-
- // Set context entries
- current_slot_a &= !CONTEXT_ENTRIES_MASK;
- current_slot_a |=
- (u32::from(new_context_entries) << CONTEXT_ENTRIES_SHIFT) & CONTEXT_ENTRIES_MASK;
-
- // Set hub data
- current_slot_a &= !(1 << 26);
- current_slot_b &= !HUB_PORTS_MASK;
- if let Some(hub_ports) = req.hub_ports {
- current_slot_a |= 1 << 26;
- current_slot_b |= (u32::from(hub_ports) << HUB_PORTS_SHIFT) & HUB_PORTS_MASK;
- }
-
- input_context.device.slot.a.write(current_slot_a);
- input_context.device.slot.b.write(current_slot_b);
-
- let control = if self.op.lock().unwrap().cie() {
- (u32::from(req.alternate_setting.unwrap_or(0)) << 16)
- | (u32::from(req.interface_desc.unwrap_or(0)) << 8)
- | u32::from(configuration_value)
- } else {
- 0
- };
- input_context.control.write(control);
- }
-
- for endp_idx in 0..endp_desc_count as u8 {
- let endp_num = endp_idx + 1;
-
- let mut port_state = self.port_states.get_mut(&port).ok_or(Error::new(EBADFD))?;
- let dev_desc = port_state.dev_desc.as_ref().unwrap();
- let endp_desc = port_state.get_endp_desc(endp_idx).ok_or_else(|| {
- warn!("failed to find endpoint {}", endp_idx);
- Error::new(EIO)
- })?;
+ let mut staged_endpoint_states = BTreeMap::new();
+ let mut endpoint_programs = Vec::new();
- let endp_num_xhc = Self::endp_num_to_dci(endp_num, endp_desc);
+ for (endp_idx, endp_desc) in endpoint_descs.iter().copied().enumerate() {
+ let endp_num = endp_idx as u8 + 1;
+ let endp_num_xhc = Self::endp_num_to_dci(endp_num, &endp_desc);
let usb_log_max_streams = endp_desc.log_max_streams();
@@ -1077,20 +1172,20 @@ impl<const N: usize> Xhci<N> {
let mult = endp_desc.isoch_mult(lec);
- let max_packet_size = Self::endp_ctx_max_packet_size(endp_desc);
- let max_burst_size = Self::endp_ctx_max_burst(speed_id, dev_desc, endp_desc);
+ let max_packet_size = Self::endp_ctx_max_packet_size(&endp_desc);
+ let max_burst_size = Self::endp_ctx_max_burst(speed_id, &dev_desc, &endp_desc);
let max_esit_payload = Self::endp_ctx_max_esit_payload(
speed_id,
- dev_desc,
- endp_desc,
+ &dev_desc,
+ &endp_desc,
max_packet_size,
max_burst_size,
);
let max_esit_payload_lo = max_esit_payload as u16;
let max_esit_payload_hi = ((max_esit_payload & 0x00FF_0000) >> 16) as u8;
- let interval = Self::endp_ctx_interval(speed_id, endp_desc);
+ let interval = Self::endp_ctx_interval(speed_id, &endp_desc);
let max_error_count = 3;
let ep_ty = endp_desc.xhci_ep_type()?;
@@ -1113,7 +1208,7 @@ impl<const N: usize> Xhci<N> {
assert_eq!(max_error_count & 0x3, max_error_count);
assert_ne!(ep_ty, 0); // 0 means invalid.
- let ring_ptr = if usb_log_max_streams.is_some() {
+ let (endpoint_state, ring_ptr) = if usb_log_max_streams.is_some() {
let mut array =
StreamContextArray::new::<N>(self.cap.ac64(), 1 << (primary_streams + 1))?;
@@ -1126,15 +1221,13 @@ impl<const N: usize> Xhci<N> {
array_ptr,
"stream ctx ptr not aligned to 16 bytes"
);
- port_state.endpoint_states.insert(
- endp_num,
+ (
EndpointState {
transfer: super::RingOrStreams::Streams(array),
driver_if_state: EndpIfState::Init,
},
- );
-
- array_ptr
+ array_ptr,
+ )
} else {
let ring = Ring::new::<N>(self.cap.ac64(), 16, true)?;
let ring_ptr = ring.register();
@@ -1144,68 +1237,185 @@ impl<const N: usize> Xhci<N> {
ring_ptr,
"ring pointer not aligned to 16 bytes"
);
- port_state.endpoint_states.insert(
- endp_num,
+ (
EndpointState {
transfer: super::RingOrStreams::Ring(ring),
driver_if_state: EndpIfState::Init,
},
- );
- ring_ptr
+ ring_ptr,
+ )
};
assert_eq!(primary_streams & 0x1F, primary_streams);
- let mut input_context = port_state.input_context.lock().unwrap();
- input_context.add_context.writef(1 << endp_num_xhc, true);
-
- let endp_i = endp_num_xhc as usize - 1;
- input_context.device.endpoints[endp_i].a.write(
- u32::from(mult) << 8
+ staged_endpoint_states.insert(endp_num, endpoint_state);
+ endpoint_programs.push(EndpointProgram {
+ endp_num_xhc,
+ a: u32::from(mult) << 8
| u32::from(primary_streams) << 10
| u32::from(linear_stream_array) << 15
| u32::from(interval) << 16
| u32::from(max_esit_payload_hi) << 24,
- );
- input_context.device.endpoints[endp_i].b.write(
- max_error_count << 1
+ b: max_error_count << 1
| u32::from(ep_ty) << 3
| u32::from(host_initiate_disable) << 7
| u32::from(max_burst_size) << 8
| u32::from(max_packet_size) << 16,
- );
-
- input_context.device.endpoints[endp_i]
- .trl
- .write(ring_ptr as u32);
- input_context.device.endpoints[endp_i]
- .trh
- .write((ring_ptr >> 32) as u32);
-
- input_context.device.endpoints[endp_i]
- .c
- .write(u32::from(avg_trb_len) | (u32::from(max_esit_payload_lo) << 16));
+ trl: ring_ptr as u32,
+ trh: (ring_ptr >> 32) as u32,
+ c: u32::from(avg_trb_len) | (u32::from(max_esit_payload_lo) << 16),
+ });
- log::debug!("initialized endpoint {}", endp_num);
+ log::debug!("staged endpoint {}", endp_num);
}
- {
+ let (configure_snapshot, endpoint_snapshots, input_context_physical) = {
let port_state = self.port_states.get(&port).ok_or(Error::new(EBADFD))?;
- let slot = port_state.slot;
- let input_context_physical = port_state.input_context.lock().unwrap().physical();
+ let mut input_context = port_state.input_context.lock().unwrap();
+
+ let configure_snapshot = ConfigureContextSnapshot {
+ add_context: input_context.add_context.read(),
+ drop_context: input_context.drop_context.read(),
+ control: input_context.control.read(),
+ slot_a: input_context.device.slot.a.read(),
+ slot_b: input_context.device.slot.b.read(),
+ };
- let (event_trb, command_trb) = self
- .execute_command(|trb, cycle| {
- trb.configure_endpoint(slot, input_context_physical, cycle)
+ let endpoint_snapshots = endpoint_programs
+ .iter()
+ .map(|program| {
+ let endp_i = program.endp_num_xhc as usize - 1;
+ (
+ endp_i,
+ EndpointContextSnapshot::capture_values(
+ input_context.device.endpoints[endp_i].a.read(),
+ input_context.device.endpoints[endp_i].b.read(),
+ input_context.device.endpoints[endp_i].trl.read(),
+ input_context.device.endpoints[endp_i].trh.read(),
+ input_context.device.endpoints[endp_i].c.read(),
+ ),
+ )
})
- .await;
+ .collect::<Vec<_>>();
- //self.event_handler_finished();
+ // Configure the slot context as well, which holds the last index of the endp descs.
+ input_context.add_context.write(1);
+ input_context.drop_context.write(0);
- handle_event_trb("CONFIGURE_ENDPOINT", &event_trb, &command_trb)?;
+ const CONTEXT_ENTRIES_MASK: u32 = 0xF800_0000;
+ const CONTEXT_ENTRIES_SHIFT: u8 = 27;
+
+ const HUB_PORTS_MASK: u32 = 0xFF00_0000;
+ const HUB_PORTS_SHIFT: u8 = 24;
+
+ let mut current_slot_a = input_context.device.slot.a.read();
+ let mut current_slot_b = input_context.device.slot.b.read();
+
+ current_slot_a &= !CONTEXT_ENTRIES_MASK;
+ current_slot_a |=
+ (u32::from(new_context_entries) << CONTEXT_ENTRIES_SHIFT) & CONTEXT_ENTRIES_MASK;
+
+ current_slot_a &= !(1 << 26);
+ current_slot_b &= !HUB_PORTS_MASK;
+ if let Some(hub_ports) = req.hub_ports {
+ current_slot_a |= 1 << 26;
+ current_slot_b |= (u32::from(hub_ports) << HUB_PORTS_SHIFT) & HUB_PORTS_MASK;
+ }
+
+ input_context.device.slot.a.write(current_slot_a);
+ input_context.device.slot.b.write(current_slot_b);
+
+ let control = if self.op.lock().unwrap().cie() {
+ (u32::from(req.alternate_setting.unwrap_or(0)) << 16)
+ | (u32::from(req.interface_desc.unwrap_or(0)) << 8)
+ | u32::from(configuration_value)
+ } else {
+ 0
+ };
+ input_context.control.write(control);
+
+ for program in &endpoint_programs {
+ let endp_i = program.endp_num_xhc as usize - 1;
+ input_context.add_context.writef(1 << program.endp_num_xhc, true);
+ input_context.device.endpoints[endp_i].a.write(program.a);
+ input_context.device.endpoints[endp_i].b.write(program.b);
+ input_context.device.endpoints[endp_i].trl.write(program.trl);
+ input_context.device.endpoints[endp_i].trh.write(program.trh);
+ input_context.device.endpoints[endp_i].c.write(program.c);
+ }
+
+ (configure_snapshot, endpoint_snapshots, input_context.physical())
+ };
+
+ let slot = self.port_states.get(&port).ok_or(Error::new(EBADFD))?.slot;
+
+ let (event_trb, command_trb) = self
+ .execute_command(|trb, cycle| trb.configure_endpoint(slot, input_context_physical, cycle))
+ .await;
+
+ if let Err(err) = handle_event_trb("CONFIGURE_ENDPOINT", &event_trb, &command_trb) {
+ self.rollback_configure_attempt(
+ port,
+ slot,
+ configure_snapshot,
+ &endpoint_snapshots,
+ "CONFIGURE_ENDPOINT failure",
+ )
+ .await;
+ return Err(err);
}
- // Tell the device about this configuration.
- self.set_configuration(port, configuration_value).await?;
+ if self.consume_test_hook("fail_after_configure_endpoint") {
+ info!(
+ "xhcid: test hook injecting failure after CONFIGURE_ENDPOINT for port {}",
+ port
+ );
+ self.rollback_configure_attempt(
+ port,
+ slot,
+ configure_snapshot,
+ &endpoint_snapshots,
+ "test hook fail_after_configure_endpoint",
+ )
+ .await;
+ return Err(Error::new(EIO));
+ }
+
+ if let Err(err) = self.set_configuration(port, configuration_value).await {
+ self.rollback_configure_attempt(
+ port,
+ slot,
+ configure_snapshot,
+ &endpoint_snapshots,
+ "set_configuration failure",
+ )
+ .await;
+ return Err(err);
+ }
+
+ if self.consume_test_hook("fail_after_set_configuration") {
+ info!(
+ "xhcid: test hook injecting failure after SET_CONFIGURATION for port {}",
+ port
+ );
+ self.rollback_configure_attempt(
+ port,
+ slot,
+ configure_snapshot,
+ &endpoint_snapshots,
+ "test hook fail_after_set_configuration",
+ )
+ .await;
+ return Err(Error::new(EIO));
+ }
+
+ {
+ let mut port_state = self.port_states.get_mut(&port).ok_or(Error::new(EBADFD))?;
+ port_state.cfg_idx = Some(configuration_value);
+ port_state.endpoint_states.retain(|endp_num, _| *endp_num == 0);
+ for (endp_num, endpoint_state) in staged_endpoint_states {
+ port_state.endpoint_states.insert(endp_num, endpoint_state);
+ }
+ }
Ok(())
}
@@ -1856,7 +2066,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 +2103,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 +2305,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 +2397,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 +2418,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 +2454,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 +2518,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 +2551,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 +2594,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 => {
@@ -2357,6 +2626,54 @@ impl<const N: usize> SchemeSync for &Xhci<N> {
}
impl<const N: usize> Xhci<N> {
+ fn ensure_port_active(&self, port_num: PortId) -> Result<()> {
+ let port_state = self.port_states.get(&port_num).ok_or(Error::new(EBADFD))?;
+
+ match port_state.pm_state {
+ super::PortPmState::Active => Ok(()),
+ super::PortPmState::Suspended => {
+ info!(
+ "xhcid: port {} rejected routable operation while suspended",
+ port_num
+ );
+ 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.pm_state != super::PortPmState::Active {
+ return Err(Error::new(EBUSY));
+ }
+
+ port_state.pm_state = super::PortPmState::Suspended;
+ info!("xhcid: suspended port {}", port_num);
+ 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))?;
+
+ if port_state.pm_state == super::PortPmState::Active {
+ return Ok(());
+ }
+
+ let slot_state = self.slot_state(port_state.slot as usize);
+ if slot_state != SlotState::Addressed as u8 && slot_state != SlotState::Configured as u8 {
+ warn!(
+ "refusing to resume port {} while slot {} is in controller state {}",
+ port_num, port_state.slot, slot_state
+ );
+ return Err(Error::new(EIO));
+ }
+
+ port_state.pm_state = super::PortPmState::Active;
+ info!("xhcid: resumed port {}", port_num);
+ 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))?;
@@ -2407,6 +2724,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));
}
diff --git a/drivers/vboxd/src/main.rs b/drivers/vboxd/src/main.rs
index bcb9bb15..b9e42d4a 100644
--- a/drivers/vboxd/src/main.rs
+++ b/drivers/vboxd/src/main.rs
@@ -199,16 +199,28 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
let mut name = pci_config.func.name();
name.push_str("_vbox");
- let bar0 = pci_config.func.bars[0].expect_port();
+ let bar0 = match pci_config.func.bars[0].try_port() {
+ Ok(port) => port,
+ Err(err) => {
+ eprintln!("vboxd: invalid BAR0: {err}");
+ std::process::exit(1);
+ }
+ };
let irq = pci_config
.func
.legacy_interrupt_line
- .expect("vboxd: no legacy interrupts supported");
+ .unwrap_or_else(|| {
+ eprintln!("vboxd: no legacy interrupts supported");
+ std::process::exit(1);
+ });
println!(" + VirtualBox {}", pci_config.func.display());
- common::acquire_port_io_rights().expect("vboxd: failed to get I/O permission");
+ if let Err(err) = common::acquire_port_io_rights() {
+ eprintln!("vboxd: failed to get I/O permission: {err}");
+ std::process::exit(1);
+ }
let mut width = 0;
let mut height = 0;
@@ -233,25 +245,55 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
}
}
- let mut irq_file = irq.irq_handle("vboxd");
+ let mut irq_file = match irq.try_irq_handle("vboxd") {
+ Ok(file) => file,
+ Err(err) => {
+ eprintln!("vboxd: failed to open IRQ handle: {err}");
+ std::process::exit(1);
+ }
+ };
- let address = unsafe { pcid_handle.map_bar(1) }.ptr.as_ptr();
+ let address = match unsafe { pcid_handle.try_map_bar(1) } {
+ Ok(bar) => bar.ptr.as_ptr(),
+ Err(err) => {
+ eprintln!("vboxd: failed to map BAR1: {err}");
+ std::process::exit(1);
+ }
+ };
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
let mut port = common::io::Pio::<u32>::new(bar0 as u16);
let vmmdev = unsafe { &mut *(address as *mut VboxVmmDev) };
- let mut guest_info = VboxGuestInfo::new().expect("vboxd: failed to map GuestInfo");
+ let mut guest_info = match VboxGuestInfo::new() {
+ Ok(value) => value,
+ Err(err) => {
+ eprintln!("vboxd: failed to map GuestInfo: {err}");
+ std::process::exit(1);
+ }
+ };
guest_info.version.write(VBOX_VMMDEV_VERSION);
guest_info.ostype.write(0x100);
port.write(guest_info.physical() as u32);
- let mut guest_caps = VboxGuestCaps::new().expect("vboxd: failed to map GuestCaps");
+ let mut guest_caps = match VboxGuestCaps::new() {
+ Ok(value) => value,
+ Err(err) => {
+ eprintln!("vboxd: failed to map GuestCaps: {err}");
+ std::process::exit(1);
+ }
+ };
guest_caps.caps.write(1 << 2);
port.write(guest_caps.physical() as u32);
- let mut set_mouse = VboxSetMouse::new().expect("vboxd: failed to map SetMouse");
+ let mut set_mouse = match VboxSetMouse::new() {
+ Ok(value) => value,
+ Err(err) => {
+ eprintln!("vboxd: failed to map SetMouse: {err}");
+ std::process::exit(1);
+ }
+ };
set_mouse.features.write(1 << 4 | 1);
port.write(set_mouse.physical() as u32);
@@ -265,34 +307,71 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
}
}
- let event_queue =
- EventQueue::<Source>::new().expect("vboxd: Could not create event queue.");
+ let event_queue = match EventQueue::<Source>::new() {
+ Ok(queue) => queue,
+ Err(err) => {
+ eprintln!("vboxd: could not create event queue: {err}");
+ std::process::exit(1);
+ }
+ };
event_queue
.subscribe(
irq_file.as_raw_fd() as usize,
Source::Irq,
event::EventFlags::READ,
)
- .unwrap();
+ .unwrap_or_else(|err| {
+ eprintln!("vboxd: failed to subscribe IRQ fd: {err}");
+ std::process::exit(1);
+ });
daemon.ready();
- libredox::call::setrens(0, 0).expect("vboxd: failed to enter null namespace");
+ if let Err(err) = libredox::call::setrens(0, 0) {
+ eprintln!("vboxd: failed to enter null namespace: {err}");
+ std::process::exit(1);
+ }
let mut bga = crate::bga::Bga::new();
- let get_mouse = VboxGetMouse::new().expect("vboxd: failed to map GetMouse");
- let display_change = VboxDisplayChange::new().expect("vboxd: failed to map DisplayChange");
- let ack_events = VboxAckEvents::new().expect("vboxd: failed to map AckEvents");
+ let get_mouse = match VboxGetMouse::new() {
+ Ok(value) => value,
+ Err(err) => {
+ eprintln!("vboxd: failed to map GetMouse: {err}");
+ std::process::exit(1);
+ }
+ };
+ let display_change = match VboxDisplayChange::new() {
+ Ok(value) => value,
+ Err(err) => {
+ eprintln!("vboxd: failed to map DisplayChange: {err}");
+ std::process::exit(1);
+ }
+ };
+ let ack_events = match VboxAckEvents::new() {
+ Ok(value) => value,
+ Err(err) => {
+ eprintln!("vboxd: failed to map AckEvents: {err}");
+ std::process::exit(1);
+ }
+ };
- for Source::Irq in iter::once(Source::Irq)
- .chain(event_queue.map(|e| e.expect("vboxd: failed to get next event").user_data))
- {
+ for Source::Irq in iter::once(Source::Irq).chain(event_queue.map(|e| match e {
+ Ok(event) => event.user_data,
+ Err(err) => {
+ eprintln!("vboxd: failed to get next event: {err}");
+ std::process::exit(1);
+ }
+ })) {
let mut irq = [0; 8];
- if irq_file.read(&mut irq).unwrap() >= irq.len() {
+ match irq_file.read(&mut irq) {
+ Ok(read) if read >= irq.len() => {
let host_events = vmmdev.host_events.read();
if host_events != 0 {
port.write(ack_events.physical() as u32);
- irq_file.write(&irq).unwrap();
+ if let Err(err) = irq_file.write(&irq) {
+ eprintln!("vboxd: failed to acknowledge IRQ: {err}");
+ std::process::exit(1);
+ }
if host_events & VBOX_EVENT_DISPLAY == VBOX_EVENT_DISPLAY {
port.write(display_change.physical() as u32);
@@ -326,8 +405,14 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
}
}
}
+ Ok(_) => {}
+ Err(err) => {
+ eprintln!("vboxd: failed to read IRQ file: {err}");
+ std::process::exit(1);
+ }
+ }
}
}
- std::process::exit(0);
+ std::process::exit(1);
}
diff --git a/drivers/virtio-core/src/arch/x86.rs b/drivers/virtio-core/src/arch/x86.rs
index aea86c4a..d8595645 100644
--- a/drivers/virtio-core/src/arch/x86.rs
+++ b/drivers/virtio-core/src/arch/x86.rs
@@ -1,6 +1,8 @@
use crate::transport::Error;
-use pcid_interface::irq_helpers::{allocate_single_interrupt_vector_for_msi, read_bsp_apic_id};
+use pcid_interface::irq_helpers::{
+ read_bsp_apic_id, try_allocate_single_interrupt_vector_for_msi,
+};
use std::fs::File;
use crate::MSIX_PRIMARY_VECTOR;
@@ -11,9 +13,10 @@ pub fn enable_msix(pcid_handle: &mut PciFunctionHandle) -> Result<File, Error> {
// Extended message signaled interrupts.
let msix_info = match pcid_handle.feature_info(PciFeature::MsiX) {
PciFeatureInfo::MsiX(capability) => capability,
- _ => unreachable!(),
+ _ => return Err(Error::MissingMsix),
};
- let mut info = unsafe { msix_info.map_and_mask_all(pcid_handle) };
+ let mut info = unsafe { msix_info.try_map_and_mask_all(pcid_handle) }
+ .map_err(|err| Error::MsixSetup(format!("failed to map MSI-X registers: {err}")))?;
// Allocate the primary MSI vector.
// FIXME allow the driver to register multiple MSI-X vectors
@@ -21,9 +24,12 @@ pub fn enable_msix(pcid_handle: &mut PciFunctionHandle) -> Result<File, Error> {
let interrupt_handle = {
let table_entry_pointer = info.table_entry_pointer(MSIX_PRIMARY_VECTOR as usize);
- let destination_id = read_bsp_apic_id().expect("virtio_core: `read_bsp_apic_id()` failed");
- let (msg_addr_and_data, interrupt_handle) =
- allocate_single_interrupt_vector_for_msi(destination_id);
+ let destination_id = read_bsp_apic_id()
+ .map_err(|err| Error::MsixSetup(format!("failed to read BSP APIC ID: {err}")))?;
+ let (msg_addr_and_data, interrupt_handle) = try_allocate_single_interrupt_vector_for_msi(
+ destination_id,
+ )
+ .map_err(|err| Error::MsixSetup(format!("failed to allocate MSI-X vector: {err}")))?;
table_entry_pointer.write_addr_and_data(msg_addr_and_data);
table_entry_pointer.unmask();
diff --git a/drivers/virtio-core/src/probe.rs b/drivers/virtio-core/src/probe.rs
index 5631ef67..3367586a 100644
--- a/drivers/virtio-core/src/probe.rs
+++ b/drivers/virtio-core/src/probe.rs
@@ -32,21 +32,21 @@ pub const MSIX_PRIMARY_VECTOR: u16 = 0;
/// * Finally start the device (via [`StandardTransport::run_device`]). At this point, the device
/// is alive.
///
-/// ## Panics
-/// This function panics if the device is not a virtio device.
pub fn probe_device(pcid_handle: &mut PciFunctionHandle) -> Result<Device, Error> {
let pci_config = pcid_handle.config();
- assert_eq!(
- pci_config.func.full_device_id.vendor_id, 6900,
- "virtio_core::probe_device: not a virtio device"
- );
+ if pci_config.func.full_device_id.vendor_id != 6900 {
+ return Err(Error::NotVirtio);
+ }
let mut common_addr = None;
let mut notify_addr = None;
let mut device_addr = None;
- for raw_capability in pcid_handle.get_vendor_capabilities() {
+ for raw_capability in pcid_handle
+ .try_get_vendor_capabilities()
+ .map_err(|err| Error::MsixSetup(format!("failed to fetch vendor capabilities: {err}")))?
+ {
// SAFETY: We have verified that the length of the data is correct.
let capability = unsafe { &*(raw_capability.data.as_ptr() as *const PciCapability) };
@@ -55,7 +55,9 @@ pub fn probe_device(pcid_handle: &mut PciFunctionHandle) -> Result<Device, Error
_ => continue,
}
- let (addr, _) = pci_config.func.bars[capability.bar as usize].expect_mem();
+ let (addr, _) = pci_config.func.bars[capability.bar as usize]
+ .try_mem()
+ .map_err(|_| Error::MissingCapability("capability BAR"))?;
let address = unsafe {
let addr = addr + capability.offset as usize;
@@ -100,19 +102,18 @@ pub fn probe_device(pcid_handle: &mut PciFunctionHandle) -> Result<Device, Error
device_addr = Some(address);
}
- _ => unreachable!(),
+ _ => continue,
}
}
- let common_addr = common_addr.expect("virtio common capability missing");
- let device_addr = device_addr.expect("virtio device capability missing");
- let (notify_addr, notify_multiplier) = notify_addr.expect("virtio notify capability missing");
+ let common_addr = common_addr.ok_or(Error::MissingCapability("common"))?;
+ let device_addr = device_addr.ok_or(Error::MissingCapability("device"))?;
+ let (notify_addr, notify_multiplier) = notify_addr.ok_or(Error::MissingCapability("notify"))?;
// FIXME this is explicitly allowed by the virtio specification to happen
- assert!(
- notify_multiplier != 0,
- "virtio-core::device_probe: device uses the same Queue Notify addresses for all queues"
- );
+ if notify_multiplier == 0 {
+ return Err(Error::InvalidNotifyMultiplier);
+ }
let common = unsafe { &mut *(common_addr as *mut CommonCfg) };
let device_space = unsafe { &mut *(device_addr as *mut u8) };
@@ -129,7 +130,9 @@ pub fn probe_device(pcid_handle: &mut PciFunctionHandle) -> Result<Device, Error
let has_msix = all_pci_features.iter().any(|feature| feature.is_msix());
// According to the virtio specification, the device REQUIRED to support MSI-X.
- assert!(has_msix, "virtio: device does not support MSI-X");
+ if !has_msix {
+ return Err(Error::MissingMsix);
+ }
let irq_handle = crate::arch::enable_msix(pcid_handle)?;
log::debug!("virtio: using standard PCI transport");
@@ -154,5 +157,6 @@ pub fn reinit(device: &Device) -> Result<(), Error> {
.insert_status(DeviceStatusFlags::ACKNOWLEDGE);
device.transport.insert_status(DeviceStatusFlags::DRIVER);
+ device.transport.finalize_features();
Ok(())
}
diff --git a/drivers/virtio-core/src/transport.rs b/drivers/virtio-core/src/transport.rs
index d3445d2d..4e116d2e 100644
--- a/drivers/virtio-core/src/transport.rs
+++ b/drivers/virtio-core/src/transport.rs
@@ -19,6 +19,20 @@ pub enum Error {
SyscallError(#[from] libredox::error::Error),
#[error("the device is incapable of {0:?}")]
InCapable(CfgType),
+ #[error("device is not a virtio device")]
+ NotVirtio,
+ #[error("virtio capability `{0}` is missing")]
+ MissingCapability(&'static str),
+ #[error("virtio notify capability has an invalid zero multiplier")]
+ InvalidNotifyMultiplier,
+ #[error("device does not support MSI-X")]
+ MissingMsix,
+ #[error("MSI-X setup failed: {0}")]
+ MsixSetup(String),
+ #[error("virtio feature negotiation failed")]
+ FeaturesNotAccepted,
+ #[error("virtio queue operation failed: {0}")]
+ QueueSetup(&'static str),
}
/// Returns the queue part sizes in bytes.
@@ -238,6 +252,26 @@ impl<'a> Queue<'a> {
}
}
+fn finalize_features_checked(transport: &StandardTransport<'_>) -> Result<(), Error> {
+ if !transport.check_device_feature(VIRTIO_F_VERSION_1) {
+ return Err(Error::FeaturesNotAccepted);
+ }
+ transport.ack_driver_feature(VIRTIO_F_VERSION_1);
+
+ let mut common = transport.common.lock().unwrap();
+
+ let status = common.device_status.get();
+ common
+ .device_status
+ .set(status | DeviceStatusFlags::FEATURES_OK);
+
+ let confirm = common.device_status.get();
+ if (confirm & DeviceStatusFlags::FEATURES_OK) != DeviceStatusFlags::FEATURES_OK {
+ return Err(Error::FeaturesNotAccepted);
+ }
+ Ok(())
+}
+
unsafe impl Sync for Queue<'_> {}
unsafe impl Send for Queue<'_> {}
@@ -590,21 +624,8 @@ impl Transport for StandardTransport<'_> {
}
fn finalize_features(&self) {
- // Check VirtIO version 1 compliance.
- assert!(self.check_device_feature(VIRTIO_F_VERSION_1));
- self.ack_driver_feature(VIRTIO_F_VERSION_1);
-
- let mut common = self.common.lock().unwrap();
-
- let status = common.device_status.get();
- common
- .device_status
- .set(status | DeviceStatusFlags::FEATURES_OK);
-
- // Re-read device status to ensure the `FEATURES_OK` bit is still set: otherwise,
- // the device does not support our subset of features and the device is unusable.
- let confirm = common.device_status.get();
- assert!((confirm & DeviceStatusFlags::FEATURES_OK) == DeviceStatusFlags::FEATURES_OK);
+ finalize_features_checked(self)
+ .unwrap_or_else(|err| panic!("{err}"))
}
fn setup_config_notify(&self, vector: u16) {
@@ -640,7 +661,9 @@ impl Transport for StandardTransport<'_> {
// Set the MSI-X vector.
common.queue_msix_vector.set(vector);
- assert!(common.queue_msix_vector.get() == vector);
+ if common.queue_msix_vector.get() != vector {
+ return Err(Error::QueueSetup("queue MSI-X vector was not accepted"));
+ }
// Enable the queue.
common.queue_enable.set(1);
@@ -685,7 +708,9 @@ impl Transport for StandardTransport<'_> {
// Set the MSI-X vector.
common.queue_msix_vector.set(queue.vector);
- assert!(common.queue_msix_vector.get() == queue.vector);
+ if common.queue_msix_vector.get() != queue.vector {
+ panic!("virtio queue MSI-X vector was not accepted during reinit");
+ }
// Enable the queue.
common.queue_enable.set(1);