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, + pub board_vendor: Option, + pub board_name: Option, + pub board_version: Option, + pub product_name: Option, + pub product_version: Option, + pub bios_version: Option, +} + +impl DmiInfo { + pub fn to_match_lines(&self) -> String { + let mut lines = Vec::new(); + if let Some(value) = &self.sys_vendor { + lines.push(format!("sys_vendor={value}")); + } + if let Some(value) = &self.board_vendor { + lines.push(format!("board_vendor={value}")); + } + if let Some(value) = &self.board_name { + lines.push(format!("board_name={value}")); + } + if let Some(value) = &self.board_version { + lines.push(format!("board_version={value}")); + } + if let Some(value) = &self.product_name { + lines.push(format!("product_name={value}")); + } + if let Some(value) = &self.product_version { + lines.push(format!("product_version={value}")); + } + if let Some(value) = &self.bios_version { + lines.push(format!("bios_version={value}")); + } + lines.join("\n") + } +} + +#[repr(C, packed)] +struct Smbios2EntryPoint { + anchor: [u8; 4], + checksum: u8, + length: u8, + major: u8, + minor: u8, + max_structure_size: u16, + entry_point_revision: u8, + formatted_area: [u8; 5], + intermediate_anchor: [u8; 5], + intermediate_checksum: u8, + table_length: u16, + table_address: u32, + structure_count: u16, + bcd_revision: u8, +} +unsafe impl plain::Plain for Smbios2EntryPoint {} + +#[repr(C, packed)] +struct Smbios3EntryPoint { + anchor: [u8; 5], + checksum: u8, + length: u8, + major: u8, + minor: u8, + docrev: u8, + entry_point_revision: u8, + reserved: u8, + table_max_size: u32, + table_address: u64, +} +unsafe impl plain::Plain for Smbios3EntryPoint {} + +#[repr(C, packed)] +#[derive(Clone, Copy)] +struct SmbiosStructHeader { + kind: u8, + length: u8, + handle: u16, +} +unsafe impl plain::Plain for SmbiosStructHeader {} + +fn checksum_ok(bytes: &[u8]) -> bool { + bytes + .iter() + .copied() + .fold(0u8, |acc, byte| acc.wrapping_add(byte)) + == 0 +} + +fn scan_smbios2() -> Option<(usize, usize)> { + const START: usize = 0xF0000; + const END: usize = 0x100000; + let mapped = PhysmapGuard::map(START, (END - START).div_ceil(PAGE_SIZE)).ok()?; + let bytes = &mapped[..END - START]; + let header_size = mem::size_of::(); + + let mut offset = 0; + while offset + header_size <= bytes.len() { + if &bytes[offset..offset + 4] == b"_SM_" { + let entry = + plain::from_bytes::(&bytes[offset..offset + header_size]) + .ok()?; + let length = entry.length as usize; + if offset + length <= bytes.len() + && length >= header_size + && checksum_ok(&bytes[offset..offset + length]) + && &entry.intermediate_anchor == b"_DMI_" + { + return Some((entry.table_address as usize, entry.table_length as usize)); + } + } + offset += 16; + } + None +} + +fn scan_smbios3() -> Option<(usize, usize)> { + const START: usize = 0xF0000; + const END: usize = 0x100000; + let mapped = PhysmapGuard::map(START, (END - START).div_ceil(PAGE_SIZE)).ok()?; + let bytes = &mapped[..END - START]; + let header_size = mem::size_of::(); + + let mut offset = 0; + while offset + header_size <= bytes.len() { + if &bytes[offset..offset + 5] == b"_SM3_" { + let entry = + plain::from_bytes::(&bytes[offset..offset + header_size]) + .ok()?; + let length = entry.length as usize; + if offset + length <= bytes.len() + && length >= header_size + && checksum_ok(&bytes[offset..offset + length]) + { + return Some((entry.table_address as usize, entry.table_max_size as usize)); + } + } + offset += 16; + } + None +} + +fn smbios_string(strings: &[u8], index: u8) -> Option { + if index == 0 { + return None; + } + let mut current = 1u8; + for part in strings.split(|b| *b == 0) { + if part.is_empty() { + break; + } + if current == index { + return Some(String::from_utf8_lossy(part).trim().to_string()) + .filter(|s| !s.is_empty()); + } + current = current.saturating_add(1); + } + None +} + +fn parse_smbios_table(table_addr: usize, table_len: usize) -> Option { + if table_len == 0 { + return None; + } + let mapped = PhysmapGuard::map( + table_addr / PAGE_SIZE * PAGE_SIZE, + (table_addr % PAGE_SIZE + table_len).div_ceil(PAGE_SIZE), + ) + .ok()?; + let start = table_addr % PAGE_SIZE; + let bytes = &mapped[start..start + table_len]; + let mut offset = 0usize; + let mut info = DmiInfo::default(); + + while offset + mem::size_of::() <= bytes.len() { + let header = plain::from_bytes::( + &bytes[offset..offset + mem::size_of::()], + ) + .ok()?; + let formatted_len = header.length as usize; + if formatted_len < mem::size_of::() + || offset + formatted_len > bytes.len() + { + break; + } + let struct_bytes = &bytes[offset..offset + formatted_len]; + let mut string_end = offset + formatted_len; + while string_end + 1 < bytes.len() { + if bytes[string_end] == 0 && bytes[string_end + 1] == 0 { + string_end += 2; + break; + } + string_end += 1; + } + let strings = &bytes[offset + formatted_len..string_end.saturating_sub(1).min(bytes.len())]; + + match header.kind { + 0 if formatted_len >= 0x09 => { + info.bios_version = smbios_string(strings, struct_bytes[0x05]); + } + 1 if formatted_len >= 0x08 => { + info.sys_vendor = smbios_string(strings, struct_bytes[0x04]); + info.product_name = smbios_string(strings, struct_bytes[0x05]); + info.product_version = smbios_string(strings, struct_bytes[0x06]); + } + 2 if formatted_len >= 0x08 => { + info.board_vendor = smbios_string(strings, struct_bytes[0x04]); + info.board_name = smbios_string(strings, struct_bytes[0x05]); + info.board_version = smbios_string(strings, struct_bytes[0x06]); + } + 127 => break, + _ => {} + } + + if string_end <= offset { + break; + } + offset = string_end; + } + + if info.to_match_lines().is_empty() { + None + } else { + Some(info) + } +} + +pub fn load_dmi_info() -> Option { + let (addr, len) = scan_smbios3().or_else(scan_smbios2)?; + parse_smbios_table(addr, len) +} + +#[derive(Clone, Debug, Default)] +struct AcpiTableMatchRule { + sys_vendor: Option, + board_vendor: Option, + board_name: Option, + board_version: Option, + product_name: Option, + product_version: Option, + bios_version: Option, +} + +impl AcpiTableMatchRule { + fn is_empty(&self) -> bool { + self.sys_vendor.is_none() + && self.board_vendor.is_none() + && self.board_name.is_none() + && self.board_version.is_none() + && self.product_name.is_none() + && self.product_version.is_none() + && self.bios_version.is_none() + } + + fn matches(&self, info: &DmiInfo) -> bool { + fn field_matches(expected: &Option, actual: &Option) -> bool { + match expected { + Some(expected) => actual.as_ref() == Some(expected), + None => true, + } + } + + field_matches(&self.sys_vendor, &info.sys_vendor) + && field_matches(&self.board_vendor, &info.board_vendor) + && field_matches(&self.board_name, &info.board_name) + && field_matches(&self.board_version, &info.board_version) + && field_matches(&self.product_name, &info.product_name) + && field_matches(&self.product_version, &info.product_version) + && field_matches(&self.bios_version, &info.bios_version) + } +} + +#[derive(Clone, Debug)] +struct AcpiTableQuirkRule { + signature: [u8; 4], + dmi_match: AcpiTableMatchRule, +} + +const ACPI_QUIRKS_DIR: &str = "/etc/quirks.d"; + +fn parse_acpi_signature(value: &str) -> Option<[u8; 4]> { + let bytes = value.as_bytes(); + if bytes.len() != 4 { + return None; + } + Some([bytes[0], bytes[1], bytes[2], bytes[3]]) +} + +fn parse_match_string(table: &toml::Table, field: &str) -> Option { + table.get(field).and_then(Value::as_str).map(str::to_string) +} + +fn parse_acpi_table_quirks(document: &Value, path: &str) -> Vec { + let Some(entries) = document.get("acpi_table_quirk").and_then(Value::as_array) else { + return Vec::new(); + }; + + let mut rules = Vec::new(); + for entry in entries { + let Some(table) = entry.as_table() else { + log::warn!("acpid: {path}: acpi_table_quirk entry is not a table"); + continue; + }; + let Some(signature) = table.get("signature").and_then(Value::as_str) else { + log::warn!("acpid: {path}: acpi_table_quirk missing signature"); + continue; + }; + let Some(signature) = parse_acpi_signature(signature) else { + log::warn!("acpid: {path}: invalid acpi table signature {signature:?}"); + continue; + }; + + let dmi_match = table + .get("match") + .and_then(Value::as_table) + .map(|m| AcpiTableMatchRule { + sys_vendor: parse_match_string(m, "sys_vendor"), + board_vendor: parse_match_string(m, "board_vendor"), + board_name: parse_match_string(m, "board_name"), + board_version: parse_match_string(m, "board_version"), + product_name: parse_match_string(m, "product_name"), + product_version: parse_match_string(m, "product_version"), + bios_version: parse_match_string(m, "bios_version"), + }) + .unwrap_or_default(); + + rules.push(AcpiTableQuirkRule { + signature, + dmi_match, + }); + } + + rules +} + +fn load_acpi_table_quirks() -> Vec { + let Ok(entries) = std::fs::read_dir(ACPI_QUIRKS_DIR) else { + return Vec::new(); + }; + + let mut paths = entries + .filter_map(Result::ok) + .map(|entry| entry.path()) + .filter(|path| path.extension().and_then(|ext| ext.to_str()) == Some("toml")) + .collect::>(); + paths.sort(); + + let mut rules = Vec::new(); + for path in paths { + let path_str = path.display().to_string(); + let Ok(contents) = std::fs::read_to_string(&path) else { + log::warn!("acpid: failed to read {path_str}"); + continue; + }; + let Ok(document) = contents.parse::() else { + log::warn!("acpid: failed to parse {path_str}"); + continue; + }; + rules.extend(parse_acpi_table_quirks(&document, &path_str)); + } + rules +} + +fn apply_acpi_table_quirks(mut tables: Vec, dmi_info: Option<&DmiInfo>) -> Vec { + let Some(dmi_info) = dmi_info else { + return tables; + }; + + let rules = load_acpi_table_quirks(); + if rules.is_empty() { + return tables; + } + + tables.retain(|table| { + let skip = rules.iter().any(|rule| { + table.signature == rule.signature + && (rule.dmi_match.is_empty() || rule.dmi_match.matches(dmi_info)) + }); + if skip { + log::warn!( + "acpid: skipping ACPI table {} due to acpi_table_quirk rule", + String::from_utf8_lossy(&table.signature) + ); + } + !skip + }); + tables +} + +#[cfg(test)] +mod tests { + use super::{ + 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, page_cache: Arc>, - aml_region_handlers: Vec<(RegionSpace, Box)>, } impl AmlSymbols { - pub fn new(aml_region_handlers: Vec<(RegionSpace, Box)>) -> Self { + pub fn new() -> Self { Self { aml_context: None, symbol_cache: FxHashMap::default(), page_cache: Arc::new(Mutex::new(AmlPageCache::default())), - aml_region_handlers, } } @@ -261,6 +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 for AmlEvalError { fn from(value: AmlError) -> Self { @@ -375,10 +1002,169 @@ impl From for AmlEvalError { } } +fn panic_payload_to_string(payload: Box) -> String { + if let Some(message) = payload.downcast_ref::<&'static str>() { + (*message).to_string() + } else if let Some(message) = payload.downcast_ref::() { + 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, + pub remaining_capacity: Option, + pub present_voltage: Option, + pub power_unit: Option, + pub design_capacity: Option, + pub last_full_capacity: Option, + pub design_voltage: Option, + pub technology: Option, + pub model: Option, + pub serial: Option, + pub battery_type: Option, + pub oem_info: Option, + pub percentage: Option, +} + +#[derive(Clone, Debug, Default)] +pub struct AcpiPowerSnapshot { + pub adapters: Vec, + pub batteries: Vec, +} + +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 { + 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 { + match value { + AmlSerdeValue::Integer(value) => Some(*value), + _ => None, + } +} + +fn aml_string(value: &AmlSerdeValue) -> Option { + 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 { + 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, dsdt: Option, fadt: Option, + pm1a_cnt_blk: u64, + pm1b_cnt_blk: u64, + slp_s5_values: RwLock>, + reset_reg: Option, + reset_value: u8, + dmi_info: Option, + pci_fd: RwLock>, aml_symbols: RwLock, @@ -397,7 +1183,8 @@ impl AcpiContext { args: Vec, ) -> Result { 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::, 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, - ec: Vec<(RegionSpace, Box)>, - ) -> 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, 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::>(); + + 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 { + self.evaluate_acpi_method(device_path, "_PPC", &[])? + .into_iter() + .next() + .ok_or(AmlEvalError::DeserializationError) + } + + pub fn init(rxsdt_physaddrs: impl Iterator) -> 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::>(); + .collect::>(), + 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 { + let symbols = self.aml_symbols()?; + let symbol_names = symbols + .symbols_cache() + .keys() + .cloned() + .collect::>(); + drop(symbols); + + let mut adapter_paths = symbol_names + .iter() + .filter_map(|symbol| symbol_parent_path(symbol, "._PSR")) + .collect::>(); + adapter_paths.sort(); + adapter_paths.dedup(); + + let mut battery_paths = symbol_names + .iter() + .filter_map(|symbol| symbol_parent_path(symbol, "._BST")) + .collect::>(); + 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 { - 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, AmlError> { + pub fn aml_symbols(&self) -> Result, 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::::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::::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::::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::::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::::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>, pci_fd: Arc>, + aml_mutexes: Arc>>>, + next_mutex_handle: Arc, +} + +#[derive(Debug, Default)] +struct AmlMutexState { + owner: Option, + depth: u32, +} + +#[derive(Debug, Default)] +struct AmlMutex { + state: Mutex, + 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> { + self.aml_mutexes + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()) + .get(&handle.0) + .cloned() + } + + fn read_phys_or_fault(&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::(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::(address) { - return value; - } - } - log::error!("failed to read u8 {:#x}", address); - 0 + self.read_phys_or_fault::(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::(address) { - return value; - } - } - log::error!("failed to read u16 {:#x}", address); - 0 + self.read_phys_or_fault::(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::(address) { - return value; - } - } - log::error!("failed to read u32 {:#x}", address); - 0 + self.read_phys_or_fault::(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::(address) { - return value; - } - } - log::error!("failed to read u64 {:#x}", address); - 0 + self.read_phys_or_fault::(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 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::::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::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 { + fn read(&self, address: u8) -> Result { 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(&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(&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 { 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 { - 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 { - 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 { - 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 { + 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 { + 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 { + 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) -> 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, 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, 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::()) - // TODO: With const generics, the compiler has some way of doing this for static sizes. - .map(|chunk| <[u8; mem::size_of::()]>::try_from(chunk).unwrap()) - .map(|chunk| u32::from_le_bytes(chunk)) - .map(u64::from); + let entry_size = mem::size_of::(); + 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 + Ok(data + .chunks_exact(entry_size) + .map(|chunk| <[u8; mem::size_of::()]>::try_from(chunk).unwrap()) + .map(u32::from_le_bytes) + .map(u64::from) + .collect()) } b"XSDT" => { - sixty_four_bit = sdt - .data() - .chunks(mem::size_of::()) - .map(|chunk| <[u8; mem::size_of::()]>::try_from(chunk).unwrap()) - .map(|chunk| u64::from_le_bytes(chunk)); + let entry_size = mem::size_of::(); + 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 + Ok(data + .chunks_exact(entry_size) + .map(|chunk| <[u8; mem::size_of::()]>::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)> = 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::() + 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::::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>, - pci_fd: Option, 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 { + Some(match name { + "sys_vendor" => dmi_info + .and_then(|info| info.sys_vendor.clone()) + .unwrap_or_default(), + "board_vendor" => dmi_info + .and_then(|info| info.board_vendor.clone()) + .unwrap_or_default(), + "board_name" => dmi_info + .and_then(|info| info.board_name.clone()) + .unwrap_or_default(), + "board_version" => dmi_info + .and_then(|info| info.board_version.clone()) + .unwrap_or_default(), + "product_name" => dmi_info + .and_then(|info| info.product_name.clone()) + .unwrap_or_default(), + "product_version" => dmi_info + .and_then(|info| info.product_version.clone()) + .unwrap_or_default(), + "bios_version" => dmi_info + .and_then(|info| info.bios_version.clone()) + .unwrap_or_default(), + "match_all" => dmi_info.map(DmiInfo::to_match_lines).unwrap_or_default(), + _ => return None, + }) +} + +fn power_bool_contents(value: bool) -> String { + if value { + String::from("1\n") + } else { + String::from("0\n") + } +} + +fn power_u64_contents(value: u64) -> String { + format!("{value}\n") +} + +fn power_f64_contents(value: f64) -> String { + format!("{value}\n") +} + +fn power_string_contents(value: &str) -> String { + format!("{value}\n") +} + +fn power_adapter_file_contents(adapter: &AcpiPowerAdapter, name: &str) -> Option { + Some(match name { + "path" => power_string_contents(&adapter.path), + "online" => power_bool_contents(adapter.online), + _ => return None, + }) +} + +fn power_adapter_entry_names() -> &'static [&'static str] { + &["path", "online"] +} + +fn power_battery_file_contents(battery: &AcpiBattery, name: &str) -> Option { + Some(match name { + "path" => power_string_contents(&battery.path), + "state" => power_u64_contents(battery.state), + "present_rate" => power_u64_contents(battery.present_rate?), + "remaining_capacity" => power_u64_contents(battery.remaining_capacity?), + "present_voltage" => power_u64_contents(battery.present_voltage?), + "power_unit" => power_string_contents(battery.power_unit.as_deref()?), + "design_capacity" => power_u64_contents(battery.design_capacity?), + "last_full_capacity" => power_u64_contents(battery.last_full_capacity?), + "design_voltage" => power_u64_contents(battery.design_voltage?), + "technology" => power_string_contents(battery.technology.as_deref()?), + "model" => power_string_contents(battery.model.as_deref()?), + "serial" => power_string_contents(battery.serial.as_deref()?), + "battery_type" => power_string_contents(battery.battery_type.as_deref()?), + "oem_info" => power_string_contents(battery.oem_info.as_deref()?), + "percentage" => power_f64_contents(battery.percentage?), + _ => return None, + }) +} + +fn power_battery_entry_names(battery: &AcpiBattery) -> Vec<&'static str> { + let mut names = vec!["path", "state"]; + + if battery.present_rate.is_some() { + names.push("present_rate"); + } + if battery.remaining_capacity.is_some() { + names.push("remaining_capacity"); + } + if battery.present_voltage.is_some() { + names.push("present_voltage"); + } + if battery.power_unit.is_some() { + names.push("power_unit"); + } + if battery.design_capacity.is_some() { + names.push("design_capacity"); + } + if battery.last_full_capacity.is_some() { + names.push("last_full_capacity"); + } + if battery.design_voltage.is_some() { + names.push("design_voltage"); + } + if battery.technology.is_some() { + names.push("technology"); + } + if battery.model.is_some() { + names.push("model"); + } + if battery.serial.is_some() { + names.push("serial"); + } + if battery.battery_type.is_some() { + names.push("battery_type"); + } + if battery.oem_info.is_some() { + names.push("oem_info"); + } + if battery.percentage.is_some() { + names.push("percentage"); + } + + names +} + +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 { + 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> { + 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> { + let normalized = path.trim_matches('/'); + if normalized.is_empty() { + return Ok(HandleKind::PowerAdaptersDir); + } + + let mut parts = normalized.split('/'); + let adapter_id = parts.next().ok_or(Error::new(ENOENT))?; + let field = parts.next(); + if parts.next().is_some() { + return Err(Error::new(ENOENT)); + } + + let snapshot = self.power_snapshot()?; + let adapter = snapshot + .adapters + .iter() + .find(|adapter| adapter.id == adapter_id) + .ok_or(Error::new(ENOENT))?; + + match field { + None | Some("") => Ok(HandleKind::PowerAdapterDir(adapter.id.clone())), + Some(name) => Ok(HandleKind::PowerFile( + power_adapter_file_contents(adapter, name).ok_or(Error::new(ENOENT))?, + )), + } + } + + fn power_battery_handle(&self, path: &str) -> Result> { + let normalized = path.trim_matches('/'); + if normalized.is_empty() { + return Ok(HandleKind::PowerBatteriesDir); + } + + let mut parts = normalized.split('/'); + let battery_id = parts.next().ok_or(Error::new(ENOENT))?; + let field = parts.next(); + if parts.next().is_some() { + return Err(Error::new(ENOENT)); + } + + let snapshot = self.power_snapshot()?; + let battery = snapshot + .batteries + .iter() + .find(|battery| battery.id == battery_id) + .ok_or(Error::new(ENOENT))?; + + match field { + None | Some("") => Ok(HandleKind::PowerBatteryDir(battery.id.clone())), + Some(name) => Ok(HandleKind::PowerFile( + power_battery_file_contents(battery, name).ok_or(Error::new(ENOENT))?, + )), + } + } } fn parse_hex_digit(hex: u8) -> Option { @@ -184,9 +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 { 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> { - 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 { + 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 { 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::::new().expect("ac97d: Could not create event queue."); + let event_queue = match EventQueue::::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::::new().expect("ihdad: Could not create event queue."); - let socket = Socket::nonblock().expect("ihdad: failed to create socket"); + let event_queue = match EventQueue::::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 = - EventQueue::new().expect("virtio-gpud: failed to create event queue"); + let event_queue: EventQueue = 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::::new().expect("e1000d: failed to create event queue"); + let event_queue = match EventQueue::::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::::new().expect("ixgbed: Could not create event queue."); + let event_queue = match EventQueue::::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 { 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, } +#[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 { 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>, + } + + 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 (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 { 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 { 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 { 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 { 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(r: &mut File) -> T { bincode::deserialize_from(&data[..]).expect("couldn't deserialize pcid message") } +fn send_result(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(r: &mut File) -> io::Result { + 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 { - 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> { + 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> { + 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 { - 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 { + 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 { + 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::()).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 { + 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::()).unwrap(), + virt_table_base: NonNull::new(virt_table_base.cast::()) + .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, 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 }, 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 "; @@ -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 { 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> { +) -> Result, 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::("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 { + 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 { 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 { + let string = self.read_to_string("pm_state")?; + Ok(string.parse()?) + } pub fn open_endpoint_ctl(&self, num: u8) -> result::Result { 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, 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, 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, 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( 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::::new(scheme_name.clone(), address, interrupt_method, pcid_handle) - .expect("xhcid: failed to allocate device"), + match Xhci::::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( 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 DeviceEnumerator { 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 DeviceEnumerator { 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 DeviceEnumerator { //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 DeviceEnumerator { 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 Xhci { pub fn with_ring T>(&self, id: RingId, function: F) -> Option { 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 Xhci { ) -> Option { 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 Xhci { + fn read_test_hook_command_from_path(path: &str) -> Option { + 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 { + let command = Self::read_test_hook_command_from_path(path)?; + let delay_ms = command.strip_prefix(prefix)?.parse::().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 { + 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( &self, @@ -104,7 +150,17 @@ impl Xhci { ); 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 { handles: CHashMap, next_handle: AtomicUsize, port_states: CHashMap>, + staged_port_states: CHashMap>, drivers: CHashMap>, scheme_name: String, @@ -311,6 +368,93 @@ struct PortState { input_context: Mutex>>, dev_desc: Option, endpoint_states: BTreeMap, + lifecycle: Arc, + 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, + 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 PortState { @@ -463,6 +607,7 @@ impl Xhci { 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 Xhci { } 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 Xhci { 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::>()? }; + 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::>()? }; - // 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::>(), - }; - 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::>(), + 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 Xhci { 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 { - 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(hci: &Arc>) { })); } +#[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/attach scheme."); static ref REGEX_PORT_DETACH: Regex = Regex::new(r"^port([\d\.]+)/detach$") .expect("Failed to create the regex for the port/detach scheme."); + static ref REGEX_PORT_SUSPEND: Regex = Regex::new(r"^port([\d\.]+)/suspend$") + .expect("Failed to create the regex for the port/suspend scheme."); + static ref REGEX_PORT_RESUME: Regex = Regex::new(r"^port([\d\.]+)/resume$") + .expect("Failed to create the regex for the port/resume scheme."); static ref REGEX_PORT_DESCRIPTORS: Regex = Regex::new(r"^port([\d\.]+)/descriptors$") .expect("Failed to create the regex for the port/descriptors"); static ref REGEX_PORT_STATE: Regex = Regex::new(r"^port([\d\.]+)/state$") .expect("Failed to create the regex for the port/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/pm_state scheme"); static ref REGEX_PORT_REQUEST: Regex = Regex::new(r"^port([\d\.]+)/request$") .expect("Failed to create the regex for the port/request scheme"); static ref REGEX_PORT_ENDPOINTS: Regex = Regex::new(r"^port([\d\.]+)/endpoints$") @@ -137,12 +144,15 @@ pub enum Handle { Port(PortId, Vec), // port, contents PortDesc(PortId, Vec), // port, contents PortState(PortId), // port + PortPmState(PortId), // port PortReq(PortId, PortReqState), // port, state Endpoints(PortId, Vec), // 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/state PortState(PortId), // port number + /// /port/pm_state + PortPmState(PortId), // port number /// /port/request PortReq(PortId), // port number /// /port/endpoints @@ -187,6 +199,10 @@ enum SchemeParameters { AttachDevice(PortId), // port number /// /port/detach DetachDevice(PortId), // port number + /// /port/suspend + SuspendDevice(PortId), // port number + /// /port/resume + ResumeDevice(PortId), // port number } impl Handle { @@ -209,6 +225,9 @@ impl Handle { Handle::PortState(port_num) => { format!("port{}/state", port_num) } + Handle::PortPmState(port_num) => { + format!("port{}/pm_state", port_num) + } Handle::PortReq(port_num, _) => { format!("port{}/request", port_num) } @@ -235,6 +254,12 @@ impl Handle { Handle::DetachDevice(port_num) => { format!("port{}/detach", port_num) } + Handle::SuspendDevice(port_num) => { + format!("port{}/suspend", port_num) + } + Handle::ResumeDevice(port_num) => { + format!("port{}/resume", port_num) + } Handle::SchemeRoot => String::from(""), } } @@ -258,10 +283,13 @@ impl Handle { &Handle::PortReq(_, PortReqState::Tmp) => unreachable!(), &Handle::PortReq(_, PortReqState::TmpSetup(_)) => unreachable!(), &Handle::PortState(_) => HandleType::Character, + &Handle::PortPmState(_) => HandleType::Character, &Handle::PortReq(_, _) => HandleType::Character, &Handle::ConfigureEndpoints(_) => HandleType::Character, &Handle::AttachDevice(_) => HandleType::Character, &Handle::DetachDevice(_) => HandleType::Character, + &Handle::SuspendDevice(_) => HandleType::Character, + &Handle::ResumeDevice(_) => HandleType::Character, &Handle::Endpoint(_, _, ref st) => match st { EndpointHandleTy::Data => HandleType::Character, EndpointHandleTy::Ctl => HandleType::Character, @@ -289,10 +317,13 @@ impl Handle { &Handle::PortReq(_, PortReqState::Tmp) => None, &Handle::PortReq(_, PortReqState::TmpSetup(_)) => None, &Handle::PortState(_) => None, + &Handle::PortPmState(_) => None, &Handle::PortReq(_, _) => None, &Handle::ConfigureEndpoints(_) => None, &Handle::AttachDevice(_) => None, &Handle::DetachDevice(_) => None, + &Handle::SuspendDevice(_) => None, + &Handle::ResumeDevice(_) => None, &Handle::Endpoint(_, _, ref st) => match st { EndpointHandleTy::Data => None, EndpointHandleTy::Ctl => None, @@ -383,6 +414,14 @@ impl SchemeParameters { let port_num = get_port_id_from_regex(®EX_PORT_DETACH, scheme, 0)?; Ok(Self::DetachDevice(port_num)) + } else if REGEX_PORT_SUSPEND.is_match(scheme) { + let port_num = get_port_id_from_regex(®EX_PORT_SUSPEND, scheme, 0)?; + + Ok(Self::SuspendDevice(port_num)) + } else if REGEX_PORT_RESUME.is_match(scheme) { + let port_num = get_port_id_from_regex(®EX_PORT_RESUME, scheme, 0)?; + + Ok(Self::ResumeDevice(port_num)) } else if REGEX_PORT_DESCRIPTORS.is_match(scheme) { let port_num = get_port_id_from_regex(®EX_PORT_DESCRIPTORS, scheme, 0)?; @@ -391,6 +430,10 @@ impl SchemeParameters { let port_num = get_port_id_from_regex(®EX_PORT_STATE, scheme, 0)?; Ok(Self::PortState(port_num)) + } else if REGEX_PORT_PM_STATE.is_match(scheme) { + let port_num = get_port_id_from_regex(®EX_PORT_PM_STATE, scheme, 0)?; + + Ok(Self::PortPmState(port_num)) } else if REGEX_PORT_REQUEST.is_match(scheme) { let port_num = get_port_id_from_regex(®EX_PORT_REQUEST, scheme, 0)?; @@ -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 Xhci { where D: FnMut(&mut Trb, bool) -> ControlFlow, { + self.ensure_port_active(port_num)?; + let future = { let mut port_state = self.port_state_mut(port_num)?; let slot = port_state.slot; @@ -709,6 +787,8 @@ impl Xhci { where D: FnMut(&mut Trb, bool) -> ControlFlow, { + self.ensure_port_active(port_num)?; + let endp_idx = endp_num.checked_sub(1).ok_or(Error::new(EIO))?; let mut port_state = self.port_state_mut(port_num)?; @@ -949,35 +1029,102 @@ impl Xhci { 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 { + 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::>(); + + 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 Xhci { } ( - 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 Xhci { 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 Xhci { 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::(self.cap.ac64(), 1 << (primary_streams + 1))?; @@ -1126,15 +1221,13 @@ impl Xhci { 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::(self.cap.ac64(), 16, true)?; let ring_ptr = ring.register(); @@ -1144,68 +1237,185 @@ impl Xhci { 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::>(); - //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 Xhci { if (flags & O_DIRECTORY != 0) || (flags & O_STAT != 0) { let mut contents = Vec::new(); - write!(contents, "descriptors\nendpoints\n").unwrap(); + write!(contents, "descriptors\nendpoints\npm_state\nsuspend\nresume\n").unwrap(); if self.slot_state( self.port_states @@ -1893,6 +2103,14 @@ impl Xhci { Ok(Handle::PortState(port_num)) } + fn open_handle_port_pm_state(&self, port_num: PortId, flags: usize) -> Result { + if flags & O_DIRECTORY != 0 && flags & O_STAT == 0 { + return Err(Error::new(ENOTDIR)); + } + + Ok(Handle::PortPmState(port_num)) + } + /// implements open() for /port/endpoints /// /// # Arguments @@ -2087,6 +2305,30 @@ impl Xhci { Ok(Handle::DetachDevice(port_num)) } + fn open_handle_suspend_device(&self, port_num: PortId, flags: usize) -> Result { + if flags & O_DIRECTORY != 0 && flags & O_STAT == 0 { + return Err(Error::new(ENOTDIR)); + } + + if flags & O_RDWR != O_WRONLY && flags & O_STAT == 0 { + return Err(Error::new(EACCES)); + } + + Ok(Handle::SuspendDevice(port_num)) + } + + fn open_handle_resume_device(&self, port_num: PortId, flags: usize) -> Result { + if flags & O_DIRECTORY != 0 && flags & O_STAT == 0 { + return Err(Error::new(ENOTDIR)); + } + + if flags & O_RDWR != O_WRONLY && flags & O_STAT == 0 { + return Err(Error::new(EACCES)); + } + + Ok(Handle::ResumeDevice(port_num)) + } + /// implements open() for /port/request /// /// # Arguments @@ -2155,6 +2397,9 @@ impl SchemeSync for &Xhci { 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 SchemeSync for &Xhci { SchemeParameters::DetachDevice(port_number) => { self.open_handle_detach_device(port_number, flags)? } + SchemeParameters::SuspendDevice(port_number) => { + self.open_handle_suspend_device(port_number, flags)? + } + SchemeParameters::ResumeDevice(port_number) => { + self.open_handle_resume_device(port_number, flags)? + } }; let fd = self.next_handle.fetch_add(1, atomic::Ordering::Relaxed); @@ -2203,7 +2454,11 @@ impl SchemeSync for &Xhci { //If we have a handle to the configure scheme, we need to mark it as write only. match &*guard { - Handle::ConfigureEndpoints(_) | Handle::AttachDevice(_) | Handle::DetachDevice(_) => { + Handle::ConfigureEndpoints(_) + | Handle::AttachDevice(_) + | Handle::DetachDevice(_) + | Handle::SuspendDevice(_) + | Handle::ResumeDevice(_) => { stat.st_mode = stat.st_mode | 0o200; } _ => {} @@ -2263,6 +2518,8 @@ impl SchemeSync for &Xhci { Handle::ConfigureEndpoints(_) => Err(Error::new(EBADF)), Handle::AttachDevice(_) => Err(Error::new(EBADF)), Handle::DetachDevice(_) => Err(Error::new(EBADF)), + Handle::SuspendDevice(_) => Err(Error::new(EBADF)), + Handle::ResumeDevice(_) => Err(Error::new(EBADF)), Handle::SchemeRoot => Err(Error::new(EBADF)), &mut Handle::Endpoint(port_num, endp_num, ref mut st) => match st { @@ -2294,6 +2551,10 @@ impl SchemeSync for &Xhci { Ok(Xhci::::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::::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 SchemeSync for &Xhci { block_on(self.detach_device(port_num))?; Ok(buf.len()) } + &mut Handle::SuspendDevice(port_num) => { + block_on(self.suspend_device(port_num))?; + Ok(buf.len()) + } + &mut Handle::ResumeDevice(port_num) => { + block_on(self.resume_device(port_num))?; + Ok(buf.len()) + } &mut Handle::Endpoint(port_num, endp_num, ref ep_file_ty) => match ep_file_ty { EndpointHandleTy::Ctl => block_on(self.on_write_endp_ctl(port_num, endp_num, buf)), EndpointHandleTy::Data => { @@ -2357,6 +2626,54 @@ impl SchemeSync for &Xhci { } impl Xhci { + 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 { let port_state = self.port_states.get(&port_num).ok_or(Error::new(EBADFD))?; @@ -2407,6 +2724,8 @@ impl Xhci { endp_num: u8, clear_feature: bool, ) -> Result<()> { + self.ensure_port_active(port_num)?; + if self.get_endp_status(port_num, endp_num)? != EndpointStatus::Halted { return Err(Error::new(EPROTO)); } 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::::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::::new().expect("vboxd: Could not create event queue."); + let event_queue = match EventQueue::::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 { // 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 { 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 { 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 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 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 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);