diff --git a/Cargo.lock b/Cargo.lock index 9fcbd662..6aa362f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,11 +31,20 @@ dependencies = [ "spinning_top", ] +[[package]] +name = "acpi-resource" +version = "0.0.1" +dependencies = [ + "serde", + "thiserror 2.0.18", +] + [[package]] name = "acpid" version = "0.1.0" dependencies = [ "acpi", + "acpi-resource", "amlserde", "arrayvec", "common", @@ -54,6 +63,7 @@ dependencies = [ "scheme-utils", "serde", "thiserror 2.0.18", + "toml", ] [[package]] @@ -80,6 +90,23 @@ dependencies = [ "memchr 2.8.0", ] +[[package]] +name = "amd-mp2-i2cd" +version = "0.1.0" +dependencies = [ + "acpi-resource", + "anyhow", + "common", + "daemon", + "i2c-interface", + "libredox", + "log", + "pcid", + "redox_syscall 0.7.4", + "ron", + "serde", +] + [[package]] name = "amlserde" version = "0.0.1" @@ -584,6 +611,7 @@ dependencies = [ "inputd", "libredox", "log", + "nom", "redox-ioctl", "redox-scheme", "redox_syscall 0.7.4", @@ -642,6 +670,22 @@ dependencies = [ "linux-raw-sys 0.9.4", ] +[[package]] +name = "dw-acpi-i2cd" +version = "0.1.0" +dependencies = [ + "acpi-resource", + "anyhow", + "common", + "daemon", + "i2c-interface", + "libredox", + "log", + "redox_syscall 0.7.4", + "ron", + "serde", +] + [[package]] name = "e1000d" version = "0.1.0" @@ -909,6 +953,22 @@ dependencies = [ "wasip3", ] +[[package]] +name = "gpiod" +version = "0.1.0" +dependencies = [ + "anyhow", + "common", + "daemon", + "libredox", + "log", + "redox-scheme", + "redox_syscall 0.7.4", + "ron", + "scheme-utils", + "serde", +] + [[package]] name = "gpt" version = "3.1.0" @@ -999,11 +1059,74 @@ dependencies = [ "common", "daemon", "fdt 0.1.5", + "libc", "libredox", "log", "ron", ] +[[package]] +name = "i2c-gpio-expanderd" +version = "0.1.0" +dependencies = [ + "acpi-resource", + "anyhow", + "common", + "daemon", + "i2c-interface", + "libredox", + "log", + "redox_syscall 0.7.4", + "ron", + "serde", +] + +[[package]] +name = "i2c-hidd" +version = "0.1.0" +dependencies = [ + "acpi-resource", + "amlserde", + "anyhow", + "common", + "daemon", + "i2c-interface", + "inputd", + "libredox", + "log", + "orbclient", + "redox-scheme", + "redox_syscall 0.7.4", + "ron", + "scheme-utils", + "serde", +] + +[[package]] +name = "i2c-interface" +version = "0.1.0" +dependencies = [ + "redox_syscall 0.7.4", + "serde", +] + +[[package]] +name = "i2cd" +version = "0.1.0" +dependencies = [ + "anyhow", + "common", + "daemon", + "i2c-interface", + "libredox", + "log", + "redox-scheme", + "redox_syscall 0.7.4", + "ron", + "scheme-utils", + "serde", +] + [[package]] name = "iana-time-zone" version = "0.1.65" @@ -1128,6 +1251,58 @@ dependencies = [ "scheme-utils", ] +[[package]] +name = "intel-gpiod" +version = "0.1.0" +dependencies = [ + "acpi-resource", + "anyhow", + "common", + "daemon", + "libredox", + "log", + "redox_syscall 0.7.4", + "ron", + "serde", +] + +[[package]] +name = "intel-lpss-i2cd" +version = "0.1.0" +dependencies = [ + "acpi-resource", + "anyhow", + "common", + "daemon", + "i2c-interface", + "libredox", + "log", + "redox_syscall 0.7.4", + "ron", + "serde", +] + +[[package]] +name = "intel-thc-hidd" +version = "0.1.0" +dependencies = [ + "acpi-resource", + "amlserde", + "anyhow", + "common", + "daemon", + "i2c-interface", + "libredox", + "log", + "pci_types", + "pcid", + "redox-scheme", + "redox_syscall 0.7.4", + "ron", + "scheme-utils", + "serde", +] + [[package]] name = "ioslice" version = "0.6.0" @@ -1174,6 +1349,7 @@ dependencies = [ "daemon", "driver-network", "libredox", + "log", "pcid", "redox_event", "redox_syscall 0.7.4", @@ -2390,6 +2566,24 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +[[package]] +name = "ucsid" +version = "0.1.0" +dependencies = [ + "acpi-resource", + "anyhow", + "common", + "daemon", + "i2c-interface", + "libredox", + "log", + "redox-scheme", + "redox_syscall 0.7.4", + "ron", + "scheme-utils", + "serde", +] + [[package]] name = "unicode-ident" version = "1.0.24" diff --git a/Cargo.toml b/Cargo.toml index 9e776232..36d87870 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,8 +20,17 @@ members = [ "drivers/common", "drivers/executor", + "drivers/acpi-resource", "drivers/acpid", + "drivers/gpio/gpiod", + "drivers/gpio/i2c-gpio-expanderd", + "drivers/gpio/intel-gpiod", "drivers/hwd", + "drivers/i2c/amd-mp2-i2cd", + "drivers/i2c/dw-acpi-i2cd", + "drivers/i2c/i2c-interface", + "drivers/i2c/i2cd", + "drivers/i2c/intel-lpss-i2cd", "drivers/pcid", "drivers/pcid-spawner", "drivers/rtcd", @@ -43,6 +52,8 @@ members = [ "drivers/graphics/virtio-gpud", "drivers/input/ps2d", + "drivers/input/i2c-hidd", + "drivers/input/intel-thc-hidd", "drivers/input/usbhidd", "drivers/net/driver-network", @@ -63,6 +74,7 @@ members = [ "drivers/storage/usbscsid", "drivers/storage/virtio-blkd", + "drivers/usb/ucsid", "drivers/usb/xhcid", "drivers/usb/usbctl", "drivers/usb/usbhubd", @@ -81,6 +93,7 @@ drm = "0.15.0" drm-sys = "0.8.1" edid = "0.3.0" #TODO: edid is abandoned, fork it and maintain? fdt = "0.1.5" +nom = "3.2.0" # transitive dep via edid; needed to match on IResult variants libc = "0.2.181" log = "0.4" libredox = "0.1.16" diff --git a/audiod/src/main.rs b/audiod/src/main.rs index 51b103af..2354cf5f 100644 --- a/audiod/src/main.rs +++ b/audiod/src/main.rs @@ -48,7 +48,14 @@ fn daemon(daemon: SchemeDaemon) -> anyhow::Result<()> { let pid = libredox::call::getpid()?; - let hw_file = Fd::open("/scheme/audiohw", flag::O_WRONLY | flag::O_CLOEXEC, 0)?; + let hw_file = match Fd::open("/scheme/audiohw", flag::O_WRONLY | flag::O_CLOEXEC, 0) { + Ok(fd) => fd, + Err(err) if err.errno() == syscall::ENODEV => { + eprintln!("audiod: no audio hardware detected"); + return Ok(()); + } + Err(err) => return Err(err).context("failed to open /scheme/audiohw"), + }; let socket = Socket::create().context("failed to create scheme")?; diff --git a/daemon/src/lib.rs b/daemon/src/lib.rs index 9f507221..4e434082 100644 --- a/daemon/src/lib.rs +++ b/daemon/src/lib.rs @@ -11,12 +11,23 @@ use redox_scheme::Socket; use redox_scheme::scheme::{SchemeAsync, SchemeSync}; unsafe fn get_fd(var: &str) -> RawFd { - let fd: RawFd = std::env::var(var).unwrap().parse().unwrap(); + let fd: RawFd = match std::env::var(var) + .map_err(|e| eprintln!("daemon: env var {var} not set: {e}")) + .ok() + .and_then(|val| { + val.parse() + .map_err(|e| eprintln!("daemon: failed to parse {var} as fd: {e}")) + .ok() + }) { + Some(fd) => fd, + None => return -1, + }; if unsafe { libc::fcntl(fd, libc::F_SETFD, libc::FD_CLOEXEC) } == -1 { - panic!( + eprintln!( "daemon: failed to set CLOEXEC flag for {var} fd: {}", io::Error::last_os_error() ); + return -1; } fd } @@ -51,31 +62,40 @@ impl Daemon { /// Notify the process that the daemon is ready to accept requests. pub fn ready(mut self) { - self.write_pipe.write_all(&[0]).unwrap(); + if let Err(err) = self.write_pipe.write_all(&[0]) { + if err.kind() != io::ErrorKind::BrokenPipe { + eprintln!("daemon::ready write failed: {err}"); + } + } } /// 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) { - let (mut read_pipe, write_pipe) = io::pipe().unwrap(); + pub fn spawn(mut cmd: Command) -> io::Result<()> { + let (mut read_pipe, write_pipe) = io::pipe().map_err(|err| { + io::Error::new(err.kind(), format!("daemon: failed to create readiness pipe: {err}")) + })?; 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}"), + )), } } } @@ -96,13 +116,16 @@ impl SchemeDaemon { /// Notify the process that the scheme daemon is ready to accept requests. pub fn ready_with_fd(self, cap_fd: Fd) -> syscall::Result<()> { - syscall::call_wo( + match syscall::call_wo( self.write_pipe.as_raw_fd() as usize, &cap_fd.into_raw().to_ne_bytes(), syscall::CallFlags::FD, &[], - )?; - Ok(()) + ) { + Ok(_) => Ok(()), + Err(err) if err.errno == syscall::EPIPE => Ok(()), + Err(err) => Err(err), + } } /// Notify the process that the synchronous scheme daemon is ready to accept requests. diff --git a/drivers/acpid/Cargo.toml b/drivers/acpid/Cargo.toml index 2d22a8f9..712b6d6e 100644 --- a/drivers/acpid/Cargo.toml +++ b/drivers/acpid/Cargo.toml @@ -8,6 +8,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +acpi-resource = { path = "../acpi-resource" } acpi = { git = "https://github.com/jackpot51/acpi.git" } arrayvec = "0.7.6" log.workspace = true @@ -21,6 +22,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..a7cde5d6 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(()) } @@ -284,7 +896,11 @@ impl AmlSymbols { match self.init(pci_fd) { Ok(()) => (), Err(err) => { - log::error!("failed to initialize AML context: {}", err); + if pci_fd.is_none() { + log::debug!("AML init deferred until PCI registration: {}", err); + } else { + log::error!("failed to initialize AML context: {}", err); + } } } } @@ -316,7 +932,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 +959,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 +995,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 +1006,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 +1187,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 +1201,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 +1326,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 +1394,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 +1544,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 +1556,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 +1829,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 +1837,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 +1907,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 +1953,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 +1973,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 +2001,12 @@ impl Fadt { context.fadt = Some(fadt.clone()); context.dsdt = Some(Dsdt(dsdt_sdt.clone())); + context.pm1a_cnt_blk = pm1a_cnt_blk; + context.pm1b_cnt_blk = pm1b_cnt_blk; + context.reset_reg = reset_reg; + context.reset_value = reset_value; context.tables.push(dsdt_sdt); } } diff --git a/drivers/acpid/src/acpi/dmar/mod.rs b/drivers/acpid/src/acpi/dmar/mod.rs index c42b379a..f4dff276 100644 --- a/drivers/acpid/src/acpi/dmar/mod.rs +++ b/drivers/acpid/src/acpi/dmar/mod.rs @@ -474,10 +474,13 @@ impl<'sdt> Iterator for DmarRawIter<'sdt> { let len_bytes = <[u8; 2]>::try_from(type_bytes) .expect("expected a 2-byte slice to be convertible to [u8; 2]"); - let ty = u16::from_ne_bytes(type_bytes); - let len = u16::from_ne_bytes(len_bytes); + let len = u16::from_ne_bytes(len_bytes) as usize; - let len = usize::try_from(len).expect("expected u16 to fit within usize"); + if len < 4 { + return None; + } + + let ty = u16::from_ne_bytes(type_bytes); if len > remainder.len() { log::warn!("DMAR remapping structure length was smaller than the remaining length of the table."); diff --git a/drivers/acpid/src/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..3b0deeab 100644 --- a/drivers/acpid/src/main.rs +++ b/drivers/acpid/src/main.rs @@ -5,107 +5,182 @@ 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 resources; mod scheme; +mod sleep; -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") + let Some(event) = event_queue.next().transpose().map_err(|error| { + StartupError::runtime("failed to read event file", error.to_string()) + })? else { break; }; @@ -114,8 +189,9 @@ fn daemon(daemon: daemon::Daemon) -> ! { loop { match handler .process_requests_nonblocking(&mut scheme) - .expect("acpid: failed to process requests") - { + .map_err(|error| { + StartupError::runtime("failed to process requests", error.to_string()) + })? { ControlFlow::Continue(()) => {} ControlFlow::Break(()) => break, } @@ -125,19 +201,139 @@ 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..4fe3b8d8 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,22 @@ 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, +}; +use crate::resources::{decode_resource_template, ResourceDescriptor}; pub struct AcpiScheme<'acpi, 'sock> { ctx: &'acpi AcpiContext, handles: HandleMap>, - pci_fd: Option, socket: &'sock Socket, } @@ -41,10 +45,204 @@ enum HandleKind<'a> { Table(SdtSignature), Symbols(RwLockReadGuard<'a, AmlSymbols>), Symbol { name: String, description: String }, + ResourcesDir, + Resources(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), + ("resources", DirentKind::Directory), + ("dmi", DirentKind::Directory), + ("reboot", DirentKind::Regular), + ]; + if power_available { + entries.push(("power", DirentKind::Directory)); + } + entries +} + +fn resource_symbol_path(path: &str) -> Option { + let normalized = path.trim_matches('/').trim_start_matches('\\'); + if normalized.is_empty() { + return None; + } + + let normalized = normalized.replace('/', "."); + if normalized.is_empty() { + None + } else { + Some(format!("{normalized}._CRS")) + } +} + +fn resource_entry_name(symbol: &str) -> Option { + symbol + .strip_suffix("._CRS") + .map(str::to_string) + .filter(|path| !path.is_empty()) +} + +fn resource_dir_entries<'a>(symbols: impl IntoIterator) -> Vec { + let mut entries = symbols + .into_iter() + .filter_map(resource_entry_name) + .collect::>(); + entries.sort_unstable(); + entries.dedup(); + entries +} + impl HandleKind<'_> { fn is_dir(&self) -> bool { match self { @@ -53,6 +251,17 @@ impl HandleKind<'_> { Self::Table(_) => false, Self::Symbols(_) => true, Self::Symbol { .. } => false, + Self::ResourcesDir => true, + Self::Resources(_) => 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 +274,21 @@ impl HandleKind<'_> { .ok_or(Error::new(EBADFD))? .length(), Self::Symbol { description, .. } => description.len(), + Self::Resources(contents) => contents.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::ResourcesDir + | 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 +299,163 @@ 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 resources_handle(&self, path: &str) -> Result> { + if !self.ctx.pci_ready() { + let display_path = if path.is_empty() { "resources" } else { path }; + log::warn!( + "Deferring ACPI resource lookup for {display_path} until PCI registration is ready" + ); + return Err(Error::new(EAGAIN)); + } + + let normalized = path.trim_matches('/'); + if normalized.is_empty() { + return Ok(HandleKind::ResourcesDir); + } + + let symbol_path = resource_symbol_path(normalized).ok_or(Error::new(ENOENT))?; + if self.ctx.aml_lookup(&symbol_path).is_none() { + return Err(Error::new(ENOENT)); + } + + let aml_name = + AmlName::from_str(&format!("\\{symbol_path}")).map_err(|_| Error::new(ENOENT))?; + let buffer = match self.ctx.aml_eval(aml_name, Vec::new()) { + Ok(AmlSerdeValue::Buffer(bytes)) => bytes, + Ok(other) => { + log::debug!( + "Skipping ACPI resources for {normalized} due to unexpected _CRS value: {:?}", + other + ); + return Err(Error::new(ENOENT)); + } + Err(error) => { + log::debug!( + "Failed to evaluate ACPI resources for {symbol_path}: {:?}", + error + ); + return Err(Error::new(ENOENT)); + } + }; + + let descriptors: Vec = + decode_resource_template(&buffer).map_err(|error| { + log::warn!("Failed to decode ACPI _CRS for {symbol_path}: {error}"); + Error::new(EIO) + })?; + let serialized = ron::ser::to_string(&descriptors).map_err(|error| { + log::warn!("Failed to serialize decoded ACPI resources for {symbol_path}: {error}"); + Error::new(EIO) + })?; + + Ok(HandleKind::Resources(serialized)) + } + + 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 { @@ -182,49 +557,97 @@ impl SchemeSync for AcpiScheme<'_, '_> { let kind = match handle.kind { HandleKind::SchemeRoot => { - // TODO: arrayvec - let components = { - let mut v = arrayvec::ArrayVec::<&str, 3>::new(); - let it = path.split('/'); - for component in it.take(3) { - v.push(component); - } - - v - }; - - match &*components { - [""] => HandleKind::TopLevel, - ["register_pci"] => HandleKind::RegisterPci, - ["tables"] => HandleKind::Tables, + if path == "resources" || path == "resources/" { + self.resources_handle("")? + } else if let Some(rest) = path.strip_prefix("resources/") { + self.resources_handle(rest)? + } else { + // TODO: arrayvec + let components = { + let mut v = arrayvec::ArrayVec::<&str, 4>::new(); + let it = path.split('/'); + for component in it.take(4) { + v.push(component); + } - ["tables", table] => { - let signature = parse_table(table.as_bytes()).ok_or(Error::new(ENOENT))?; - HandleKind::Table(signature) - } + v + }; + + 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, + + ["tables", table] => { + let signature = + parse_table(table.as_bytes()).ok_or(Error::new(ENOENT))?; + HandleKind::Table(signature) + } - ["symbols"] => { - if let Ok(aml_symbols) = self.ctx.aml_symbols(self.pci_fd.as_ref()) { - HandleKind::Symbols(aml_symbols) - } else { - return Err(Error::new(EIO)); + ["symbols"] => { + 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)); + } } - } - ["symbols", symbol] => { - if let Some(description) = self.ctx.aml_lookup(symbol) { - HandleKind::Symbol { - name: (*symbol).to_owned(), - description, + ["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(), + description, + } + } else { + return Err(Error::new(ENOENT)); } - } else { - return Err(Error::new(ENOENT)); } - } - _ => return Err(Error::new(ENOENT)), + _ => 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::ResourcesDir => self.resources_handle(path)?, HandleKind::Symbols(ref aml_symbols) => { if let Some(description) = aml_symbols.lookup(path) { HandleKind::Symbol { @@ -235,6 +658,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 +736,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 +749,9 @@ impl SchemeSync for AcpiScheme<'_, '_> { .ok_or(Error::new(EBADFD))? .as_slice(), HandleKind::Symbol { description, .. } => description.as_bytes(), + HandleKind::Resources(contents) => contents.as_bytes(), + HandleKind::Dmi(contents) => contents.as_bytes(), + HandleKind::PowerFile(contents) => contents.as_bytes(), _ => return Err(Error::new(EINVAL)), }; @@ -328,13 +771,95 @@ 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::ResourcesDir => { + let aml_symbols = self.ctx.aml_symbols().map_err(|_| Error::new(EIO))?; + let entries = + resource_dir_entries(aml_symbols.symbols_cache().keys().map(String::as_str)); + for (idx, name) in 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 +868,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 +1003,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 +1061,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 +1072,90 @@ impl SchemeSync for AcpiScheme<'_, '_> { self.handles.remove(id); } } + +#[cfg(test)] +mod tests { + use super::{dmi_contents, resource_dir_entries, resource_symbol_path, 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) })); + } + + #[test] + fn top_level_entries_always_include_resources() { + let entries = top_level_entries(false); + assert!(entries + .iter() + .any(|(name, kind)| { *name == "resources" && matches!(kind, DirentKind::Directory) })); + } + + #[test] + fn resource_symbol_path_accepts_dotted_and_slash_paths() { + assert_eq!( + resource_symbol_path("_SB.PCI0.I2C0.TPD0").as_deref(), + Some("_SB.PCI0.I2C0.TPD0._CRS") + ); + assert_eq!( + resource_symbol_path("\\_SB/PCI0/I2C0/TPD0").as_deref(), + Some("_SB.PCI0.I2C0.TPD0._CRS") + ); + } + + #[test] + fn resource_dir_entries_list_devices_with_crs_suffix() { + assert_eq!( + resource_dir_entries([ + "_SB.PCI0.I2C0.TPD0._CRS", + "_SB.PCI0.I2C1.TPD1._HID", + "_SB.PCI0.I2C0.TPD0._CRS", + ]), + vec!["_SB.PCI0.I2C0.TPD0".to_string()] + ); + } +} 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/hda/device.rs b/drivers/audio/ihdad/src/hda/device.rs index 78e8f0a2..e5742f80 100755 --- a/drivers/audio/ihdad/src/hda/device.rs +++ b/drivers/audio/ihdad/src/hda/device.rs @@ -1,6 +1,6 @@ #![allow(dead_code)] -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::fmt::Write; use std::str; use std::task::Poll; @@ -14,20 +14,55 @@ use redox_scheme::scheme::SchemeSync; use redox_scheme::CallerCtx; use redox_scheme::OpenResult; use scheme_utils::{FpathWriter, HandleMap}; -use syscall::error::{Error, Result, EACCES, EBADF, EIO, ENODEV, EWOULDBLOCK}; +use syscall::error::{Error, Result, EACCES, EBADF, EIO, ENODEV, ENOENT, EWOULDBLOCK}; use spin::Mutex; use syscall::schemev2::NewFdFlags; use super::common::*; +use super::parser::AutoPinConfig; use super::BitsPerSample; use super::BufferDescriptorListEntry; use super::CommandBuffer; +use super::digital::DigitalCodecInfo; +use super::dispatch::RouteDecision; +use super::FixupEngine; use super::HDANode; +use super::InputStream; use super::OutputStream; use super::StreamBuffer; use super::StreamDescriptorRegs; +#[derive(Debug, Clone)] +pub struct ControllerPolicy { + pub prefer_msi: bool, + pub single_cmd_fallback: bool, + pub probe_mask: u16, + pub position_fix: PositionFixPolicy, + pub poll_jack: bool, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PositionFixPolicy { + Auto, + Lpib, + Posbuf, + Dpic, + None, +} + +impl Default for ControllerPolicy { + fn default() -> Self { + ControllerPolicy { + prefer_msi: true, + single_cmd_fallback: false, + probe_mask: 0xFFFF, + position_fix: PositionFixPolicy::Auto, + poll_jack: false, + } + } +} + // GCTL - Global Control const CRST: u32 = 1 << 0; // 1 bit const FNCTRL: u32 = 1 << 1; // 1 bit @@ -55,6 +90,20 @@ const RIRBDMAEN: u8 = 1 << 1; // 1 bit const ICB: u16 = 1 << 0; const IRV: u16 = 1 << 1; +// INTCTL bits +const INTCTL_GIE: u32 = 1 << 31; // Global Interrupt Enable +const INTCTL_CIE: u32 = 1 << 30; // Controller Interrupt Enable + +// RIRBSTS bits (write-1-to-clear) +const RIRBSTS_RINTFL: u8 = 1 << 0; // Response Interrupt Flag +const RIRBSTS_RIRBOIS: u8 = 1 << 2; // RIRB Overrun Interrupt Status + +// CORBSTS bits (write-1-to-clear) +const CORBSTS_CMEI: u8 = 1 << 0; // CORB Memory Error Interrupt + +// STATESTS mask — one bit per codec slot (bits 0-14) +const STATESTS_MASK: u16 = 0x7FFF; + // CORB and RIRB offset const COMMAND_BUFFER_OFFSET: usize = 0x40; @@ -63,9 +112,8 @@ const NUM_SUB_BUFFS: usize = 32; const SUB_BUFF_SIZE: usize = 2048; enum Handle { - Todo, - Pcmout(usize, usize, usize), // Card, index, block_ptr - Pcmin(usize, usize, usize), // Card, index, block_ptr + Pcmout { stream_index: usize }, + Pcmin { stream_index: usize }, StrBuf(Vec), SchemeRoot, } @@ -121,6 +169,34 @@ struct Regs { dpubase: Mmio, // 0x74 } +struct CodecTopology { + codec_addr: CodecAddr, + afgs: Vec, + widget_map: HashMap, + outputs: Vec, + inputs: Vec, + output_pins: Vec, + input_pins: Vec, + beep_addr: Option, + pin_config: AutoPinConfig, +} + +impl CodecTopology { + fn new(codec_addr: CodecAddr) -> Self { + CodecTopology { + codec_addr, + afgs: Vec::new(), + widget_map: HashMap::new(), + outputs: Vec::new(), + inputs: Vec::new(), + output_pins: Vec::new(), + input_pins: Vec::new(), + beep_addr: None, + pin_config: AutoPinConfig::default(), + } + } +} + pub struct IntelHDA { vend_prod: u32, @@ -131,6 +207,7 @@ pub struct IntelHDA { cmd: CommandBuffer, codecs: Vec, + codecs_topology: HashMap, outputs: Vec, inputs: Vec, @@ -140,15 +217,20 @@ pub struct IntelHDA { output_pins: Vec, input_pins: Vec, - beep_addr: WidgetAddr, + beep_addr: Option, buff_desc: Dma<[BufferDescriptorListEntry; 256]>, + input_buff_desc: Dma<[BufferDescriptorListEntry; 256]>, output_streams: Vec, + input_streams: Vec, buffs: Vec>, int_counter: usize, + policy: ControllerPolicy, + fixup_engine: FixupEngine, + digital_codecs: Vec, handles: Mutex>, } @@ -160,6 +242,10 @@ impl IntelHDA { .expect("Could not allocate physical memory for buffer descriptor list.") .assume_init(); + let input_buff_desc = Dma::<[BufferDescriptorListEntry; 256]>::zeroed() + .expect("Could not allocate physical memory for input buffer descriptor list.") + .assume_init(); + log::debug!( "Virt: {:016X}, Phys: {:016X}", buff_desc.as_ptr() as usize, @@ -182,11 +268,12 @@ impl IntelHDA { cmd: CommandBuffer::new(base + COMMAND_BUFFER_OFFSET, cmd_buff), - beep_addr: (0, 0), + beep_addr: None, widget_map: HashMap::::new(), codecs: Vec::::new(), + codecs_topology: HashMap::::new(), outputs: Vec::::new(), inputs: Vec::::new(), @@ -195,21 +282,34 @@ impl IntelHDA { input_pins: Vec::::new(), buff_desc, + input_buff_desc, output_streams: Vec::::new(), + input_streams: Vec::::new(), buffs: Vec::>::new(), int_counter: 0, + policy: ControllerPolicy::default(), + fixup_engine: FixupEngine::new(), + digital_codecs: Vec::new(), handles: Mutex::new(HandleMap::new()), }; module.init()?; + let vendor_id = ((module.vend_prod >> 16) & 0xFFFF) as u16; + let device_id = (module.vend_prod & 0xFFFF) as u16; + let route = RouteDecision::decide(vendor_id, device_id, 0x04, 0x03); + log::info!("IHDA: audio route decision: {:?} ({})", route.route, route.reason); + module.info(); module.enumerate()?; module.configure()?; + if let Err(err) = module.configure_input() { + log::debug!("IHDA: input configuration skipped: {:?}", err); + } log::debug!("IHDA: Initialization finished."); Ok(module) } @@ -219,23 +319,28 @@ impl IntelHDA { let use_immediate_command_interface = match self.vend_prod { 0x8086_2668 => false, - _ => true, + _ => !self.policy.single_cmd_fallback, }; self.cmd.init(use_immediate_command_interface)?; + + self.regs.gctl.writef(UNSOL, true); + self.init_interrupts(); Ok(()) } pub fn init_interrupts(&mut self) { - // TODO: provide a function to enable certain interrupts - // This just enables the first output stream interupt and the global interrupt - let iss = self.num_input_streams(); - self.regs - .intctl - .write((1 << 31) | /* (1 << 30) |*/ (1 << iss)); + + let mut wakeen: u16 = 0; + for &codec in &self.codecs { + wakeen |= 1 << codec; + } + self.regs.wakeen.write(wakeen); + + self.regs.intctl.write(INTCTL_GIE | INTCTL_CIE | (1 << iss) | (1 << 0)); } pub fn irq(&mut self) -> bool { @@ -248,6 +353,19 @@ impl IntelHDA { self.int_counter } + pub fn policy(&self) -> &ControllerPolicy { + &self.policy + } + + pub fn set_policy(&mut self, policy: ControllerPolicy) { + log::info!( + "IHDA: policy updated msi={} single_cmd={} probe_mask={:04X} pos_fix={:?} poll_jack={}", + policy.prefer_msi, policy.single_cmd_fallback, policy.probe_mask, + policy.position_fix, policy.poll_jack + ); + self.policy = policy; + } + pub fn read_node(&mut self, addr: WidgetAddr) -> Result { let mut node = HDANode::new(); let mut temp: u64; @@ -341,70 +459,156 @@ impl IntelHDA { } pub fn enumerate(&mut self) -> Result<()> { + // Clear old global state (kept for migration safety) self.output_pins.clear(); self.input_pins.clear(); + self.outputs.clear(); + self.inputs.clear(); + self.widget_map.clear(); + self.codecs_topology.clear(); + + let codec_addrs = self.codecs.clone(); + for codec in codec_addrs { + let mut topo = CodecTopology::new(codec); + + let root = self.read_node((codec, 0))?; + log::debug!("{}", root); + + let root_count = root.subnode_count; + let root_start = root.subnode_start; + + for i in 0..root_count { + let afg = self.read_node((codec, root_start + i))?; + log::debug!("{}", afg); + + // Only process audio function groups (type 0x01) + if afg.function_group_type != 0x01 { + log::debug!( + "Codec {}: function group {} is type {}, not audio \u{2014} skipping", + codec, + afg.addr.1, + afg.function_group_type + ); + continue; + } + + topo.afgs.push(afg.addr.1); - let codec: u8 = 0; - - let root = self.read_node((codec, 0))?; - - log::debug!("{}", root); - - let root_count = root.subnode_count; - let root_start = root.subnode_start; - - //FIXME: So basically the way this is set up is to only support one codec and hopes the first one is an audio - for i in 0..root_count { - let afg = self.read_node((codec, root_start + i))?; - log::debug!("{}", afg); - let afg_count = afg.subnode_count; - let afg_start = afg.subnode_start; - - for j in 0..afg_count { - let mut widget = self.read_node((codec, afg_start + j))?; - widget.is_widget = true; - match widget.widget_type() { - HDAWidgetType::AudioOutput => self.outputs.push(widget.addr), - HDAWidgetType::AudioInput => self.inputs.push(widget.addr), - HDAWidgetType::BeepGenerator => self.beep_addr = widget.addr, - HDAWidgetType::PinComplex => { - let config = widget.configuration_default(); - if config.is_output() { - self.output_pins.push(widget.addr); - } else if config.is_input() { - self.input_pins.push(widget.addr); + let afg_count = afg.subnode_count; + let afg_start = afg.subnode_start; + + for j in 0..afg_count { + let mut widget = self.read_node((codec, afg_start + j))?; + widget.is_widget = true; + match widget.widget_type() { + HDAWidgetType::AudioOutput => { + self.outputs.push(widget.addr); + topo.outputs.push(widget.addr); + } + HDAWidgetType::AudioInput => { + self.inputs.push(widget.addr); + topo.inputs.push(widget.addr); } + HDAWidgetType::BeepGenerator => { + self.beep_addr = Some(widget.addr); + topo.beep_addr = Some(widget.addr); + } + HDAWidgetType::PinComplex => { + let config = widget.configuration_default(); + if config.is_output() { + self.output_pins.push(widget.addr); + topo.output_pins.push(widget.addr); + } else if config.is_input() { + self.input_pins.push(widget.addr); + topo.input_pins.push(widget.addr); + } + } + _ => {} } - _ => {} + + log::debug!("{}", widget); + self.widget_map.insert(widget.addr(), widget.clone()); + topo.widget_map.insert(widget.addr(), widget); } + } - log::debug!("{}", widget); - self.widget_map.insert(widget.addr(), widget); + log::debug!( + "Codec {}: {} AFGs, {} outputs, {} inputs, {} output pins, {} input pins", + codec, + topo.afgs.len(), + topo.outputs.len(), + topo.inputs.len(), + topo.output_pins.len(), + topo.input_pins.len(), + ); + + let widget_list: Vec<(WidgetAddr, HDANode)> = topo.widget_map.iter().map(|(a, n)| (*a, n.clone())).collect(); + topo.pin_config = AutoPinConfig::parse(&widget_list); + + self.codecs_topology.insert(codec, topo); + } + + for (codec, topo) in &self.codecs_topology { + if let Some(digi) = DigitalCodecInfo::detect_from_topology(*codec, &topo.widget_map) { + log::info!( + "IHDA: digital codec detected at {} (hdmi={}, {} pins, {} converters)", + codec, digi.is_hdmi, digi.pin_widgets.len(), digi.converter_widgets.len() + ); + self.digital_codecs.push(digi); } } Ok(()) } - pub fn find_best_output_pin(&mut self) -> Result { - let outs = &self.output_pins; + fn pick_primary_codec_for_output(&self) -> Option { + let mut candidates: Vec = self + .codecs_topology + .values() + .filter(|topo| !topo.output_pins.is_empty() && !topo.outputs.is_empty()) + .map(|topo| topo.codec_addr) + .collect(); + candidates.sort(); + candidates.into_iter().next() + } + + pub fn find_best_output_pin(&mut self, codec: CodecAddr) -> Result { + let outs: Vec = self + .codecs_topology + .get(&codec) + .ok_or_else(|| { + log::error!("No topology for codec {}", codec); + Error::new(ENODEV) + })? + .output_pins + .clone(); + if outs.len() == 1 { return Ok(outs[0]); } else if outs.len() > 1 { - //TODO: change output based on "unsolicited response" interrupts - // Check for devices in this order: Headphone, Speaker, Line Out for supported_device in &[DefaultDevice::HPOut, DefaultDevice::Speaker] { - for &out in outs { - let widget = self.widget_map.get(&out).unwrap(); - let cd = widget.configuration_default(); + for &out in &outs { + let (addr, config_default) = { + let widget = self + .codecs_topology + .get(&codec) + .and_then(|t| t.widget_map.get(&out)) + .ok_or_else(|| { + log::error!( + "Widget {:?} not found in codec {} topology", + out, + codec + ); + Error::new(ENODEV) + })?; + (widget.addr, widget.config_default) + }; + let cd = ConfigurationDefault::from_u32(config_default); if cd.sequence() == 0 && &cd.default_device() == supported_device { - // Check for jack detect bit - let pin_caps = self.cmd.cmd12(widget.addr, 0xF00, 0x0C)?; + let pin_caps = self.cmd.cmd12(addr, 0xF00, 0x0C)?; if pin_caps & (1 << 2) != 0 { - // Check for presence - let pin_sense = self.cmd.cmd12(widget.addr, 0xF09, 0)?; + let pin_sense = self.cmd.cmd12(addr, 0xF09, 0)?; if pin_sense & (1 << 31) == 0 { - // Skip if nothing is plugged in continue; } } @@ -416,13 +620,26 @@ impl IntelHDA { Err(Error::new(ENODEV)) } - pub fn find_path_to_dac(&self, addr: WidgetAddr) -> Option> { - let widget = self.widget_map.get(&addr).unwrap(); + pub fn find_path_to_dac( + &self, + addr: WidgetAddr, + codec: CodecAddr, + visited: &mut HashSet, + ) -> Option> { + if visited.contains(&addr) { + log::warn!("Cycle detected in widget graph at {:?}", addr); + return None; + } + visited.insert(addr); + + let topo = self.codecs_topology.get(&codec)?; + let widget = topo.widget_map.get(&addr)?; + if widget.widget_type() == HDAWidgetType::AudioOutput { Some(vec![addr]) } else { let connection = widget.connections.get(widget.connection_default as usize)?; - let mut path = self.find_path_to_dac(*connection)?; + let mut path = self.find_path_to_dac(*connection, codec, visited)?; path.insert(0, addr); Some(path) } @@ -466,72 +683,92 @@ impl IntelHDA { } pub fn configure(&mut self) -> Result<()> { - let outpin = self.find_best_output_pin()?; + let codec = self.pick_primary_codec_for_output().ok_or_else(|| { + log::error!("No suitable codec found for audio output"); + Error::new(ENODEV) + })?; + + log::debug!("Selected codec {} for output", codec); + + let topo = self.codecs_topology.get(&codec).ok_or_else(|| { + log::error!("No topology for codec {}", codec); + Error::new(ENODEV) + })?; + + let vendor_id = ((self.vend_prod >> 16) & 0xFFFF) as u16; + let device_id = (self.vend_prod & 0xFFFF) as u16; + self.fixup_engine.match_fixups(vendor_id, device_id, None, &topo.pin_config); + + let primary_pins = topo.pin_config.primary_output_pins(); + let outpin = primary_pins.first().map(|p| p.addr).ok_or_else(|| { + log::error!("No primary output pins found by parser on codec {}", codec); + Error::new(ENODEV) + })?; log::debug!("Best pin: {:01X}:{:02X}", outpin.0, outpin.1); - let path = self.find_path_to_dac(outpin).unwrap(); + let path = { + let mut visited = HashSet::new(); + self.find_path_to_dac(outpin, codec, &mut visited) + .ok_or_else(|| { + log::error!( + "No path to DAC from pin {:01X}:{:02X} on codec {}", + outpin.0, + outpin.1, + codec + ); + Error::new(ENODEV) + })? + }; - let dac = *path.last().unwrap(); - let pin = *path.first().unwrap(); + let dac = *path.last().ok_or_else(|| { + log::error!("Empty DAC path for pin {:01X}:{:02X}", outpin.0, outpin.1); + Error::new(ENODEV) + })?; + let pin = *path.first().ok_or_else(|| { + log::error!("Empty path (no pin) for pin {:01X}:{:02X}", outpin.0, outpin.1); + Error::new(ENODEV) + })?; log::debug!("Path to DAC: {:X?}", path); - // Set power state 0 (on) for all widgets in path for &addr in &path { self.set_power_state(addr, 0)?; } - // Pin enable (0x80 = headphone amp enable, 0x40 = output enable) self.cmd.cmd12(pin, 0x707, 0xC0)?; - - // EAPD enable self.cmd.cmd12(pin, 0x70C, 2)?; - - // Set DAC stream and channel + self.cmd.cmd4(pin, 0x708, (1 << 8) | 1)?; self.set_stream_channel(dac, 1, 0)?; self.update_sound_buffers(); - log::debug!( - "Supported Formats: {:08X}", - self.get_supported_formats((0, 0x1))? - ); - log::debug!("Capabilities: {:08X}", self.get_capabilities(path[0])?); + let (rate, bps, channels) = self.negotiate_stream_format(dac)?; + log::debug!("IHDA: negotiated stream format bps={:?} ch={}", bps, channels); - // Create output stream let output = self.get_output_stream_descriptor(0).unwrap(); output.set_address(self.buff_desc.physical()); - output.set_pcm_format(&super::SR_44_1, BitsPerSample::Bits16, 2); - output.set_cyclic_buffer_length((NUM_SUB_BUFFS * SUB_BUFF_SIZE) as u32); // number of bytes + output.set_pcm_format(rate, bps, channels); + output.set_cyclic_buffer_length((NUM_SUB_BUFFS * SUB_BUFF_SIZE) as u32); output.set_stream_number(1); output.set_last_valid_index((NUM_SUB_BUFFS - 1) as u16); output.set_interrupt_on_completion(true); - // Set DAC converter format - self.set_converter_format(dac, &super::SR_44_1, BitsPerSample::Bits16, 2)?; + self.set_converter_format(dac, rate, bps, channels)?; - // Get DAC converter format - //TODO: should validate? self.cmd.cmd12(dac, 0xA00, 0)?; - // Unmute and set gain to 0db for input and output amplifiers on all widgets in path for &addr in &path { - // Read widget capabilities let caps = self.cmd.cmd12(addr, 0xF00, 0x09)?; - //TODO: do we need to set any other indexes? let left = true; let right = true; let index = 0; let mute = false; - // Check for input amp if (caps & (1 << 1)) != 0 { - // Read input capabilities let in_caps = self.cmd.cmd12(addr, 0xF00, 0x0D)?; let in_gain = (in_caps & 0x7f) as u8; - // Set input gain let output = false; let input = true; self.set_amplifier_gain_mute( @@ -540,12 +777,9 @@ impl IntelHDA { log::debug!("Set {:X?} input gain to 0x{:X}", addr, in_gain); } - // Check for output amp if (caps & (1 << 2)) != 0 { - // Read output capabilities let out_caps = self.cmd.cmd12(addr, 0xF00, 0x12)?; let out_gain = (out_caps & 0x7f) as u8; - // Set output gain let output = true; let input = false; self.set_amplifier_gain_mute( @@ -555,8 +789,6 @@ impl IntelHDA { } } - //TODO: implement hda-verb? - output.run(); { log::debug!("Waiting for output 0 to start running..."); @@ -632,20 +864,21 @@ impl IntelHDA { */ - pub fn dump_codec(&self, codec: u8) -> String { + pub fn dump_all_codecs(&self) -> String { let mut string = String::new(); - for (_, widget) in self.widget_map.iter() { - let _ = writeln!(string, "{}", widget); + for (&codec, topo) in &self.codecs_topology { + let _ = writeln!(string, "Codec {}:", codec); + for (_, widget) in topo.widget_map.iter() { + let _ = writeln!(string, " {}", widget); + } } string } - // BEEP!! pub fn beep(&mut self, div: u8) { - let addr = self.beep_addr; - if addr != (0, 0) { + if let Some(addr) = self.beep_addr { let _ = self.cmd.cmd12(addr, 0xF0A, div); } } @@ -700,7 +933,7 @@ impl IntelHDA { log::debug!("Statests: {:04X}", statests); for i in 0..15 { - if (statests >> i) & 0x1 == 1 { + if (statests >> i) & 0x1 == 1 && (self.policy.probe_mask >> i) & 0x1 == 1 { self.codecs.push(i as CodecAddr); } } @@ -812,6 +1045,54 @@ impl IntelHDA { Ok(self.cmd.cmd12(addr, 0xF00, 0x0A)? as u32) } + fn negotiate_stream_format( + &mut self, + dac: WidgetAddr, + ) -> Result<(&'static super::SampleRate, BitsPerSample, u8)> { + let fmt = self.get_supported_formats(dac)?; + log::debug!("IHDA: DAC {:01X}:{:02X} supported formats: {:08X}", dac.0, dac.1, fmt); + + let rate = if fmt & (1 << 14) != 0 { + &super::SR_48 + } else if fmt & (1 << 13) != 0 { + &super::SR_44_1 + } else if fmt & (1 << 12) != 0 { + &super::SR_32 + } else if fmt & (1 << 11) != 0 { + &super::SR_22_05 + } else if fmt & (1 << 10) != 0 { + &super::SR_16 + } else if fmt & (1 << 9) != 0 { + &super::SR_11_025 + } else if fmt & (1 << 8) != 0 { + &super::SR_8 + } else { + log::error!("IHDA: no supported sample rate found in format {:08X}", fmt); + return Err(Error::new(ENODEV)); + }; + + let bps = if fmt & (1 << 21) != 0 { + BitsPerSample::Bits16 + } else if fmt & (1 << 23) != 0 { + BitsPerSample::Bits24 + } else if fmt & (1 << 24) != 0 { + BitsPerSample::Bits32 + } else if fmt & (1 << 22) != 0 { + BitsPerSample::Bits20 + } else if fmt & (1 << 20) != 0 { + BitsPerSample::Bits8 + } else { + log::error!("IHDA: no supported bit depth found in format {:08X}", fmt); + return Err(Error::new(ENODEV)); + }; + + let caps = self.get_capabilities(dac)?; + let max_channels = ((caps >> 0) & 0xFF) as u8 + 1; + let channels = if max_channels >= 2 { 2 } else { max_channels }; + + Ok((rate, bps, channels)) + } + fn get_capabilities(&mut self, addr: WidgetAddr) -> Result { Ok(self.cmd.cmd12(addr, 0xF00, 0x09)? as u32) } @@ -873,13 +1154,98 @@ impl IntelHDA { //log::trace!("Status: {:02X} Pos: {:08X} Output CTL: {:06X}", output.status(), output.link_position(), output.control()); if os.current_block() == (open_block + 3) % NUM_SUB_BUFFS { - // Block if we already are 3 buffers ahead Poll::Pending } else { Poll::Ready(os.write_block(buf)) } } + pub fn configure_input(&mut self) -> Result<()> { + let primary_codec = match self.pick_primary_codec_for_output() { + Some(c) => c, + None => return Err(Error::new(ENODEV)), + }; + + let topo = self.codecs_topology.get(&primary_codec).ok_or_else(|| { + log::error!("IHDA: no topology for codec {}", primary_codec); + Error::new(ENODEV) + })?; + + let input_pin = topo.input_pins.first().cloned().ok_or_else(|| { + log::debug!("IHDA: no input pins found on codec {}", primary_codec); + Error::new(ENODEV) + })?; + + let adc = topo.inputs.first().cloned().ok_or_else(|| { + log::debug!("IHDA: no ADC widgets found on codec {}", primary_codec); + Error::new(ENODEV) + })?; + + log::debug!( + "IHDA: configuring input: pin={:01X}:{:02X} adc={:01X}:{:02X}", + input_pin.0, input_pin.1, adc.0, adc.1 + ); + + self.cmd.cmd12(input_pin, 0x707, 0xC0)?; + self.set_power_state(input_pin, 0)?; + self.set_power_state(adc, 0)?; + self.set_stream_channel(adc, 2, 0)?; + + let iss = self.num_input_streams(); + if iss == 0 { + log::warn!("IHDA: no input streams available"); + return Err(Error::new(ENODEV)); + } + + let input_regs = self.get_input_stream_descriptor(0).ok_or_else(|| { + log::error!("IHDA: failed to get input stream descriptor 0"); + Error::new(ENODEV) + })?; + + let mut input_stream = InputStream::new(NUM_SUB_BUFFS, SUB_BUFF_SIZE, input_regs); + + for i in 0..NUM_SUB_BUFFS { + self.input_buff_desc[i].set_address(input_stream.phys() as u64 + (i * SUB_BUFF_SIZE) as u64); + self.input_buff_desc[i].set_length(SUB_BUFF_SIZE as u32); + self.input_buff_desc[i].set_interrupt_on_complete(true); + } + + let (rate, bps, channels) = self.negotiate_stream_format(adc)?; + + input_stream.regs().set_address(self.input_buff_desc.physical()); + input_stream.regs().set_pcm_format(rate, bps, channels); + input_stream.regs().set_cyclic_buffer_length((NUM_SUB_BUFFS * SUB_BUFF_SIZE) as u32); + input_stream.regs().set_stream_number(2); + input_stream.regs().set_last_valid_index((NUM_SUB_BUFFS - 1) as u16); + input_stream.regs().set_interrupt_on_completion(true); + + self.set_converter_format(adc, rate, bps, channels)?; + + self.input_streams.push(input_stream); + + let input_ref = self.input_streams.last_mut().unwrap(); + input_ref.regs().run(); + + log::debug!("IHDA: input stream 0 configured and running"); + Ok(()) + } + + pub fn read_from_input(&mut self, index: usize, buf: &mut [u8]) -> Poll> { + let input_stream = match self.input_streams.get_mut(index) { + Some(s) => s, + None => return Poll::Ready(Err(Error::new(EBADF))), + }; + + let pos = input_stream.regs().link_position() as usize; + let hw_block = pos / input_stream.block_size(); + + if input_stream.current_block() == hw_block { + Poll::Pending + } else { + Poll::Ready(input_stream.read_block(buf)) + } + } + pub fn handle_interrupts(&mut self) -> bool { let intsts = self.regs.intsts.read(); if ((intsts >> 31) & 1) == 1 { @@ -897,7 +1263,56 @@ impl IntelHDA { intsts != 0 } - pub fn handle_controller_interrupt(&mut self) {} + pub fn handle_controller_interrupt(&mut self) { + let statests = self.regs.statests.read(); + if statests & STATESTS_MASK != 0 { + for i in 0..15 { + if (statests >> i) & 1 != 0 { + log::info!("IHDA: state change on codec {}", i); + } + } + self.regs.statests.write(statests); + } + + let rirbsts = self.regs.rirbsts.read(); + if rirbsts & (RIRBSTS_RINTFL | RIRBSTS_RIRBOIS) != 0 { + let rirbwp = self.regs.rirbwp.read(); + let wp = rirbwp & 0xFF; + if wp != 0 { + log::debug!("IHDA: RIRB response available, wp={}", wp); + } + if rirbsts & RIRBSTS_RIRBOIS != 0 { + log::warn!("IHDA: RIRB overrun, clearing"); + } + self.regs.rirbsts.write(rirbsts & (RIRBSTS_RINTFL | RIRBSTS_RIRBOIS)); + } + + let corbsts = self.regs.corbsts.read(); + if corbsts & CORBSTS_CMEI != 0 { + log::error!("IHDA: CORB memory error, clearing"); + self.regs.corbsts.write(CORBSTS_CMEI); + } + } + + fn handle_unsolicited_response(&mut self, codec_addr: CodecAddr, response: u32) { + let tag = (response >> 26) & 0xF; + let payload = response & 0x03FFFFFF; + + log::info!( + "IHDA: unsolicited response codec {} tag={} payload={:06X}", + codec_addr, tag, payload + ); + + if tag == 1 { + let pin_widget = payload & 0x7F; + let plugged = (payload >> 31) & 1; + log::info!( + "IHDA: jack sense codec {} pin {} {}", + codec_addr, pin_widget, + if plugged != 0 { "plugged" } else { "unplugged" } + ); + } + } pub fn handle_stream_interrupts(&mut self, sis: u32) { let iss = self.num_input_streams(); @@ -1017,9 +1432,10 @@ impl SchemeSync for IntelHDA { return Err(Error::new(EACCES)); } let handle = match path.trim_matches('/') { - //TODO: allow multiple codecs - "codec" => Handle::StrBuf(self.dump_codec(0).into_bytes()), - _ => Handle::Todo, + "codec" => Handle::StrBuf(self.dump_all_codecs().into_bytes()), + "" | "pcmout" | "pcmout0" => Handle::Pcmout { stream_index: 0 }, + "pcmin" | "pcmin0" => Handle::Pcmin { stream_index: 0 }, + _ => return Err(Error::new(ENOENT)), }; let id = self.handles.lock().insert(handle); @@ -1038,18 +1454,44 @@ impl SchemeSync for IntelHDA { _flags: u32, _ctx: &CallerCtx, ) -> Result { - let handles = self.handles.lock(); - let Handle::StrBuf(strbuf) = handles.get(id)? else { - return Err(Error::new(EBADF)); + let (is_strbuf, is_pcmin) = { + let handles = self.handles.lock(); + match handles.get(id)? { + Handle::StrBuf(_) => (true, false), + Handle::Pcmin { .. } => (false, true), + _ => return Err(Error::new(EBADF)), + } }; - let src = usize::try_from(offset) - .ok() - .and_then(|o| strbuf.get(o..)) - .unwrap_or(&[]); - let len = src.len().min(buf.len()); - buf[..len].copy_from_slice(&src[..len]); - Ok(len) + if is_strbuf { + let handles = self.handles.lock(); + let Handle::StrBuf(strbuf) = handles.get(id)? else { + return Err(Error::new(EBADF)); + }; + let src = usize::try_from(offset) + .ok() + .and_then(|o| strbuf.get(o..)) + .unwrap_or(&[]); + let len = src.len().min(buf.len()); + buf[..len].copy_from_slice(&src[..len]); + return Ok(len); + } + + if is_pcmin { + let index = { + let handles = self.handles.lock(); + match handles.get(id)? { + Handle::Pcmin { stream_index, .. } => *stream_index, + _ => return Err(Error::new(EBADF)), + } + }; + return match self.read_from_input(index, buf) { + Poll::Ready(r) => r, + Poll::Pending => Err(Error::new(EWOULDBLOCK)), + }; + } + + Err(Error::new(EBADF)) } fn write( @@ -1061,23 +1503,29 @@ impl SchemeSync for IntelHDA { _ctx: &CallerCtx, ) -> Result { let index = { - let mut handles = self.handles.lock(); - match handles.get_mut(id)? { - Handle::Todo => 0, + let handles = self.handles.lock(); + match handles.get(id)? { + Handle::Pcmout { stream_index, .. } => *stream_index as u8, _ => return Err(Error::new(EBADF)), } }; - //log::debug!("Int count: {}", self.int_counter); - match self.write_to_output(index, buf) { Poll::Ready(r) => r, Poll::Pending => Err(Error::new(EWOULDBLOCK)), } } - fn fpath(&mut self, _id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> Result { - FpathWriter::with(buf, "audiohw", |_| Ok(())) + fn fpath(&mut self, id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> Result { + let handles = self.handles.lock(); + let handle = handles.get(id)?; + let path = match handle { + Handle::Pcmout { .. } => "audiohw:pcmout0", + Handle::Pcmin { .. } => "audiohw:pcmin0", + Handle::StrBuf(_) => "audiohw:codec", + Handle::SchemeRoot => "audiohw:", + }; + FpathWriter::with(buf, path, |_| Ok(())) } fn on_close(&mut self, id: usize) { diff --git a/drivers/audio/ihdad/src/hda/mod.rs b/drivers/audio/ihdad/src/hda/mod.rs index 7f01daf8..82ba89ae 100644 --- a/drivers/audio/ihdad/src/hda/mod.rs +++ b/drivers/audio/ihdad/src/hda/mod.rs @@ -2,7 +2,11 @@ pub mod cmdbuff; pub mod common; pub mod device; +pub mod digital; +pub mod dispatch; +pub mod fixup; pub mod node; +pub mod parser; pub mod stream; pub use self::node::*; @@ -10,6 +14,8 @@ pub use self::stream::*; pub use self::cmdbuff::*; pub use self::device::IntelHDA; +pub use self::fixup::FixupEngine; +pub use self::parser::AutoPinConfig; pub use self::stream::BitsPerSample; pub use self::stream::BufferDescriptorListEntry; pub use self::stream::StreamBuffer; diff --git a/drivers/audio/ihdad/src/hda/node.rs b/drivers/audio/ihdad/src/hda/node.rs index 06c5121f..c1f9c31f 100644 --- a/drivers/audio/ihdad/src/hda/node.rs +++ b/drivers/audio/ihdad/src/hda/node.rs @@ -1,7 +1,7 @@ use super::common::*; use std::{fmt, mem}; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct HDANode { pub addr: WidgetAddr, diff --git a/drivers/audio/ihdad/src/hda/stream.rs b/drivers/audio/ihdad/src/hda/stream.rs index caa3c364..a3f5ed73 100644 --- a/drivers/audio/ihdad/src/hda/stream.rs +++ b/drivers/audio/ihdad/src/hda/stream.rs @@ -14,9 +14,9 @@ pub enum BaseRate { } pub struct SampleRate { - base: BaseRate, - mult: u16, - div: u16, + pub base: BaseRate, + pub mult: u16, + pub div: u16, } use self::BaseRate::{BR44_1, BR48}; @@ -78,6 +78,7 @@ pub const SR_192: SampleRate = SampleRate { div: 1, }; +#[derive(Debug, Clone, Copy)] #[repr(u8)] pub enum BitsPerSample { Bits8 = 0, @@ -271,6 +272,52 @@ impl OutputStream { } } +pub struct InputStream { + buff: StreamBuffer, + desc_regs: &'static mut StreamDescriptorRegs, +} + +impl InputStream { + pub fn new( + block_count: usize, + block_length: usize, + regs: &'static mut StreamDescriptorRegs, + ) -> InputStream { + InputStream { + buff: StreamBuffer::new(block_length, block_count).unwrap(), + desc_regs: regs, + } + } + + pub fn read_block(&mut self, buf: &mut [u8]) -> Result { + self.buff.read_block(buf) + } + + pub fn block_size(&self) -> usize { + self.buff.block_size() + } + + pub fn block_count(&self) -> usize { + self.buff.block_count() + } + + pub fn current_block(&self) -> usize { + self.buff.current_block() + } + + pub fn addr(&self) -> usize { + self.buff.addr() + } + + pub fn phys(&self) -> usize { + self.buff.phys() + } + + pub fn regs(&mut self) -> &mut StreamDescriptorRegs { + self.desc_regs + } +} + #[repr(C, packed)] pub struct BufferDescriptorListEntry { addr_low: Mmio, @@ -379,6 +426,20 @@ impl StreamBuffer { Ok(len) } + + pub fn read_block(&mut self, buf: &mut [u8]) -> Result { + let len = min(self.block_size(), buf.len()); + unsafe { + copy_nonoverlapping( + (self.addr() + self.current_block() * self.block_size()) as *const u8, + buf.as_mut_ptr(), + len, + ); + } + self.cur_pos += 1; + self.cur_pos %= self.block_count(); + Ok(len) + } } impl Drop for StreamBuffer { fn drop(&mut self) { 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/common/src/logger.rs b/drivers/common/src/logger.rs index 82ec2bd0..a531edd9 100644 --- a/drivers/common/src/logger.rs +++ b/drivers/common/src/logger.rs @@ -44,6 +44,7 @@ pub fn setup_logging( Ok(b) => { logger = logger.with_output(b.with_filter(file_level).flush_on_newline(true).build()) } + Err(error) if error.raw_os_error() == Some(19) => {} Err(error) => eprintln!("Failed to create {logfile_base}.log: {}", error), } @@ -61,6 +62,7 @@ pub fn setup_logging( .build(), ) } + Err(error) if error.raw_os_error() == Some(19) => {} Err(error) => eprintln!("Failed to create {logfile_base}.ansi.log: {}", error), } diff --git a/drivers/graphics/console-draw/src/lib.rs b/drivers/graphics/console-draw/src/lib.rs index 5eb951df..0b959e5c 100644 --- a/drivers/graphics/console-draw/src/lib.rs +++ b/drivers/graphics/console-draw/src/lib.rs @@ -59,19 +59,19 @@ pub struct V2DisplayMap { impl V2DisplayMap { pub fn new(display_handle: V2GraphicsHandle) -> io::Result { - let connector = display_handle.first_display().unwrap(); - let connector_info = display_handle.get_connector(connector, true).unwrap(); + let connector = display_handle.first_display()?; + let connector_info = display_handle.get_connector(connector, true)?; let mode = connector_info.modes()[0]; let (width, height) = mode.size(); // FIXME do something smarter that avoids conflicts - let crtc = display_handle.resource_handles().unwrap().filter_crtcs( - display_handle - .get_encoder(connector_info.encoders()[0]) - .unwrap() - .possible_crtcs(), - )[0]; + let crtc = { + let res_handles = display_handle.resource_handles()?; + let encoder = display_handle + .get_encoder(connector_info.encoders()[0])?; + res_handles.filter_crtcs(encoder.possible_crtcs())[0] + }; let buffer = CpuBackedBuffer::new( &display_handle, @@ -338,12 +338,12 @@ impl TextScreen { line_changed(y); } - let width = map.width.try_into().unwrap(); + let width: u32 = map.width.try_into().unwrap_or(0); let damage = Damage { x: 0, - y: u32::try_from(min_changed).unwrap() * 16, + y: u32::try_from(min_changed).unwrap_or(0) * 16, width, - height: u32::try_from(max_changed.saturating_sub(min_changed) + 1).unwrap() * 16, + height: u32::try_from(max_changed.saturating_sub(min_changed) + 1).unwrap_or(0) * 16, }; damage @@ -445,7 +445,9 @@ impl TextBuffer { } for &byte in buf { - self.lines.back_mut().unwrap().push(byte); + if let Some(last) = self.lines.back_mut() { + last.push(byte); + } if byte == b'\n' { self.lines.push_back(Vec::new()); diff --git a/drivers/graphics/driver-graphics/Cargo.toml b/drivers/graphics/driver-graphics/Cargo.toml index 31e02335..fc747cce 100644 --- a/drivers/graphics/driver-graphics/Cargo.toml +++ b/drivers/graphics/driver-graphics/Cargo.toml @@ -9,6 +9,7 @@ drm-fourcc = "2.2.0" drm-sys.workspace = true edid.workspace = true #TODO: edid is abandoned, fork it and maintain? log.workspace = true +nom.workspace = true redox-ioctl.workspace = true redox-scheme.workspace = true scheme-utils = { path = "../../../scheme-utils" } diff --git a/drivers/graphics/driver-graphics/src/kms/connector.rs b/drivers/graphics/driver-graphics/src/kms/connector.rs index c885f413..19037fec 100644 --- a/drivers/graphics/driver-graphics/src/kms/connector.rs +++ b/drivers/graphics/driver-graphics/src/kms/connector.rs @@ -21,7 +21,14 @@ impl KmsObjects { ) -> KmsObjectId { let mut possible_crtcs = 0; for &crtc in crtcs { - possible_crtcs = 1 << self.get_crtc(crtc).unwrap().lock().unwrap().crtc_index; + if let Ok(crtc_guard) = self.get_crtc(crtc) { + match crtc_guard.lock() { + Ok(locked) => possible_crtcs = 1 << locked.crtc_index, + Err(e) => log::error!("add_connector: crtc lock poisoned: {e}"), + } + } else { + log::error!("add_connector: failed to get crtc {}", crtc.0); + } } let encoder_id = self.add(KmsEncoder { @@ -61,7 +68,7 @@ impl KmsObjects { pub fn connectors(&self) -> impl Iterator>> + use<'_, T> { self.connectors .iter() - .map(|&id| self.get_connector(id).unwrap()) + .filter_map(|&id| self.get_connector(id).ok()) } pub fn get_connector(&self, id: KmsObjectId) -> Result<&Mutex>> { @@ -136,10 +143,16 @@ impl KmsConnector { } pub fn update_from_edid(&mut self, edid: &[u8]) { - let edid = edid::parse(edid).unwrap().1; + let edid_data = match edid::parse(edid) { + nom::IResult::Done(_, data) => data, + _ => { + log::error!("failed to parse EDID: parse returned error or incomplete"); + return; + } + }; if let Some(first_detailed_timing) = - edid.descriptors + edid_data.descriptors .iter() .find_map(|descriptor| match descriptor { edid::Descriptor::DetailedTiming(detailed_timing) => Some(detailed_timing), @@ -152,7 +165,7 @@ impl KmsConnector { log::error!("No edid timing descriptor detected"); } - self.modes = edid + self.modes = edid_data .descriptors .iter() .filter_map(|descriptor| { diff --git a/drivers/graphics/driver-graphics/src/kms/objects.rs b/drivers/graphics/driver-graphics/src/kms/objects.rs index 1daf3221..55c60167 100644 --- a/drivers/graphics/driver-graphics/src/kms/objects.rs +++ b/drivers/graphics/driver-graphics/src/kms/objects.rs @@ -95,7 +95,7 @@ impl KmsObjects { pub fn crtcs(&self) -> impl Iterator>> + use<'_, T> { self.crtcs .iter() - .map(|&id| self.get::>>(id).unwrap()) + .filter_map(|&id| self.get::>>(id).ok()) } pub fn get_crtc(&self, id: KmsObjectId) -> Result<&Mutex>> { @@ -115,7 +115,12 @@ impl KmsObjects { let KmsObject::Framebuffer(_) = object else { return Err(Error::new(EINVAL)); }; - self.objects.remove(&id).unwrap(); + self.objects + .remove(&id) + .ok_or_else(|| { + log::error!("remove_framebuffer: object {} vanished during removal", id.0); + Error::new(EINVAL) + })?; Ok(()) } diff --git a/drivers/graphics/driver-graphics/src/kms/properties.rs b/drivers/graphics/driver-graphics/src/kms/properties.rs index e22527a7..c75df3b0 100644 --- a/drivers/graphics/driver-graphics/src/kms/properties.rs +++ b/drivers/graphics/driver-graphics/src/kms/properties.rs @@ -21,7 +21,11 @@ impl KmsObjects { kind: KmsPropertyKind, ) -> KmsObjectId { match &kind { - KmsPropertyKind::Range(start, end) => assert!(start < end), + KmsPropertyKind::Range(start, end) => { + if start >= end { + log::error!("Range property '{name}' has invalid range: start ({start}) >= end ({end})"); + } + } KmsPropertyKind::Enum(_variants) => { // FIXME check duplicate variant numbers } @@ -30,7 +34,11 @@ impl KmsObjects { // FIXME check overlapping flag numbers } KmsPropertyKind::Object { type_: _ } => {} - KmsPropertyKind::SignedRange(start, end) => assert!(start < end), + KmsPropertyKind::SignedRange(start, end) => { + if start >= end { + log::error!("SignedRange property '{name}' has invalid range: start ({start}) >= end ({end})"); + } + } } let mut name_bytes = [0; DRM_PROP_NAME_LEN as usize]; @@ -54,7 +62,13 @@ impl KmsObjects { let object = self.objects.get(&id).ok_or(Error::new(EINVAL))?; match object { KmsObject::Crtc(crtc) => { - let crtc = crtc.lock().unwrap(); + let crtc = match crtc.lock() { + Ok(g) => g, + Err(e) => { + log::error!("get_object_properties_data: crtc lock poisoned: {e}"); + return Err(Error::new(EINVAL)); + } + }; let props = &crtc.properties; Ok(( props.iter().map(|prop| prop.id.0).collect::>(), @@ -65,7 +79,13 @@ impl KmsObjects { )) } KmsObject::Connector(connector) => { - let connector = connector.lock().unwrap(); + let connector = match connector.lock() { + Ok(g) => g, + Err(e) => { + log::error!("get_object_properties_data: connector lock poisoned: {e}"); + return Err(Error::new(EINVAL)); + } + }; let props = &connector.properties; Ok(( props.iter().map(|prop| prop.id.0).collect::>(), @@ -97,7 +117,7 @@ pub struct KmsPropertyName(pub [c_char; DRM_PROP_NAME_LEN as usize]); impl KmsPropertyName { fn new(context: &str, name: &str) -> KmsPropertyName { if name.len() > DRM_PROP_NAME_LEN as usize { - panic!("{context} {name} is too long"); + log::error!("{context} {name} is too long, truncating"); } let mut name_bytes = [0; DRM_PROP_NAME_LEN as usize]; @@ -151,12 +171,16 @@ macro_rules! define_properties { pub(super) fn init_standard_props(objects: &mut KmsObjects) { $( - assert_eq!(objects.add_property( + let prop_id = objects.add_property( define_properties!(@prop_name $prop $($prop_name)?), define_properties!(@is_immutable $($prop_flag)?), define_properties!(@is_atomic $($prop_flag)?), define_properties!(@prop_kind $prop_type $({$($prop_content)*})?), - ), $prop); + ); + if prop_id != $prop { + log::error!("property ID mismatch for {}: expected {:?}, got {:?}", + stringify!($prop), $prop, prop_id); + } )* } }; diff --git a/drivers/graphics/driver-graphics/src/lib.rs b/drivers/graphics/driver-graphics/src/lib.rs index eab0be9c..4fe7ecb6 100644 --- a/drivers/graphics/driver-graphics/src/lib.rs +++ b/drivers/graphics/driver-graphics/src/lib.rs @@ -136,13 +136,20 @@ pub struct GraphicsScheme { impl GraphicsScheme { pub fn new(mut adapter: T, scheme_name: String, early: bool) -> Self { - assert!(scheme_name.starts_with("display")); - let socket = Socket::nonblock().expect("failed to create graphics scheme"); + if !scheme_name.starts_with("display") { + log::error!("graphics scheme name must start with 'display': {scheme_name}"); + std::process::exit(1); + } + let socket = match Socket::nonblock() { + Ok(s) => s, + Err(e) => { + log::error!("failed to create graphics scheme: {e}"); + std::process::exit(1); + } + }; - let disable_graphical_debug = Some( - File::open("/scheme/debug/disable-graphical-debug") - .expect("vesad: Failed to open /scheme/debug/disable-graphical-debug"), - ); + let disable_graphical_debug = + File::open("/scheme/debug/disable-graphical-debug").ok(); let mut objects = KmsObjects::new(); adapter.init(&mut objects); @@ -161,14 +168,34 @@ impl GraphicsScheme { vts: HashMap::new(), }; - let cap_id = inner.scheme_root().expect("failed to get this scheme root"); - register_scheme_inner(&inner.socket, &inner.scheme_name, cap_id) - .expect("failed to register graphics scheme root"); + let cap_id = match inner.scheme_root() { + Ok(id) => id, + Err(e) => { + log::error!("failed to get this scheme root: {e}"); + std::process::exit(1); + } + }; + if let Err(e) = register_scheme_inner(&inner.socket, &inner.scheme_name, cap_id) { + log::error!("failed to register graphics scheme root: {e}"); + std::process::exit(1); + } let display_handle = if early { - DisplayHandle::new_early(&inner.scheme_name).unwrap() + match DisplayHandle::new_early(&inner.scheme_name) { + Ok(h) => h, + Err(e) => { + log::error!("failed to create early display handle: {e}"); + std::process::exit(1); + } + } } else { - DisplayHandle::new(&inner.scheme_name).unwrap() + match DisplayHandle::new(&inner.scheme_name) { + Ok(h) => h, + Err(e) => { + log::error!("failed to create display handle: {e}"); + std::process::exit(1); + } + } }; Self { @@ -207,11 +234,15 @@ impl GraphicsScheme { } pub fn handle_vt_events(&mut self) { - while let Some(vt_event) = self - .inputd_handle - .read_vt_event() - .expect("driver-graphics: failed to read display handle") - { + loop { + let vt_event = match self.inputd_handle.read_vt_event() { + Ok(Some(event)) => event, + Ok(None) => break, + Err(e) => { + log::error!("driver-graphics: failed to read display handle: {e}"); + break; + } + }; match vt_event.kind { VtEventKind::Activate => self.inner.activate_vt(vt_event.vt), } @@ -235,16 +266,26 @@ impl GraphicsScheme { std::process::exit(0); } Err(err) if err.errno == EAGAIN => break, - Err(err) => panic!("driver-graphics: failed to read display scheme: {err}"), + Err(err) => { + log::error!("driver-graphics: failed to read display scheme: {err}"); + break; + } }; match request.kind() { RequestKind::Call(call) => { let response = call.handle_sync(&mut self.inner, &mut self.state); - self.inner + if let Err(e) = self + .inner .socket .write_response(response, SignalBehavior::Restart) - .expect("driver-graphics: failed to write response"); + { + log::error!("driver-graphics: failed to write response: {e}"); + return Err(io::Error::new( + io::ErrorKind::Other, + format!("driver-graphics: failed to write response: {e}"), + )); + } } RequestKind::OnClose { id } => { self.inner.on_close(id); @@ -294,11 +335,28 @@ impl GraphicsSchemeInner { vts.entry(vt).or_insert_with(|| VtState { connector_state: objects .connectors() - .map(|connector| connector.lock().unwrap().state.clone()) + .map(|connector| { + connector + .lock() + .unwrap_or_else(|e| { + log::error!("get_or_create_vt: connector lock poisoned: {e}"); + e.into_inner() + }) + .state + .clone() + }) .collect(), crtc_state: objects .crtcs() - .map(|crtc| crtc.lock().unwrap().state.clone()) + .map(|crtc| { + crtc.lock() + .unwrap_or_else(|e| { + log::error!("get_or_create_vt: crtc lock poisoned: {e}"); + e.into_inner() + }) + .state + .clone() + }) .collect(), cursor_plane: CursorPlane { x: 0, @@ -327,47 +385,71 @@ impl GraphicsSchemeInner { for (connector_idx, connector_state) in vt_state.connector_state.iter().enumerate() { let connector_id = self.objects.connector_ids()[connector_idx]; - let mut connector = self - .objects - .get_connector(connector_id) - .unwrap() - .lock() - .unwrap(); + let connector_guard = match self.objects.get_connector(connector_id) { + Ok(g) => g, + Err(e) => { + log::error!("activate_vt: failed to get connector {}: {e}", connector_id.0); + continue; + } + }; + let mut connector = match connector_guard.lock() { + Ok(g) => g, + Err(e) => { + log::error!("activate_vt: connector {} lock poisoned: {e}", connector_id.0); + e.into_inner() + } + }; connector.state = connector_state.clone(); } for (crtc_idx, crtc_state) in vt_state.crtc_state.iter().enumerate() { let crtc_id = self.objects.crtc_ids()[crtc_idx]; - let crtc = self.objects.get_crtc(crtc_id).unwrap(); + let crtc = match self.objects.get_crtc(crtc_id) { + Ok(c) => c, + Err(e) => { + log::error!("activate_vt: failed to get crtc {}: {e}", crtc_id.0); + continue; + } + }; let connector_id = self.objects.connector_ids()[crtc_idx]; - let fb = crtc_state.fb_id.map(|fb_id| { + let fb = crtc_state.fb_id.and_then(|fb_id| { self.objects .get_framebuffer(fb_id) - .expect("removed framebuffers should be unset") + .map_err(|e| { + log::error!("activate_vt: framebuffer {} missing: {e}", fb_id.0); + e + }) + .ok() }); - self.adapter - .set_crtc( - &self.objects, - crtc, - crtc_state.clone(), - Damage { - x: 0, - y: 0, - width: fb.map_or(0, |fb| fb.width), - height: fb.map_or(0, |fb| fb.height), - }, - ) - .unwrap(); - - self.objects - .get_connector(connector_id) - .unwrap() - .lock() - .unwrap() - .state - .crtc_id = crtc_id; + if let Err(e) = self.adapter.set_crtc( + &self.objects, + crtc, + crtc_state.clone(), + Damage { + x: 0, + y: 0, + width: fb.as_ref().map_or(0, |fb| fb.width), + height: fb.as_ref().map_or(0, |fb| fb.height), + }, + ) { + log::error!("activate_vt: set_crtc failed for crtc {}: {e}", crtc_id.0); + continue; + } + + match self.objects.get_connector(connector_id) { + Ok(conn_guard) => match conn_guard.lock() { + Ok(mut conn) => conn.state.crtc_id = crtc_id, + Err(e) => { + log::error!("activate_vt: connector {} lock poisoned: {e}", connector_id.0); + e.into_inner().state.crtc_id = crtc_id; + } + }, + Err(e) => { + log::error!("activate_vt: failed to get connector {}: {e}", connector_id.0); + } + } } if self.adapter.hw_cursor_size().is_some() { @@ -430,7 +512,12 @@ impl SchemeSync for GraphicsSchemeInner { vt, next_id: _, buffers: _, - } => write!(w, "v2/{vt}").unwrap(), + } => { + if let Err(e) = write!(w, "v2/{vt}") { + log::error!("fpath: write failed: {e}"); + return Err(Error::new(EINVAL)); + } + } Handle::SchemeRoot => return Err(Error::new(EOPNOTSUPP)), }; Ok(()) @@ -531,7 +618,10 @@ impl SchemeSync for GraphicsSchemeInner { .objects .get_crtc(KmsObjectId(data.crtc_id()))? .lock() - .unwrap(); + .map_err(|e| { + log::error!("MODE_GET_CRTC: crtc lock poisoned: {e}"); + Error::new(EINVAL) + })?; // Don't touch set_connectors, that is only used by MODE_SET_CRTC data.set_fb_id(crtc.state.fb_id.unwrap_or(KmsObjectId::INVALID).0); // FIXME fill x and y with the data from the primary plane @@ -565,7 +655,10 @@ impl SchemeSync for GraphicsSchemeInner { } else { None }; - let mut new_state = crtc.lock().unwrap().state.clone(); + let mut new_state = crtc.lock().map_err(|e| { + log::error!("MODE_SET_CRTC: crtc lock poisoned: {e}"); + Error::new(EINVAL) + })?.state.clone(); new_state.fb_id = fb_id; new_state.mode = mode; if *vt == self.active_vt { @@ -582,20 +675,34 @@ impl SchemeSync for GraphicsSchemeInner { )?; for connector in connector_ids { - self.objects - .get_connector(connector)? - .lock() - .unwrap() - .state - .crtc_id = KmsObjectId(data.crtc_id()); + let conn_guard = self.objects.get_connector(connector)?; + match conn_guard.lock() { + Ok(mut conn) => conn.state.crtc_id = KmsObjectId(data.crtc_id()), + Err(e) => { + log::error!("MODE_SET_CRTC: connector lock poisoned: {e}"); + e.into_inner().state.crtc_id = KmsObjectId(data.crtc_id()); + } + } } } - self.vts.get_mut(vt).unwrap().crtc_state - [crtc.lock().unwrap().crtc_index as usize] = new_state; + { + let crtc_index = crtc.lock().map_err(|e| { + log::error!("MODE_SET_CRTC: crtc lock poisoned: {e}"); + Error::new(EINVAL) + })?.crtc_index as usize; + let vt_state = self.vts.get_mut(vt).ok_or_else(|| { + log::error!("MODE_SET_CRTC: vt {} not found", vt); + Error::new(EINVAL) + })?; + vt_state.crtc_state[crtc_index] = new_state; + } Ok(0) }), ipc::MODE_CURSOR => ipc::DrmModeCursor::with(payload, |data| { - let vt_state = self.vts.get_mut(vt).unwrap(); + let vt_state = self.vts.get_mut(vt).ok_or_else(|| { + log::error!("MODE_CURSOR: vt {} not found", vt); + Error::new(EINVAL) + })?; let cursor_plane = &mut vt_state.cursor_plane; @@ -635,7 +742,10 @@ impl SchemeSync for GraphicsSchemeInner { .objects .get_connector(KmsObjectId(data.connector_id()))? .lock() - .unwrap(); + .map_err(|e| { + log::error!("MODE_GET_CONNECTOR: connector lock poisoned: {e}"); + Error::new(EINVAL) + })?; data.set_encoders_ptr(&[connector.encoder_id.0]); data.set_modes_ptr(&connector.modes); data.set_connector_type(data.connector_type()); @@ -772,20 +882,23 @@ impl SchemeSync for GraphicsSchemeInner { if *vt != self.active_vt { continue; } - let crtc = self.objects.crtcs().nth(crtc_idx).unwrap(); - self.adapter - .set_crtc( - &self.objects, - crtc, - crtc_state.clone(), - Damage { - x: 0, - y: 0, - width: 0, - height: 0, - }, - ) - .unwrap(); + let Some(crtc) = self.objects.crtcs().nth(crtc_idx) else { + log::error!("MODE_RM_FB: crtc index {crtc_idx} out of bounds"); + continue; + }; + if let Err(e) = self.adapter.set_crtc( + &self.objects, + crtc, + crtc_state.clone(), + Damage { + x: 0, + y: 0, + width: 0, + height: 0, + }, + ) { + log::error!("MODE_RM_FB: set_crtc failed for crtc {crtc_idx}: {e}"); + } } } @@ -813,7 +926,10 @@ impl SchemeSync for GraphicsSchemeInner { if *vt == self.active_vt { for crtc in self.objects.crtcs() { - let state = crtc.lock().unwrap().state.clone(); + let state = crtc.lock().map_err(|e| { + log::error!("MODE_DIRTYFB: crtc lock poisoned: {e}"); + Error::new(EINVAL) + })?.state.clone(); if state.fb_id == Some(KmsObjectId(data.fb_id())) { self.adapter.set_crtc(&self.objects, crtc, state, damage)?; } @@ -850,7 +966,13 @@ impl SchemeSync for GraphicsSchemeInner { } // FIXME use a better scheme for creating map offsets - assert!(buffers[&buffer_id].size() < MAP_FAKE_OFFSET_MULTIPLIER); + let buf_size = buffers[&buffer_id].size(); + if buf_size >= MAP_FAKE_OFFSET_MULTIPLIER { + log::error!( + "MODE_MAP_DUMB: buffer size {buf_size} exceeds offset multiplier {MAP_FAKE_OFFSET_MULTIPLIER}" + ); + return Err(Error::new(EINVAL)); + } data.set_offset((buffer_id as usize * MAP_FAKE_OFFSET_MULTIPLIER) as u64); @@ -874,11 +996,14 @@ impl SchemeSync for GraphicsSchemeInner { ipc::MODE_GET_PLANE => ipc::DrmModeGetPlane::with(payload, |mut data| { let i = id_index(data.plane_id()); let crtc_id = self.objects.crtc_ids()[i as usize]; - let crtc = self.objects.get_crtc(crtc_id).unwrap(); + let crtc = self.objects.get_crtc(crtc_id)?; data.set_crtc_id(crtc_id.0); + let crtc_locked = crtc.lock().map_err(|e| { + log::error!("MODE_GET_PLANE: crtc lock poisoned: {e}"); + Error::new(EINVAL) + })?; data.set_fb_id( - crtc.lock() - .unwrap() + crtc_locked .state .fb_id .unwrap_or(KmsObjectId::INVALID) @@ -907,7 +1032,10 @@ impl SchemeSync for GraphicsSchemeInner { }) } ipc::MODE_CURSOR2 => ipc::DrmModeCursor2::with(payload, |data| { - let vt_state = self.vts.get_mut(vt).unwrap(); + let vt_state = self.vts.get_mut(vt).ok_or_else(|| { + log::error!("MODE_CURSOR2: vt {} not found", vt); + Error::new(EINVAL) + })?; let cursor_plane = &mut vt_state.cursor_plane; @@ -970,8 +1098,7 @@ impl SchemeSync for GraphicsSchemeInner { } => ( buffers .get(&((offset as usize / MAP_FAKE_OFFSET_MULTIPLIER) as u32)) - .ok_or(Error::new(EINVAL)) - .unwrap(), + .ok_or(Error::new(EINVAL))?, offset & (MAP_FAKE_OFFSET_MULTIPLIER as u64 - 1), ), Handle::SchemeRoot => return Err(Error::new(EOPNOTSUPP)), diff --git a/drivers/graphics/fbbootlogd/src/main.rs b/drivers/graphics/fbbootlogd/src/main.rs index 3e42d590..62749577 100644 --- a/drivers/graphics/fbbootlogd/src/main.rs +++ b/drivers/graphics/fbbootlogd/src/main.rs @@ -24,7 +24,13 @@ fn main() { daemon::SchemeDaemon::new(daemon); } fn daemon(daemon: daemon::SchemeDaemon) -> ! { - let event_queue = EventQueue::new().expect("fbbootlogd: failed to create event queue"); + let event_queue = match EventQueue::new() { + Ok(eq) => eq, + Err(err) => { + eprintln!("fbbootlogd: failed to create event queue: {err}"); + std::process::exit(1); + } + }; event::user_data! { enum Source { @@ -33,78 +39,105 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! { } } - let socket = Socket::nonblock().expect("fbbootlogd: failed to create fbbootlog scheme"); + let socket = match Socket::nonblock() { + Ok(s) => s, + Err(err) => { + eprintln!("fbbootlogd: failed to create fbbootlog scheme: {err}"); + std::process::exit(1); + } + }; let mut scheme = FbbootlogScheme::new(); let mut handler = Blocking::new(&socket, 16); - event_queue - .subscribe( - socket.inner().raw(), - Source::Scheme, - event::EventFlags::READ, - ) - .expect("fbbootlogd: failed to subscribe to scheme events"); + if let Err(err) = event_queue.subscribe( + socket.inner().raw(), + Source::Scheme, + event::EventFlags::READ, + ) { + eprintln!("fbbootlogd: failed to subscribe to scheme events: {err}"); + std::process::exit(1); + } - event_queue - .subscribe( - scheme.input_handle.event_handle().as_raw_fd() as usize, - Source::Input, - event::EventFlags::READ, - ) - .expect("fbbootlogd: failed to subscribe to scheme events"); + if let Err(err) = event_queue.subscribe( + scheme.input_handle.event_handle().as_raw_fd() as usize, + Source::Input, + event::EventFlags::READ, + ) { + eprintln!("fbbootlogd: failed to subscribe to input events: {err}"); + std::process::exit(1); + } { - let log_fd = socket - .create_this_scheme_fd(0, 0, 0, 0) - .expect("fbbootlogd: failed to create log fd"); + let log_fd = match socket.create_this_scheme_fd(0, 0, 0, 0) { + Ok(fd) => fd, + Err(err) => { + eprintln!("fbbootlogd: failed to create log fd: {err}"); + std::process::exit(1); + } + }; // Add ourself as log sink - let log_file = libredox::Fd::open( + let log_file = match libredox::Fd::open( "/scheme/log/add_sink", libredox::flag::O_WRONLY | libredox::flag::O_CLOEXEC, 0, - ) - .expect("fbbootlogd: failed to open log/add_sink"); - log_file - .call_wo(&log_fd.to_ne_bytes(), syscall::CallFlags::FD, &[]) - .expect("fbbootlogd: failed to send log fd to log scheme."); + ) { + Ok(fd) => fd, + Err(err) => { + eprintln!("fbbootlogd: failed to open log/add_sink: {err}"); + std::process::exit(1); + } + }; + if let Err(err) = + log_file.call_wo(&log_fd.to_ne_bytes(), syscall::CallFlags::FD, &[]) + { + eprintln!("fbbootlogd: failed to send log fd to log scheme: {err}"); + std::process::exit(1); + } } let _ = daemon.ready_sync_scheme(&socket, &mut scheme); // This is not possible for now as fbbootlogd needs to open new displays at runtime for graphics // driver handoff. In the future inputd may directly pass a handle to the display instead. - //libredox::call::setrens(0, 0).expect("fbbootlogd: failed to enter null namespace"); for event in event_queue { - match event.expect("fbbootlogd: failed to get event").user_data { + let event = match event { + Ok(e) => e, + Err(err) => { + eprintln!("fbbootlogd: failed to get event: {err}"); + continue; + } + }; + match event.user_data { Source::Scheme => loop { - match handler - .process_requests_nonblocking(&mut scheme) - .expect("fbbootlogd: failed to process requests") - { - ControlFlow::Continue(()) => {} - ControlFlow::Break(()) => break, + match handler.process_requests_nonblocking(&mut scheme) { + Ok(ControlFlow::Continue(())) => {} + Ok(ControlFlow::Break(())) => break, + Err(err) => { + eprintln!("fbbootlogd: failed to process requests: {err}"); + break; + } } }, Source::Input => { let mut events = [Event::new(); 16]; loop { - match scheme - .input_handle - .read_events(&mut events) - .expect("fbbootlogd: error while reading events") - { - ConsumerHandleEvent::Events(&[]) => break, - ConsumerHandleEvent::Events(events) => { + match scheme.input_handle.read_events(&mut events) { + Ok(ConsumerHandleEvent::Events(&[])) => break, + Ok(ConsumerHandleEvent::Events(events)) => { for event in events { scheme.handle_input(&event); } } - ConsumerHandleEvent::Handoff => { + Ok(ConsumerHandleEvent::Handoff) => { eprintln!("fbbootlogd: handoff requested"); scheme.handle_handoff(); } + Err(err) => { + eprintln!("fbbootlogd: error while reading events: {err}"); + break; + } } } } diff --git a/drivers/graphics/fbbootlogd/src/scheme.rs b/drivers/graphics/fbbootlogd/src/scheme.rs index 812c4a5b..9e1869c3 100644 --- a/drivers/graphics/fbbootlogd/src/scheme.rs +++ b/drivers/graphics/fbbootlogd/src/scheme.rs @@ -26,7 +26,13 @@ pub struct FbbootlogScheme { impl FbbootlogScheme { pub fn new() -> FbbootlogScheme { let mut scheme = FbbootlogScheme { - input_handle: ConsumerHandle::bootlog_vt().expect("fbbootlogd: Failed to open vt"), + input_handle: match ConsumerHandle::bootlog_vt() { + Ok(handle) => handle, + Err(err) => { + eprintln!("fbbootlogd: failed to open vt: {err}"); + std::process::exit(1); + } + }, display_map: None, text_screen: console_draw::TextScreen::new(), text_buffer: console_draw::TextBuffer::new(1000), @@ -42,7 +48,13 @@ impl FbbootlogScheme { pub fn handle_handoff(&mut self) { let new_display_handle = match self.input_handle.open_display_v2() { - Ok(display) => V2GraphicsHandle::from_file(display).unwrap(), + Ok(display) => match V2GraphicsHandle::from_file(display) { + Ok(handle) => handle, + Err(err) => { + eprintln!("fbbootlogd: failed to create graphics handle: {err}"); + return; + } + }, Err(err) => { eprintln!("fbbootlogd: No display present yet: {err}"); return; @@ -140,7 +152,9 @@ impl FbbootlogScheme { total_damage = total_damage.merge(damage); } } - map.dirty_fb(total_damage).unwrap(); + if let Err(err) = map.dirty_fb(total_damage) { + eprintln!("fbbootlogd: failed to flush scrollback damage: {err}"); + } } fn handle_resize(map: &mut V2DisplayMap, text_screen: &mut TextScreen) { @@ -234,7 +248,9 @@ impl SchemeSync for FbbootlogScheme { let damage = self.text_screen.write(map, buf, &mut VecDeque::new()); if let Some(map) = &mut self.display_map { - map.dirty_fb(damage).unwrap(); + if let Err(err) = map.dirty_fb(damage) { + eprintln!("fbbootlogd: failed to flush write damage: {err}"); + } } } } diff --git a/drivers/graphics/fbcond/src/display.rs b/drivers/graphics/fbcond/src/display.rs index eb09b97e..957a6d88 100644 --- a/drivers/graphics/fbcond/src/display.rs +++ b/drivers/graphics/fbcond/src/display.rs @@ -31,7 +31,13 @@ impl Display { return; } }; - let new_display_handle = V2GraphicsHandle::from_file(display_file).unwrap(); + let new_display_handle = match V2GraphicsHandle::from_file(display_file) { + Ok(h) => h, + Err(err) => { + log::error!("fbcond: failed to create display handle: {err}"); + return; + } + }; log::debug!("fbcond: Opened new display"); @@ -77,7 +83,9 @@ impl Display { pub fn sync_rect(&mut self, damage: Damage) { if let Some(map) = &mut self.map { - map.dirty_fb(damage).unwrap(); + if let Err(err) = map.dirty_fb(damage) { + log::error!("fbcond: failed to sync display rect: {err}"); + } } } } diff --git a/drivers/graphics/fbcond/src/main.rs b/drivers/graphics/fbcond/src/main.rs index eb4f9add..7acc488f 100644 --- a/drivers/graphics/fbcond/src/main.rs +++ b/drivers/graphics/fbcond/src/main.rs @@ -21,7 +21,15 @@ fn main() { fn daemon(daemon: daemon::SchemeDaemon) -> ! { let vt_ids = env::args() .skip(1) - .map(|arg| arg.parse().expect("invalid vt number")) + .filter_map(|arg| { + match arg.parse() { + Ok(v) => Some(v), + Err(_) => { + eprintln!("fbcond: invalid vt number '{}', skipping", arg); + None + } + } + }) .collect::>(); common::setup_logging( @@ -31,18 +39,31 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! { common::output_level(), common::file_level(), ); - let mut event_queue = EventQueue::new().expect("fbcond: failed to create event queue"); + let mut event_queue = match EventQueue::new() { + Ok(eq) => eq, + Err(err) => { + eprintln!("fbcond: failed to create event queue: {}", err); + std::process::exit(1); + } + }; // FIXME listen for resize events from inputd and handle them - let mut socket = Socket::nonblock().expect("fbcond: failed to create fbcon scheme"); - event_queue - .subscribe( - socket.inner().raw(), - VtIndex::SCHEMA_SENTINEL, - event::EventFlags::READ, - ) - .expect("fbcond: failed to subscribe to scheme events"); + let mut socket = match Socket::nonblock() { + Ok(s) => s, + Err(err) => { + eprintln!("fbcond: failed to create fbcon scheme: {}", err); + std::process::exit(1); + } + }; + if let Err(err) = event_queue.subscribe( + socket.inner().raw(), + VtIndex::SCHEMA_SENTINEL, + event::EventFlags::READ, + ) { + eprintln!("fbcond: failed to subscribe to scheme events: {}", err); + std::process::exit(1); + } let mut state = SchemeState::new(); let mut scheme = FbconScheme::new(&vt_ids, &mut event_queue); @@ -51,7 +72,6 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! { // This is not possible for now as fbcond needs to open new displays at runtime for graphics // driver handoff. In the future inputd may directly pass a handle to the display instead. - // libredox::call::setrens(0, 0).expect("fbcond: failed to enter null namespace"); let mut blocked = Vec::new(); @@ -68,7 +88,13 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! { } for event in event_queue { - let event = event.expect("fbcond: failed to read event from event queue"); + let event = match event { + Ok(ev) => ev, + Err(err) => { + eprintln!("fbcond: failed to read event from event queue: {}", err); + continue; + } + }; handle_event( &mut socket, &mut scheme, @@ -99,7 +125,10 @@ fn handle_event( Err(err) if err.errno == EAGAIN => { break; } - Err(err) => panic!("fbcond: failed to read display scheme: {err}"), + Err(err) => { + eprintln!("fbcond: failed to read display scheme: {err}"); + break; + } }; match request.kind() { @@ -108,12 +137,12 @@ fn handle_event( let mut op = match req.op() { Ok(op) => op, Err(req) => { - let _ = socket - .write_response( - Response::err(EOPNOTSUPP, req), - SignalBehavior::Restart, - ) - .expect("fbcond: failed to write responses to fbcon scheme"); + if let Err(err) = socket.write_response( + Response::err(EOPNOTSUPP, req), + SignalBehavior::Restart, + ) { + eprintln!("fbcond: failed to write response: {}", err); + } continue; } }; @@ -125,25 +154,27 @@ fn handle_event( blocked.push((op, caller)); } SchemeResponse::Regular(r) => { - let _ = socket + if let Err(err) = socket .write_response(Response::new(r, op), SignalBehavior::Restart) - .expect("fbcond: failed to write responses to fbcon scheme"); + { + eprintln!("fbcond: failed to write response: {}", err); + } } SchemeResponse::Opened(o) => { - let _ = socket - .write_response( - Response::open_dup_like(o, op), - SignalBehavior::Restart, - ) - .expect("fbcond: failed to write responses to fbcon scheme"); + if let Err(err) = socket.write_response( + Response::open_dup_like(o, op), + SignalBehavior::Restart, + ) { + eprintln!("fbcond: failed to write response: {}", err); + } } SchemeResponse::RegularAndNotifyOnDetach(status) => { - let _ = socket - .write_response( - Response::new_notify_on_detach(status, op), - SignalBehavior::Restart, - ) - .expect("fbcond: failed to write scheme"); + if let Err(err) = socket.write_response( + Response::new_notify_on_detach(status, op), + SignalBehavior::Restart, + ) { + eprintln!("fbcond: failed to write response: {}", err); + } } } } @@ -157,25 +188,32 @@ fn handle_event( { let (blocked_req, _) = blocked.remove(i); let resp = Response::err(EINTR, blocked_req); - socket - .write_response(resp, SignalBehavior::Restart) - .expect("vesad: failed to write display scheme"); + if let Err(err) = + socket.write_response(resp, SignalBehavior::Restart) + { + eprintln!("fbcond: failed to write cancellation response: {}", err); + } } } _ => {} } }, vt_i => { - let vt = scheme.vts.get_mut(&vt_i).unwrap(); + let Some(vt) = scheme.vts.get_mut(&vt_i) else { + eprintln!("fbcond: unknown vt index {:?}", vt_i); + return; + }; let mut events = [Event::new(); 16]; loop { - match vt - .display - .input_handle - .read_events(&mut events) - .expect("fbcond: Error while reading events") - { + let read_result = match vt.display.input_handle.read_events(&mut events) { + Ok(r) => r, + Err(err) => { + eprintln!("fbcond: error while reading events: {}", err); + break; + } + }; + match read_result { ConsumerHandleEvent::Events(&[]) => break, ConsumerHandleEvent::Events(events) => { @@ -193,9 +231,9 @@ fn handle_event( { let mut i = 0; while i < blocked.len() { - let (op, caller) = blocked - .get_mut(i) - .expect("vesad: Failed to get blocked request"); + let Some((op, caller)) = blocked.get_mut(i) else { + break; + }; let resp = match op.handle_sync_dont_consume(&caller, scheme, state) { SchemeResponse::Opened(Err(e)) | SchemeResponse::Regular(Err(e)) if libredox::error::Error::from(e).is_wouldblock() @@ -217,9 +255,9 @@ fn handle_event( Response::new_notify_on_detach(status, op) } }; - let _ = socket - .write_response(resp, SignalBehavior::Restart) - .expect("vesad: failed to write display scheme"); + if let Err(err) = socket.write_response(resp, SignalBehavior::Restart) { + eprintln!("fbcond: failed to write blocked response: {}", err); + } } } @@ -242,9 +280,9 @@ fn handle_event( if !handle.notified_read { handle.notified_read = true; let response = Response::post_fevent(*handle_id, EVENT_READ.bits()); - socket - .write_response(response, SignalBehavior::Restart) - .expect("fbcond: failed to write display event"); + if let Err(err) = socket.write_response(response, SignalBehavior::Restart) { + eprintln!("fbcond: failed to write display event: {}", err); + } } } else { handle.notified_read = false; diff --git a/drivers/graphics/fbcond/src/scheme.rs b/drivers/graphics/fbcond/src/scheme.rs index 1bee134e..973ff31e 100644 --- a/drivers/graphics/fbcond/src/scheme.rs +++ b/drivers/graphics/fbcond/src/scheme.rs @@ -6,7 +6,7 @@ use redox_scheme::scheme::SchemeSync; use redox_scheme::{CallerCtx, OpenResult}; use scheme_utils::{FpathWriter, HandleMap}; use syscall::schemev2::NewFdFlags; -use syscall::{Error, EventFlags, Result, EACCES, EAGAIN, EBADF, ENOENT, O_NONBLOCK}; +use syscall::{Error, EventFlags, Result, EACCES, EAGAIN, EBADF, EINVAL, ENOENT, O_NONBLOCK}; use crate::display::Display; use crate::text::TextScreen; @@ -50,14 +50,21 @@ impl FbconScheme { let mut vts = BTreeMap::new(); for &vt_i in vt_ids { - let display = Display::open_new_vt().expect("Failed to open display for vt"); - event_queue - .subscribe( - display.input_handle.event_handle().as_raw_fd() as usize, - VtIndex(vt_i), - event::EventFlags::READ, - ) - .expect("Failed to subscribe to input events for vt"); + let display = match Display::open_new_vt() { + Ok(d) => d, + Err(err) => { + eprintln!("fbcond: failed to open display for vt {}: {}", vt_i, err); + continue; + } + }; + if let Err(err) = event_queue.subscribe( + display.input_handle.event_handle().as_raw_fd() as usize, + VtIndex(vt_i), + event::EventFlags::READ, + ) { + eprintln!("fbcond: failed to subscribe to input events for vt {}: {}", vt_i, err); + continue; + } vts.insert(VtIndex(vt_i), TextScreen::new(display)); } @@ -127,7 +134,7 @@ impl SchemeSync for FbconScheme { fn fpath(&mut self, id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> Result { FpathWriter::with_legacy(buf, "fbcon", |w| { let handle = self.get_vt_handle_mut(id)?; - write!(w, "{}", handle.vt_i.0).unwrap(); + write!(w, "{}", handle.vt_i.0).map_err(|_| Error::new(EINVAL))?; Ok(()) }) } diff --git a/drivers/graphics/fbcond/src/text.rs b/drivers/graphics/fbcond/src/text.rs index 8a24bbeb..c7272ab7 100644 --- a/drivers/graphics/fbcond/src/text.rs +++ b/drivers/graphics/fbcond/src/text.rs @@ -113,7 +113,7 @@ impl TextScreen { let mut i = 0; while i < buf.len() && !self.input.is_empty() { - buf[i] = self.input.pop_front().unwrap(); + buf[i] = self.input.pop_front().unwrap_or(0); i += 1; } diff --git a/drivers/graphics/graphics-ipc/src/lib.rs b/drivers/graphics/graphics-ipc/src/lib.rs index 285b3043..7451c90a 100644 --- a/drivers/graphics/graphics-ipc/src/lib.rs +++ b/drivers/graphics/graphics-ipc/src/lib.rs @@ -29,12 +29,16 @@ impl drm::control::Device for V2GraphicsHandle {} impl V2GraphicsHandle { pub fn from_file(file: File) -> io::Result { let handle = V2GraphicsHandle { file }; - assert!(handle.get_driver_capability(DriverCapability::DumbBuffer)? == 1); + if handle.get_driver_capability(DriverCapability::DumbBuffer)? != 1 { + return Err(io::Error::other( + "graphics device does not support dumb buffers", + )); + } Ok(handle) } pub fn first_display(&self) -> io::Result { - for &connector in self.resource_handles().unwrap().connectors() { + for &connector in self.resource_handles()?.connectors() { if self.get_connector(connector, true)?.state() == State::Connected { return Ok(connector); } @@ -95,13 +99,28 @@ impl CpuBackedBuffer { return; // No shadow buffer; all writes are already propagated to the GPU. }; - assert!(x.checked_add(width).unwrap() <= self.buffer.size().0); - assert!(y.checked_add(height).unwrap() <= self.buffer.size().1); + let Some(x_end) = x.checked_add(width) else { + return; + }; + let Some(y_end) = y.checked_add(height) else { + return; + }; + if x_end > self.buffer.size().0 || y_end > self.buffer.size().1 { + return; + } - let start_x: usize = x.try_into().unwrap(); - let start_y: usize = y.try_into().unwrap(); - let w: usize = width.try_into().unwrap(); - let h: usize = height.try_into().unwrap(); + let Ok(start_x) = usize::try_from(x) else { + return; + }; + let Ok(start_y) = usize::try_from(y) else { + return; + }; + let Ok(w) = usize::try_from(width) else { + return; + }; + let Ok(h) = usize::try_from(height) else { + return; + }; let offscreen_ptr = shadow.as_ptr().cast::(); let onscreen_ptr = self.map.as_mut_ptr().cast::(); 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/ddi.rs b/drivers/graphics/ihdgd/src/device/ddi.rs index ac4ce1bd..b851d169 100644 --- a/drivers/graphics/ihdgd/src/device/ddi.rs +++ b/drivers/graphics/ihdgd/src/device/ddi.rs @@ -347,9 +347,12 @@ impl Ddi { // Last setting is the default //TODO: get correct setting index from BIOS - let setting = settings.last().unwrap(); + let Some(setting) = settings.last() else { + log::error!("no voltage swing settings available"); + return Err(Error::new(EIO)); + }; - // This allows unwraps on port functions below without panic + // All port registers below require port_base to be set (checked above) if self.port_base.is_none() { log::error!("HDMI voltage swing procedure only implemented on combo DDI"); return Err(Error::new(EIO)); @@ -358,9 +361,15 @@ impl Ddi { // Clear cmnkeeper_enable for HDMI { // It is not possible to read from GRP register, so use LN0 as template - let pcs_dw1_ln0 = self.port_pcs(PortPcsReg::Dw1, PortLane::Ln0).unwrap(); - let mut pcs_dw1_grp = - WriteOnly::new(self.port_pcs(PortPcsReg::Dw1, PortLane::Grp).unwrap()); + let Some(pcs_dw1_ln0) = self.port_pcs(PortPcsReg::Dw1, PortLane::Ln0) else { + log::error!("failed to get PCS_DW1_LN0 for DDI {}", self.name); + return Err(Error::new(EIO)); + }; + let Some(pcs_dw1_grp_raw) = self.port_pcs(PortPcsReg::Dw1, PortLane::Grp) else { + log::error!("failed to get PCS_DW1_GRP for DDI {}", self.name); + return Err(Error::new(EIO)); + }; + let mut pcs_dw1_grp = WriteOnly::new(pcs_dw1_grp_raw); let mut v = pcs_dw1_ln0.read(); v &= !PORT_PCS_DW1_CMNKEEPER_ENABLE; pcs_dw1_grp.write(v); @@ -369,28 +378,50 @@ impl Ddi { // Program loadgen select //TODO: this assumes bit rate <= 6 GHz and 4 lanes enabled { - let mut tx_dw4_ln0 = self.port_tx(PortTxReg::Dw4, PortLane::Ln0).unwrap(); + let Some(mut tx_dw4_ln0) = self.port_tx(PortTxReg::Dw4, PortLane::Ln0) else { + log::error!("failed to get TX_DW4_LN0 for DDI {}", self.name); + return Err(Error::new(EIO)); + }; tx_dw4_ln0.writef(PORT_TX_DW4_SELECT, false); - let mut tx_dw4_ln1 = self.port_tx(PortTxReg::Dw4, PortLane::Ln1).unwrap(); + let Some(mut tx_dw4_ln1) = self.port_tx(PortTxReg::Dw4, PortLane::Ln1) else { + log::error!("failed to get TX_DW4_LN1 for DDI {}", self.name); + return Err(Error::new(EIO)); + }; tx_dw4_ln1.writef(PORT_TX_DW4_SELECT, true); - let mut tx_dw4_ln2 = self.port_tx(PortTxReg::Dw4, PortLane::Ln2).unwrap(); + let Some(mut tx_dw4_ln2) = self.port_tx(PortTxReg::Dw4, PortLane::Ln2) else { + log::error!("failed to get TX_DW4_LN2 for DDI {}", self.name); + return Err(Error::new(EIO)); + }; tx_dw4_ln2.writef(PORT_TX_DW4_SELECT, true); - let mut tx_dw4_ln3 = self.port_tx(PortTxReg::Dw4, PortLane::Ln3).unwrap(); + let Some(mut tx_dw4_ln3) = self.port_tx(PortTxReg::Dw4, PortLane::Ln3) else { + log::error!("failed to get TX_DW4_LN3 for DDI {}", self.name); + return Err(Error::new(EIO)); + }; tx_dw4_ln3.writef(PORT_TX_DW4_SELECT, true); } // Set PORT_CL_DW5 sus clock config to 11b { - let mut cl_dw5 = self.port_cl(PortClReg::Dw5).unwrap(); + let Some(mut cl_dw5) = self.port_cl(PortClReg::Dw5) else { + log::error!("failed to get CL_DW5 for DDI {}", self.name); + return Err(Error::new(EIO)); + }; cl_dw5.writef(PORT_CL_DW5_SUS_CLOCK_MASK, true); } // Clear training enable to change swing values - let tx_dw5_ln0 = self.port_tx(PortTxReg::Dw5, PortLane::Ln0).unwrap(); - let mut tx_dw5_grp = WriteOnly::new(self.port_tx(PortTxReg::Dw5, PortLane::Grp).unwrap()); + let Some(tx_dw5_ln0) = self.port_tx(PortTxReg::Dw5, PortLane::Ln0) else { + log::error!("failed to get TX_DW5_LN0 for DDI {}", self.name); + return Err(Error::new(EIO)); + }; + let Some(tx_dw5_grp_raw) = self.port_tx(PortTxReg::Dw5, PortLane::Grp) else { + log::error!("failed to get TX_DW5_GRP for DDI {}", self.name); + return Err(Error::new(EIO)); + }; + let mut tx_dw5_grp = WriteOnly::new(tx_dw5_grp_raw); { let mut v = tx_dw5_ln0.read(); v &= !PORT_TX_DW5_TRAINING_ENABLE; @@ -400,7 +431,10 @@ impl Ddi { // Program swing and de-emphasis // Disable eDP bits in PORT_CL_DW10 - let mut cl_dw10 = self.port_cl(PortClReg::Dw10).unwrap(); + let Some(mut cl_dw10) = self.port_cl(PortClReg::Dw10) else { + log::error!("failed to get CL_DW10 for DDI {}", self.name); + return Err(Error::new(EIO)); + }; cl_dw10.writef( PORT_CL_DW10_EDP4K2K_MODE_OVRD_EN | PORT_CL_DW10_EDP4K2K_MODE_OVRD_VAL, false, @@ -435,7 +469,10 @@ impl Ddi { // - Set swing sel from settings // - Set rcomp scalar to 0x98 for lane in lanes { - let mut tx_dw2 = self.port_tx(PortTxReg::Dw2, lane).unwrap(); + let Some(mut tx_dw2) = self.port_tx(PortTxReg::Dw2, lane) else { + log::error!("failed to get TX_DW2 for {:?} on DDI {}", lane, self.name); + continue; + }; let mut v = tx_dw2.read(); v &= !(PORT_TX_DW2_SWING_SEL_UPPER_MASK | PORT_TX_DW2_SWING_SEL_LOWER_MASK @@ -451,7 +488,10 @@ impl Ddi { // - Set post cursor 2 to 0x0 // - Set cursor coeff from settings for lane in lanes { - let mut tx_dw4 = self.port_tx(PortTxReg::Dw4, lane).unwrap(); + let Some(mut tx_dw4) = self.port_tx(PortTxReg::Dw4, lane) else { + log::error!("failed to get TX_DW4 for {:?} on DDI {}", lane, self.name); + continue; + }; let mut v = tx_dw4.read(); v &= !(PORT_TX_DW4_POST_CURSOR_1_MASK | PORT_TX_DW4_POST_CURSOR_2_MASK @@ -464,7 +504,10 @@ impl Ddi { // For PORT_TX_DW7: // - Set n scalar from settings for lane in lanes { - let mut tx_dw7 = self.port_tx(PortTxReg::Dw7, lane).unwrap(); + let Some(mut tx_dw7) = self.port_tx(PortTxReg::Dw7, lane) else { + log::error!("failed to get TX_DW7 for {:?} on DDI {}", lane, self.name); + continue; + }; // All other bits are spare tx_dw7.write(setting.dw7_n_scalar << PORT_TX_DW7_N_SCALAR_SHIFT); } diff --git a/drivers/graphics/ihdgd/src/device/ggtt.rs b/drivers/graphics/ihdgd/src/device/ggtt.rs index 5e39827a..0ec5358b 100644 --- a/drivers/graphics/ihdgd/src/device/ggtt.rs +++ b/drivers/graphics/ihdgd/src/device/ggtt.rs @@ -3,7 +3,7 @@ use std::{mem, ptr}; use pcid_interface::PciFunctionHandle; use range_alloc::RangeAllocator; -use syscall::{Error, EIO}; +use syscall::{Error, EIO, EINVAL}; use crate::device::MmioRegion; @@ -88,20 +88,36 @@ impl GlobalGtt { } } - pub fn reserve(&mut self, surf: u32, surf_size: u32) { - assert!(surf.is_multiple_of(GTT_PAGE_SIZE)); - assert!(surf_size.is_multiple_of(GTT_PAGE_SIZE)); + pub fn reserve(&mut self, surf: u32, surf_size: u32) -> syscall::Result<()> { + if !surf.is_multiple_of(GTT_PAGE_SIZE) { + log::error!( + "reserve: surface address 0x{:x} is not aligned to GTT page size", + surf + ); + return Err(Error::new(EINVAL)); + } + if !surf_size.is_multiple_of(GTT_PAGE_SIZE) { + log::error!( + "reserve: surface size 0x{:x} is not aligned to GTT page size", + surf_size + ); + return Err(Error::new(EINVAL)); + } - self.gm_alloc - .allocate_exact_range( - surf / GTT_PAGE_SIZE..surf / GTT_PAGE_SIZE + surf_size / GTT_PAGE_SIZE, - ) - .unwrap_or_else(|err| { - panic!( + match self.gm_alloc.allocate_exact_range( + surf / GTT_PAGE_SIZE..surf / GTT_PAGE_SIZE + surf_size / GTT_PAGE_SIZE, + ) { + Ok(_range) => Ok(()), + Err(err) => { + log::error!( "failed to allocate pre-existing surface at 0x{:x} of size {}: {:?}", - surf, surf_size, err + surf, + surf_size, + err ); - }); + Err(Error::new(EIO)) + } + } } pub fn alloc_phys_mem(&mut self, size: u32) -> syscall::Result { diff --git a/drivers/graphics/ihdgd/src/device/mod.rs b/drivers/graphics/ihdgd/src/device/mod.rs index ced9dd56..fc2a1108 100644 --- a/drivers/graphics/ihdgd/src/device/mod.rs +++ b/drivers/graphics/ihdgd/src/device/mod.rs @@ -51,8 +51,9 @@ impl<'a, T, F: FnOnce(&mut T)> CallbackGuard<'a, T, F> { impl<'a, T, F: FnOnce(&mut T)> Drop for CallbackGuard<'a, T, F> { fn drop(&mut self) { - let fini = self.fini.take().unwrap(); - fini(&mut self.value); + if let Some(fini) = self.fini.take() { + fini(&mut self.value); + } } } @@ -246,7 +247,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 +258,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); @@ -453,7 +458,12 @@ impl Device { // Probe all DDIs let ddi_names: Vec<&str> = self.ddis.iter().map(|ddi| ddi.name).collect(); for ddi_name in ddi_names { - self.probe_ddi(ddi_name).expect("failed to probe DDI"); + match self.probe_ddi(ddi_name) { + Ok(_) => {} + Err(err) => { + log::error!("failed to probe DDI {}: {}", ddi_name, err); + } + } } self.dump(); diff --git a/drivers/graphics/ihdgd/src/device/pipe.rs b/drivers/graphics/ihdgd/src/device/pipe.rs index 0e99ffe4..779e4c5f 100644 --- a/drivers/graphics/ihdgd/src/device/pipe.rs +++ b/drivers/graphics/ihdgd/src/device/pipe.rs @@ -76,14 +76,12 @@ impl Plane { let buf_cfg = self.buf_cfg.read(); let buffer_start = buf_cfg & 0x7FF; let buffer_end = (buf_cfg >> 16) & 0x7FF; - alloc_buffers - .allocate_exact_range(buffer_start..(buffer_end + 1)) - .unwrap_or_else(|err| { - panic!( - "failed to allocate pre-existing buffer blocks {} to {}: {:?}", - buffer_start, buffer_end, err - ); - }); + if let Err(err) = alloc_buffers.allocate_exact_range(buffer_start..(buffer_end + 1)) { + log::error!( + "failed to allocate pre-existing buffer blocks {} to {}: {:?}", + buffer_start, buffer_end, err + ); + } } pub fn modeset(&mut self, alloc_buffers: &mut RangeAllocator) -> syscall::Result<()> { @@ -122,7 +120,13 @@ impl Plane { let surf = self.surf.read() & 0xFFFFF000; //TODO: read bits per pixel let surf_size = (stride * height).next_multiple_of(4096); - ggtt.reserve(surf, surf_size); + ggtt.reserve(surf, surf_size).unwrap_or_else(|err| { + log::warn!( + "failed to reserve GTT entries for existing framebuffer at 0x{:x}: {}", + surf, + err + ); + }); unsafe { DeviceFb::new(gm, surf, width, height, stride, true) } } diff --git a/drivers/graphics/ihdgd/src/device/scheme.rs b/drivers/graphics/ihdgd/src/device/scheme.rs index 95db5bbf..3554a35e 100644 --- a/drivers/graphics/ihdgd/src/device/scheme.rs +++ b/drivers/graphics/ihdgd/src/device/scheme.rs @@ -68,7 +68,20 @@ impl GraphicsAdapter for Device { } fn probe_connector(&mut self, objects: &mut KmsObjects, id: KmsObjectId) { - let mut connector = objects.get_connector(id).unwrap().lock().unwrap(); + let connector_guard = match objects.get_connector(id) { + Ok(guard) => guard, + Err(e) => { + log::error!("probe_connector: connector {:?} not found: {}", id, e); + return; + } + }; + let mut connector = match connector_guard.lock() { + Ok(guard) => guard, + Err(err) => { + log::error!("probe_connector: failed to lock connector {:?}: {}", id, err); + return; + } + }; let framebuffer = &self.framebuffers[connector.driver_data.framebuffer_id]; connector.connection = KmsConnectorStatus::Connected; connector.update_from_size(framebuffer.width as u32, framebuffer.height as u32); @@ -94,7 +107,10 @@ impl GraphicsAdapter for Device { state: KmsCrtcState, damage: Damage, ) -> syscall::Result<()> { - let mut crtc = crtc.lock().unwrap(); + let mut crtc = crtc.lock().map_err(|err| { + log::error!("set_crtc: failed to lock crtc: {}", err); + syscall::Error::new(EINVAL) + })?; let buffer = state .fb_id .map(|fb_id| objects.get_framebuffer(fb_id)) @@ -102,7 +118,13 @@ impl GraphicsAdapter for Device { crtc.state = state; for connector in objects.connectors() { - let connector = connector.lock().unwrap(); + let connector = match connector.lock() { + Ok(c) => c, + Err(err) => { + log::error!("set_crtc: failed to lock connector: {}", err); + continue; + } + }; if connector.state.crtc_id != objects.crtc_ids()[crtc.crtc_index as usize] { continue; @@ -161,9 +183,9 @@ impl DumbFb { fn layout(len: usize) -> Layout { // optimizes to an integer mul Layout::array::(len) - .unwrap() + .unwrap_or_else(|_| Layout::from_size_align(len * 4, PAGE_SIZE).unwrap_or(Layout::new::())) .align_to(PAGE_SIZE) - .unwrap() + .unwrap_or_else(|_| Layout::new::().align_to(PAGE_SIZE).unwrap_or(Layout::new::())) } } @@ -182,15 +204,38 @@ impl Buffer for DumbFb { impl DumbFb { fn sync(&self, framebuffer: &mut DeviceFb, sync_rect: Damage) { - let sync_rect = sync_rect.clip( - self.width.try_into().unwrap(), - self.height.try_into().unwrap(), - ); - - let start_x: usize = sync_rect.x.try_into().unwrap(); - let start_y: usize = sync_rect.y.try_into().unwrap(); - let w: usize = sync_rect.width.try_into().unwrap(); - let h: usize = sync_rect.height.try_into().unwrap(); + let fb_w: u32 = match self.width.try_into() { + Ok(v) => v, + Err(_) => { + log::error!("sync: framebuffer width {} overflow", self.width); + return; + } + }; + let fb_h: u32 = match self.height.try_into() { + Ok(v) => v, + Err(_) => { + log::error!("sync: framebuffer height {} overflow", self.height); + return; + } + }; + let sync_rect = sync_rect.clip(fb_w, fb_h); + + let start_x: usize = match sync_rect.x.try_into() { + Ok(v) => v, + Err(_) => return, + }; + let start_y: usize = match sync_rect.y.try_into() { + Ok(v) => v, + Err(_) => return, + }; + let w: usize = match sync_rect.width.try_into() { + Ok(v) => v, + Err(_) => return, + }; + let h: usize = match sync_rect.height.try_into() { + Ok(v) => v, + Err(_) => return, + }; let offscreen_ptr = self.ptr.as_ptr() as *mut u32; let onscreen_ptr = framebuffer.buffer.virt.cast::(); diff --git a/drivers/graphics/ihdgd/src/main.rs b/drivers/graphics/ihdgd/src/main.rs index a8b6cc60..84d58a3e 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,16 +29,32 @@ 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.*. // FIXME change the initnsmgr to not block on openat for the target scheme. - let event_queue: EventQueue = - EventQueue::new().expect("ihdgd: failed to create event queue"); + let event_queue: EventQueue = match EventQueue::new() { + Ok(eq) => eq, + Err(err) => { + log::error!("ihdgd: failed to create event queue: {err}"); + std::process::exit(1); + } + }; let mut scheme = GraphicsScheme::new(device, format!("display.ihdg.{}", name), false); @@ -50,53 +66,69 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { } } - event_queue - .subscribe( - scheme.inputd_event_handle().as_raw_fd() as usize, - Source::Input, - event::EventFlags::READ, - ) - .unwrap(); - event_queue - .subscribe( - irq_file.irq_handle().as_raw_fd() as usize, - Source::Irq, - event::EventFlags::READ, - ) - .unwrap(); - event_queue - .subscribe( - scheme.event_handle().raw(), - Source::Scheme, - event::EventFlags::READ, - ) - .unwrap(); - - libredox::call::setrens(0, 0).expect("ihdgd: failed to enter null namespace"); + if let Err(err) = event_queue.subscribe( + scheme.inputd_event_handle().as_raw_fd() as usize, + Source::Input, + event::EventFlags::READ, + ) { + log::error!("ihdgd: failed to subscribe to input events: {err}"); + } + if let Err(err) = event_queue.subscribe( + irq_file.irq_handle().as_raw_fd() as usize, + Source::Irq, + event::EventFlags::READ, + ) { + log::error!("ihdgd: failed to subscribe to IRQ events: {err}"); + } + if let Err(err) = event_queue.subscribe( + scheme.event_handle().raw(), + Source::Scheme, + event::EventFlags::READ, + ) { + log::error!("ihdgd: failed to subscribe to scheme events: {err}"); + } + + if let Err(err) = libredox::call::setrens(0, 0) { + log::error!("ihdgd: failed to enter null namespace: {err}"); + std::process::exit(1); + } daemon.ready(); let all = [Source::Input, Source::Irq, Source::Scheme]; - for event in all - .into_iter() - .chain(event_queue.map(|e| e.expect("ihdgd: failed to get next event").user_data)) - { + for event in all.into_iter().chain( + event_queue.filter_map(|e| match e { + Ok(event) => Some(event.user_data), + Err(err) => { + log::error!("ihdgd: failed to get next event: {err}"); + None + } + }), + ) { match event { Source::Input => scheme.handle_vt_events(), Source::Irq => { let mut irq = [0; 8]; - irq_file.irq_handle().read(&mut irq).unwrap(); + if irq_file.irq_handle().read(&mut irq).is_err() { + log::error!("ihdgd: failed to read IRQ"); + continue; + } if scheme.adapter_mut().handle_irq() { - irq_file.irq_handle().write(&mut irq).unwrap(); + if let Err(err) = irq_file.irq_handle().write(&mut irq) { + log::error!("ihdgd: failed to write IRQ: {err}"); + continue; + } scheme.adapter_mut().handle_events(); - scheme.tick().unwrap(); + if let Err(err) = scheme.tick() { + log::error!("ihdgd: failed to handle display events after IRQ: {err}"); + } } } Source::Scheme => { - scheme - .tick() - .expect("ihdgd: failed to handle scheme events"); + if let Err(err) = scheme.tick() { + log::error!("ihdgd: failed to handle scheme events: {err}"); + } } } } diff --git a/drivers/graphics/vesad/src/main.rs b/drivers/graphics/vesad/src/main.rs index a4c07d1e..41faa0e2 100644 --- a/drivers/graphics/vesad/src/main.rs +++ b/drivers/graphics/vesad/src/main.rs @@ -23,25 +23,49 @@ fn daemon(daemon: daemon::Daemon) -> ! { } let width = usize::from_str_radix( - &env::var("FRAMEBUFFER_WIDTH").expect("FRAMEBUFFER_WIDTH not set"), + &env::var("FRAMEBUFFER_WIDTH").unwrap_or_else(|_| { + eprintln!("vesad: FRAMEBUFFER_WIDTH not set"); + std::process::exit(1); + }), 16, ) - .expect("failed to parse FRAMEBUFFER_WIDTH"); + .unwrap_or_else(|err| { + eprintln!("vesad: failed to parse FRAMEBUFFER_WIDTH: {}", err); + std::process::exit(1); + }); let height = usize::from_str_radix( - &env::var("FRAMEBUFFER_HEIGHT").expect("FRAMEBUFFER_HEIGHT not set"), + &env::var("FRAMEBUFFER_HEIGHT").unwrap_or_else(|_| { + eprintln!("vesad: FRAMEBUFFER_HEIGHT not set"); + std::process::exit(1); + }), 16, ) - .expect("failed to parse FRAMEBUFFER_HEIGHT"); + .unwrap_or_else(|err| { + eprintln!("vesad: failed to parse FRAMEBUFFER_HEIGHT: {}", err); + std::process::exit(1); + }); let phys = usize::from_str_radix( - &env::var("FRAMEBUFFER_ADDR").expect("FRAMEBUFFER_ADDR not set"), + &env::var("FRAMEBUFFER_ADDR").unwrap_or_else(|_| { + eprintln!("vesad: FRAMEBUFFER_ADDR not set"); + std::process::exit(1); + }), 16, ) - .expect("failed to parse FRAMEBUFFER_ADDR"); + .unwrap_or_else(|err| { + eprintln!("vesad: failed to parse FRAMEBUFFER_ADDR: {}", err); + std::process::exit(1); + }); let stride = usize::from_str_radix( - &env::var("FRAMEBUFFER_STRIDE").expect("FRAMEBUFFER_STRIDE not set"), + &env::var("FRAMEBUFFER_STRIDE").unwrap_or_else(|_| { + eprintln!("vesad: FRAMEBUFFER_STRIDE not set"); + std::process::exit(1); + }), 16, ) - .expect("failed to parse FRAMEBUFFER_STRIDE"); + .unwrap_or_else(|err| { + eprintln!("vesad: failed to parse FRAMEBUFFER_STRIDE: {}", err); + std::process::exit(1); + }); println!( "vesad: {}x{} stride {} at 0x{:X}", @@ -57,14 +81,20 @@ fn daemon(daemon: daemon::Daemon) -> ! { let mut framebuffers = vec![unsafe { FrameBuffer::new(phys, width, height, stride) }]; //TODO: ideal maximum number of outputs? - let bootloader_env = std::fs::read_to_string("/scheme/sys/env") - .expect("failed to read env") - .lines() - .map(|line| { - let (env, value) = line.split_once('=').unwrap(); - (env.to_owned(), value.to_owned()) - }) - .collect::>(); + let bootloader_env: HashMap = + match std::fs::read_to_string("/scheme/sys/env") { + Ok(content) => content + .lines() + .filter_map(|line| { + let (env, value) = line.split_once('=')?; + Some((env.to_owned(), value.to_owned())) + }) + .collect(), + Err(err) => { + eprintln!("vesad: failed to read bootloader env: {}", err); + HashMap::new() + } + }; for i in 1..1024 { match bootloader_env.get(&format!("FRAMEBUFFER{}", i)) { Some(var) => match unsafe { FrameBuffer::parse(&var) } { @@ -93,38 +123,51 @@ fn daemon(daemon: daemon::Daemon) -> ! { } } - let event_queue: EventQueue = - EventQueue::new().expect("vesad: failed to create event queue"); - event_queue - .subscribe( - scheme.inputd_event_handle().as_raw_fd() as usize, - Source::Input, - event::EventFlags::READ, - ) - .unwrap(); - event_queue - .subscribe( - scheme.event_handle().raw(), - Source::Scheme, - event::EventFlags::READ, - ) - .unwrap(); - - libredox::call::setrens(0, 0).expect("vesad: failed to enter null namespace"); + let event_queue: EventQueue = match EventQueue::new() { + Ok(eq) => eq, + Err(err) => { + eprintln!("vesad: failed to create event queue: {}", err); + daemon.ready(); + std::process::exit(1); + } + }; + if let Err(err) = event_queue.subscribe( + scheme.inputd_event_handle().as_raw_fd() as usize, + Source::Input, + event::EventFlags::READ, + ) { + eprintln!("vesad: failed to subscribe to input events: {}", err); + } + if let Err(err) = event_queue.subscribe( + scheme.event_handle().raw(), + Source::Scheme, + event::EventFlags::READ, + ) { + eprintln!("vesad: failed to subscribe to scheme events: {}", err); + } + + if let Err(err) = libredox::call::setrens(0, 0) { + eprintln!("vesad: failed to enter null namespace: {}", err); + } daemon.ready(); let all = [Source::Input, Source::Scheme]; - for event in all - .into_iter() - .chain(event_queue.map(|e| e.expect("vesad: failed to get next event").user_data)) - { + for event in all.into_iter().chain(event_queue.filter_map(|e| { + match e { + Ok(ev) => Some(ev.user_data), + Err(err) => { + eprintln!("vesad: failed to get next event: {}", err); + None + } + } + })) { match event { Source::Input => scheme.handle_vt_events(), Source::Scheme => { - scheme - .tick() - .expect("vesad: failed to handle scheme events"); + if let Err(err) = scheme.tick() { + eprintln!("vesad: failed to handle scheme events: {}", err); + } } } } diff --git a/drivers/graphics/vesad/src/scheme.rs b/drivers/graphics/vesad/src/scheme.rs index 5bf2be91..20d755d2 100644 --- a/drivers/graphics/vesad/src/scheme.rs +++ b/drivers/graphics/vesad/src/scheme.rs @@ -74,7 +74,17 @@ impl GraphicsAdapter for FbAdapter { } fn probe_connector(&mut self, objects: &mut KmsObjects, id: KmsObjectId) { - let mut connector = objects.get_connector(id).unwrap().lock().unwrap(); + let connector_mutex = match objects.get_connector(id) { + Ok(c) => c, + Err(err) => { + eprintln!("vesad: probe_connector: connector {:?} not found: {}", id, err); + return; + } + }; + let mut connector = connector_mutex.lock().unwrap_or_else(|e| { + eprintln!("vesad: probe_connector: connector lock poisoned, recovering"); + e.into_inner() + }); let connector = &mut *connector; connector.connection = KmsConnectorStatus::Connected; connector.update_from_size(connector.driver_data.width, connector.driver_data.height); @@ -102,7 +112,10 @@ impl GraphicsAdapter for FbAdapter { state: KmsCrtcState, damage: Damage, ) -> syscall::Result<()> { - let mut crtc = crtc.lock().unwrap(); + let mut crtc = crtc.lock().unwrap_or_else(|e| { + eprintln!("vesad: set_crtc: crtc lock poisoned, recovering"); + e.into_inner() + }); let buffer = state .fb_id .map(|fb_id| objects.get_framebuffer(fb_id)) @@ -110,7 +123,10 @@ impl GraphicsAdapter for FbAdapter { crtc.state = state; for connector in objects.connectors() { - let connector = connector.lock().unwrap(); + let connector = connector.lock().unwrap_or_else(|e| { + eprintln!("vesad: set_crtc: connector lock poisoned, recovering"); + e.into_inner() + }); if connector.state.crtc_id != objects.crtc_ids()[crtc.crtc_index as usize] { continue; @@ -159,7 +175,7 @@ pub struct FrameBuffer { impl FrameBuffer { pub unsafe fn new(phys: usize, width: usize, height: usize, stride: usize) -> Self { let size = stride * height; - let virt = common::physmap( + let virt = match common::physmap( phys, size * 4, common::Prot { @@ -167,8 +183,13 @@ impl FrameBuffer { write: true, }, common::MemoryType::WriteCombining, - ) - .expect("vesad: failed to map framebuffer") as *mut u32; + ) { + Ok(v) => v as *mut u32, + Err(err) => { + eprintln!("vesad: failed to map framebuffer at 0x{:X}: {}", phys, err); + std::process::exit(1); + } + }; let onscreen = ptr::slice_from_raw_parts_mut(virt, size); @@ -228,9 +249,11 @@ impl GraphicScreen { fn layout(len: usize) -> Layout { // optimizes to an integer mul Layout::array::(len) - .unwrap() - .align_to(PAGE_SIZE) - .unwrap() + .and_then(|l| l.align_to(PAGE_SIZE)) + .unwrap_or_else(|err| { + eprintln!("vesad: failed to compute buffer layout (len={}): {}", len, err); + std::process::exit(1); + }) } } @@ -249,15 +272,26 @@ impl Buffer for GraphicScreen { impl GraphicScreen { fn sync(&self, framebuffer: &mut FrameBuffer, sync_rect: Damage) { - let sync_rect = sync_rect.clip( - self.width.try_into().unwrap(), - self.height.try_into().unwrap(), - ); - - let start_x: usize = sync_rect.x.try_into().unwrap(); - let start_y: usize = sync_rect.y.try_into().unwrap(); - let w: usize = sync_rect.width.try_into().unwrap(); - let h: usize = sync_rect.height.try_into().unwrap(); + let Ok(fb_width) = u32::try_from(self.width) else { + return; + }; + let Ok(fb_height) = u32::try_from(self.height) else { + return; + }; + let sync_rect = sync_rect.clip(fb_width, fb_height); + + let Ok(start_x): Result = sync_rect.x.try_into() else { + return; + }; + let Ok(start_y): Result = sync_rect.y.try_into() else { + return; + }; + let Ok(w): Result = sync_rect.width.try_into() else { + return; + }; + let Ok(h): Result = sync_rect.height.try_into() else { + return; + }; let offscreen_ptr = self.ptr.as_ptr() as *mut u32; let onscreen_ptr = framebuffer.onscreen as *mut u32; // FIXME use as_mut_ptr once stable diff --git a/drivers/graphics/virtio-gpud/src/main.rs b/drivers/graphics/virtio-gpud/src/main.rs index b27f4c56..f3514c8e 100644 --- a/drivers/graphics/virtio-gpud/src/main.rs +++ b/drivers/graphics/virtio-gpud/src/main.rs @@ -482,8 +482,11 @@ fn main() { } fn daemon_runner(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { - deamon(daemon, pcid_handle).unwrap(); - unreachable!(); + deamon(daemon, pcid_handle).unwrap_or_else(|err| { + log::error!("virtio-gpud: daemon failed: {err}"); + std::process::exit(1); + }); + std::process::exit(0); } fn deamon(deamon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> anyhow::Result<()> { @@ -500,7 +503,10 @@ 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 { + log::error!("virtio-gpud: unexpected device ID {:#06x}, expected 0x1050", pci_config.func.full_device_id.device_id); + std::process::exit(1); + } log::info!("virtio-gpu: initiating startup sequence :^)"); let device = DEVICE.try_call_once(|| virtio_core::probe_device(&mut pcid_handle))?; @@ -531,7 +537,10 @@ fn deamon(deamon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> anyhow: // /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"); + EventQueue::new().unwrap_or_else(|err| { + log::error!("virtio-gpud: failed to create event queue: {err}"); + std::process::exit(1); + }); let mut scheme = scheme::GpuScheme::new( config, @@ -556,33 +565,48 @@ fn deamon(deamon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> anyhow: Source::Input, event::EventFlags::READ, ) - .unwrap(); + .unwrap_or_else(|err| { + log::error!("virtio-gpud: failed to subscribe to input events: {err}"); + std::process::exit(1); + }); event_queue .subscribe( scheme.event_handle().raw(), Source::Scheme, event::EventFlags::READ, ) - .unwrap(); + .unwrap_or_else(|err| { + log::error!("virtio-gpud: failed to subscribe to scheme events: {err}"); + std::process::exit(1); + }); event_queue .subscribe( device.irq_handle.as_raw_fd() as usize, Source::Interrupt, event::EventFlags::READ, ) - .unwrap(); + .unwrap_or_else(|err| { + log::error!("virtio-gpud: failed to subscribe to interrupt events: {err}"); + std::process::exit(1); + }); let all = [Source::Input, Source::Scheme, Source::Interrupt]; for event in all .into_iter() - .chain(event_queue.map(|e| e.expect("virtio-gpud: failed to get next event").user_data)) + .chain(event_queue.filter_map(|e| match e { + Ok(ev) => Some(ev.user_data), + Err(err) => { + log::error!("virtio-gpud: failed to get next event: {err}"); + None + } + })) { match event { Source::Input => scheme.handle_vt_events(), Source::Scheme => { - scheme - .tick() - .expect("virtio-gpud: failed to process scheme events"); + if let Err(err) = scheme.tick() { + log::error!("virtio-gpud: failed to process scheme events: {err}"); + } } Source::Interrupt => loop { let before_gen = device.transport.config_generation(); @@ -591,7 +615,11 @@ fn deamon(deamon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> anyhow: if events & VIRTIO_GPU_EVENT_DISPLAY != 0 { let (adapter, objects) = scheme.adapter_and_kms_objects_mut(); - futures::executor::block_on(async { adapter.update_displays().await.unwrap() }); + futures::executor::block_on(async { + if let Err(err) = adapter.update_displays().await { + log::error!("virtio-gpud: failed to update displays: {err}"); + } + }); for connector_id in objects.connector_ids().to_vec() { adapter.probe_connector(objects, connector_id); } diff --git a/drivers/graphics/virtio-gpud/src/scheme.rs b/drivers/graphics/virtio-gpud/src/scheme.rs index 22a985ee..c60facfd 100644 --- a/drivers/graphics/virtio-gpud/src/scheme.rs +++ b/drivers/graphics/virtio-gpud/src/scheme.rs @@ -64,10 +64,23 @@ impl DrmBuffer for VirtGpuFramebuffer<'_> { impl Drop for VirtGpuFramebuffer<'_> { fn drop(&mut self) { - futures::executor::block_on(async { - let request = Dma::new(ResourceUnref::new(self.id)).unwrap(); + let request = match Dma::new(ResourceUnref::new(self.id)) { + Ok(r) => r, + Err(err) => { + log::error!("virtio-gpud: failed to allocate unref request DMA: {err}"); + return; + } + }; + + let header = match Dma::new(ControlHeader::default()) { + Ok(h) => h, + Err(err) => { + log::error!("virtio-gpud: failed to allocate unref header DMA: {err}"); + return; + } + }; - let header = Dma::new(ControlHeader::default()).unwrap(); + futures::executor::block_on(async { let command = ChainBuilder::new() .chain(Buffer::new(&request)) .chain(Buffer::new(&header).flags(DescriptorFlags::WRITE_ONLY)) @@ -182,7 +195,9 @@ impl VirtGpuAdapter<'_> { .build(); self.control_queue.send(command).await; - assert!(response.header.ty == CommandTy::RespOkDisplayInfo); + if response.header.ty != CommandTy::RespOkDisplayInfo { + return Err(Error::Probe("unexpected display info response type")); + } Ok(response) } @@ -197,7 +212,9 @@ impl VirtGpuAdapter<'_> { .build(); self.control_queue.send(command).await; - assert!(response.header.ty == CommandTy::RespOkEdid); + if response.header.ty != CommandTy::RespOkEdid { + return Err(Error::Probe("unexpected EDID response type")); + } Ok(response) } @@ -212,7 +229,7 @@ impl VirtGpuAdapter<'_> { ) { //Transfering cursor resource to host futures::executor::block_on(async { - let transfer_request = Dma::new(XferToHost2d::new( + let transfer_request = match Dma::new(XferToHost2d::new( cursor.id, GpuRect { x: 0, @@ -221,14 +238,33 @@ impl VirtGpuAdapter<'_> { height: 64, }, 0, - )) - .unwrap(); - let header = self.send_request_fenced(transfer_request).await.unwrap(); - assert_eq!(header.ty, CommandTy::RespOkNodata); + )) { + Ok(r) => r, + Err(err) => { + log::error!("virtio-gpud: failed to allocate cursor transfer DMA: {err}"); + return; + } + }; + let header = match self.send_request_fenced(transfer_request).await { + Ok(h) => h, + Err(err) => { + log::error!("virtio-gpud: failed to send cursor transfer: {err}"); + return; + } + }; + if header.ty != CommandTy::RespOkNodata { + log::error!("virtio-gpud: cursor transfer returned {:?}", header.ty); + } }); //Update the cursor position - let request = Dma::new(UpdateCursor::update_cursor(x, y, hot_x, hot_y, cursor.id)).unwrap(); + let request = match Dma::new(UpdateCursor::update_cursor(x, y, hot_x, hot_y, cursor.id)) { + Ok(r) => r, + Err(err) => { + log::error!("virtio-gpud: failed to allocate cursor update DMA: {err}"); + return; + } + }; futures::executor::block_on(async { let command = ChainBuilder::new().chain(Buffer::new(&request)).build(); self.cursor_queue.send(command).await; @@ -236,7 +272,13 @@ impl VirtGpuAdapter<'_> { } fn move_cursor(&mut self, x: i32, y: i32) { - let request = Dma::new(MoveCursor::move_cursor(x, y)).unwrap(); + let request = match Dma::new(MoveCursor::move_cursor(x, y)) { + Ok(r) => r, + Err(err) => { + log::error!("virtio-gpud: failed to allocate move cursor DMA: {err}"); + return; + } + }; futures::executor::block_on(async { let command = ChainBuilder::new().chain(Buffer::new(&request)).build(); @@ -246,7 +288,7 @@ impl VirtGpuAdapter<'_> { fn disable_cursor(&mut self) { if self.hidden_cursor.is_none() { - let (width, height) = self.hw_cursor_size().unwrap(); + let (width, height) = self.hw_cursor_size().unwrap_or((64, 64)); let (cursor, stride) = self.create_dumb_buffer(width, height); unsafe { core::ptr::write_bytes( @@ -257,7 +299,10 @@ impl VirtGpuAdapter<'_> { } self.hidden_cursor = Some(Arc::new(cursor)); } - let hidden_cursor = self.hidden_cursor.as_ref().unwrap().clone(); + let hidden_cursor = self.hidden_cursor.as_ref().unwrap_or_else(|| { + log::error!("virtio-gpud: hidden_cursor missing after initialization"); + std::process::exit(1); + }).clone(); self.update_cursor(&hidden_cursor, 0, 0, 0, 0); } @@ -280,7 +325,9 @@ impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> { fn init(&mut self, objects: &mut KmsObjects) { futures::executor::block_on(async { - self.update_displays().await.unwrap(); + if let Err(err) = self.update_displays().await { + log::error!("virtio-gpud: failed to update displays during init: {err}"); + } }); for display_id in 0..self.config.num_scanouts.get() { @@ -310,7 +357,13 @@ impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> { fn probe_connector(&mut self, objects: &mut KmsObjects, id: KmsObjectId) { futures::executor::block_on(async { - let mut connector = objects.get_connector(id).unwrap().lock().unwrap(); + let mut connector = match objects.get_connector(id) { + Ok(c) => c.lock().unwrap(), + Err(err) => { + log::error!("virtio-gpud: connector {:?} not found: {}", id, err); + return; + } + }; let display = &self.displays[connector.driver_data.display_id as usize]; connector.connection = if display.enabled { @@ -325,7 +378,10 @@ impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> { drop(connector); let blob = objects.add_blob(display.edid.clone()); - objects.get_connector(id).unwrap().lock().unwrap().edid = blob; + match objects.get_connector(id) { + Ok(c) => c.lock().unwrap().edid = blob, + Err(err) => log::error!("virtio-gpud: connector {:?} not found on second access: {}", id, err), + } } else { connector.update_from_size(display.width, display.height); } @@ -336,7 +392,13 @@ impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> { futures::executor::block_on(async { let bpp = 32; let fb_size = width as usize * height as usize * bpp / 8; - let sgl = sgl::Sgl::new(fb_size).unwrap(); + let sgl = match sgl::Sgl::new(fb_size) { + Ok(s) => s, + Err(err) => { + log::error!("virtio-gpud: failed to allocate SGL: {err}"); + std::process::exit(1); + } + }; unsafe { core::ptr::write_bytes(sgl.as_ptr() as *mut u8, 255, fb_size); @@ -345,22 +407,43 @@ impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> { let res_id = ResourceId::alloc(); // Create a host resource using `VIRTIO_GPU_CMD_RESOURCE_CREATE_2D`. - let request = Dma::new(ResourceCreate2d::new( + let request = match Dma::new(ResourceCreate2d::new( res_id, ResourceFormat::Bgrx, width, height, - )) - .unwrap(); + )) { + Ok(r) => r, + Err(err) => { + log::error!("virtio-gpud: failed to allocate create 2d DMA: {err}"); + std::process::exit(1); + } + }; - let header = self.send_request(request).await.unwrap(); - assert_eq!(header.ty, CommandTy::RespOkNodata); + let header = match self.send_request(request).await { + Ok(h) => h, + Err(err) => { + log::error!("virtio-gpud: failed to send create 2d: {err}"); + std::process::exit(1); + } + }; + if header.ty != CommandTy::RespOkNodata { + log::error!("virtio-gpud: create 2d returned {:?}", header.ty); + std::process::exit(1); + } // Use the allocated framebuffer from the guest ram, and attach it as backing // storage to the resource just created, using `VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING`. - let mut mem_entries = - unsafe { Dma::zeroed_slice(sgl.chunks().len()).unwrap().assume_init() }; + let mut mem_entries = unsafe { + match Dma::zeroed_slice(sgl.chunks().len()) { + Ok(dma) => dma.assume_init(), + Err(err) => { + log::error!("virtio-gpud: failed to allocate mem entries DMA: {err}"); + std::process::exit(1); + } + } + }; for (entry, chunk) in mem_entries.iter_mut().zip(sgl.chunks().iter()) { *entry = MemEntry { address: chunk.phys as u64, @@ -369,9 +452,20 @@ impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> { }; } - let attach_request = - Dma::new(AttachBacking::new(res_id, mem_entries.len() as u32)).unwrap(); - let header = Dma::new(ControlHeader::default()).unwrap(); + let attach_request = match Dma::new(AttachBacking::new(res_id, mem_entries.len() as u32)) { + Ok(r) => r, + Err(err) => { + log::error!("virtio-gpud: failed to allocate attach backing DMA: {err}"); + std::process::exit(1); + } + }; + let header = match Dma::new(ControlHeader::default()) { + Ok(h) => h, + Err(err) => { + log::error!("virtio-gpud: failed to allocate attach header DMA: {err}"); + std::process::exit(1); + } + }; let command = ChainBuilder::new() .chain(Buffer::new(&attach_request)) .chain(Buffer::new_unsized(&mem_entries)) @@ -379,7 +473,9 @@ impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> { .build(); self.control_queue.send(command).await; - assert_eq!(header.ty, CommandTy::RespOkNodata); + if header.ty != CommandTy::RespOkNodata { + log::error!("virtio-gpud: attach backing returned {:?}", header.ty); + } ( VirtGpuFramebuffer { @@ -427,19 +523,27 @@ impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> { let display_id = connector.driver_data.display_id; let Some(framebuffer) = framebuffer else { - let scanout_request = Dma::new(SetScanout::new( + let scanout_request = match Dma::new(SetScanout::new( display_id, ResourceId::NONE, GpuRect::new(0, 0, 0, 0), - )) - .unwrap(); - let header = self.send_request(scanout_request).await.unwrap(); - assert_eq!(header.ty, CommandTy::RespOkNodata); + )) { + Ok(r) => r, + Err(err) => { + log::error!("virtio-gpud: failed to allocate scanout clear DMA: {err}"); + return Ok(()); + } + }; + match self.send_request(scanout_request).await { + Ok(header) if header.ty == CommandTy::RespOkNodata => {} + Ok(header) => log::error!("virtio-gpud: scanout clear returned {:?}", header.ty), + Err(err) => log::error!("virtio-gpud: failed to send scanout clear: {err}"), + } self.displays[display_id as usize].active_resource = None; return Ok(()); }; - let req = Dma::new(XferToHost2d::new( + let req = match Dma::new(XferToHost2d::new( framebuffer.buffer.id, GpuRect { x: 0, @@ -448,22 +552,38 @@ impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> { height: framebuffer.height, }, 0, - )) - .unwrap(); - let header = self.send_request(req).await.unwrap(); - assert_eq!(header.ty, CommandTy::RespOkNodata); + )) { + Ok(r) => r, + Err(err) => { + log::error!("virtio-gpud: failed to allocate xfer DMA: {err}"); + return Ok(()); + } + }; + match self.send_request(req).await { + Ok(header) if header.ty == CommandTy::RespOkNodata => {} + Ok(header) => log::error!("virtio-gpud: xfer returned {:?}", header.ty), + Err(err) => log::error!("virtio-gpud: failed to send xfer: {err}"), + } // FIXME once we support resizing we also need to check that the current and target size match if self.displays[display_id as usize].active_resource != Some(framebuffer.buffer.id) { - let scanout_request = Dma::new(SetScanout::new( + let scanout_request = match Dma::new(SetScanout::new( display_id, framebuffer.buffer.id, GpuRect::new(0, 0, framebuffer.width, framebuffer.height), - )) - .unwrap(); - let header = self.send_request(scanout_request).await.unwrap(); - assert_eq!(header.ty, CommandTy::RespOkNodata); + )) { + Ok(r) => r, + Err(err) => { + log::error!("virtio-gpud: failed to allocate scanout DMA: {err}"); + return Ok(()); + } + }; + match self.send_request(scanout_request).await { + Ok(header) if header.ty == CommandTy::RespOkNodata => {} + Ok(header) => log::error!("virtio-gpud: scanout returned {:?}", header.ty), + Err(err) => log::error!("virtio-gpud: failed to send scanout: {err}"), + } self.displays[display_id as usize].active_resource = Some(framebuffer.buffer.id); } @@ -472,8 +592,18 @@ impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> { framebuffer.buffer.id, damage.clip(framebuffer.width, framebuffer.height).into(), ); - let header = self.send_request(Dma::new(flush).unwrap()).await.unwrap(); - assert_eq!(header.ty, CommandTy::RespOkNodata); + let flush_dma = match Dma::new(flush) { + Ok(d) => d, + Err(err) => { + log::error!("virtio-gpud: failed to allocate flush DMA: {err}"); + return Ok(()); + } + }; + match self.send_request(flush_dma).await { + Ok(header) if header.ty == CommandTy::RespOkNodata => {} + Ok(header) => log::error!("virtio-gpud: flush returned {:?}", header.ty), + Err(err) => log::error!("virtio-gpud: failed to send flush: {err}"), + } } Ok(()) diff --git a/drivers/hwd/Cargo.toml b/drivers/hwd/Cargo.toml index 3d37cfb3..40b51a1b 100644 --- a/drivers/hwd/Cargo.toml +++ b/drivers/hwd/Cargo.toml @@ -6,6 +6,7 @@ edition = "2018" [dependencies] fdt.workspace = true +libc.workspace = true log.workspace = true ron.workspace = true libredox = { workspace = true, default-features = false, features = ["std", "call"] } diff --git a/drivers/hwd/src/backend/acpi.rs b/drivers/hwd/src/backend/acpi.rs index 3da41d63..12d26261 100644 --- a/drivers/hwd/src/backend/acpi.rs +++ b/drivers/hwd/src/backend/acpi.rs @@ -1,27 +1,36 @@ use amlserde::{AmlSerde, AmlSerdeValue}; -use std::{error::Error, fs, process::Command}; +use std::{error::Error, fs}; use super::Backend; pub struct AcpiBackend { - rxsdt: Vec, + _rxsdt: Vec, } impl Backend for AcpiBackend { fn new() -> Result> { let rxsdt = fs::read("/scheme/kernel.acpi/rxsdt")?; - // 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")); - - Ok(Self { rxsdt }) + Ok(Self { _rxsdt: rxsdt }) } fn probe(&mut self) -> Result<(), Box> { + let mut boot_critical_input_candidates = 0usize; + let mut thc_candidates = 0usize; + let mut non_hid_i2c_candidates = 0usize; + // Read symbols from acpi scheme - let entries = fs::read_dir("/scheme/acpi/symbols")?; + let entries = match fs::read_dir("/scheme/acpi/symbols") { + Ok(entries) => entries, + Err(err) + if err.kind() == std::io::ErrorKind::WouldBlock + || err.raw_os_error() == Some(11) => + { + log::debug!("hwd: ACPI symbols are not ready yet"); + return Ok(()); + } + Err(err) => return Err(Box::new(err)), + }; // TODO: Reimplement with getdents? let symbols_fd = libredox::Fd::open( "/scheme/acpi/symbols", @@ -100,12 +109,103 @@ impl Backend for AcpiBackend { "PNP0C0F" => "PCI interrupt link", "PNP0C50" => "I2C HID", "PNP0F13" => "PS/2 port for PS/2-style mouse", + "80860F41" | "808622C1" => "DesignWare I2C controller", + "AMDI0010" | "AMDI0019" | "AMDI0510" => "AMD laptop I2C controller", + "INT33C2" | "INT33C3" | "INT3432" | "INT3433" | "INTC10EF" => { + "Intel LPSS/SerialIO I2C controller" + } + "INT34C5" | "INTC1055" => "Intel GPIO controller", + "INTC1050" | "INTC1051" | "INTC1080" | "INTC1081" | "INTC1082" => { + "Intel THC companion (QuickI2C/QuickSPI path)" + } + _ if is_elan_touchpad_id(&id) => "ELAN touchpad (I2C/SMBus path)", + _ if is_cypress_touchpad_id(&id) => "Cypress/Trackpad (non-HID I2C path)", + _ if is_synaptics_rmi_id(&id) => "Synaptics RMI touchpad (I2C/SMBus path)", _ => "?", }; log::debug!("{}: {} ({})", name, id, what); + if is_boot_critical_i2c_surface(&id) { + boot_critical_input_candidates += 1; + log::info!("{}: {} is boot-critical for laptop input path", name, id); + } + if is_thc_companion(&id) { + thc_candidates += 1; + log::warn!( + "{}: {} indicates Intel THC path; DMA/report fast-path is not complete yet", + name, + id + ); + } + if is_non_hid_i2c_input_id(&id) { + non_hid_i2c_candidates += 1; + } } } } + + if boot_critical_input_candidates == 0 { + log::warn!( + "hwd: no ACPI boot-critical I2C input candidates found; built-in laptop input may require additional controller/device support" + ); + } else { + log::info!( + "hwd: ACPI input candidates: total={} thc={} non_hid_i2c={}", + boot_critical_input_candidates, + thc_candidates, + non_hid_i2c_candidates + ); + } + Ok(()) } } + +fn is_boot_critical_i2c_surface(id: &str) -> bool { + matches!( + id, + "PNP0C50" + | "ACPI0C50" + | "80860F41" + | "808622C1" + | "AMDI0010" + | "AMDI0019" + | "AMDI0510" + | "INT33C2" + | "INT33C3" + | "INT3432" + | "INT3433" + | "INTC10EF" + | "INT34C5" + | "INTC1055" + | "INTC1050" + | "INTC1051" + | "INTC1080" + | "INTC1081" + | "INTC1082" + ) || is_elan_touchpad_id(id) + || is_cypress_touchpad_id(id) + || is_synaptics_rmi_id(id) +} + +fn is_thc_companion(id: &str) -> bool { + matches!( + id, + "INTC1050" | "INTC1051" | "INTC1080" | "INTC1081" | "INTC1082" + ) +} + +fn is_elan_touchpad_id(id: &str) -> bool { + id.starts_with("ELAN") +} + +fn is_cypress_touchpad_id(id: &str) -> bool { + id.starts_with("CYAP") +} + +fn is_synaptics_rmi_id(id: &str) -> bool { + id.starts_with("SYNA") +} + +fn is_non_hid_i2c_input_id(id: &str) -> bool { + is_elan_touchpad_id(id) || is_cypress_touchpad_id(id) || is_synaptics_rmi_id(id) +} diff --git a/drivers/hwd/src/main.rs b/drivers/hwd/src/main.rs index 79360e34..a0462f51 100644 --- a/drivers/hwd/src/main.rs +++ b/drivers/hwd/src/main.rs @@ -1,3 +1,5 @@ +use std::os::fd::AsRawFd; +use std::os::unix::process::CommandExt; use std::process; mod backend; @@ -37,8 +39,34 @@ 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")); + // Fire-and-forget: daemon::Daemon::spawn blocks until pcid signals readiness, + // but pcid only signals after full PCI enumeration. If enumeration hangs on + // real hardware (unresponsive device, complex AML), hwd deadlocks initfs. + { + match std::io::pipe() { + Ok((_read_end, write_end)) => { + let write_fd: std::os::fd::OwnedFd = write_end.into(); + let raw_fd = write_fd.as_raw_fd(); + let mut cmd = std::process::Command::new("pcid"); + cmd.env("INIT_NOTIFY", raw_fd.to_string()); + unsafe { + cmd.pre_exec(move || { + if libc::fcntl(raw_fd, libc::F_SETFD, 0) == -1 { + return Err(std::io::Error::last_os_error()); + } + Ok(()) + }); + } + match cmd.spawn() { + Ok(_) => {} + Err(err) => log::error!("hwd: failed to spawn pcid: {}", err), + } + } + Err(err) => { + log::error!("hwd: failed to create pcid notification pipe: {}", err); + } + } + } daemon.ready(); diff --git a/drivers/input/ps2d/src/controller.rs b/drivers/input/ps2d/src/controller.rs index d7af4cba..638b7cc1 100644 --- a/drivers/input/ps2d/src/controller.rs +++ b/drivers/input/ps2d/src/controller.rs @@ -97,6 +97,14 @@ enum KeyboardCommandData { const DEFAULT_TIMEOUT: u64 = 50_000; // Reset timeout in microseconds const RESET_TIMEOUT: u64 = 1_000_000; +// Maximum bytes to drain during flush (Linux: I8042_BUFFER_SIZE) +const FLUSH_LIMIT: usize = 4096; +// Controller self-test pass value (Linux: I8042_RET_CTL_TEST) +const SELFTEST_PASS: u8 = 0x55; +// Controller self-test retries (Linux: 5 attempts) +const SELFTEST_RETRIES: usize = 5; +// AUX port test pass value (Linux returns 0x00 on success) +const AUX_TEST_PASS: u8 = 0x00; #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] pub struct Ps2 { @@ -271,6 +279,50 @@ impl Ps2 { } } + /// Drain all pending bytes from the controller output buffer. + /// Borrowed from Linux i8042_flush(): stale firmware/BIOS bytes can be + /// misinterpreted as device responses during initialization. + fn flush(&mut self) -> usize { + let mut count = 0; + while self.status().contains(StatusFlags::OUTPUT_FULL) { + if count >= FLUSH_LIMIT { + warn!("flush: exceeded limit, controller may be stuck"); + break; + } + let data = self.data.read(); + trace!("flush: discarded {:02X}", data); + count += 1; + } + if count > 0 { + debug!("flushed {} stale bytes from controller", count); + } + count + } + + /// Test the AUX (mouse) port via controller command 0xA9. + /// Borrowed from Linux: verifies electrical connectivity before + /// attempting to talk to the mouse. Returns true if the port passed. + fn test_aux_port(&mut self) -> bool { + if let Err(err) = self.command(Command::TestSecond) { + warn!("aux port test command failed: {:?}", err); + return false; + } + match self.read() { + Ok(AUX_TEST_PASS) => { + debug!("aux port test passed"); + true + } + Ok(val) => { + warn!("aux port test failed: {:02X}", val); + false + } + Err(err) => { + warn!("aux port test read timeout: {:?}", err); + false + } + } + } + pub fn init_keyboard(&mut self) -> Result<(), Error> { let mut b; @@ -308,66 +360,125 @@ impl Ps2 { } pub fn init(&mut self) -> Result<(), Error> { + // Linux i8042_controller_check(): verify controller is present by + // flushing any stale data. A stuck output buffer means no controller. + self.flush(); + + // Bare-metal controllers may be slow after firmware handoff. + // Give the controller a moment to finish POST before sending commands. + std::thread::sleep(std::time::Duration::from_millis(50)); + { - // Disable devices - self.command(Command::DisableFirst)?; - self.command(Command::DisableSecond)?; + // Disable both ports first — use retry because the controller + // may still be settling or temporarily unresponsive. + // Failure here is non-fatal: we continue and attempt the rest + // of initialization. A truly absent controller will fail later + // at self-test or keyboard reset. + if let Err(err) = self.retry( + format_args!("disable first port"), + 3, + |x| x.command(Command::DisableFirst), + ) { + warn!("disable first port failed: {:?}", err); + } + if let Err(err) = self.retry( + format_args!("disable second port"), + 3, + |x| x.command(Command::DisableSecond), + ) { + warn!("disable second port failed: {:?}", err); + } } - // Disable clocks, disable interrupts, and disable translate + // Flush again after disabling — firmware may have queued more bytes + self.flush(); + + // Linux i8042_controller_init() step 1: write a known-safe config + // (interrupts off, both ports disabled) so stale config can't cause + // spurious interrupts during the rest of init. { - // Since the default config may have interrupts enabled, and the kernel may eat up - // our data in that case, we will write a config without reading the current one let config = ConfigFlags::POST_PASSED | ConfigFlags::FIRST_DISABLED | ConfigFlags::SECOND_DISABLED; self.set_config(config)?; } - // The keyboard seems to still collect bytes even when we disable - // the port, so we must disable the keyboard too + // Linux i8042_controller_selftest(): retry up to 5 times with delay. + // "On some really fragile systems this does not take the first time." + { + let mut passed = false; + for attempt in 0..SELFTEST_RETRIES { + if let Err(err) = self.command(Command::TestController) { + warn!("self-test command failed (attempt {}): {:?}", attempt + 1, err); + continue; + } + match self.read() { + Ok(SELFTEST_PASS) => { + passed = true; + break; + } + Ok(val) => { + warn!( + "self-test unexpected value {:02X} (attempt {}/{})", + val, + attempt + 1, + SELFTEST_RETRIES + ); + } + Err(err) => { + warn!("self-test read timeout (attempt {}): {:?}", attempt + 1, err); + } + } + // Linux: msleep(50) between retries + std::thread::sleep(std::time::Duration::from_millis(50)); + } + if !passed { + // Linux on x86: "giving up on controller selftest, continuing anyway" + warn!("controller self-test did not pass after {} attempts, continuing", SELFTEST_RETRIES); + } + } + + // Flush any bytes the self-test may have left behind + self.flush(); + + // Linux i8042_controller_init() step 2: set keyboard defaults + // (disable scanning so keyboard doesn't send scancodes during init) self.retry(format_args!("keyboard defaults"), 4, |x| { - // Set defaults and disable scanning let b = x.keyboard_command(KeyboardCommand::SetDefaultsDisable)?; if b != 0xFA { error!("keyboard failed to set defaults: {:02X}", b); return Err(Error::CommandRetry); } - Ok(b) })?; - { - // Perform the self test - self.command(Command::TestController)?; - let r = self.read()?; - if r != 0x55 { - warn!("self test unexpected value: {:02X}", r); - } - } - // Initialize keyboard if let Err(err) = self.init_keyboard() { error!("failed to initialize keyboard: {:?}", err); return Err(err); } - // Enable second device - let enable_mouse = match self.command(Command::EnableSecond) { - Ok(()) => true, - Err(err) => { - error!("failed to initialize mouse: {:?}", err); - false + // Linux: test AUX port (command 0xA9) before enabling. + // Skips mouse init entirely if the port is not electrically present. + let aux_ok = self.test_aux_port(); + + // Enable second device (mouse) only if AUX port tested OK + let enable_mouse = if aux_ok { + match self.command(Command::EnableSecond) { + Ok(()) => true, + Err(err) => { + warn!("failed to enable aux port after test passed: {:?}", err); + false + } } + } else { + info!("skipping mouse init: aux port test did not pass"); + false }; { - // Enable keyboard data reporting - // Use inner function to prevent retries - // Response is ignored since scanning is now on if let Err(err) = self.keyboard_command_inner(KeyboardCommand::EnableReporting as u8) { error!("failed to initialize keyboard reporting: {:?}", err); - //TODO: fix by using interrupts? } } diff --git a/drivers/input/ps2d/src/main.rs b/drivers/input/ps2d/src/main.rs index db17de2a..1ae055e4 100644 --- a/drivers/input/ps2d/src/main.rs +++ b/drivers/input/ps2d/src/main.rs @@ -11,7 +11,7 @@ use std::process; use common::acquire_port_io_rights; use event::{user_data, EventQueue}; -use inputd::ProducerHandle; +use inputd::InputProducer; use crate::state::Ps2d; @@ -31,7 +31,10 @@ fn daemon(daemon: daemon::Daemon) -> ! { acquire_port_io_rights().expect("ps2d: failed to get I/O permission"); - let input = ProducerHandle::new().expect("ps2d: failed to open input producer"); + let keyboard_input = InputProducer::new_named_or_fallback("ps2-keyboard") + .expect("ps2d: failed to open input producer"); + let mouse_input = InputProducer::new_named_or_fallback("ps2-mouse") + .expect("ps2d: failed to open input producer"); user_data! { enum Source { @@ -93,7 +96,7 @@ fn daemon(daemon: daemon::Daemon) -> ! { daemon.ready(); - let mut ps2d = Ps2d::new(input, time_file); + let mut ps2d = Ps2d::new(keyboard_input, mouse_input, time_file); let mut data = [0; 256]; for event in event_queue.map(|e| e.expect("ps2d: failed to get next event").user_data) { diff --git a/drivers/input/ps2d/src/mouse.rs b/drivers/input/ps2d/src/mouse.rs index 9e95ab88..8087c8c4 100644 --- a/drivers/input/ps2d/src/mouse.rs +++ b/drivers/input/ps2d/src/mouse.rs @@ -5,6 +5,11 @@ pub const RESET_RETRIES: usize = 10; pub const RESET_TIMEOUT: Duration = Duration::from_millis(1000); pub const COMMAND_TIMEOUT: Duration = Duration::from_millis(100); +const CMD_ACK: u8 = 0xFA; +const CMD_RESEND: u8 = 0xFE; +const BAT_COMPLETE: u8 = 0xAA; +const BAT_FAIL: u8 = 0xFC; + #[derive(Clone, Copy, Debug)] #[repr(u8)] #[allow(dead_code)] @@ -58,9 +63,11 @@ impl MouseTx { fn handle(&mut self, data: u8, ps2: &mut Ps2) -> Result { if self.write_i < self.write.len() { - if data == 0xFA { + if data == CMD_ACK { self.write_i += 1; self.try_write(ps2)?; + } else if data == CMD_RESEND { + self.try_write(ps2)?; } else { log::error!("unknown mouse response {:02X}", data); return Err(()); @@ -251,25 +258,43 @@ impl MouseState { MouseResult::None } MouseState::Reset => { - if data == 0xFA { - log::debug!("mouse reset ok"); + if data == CMD_ACK { + log::debug!("mouse reset ack"); MouseResult::Timeout(RESET_TIMEOUT) - } else if data == 0xAA { + } else if data == BAT_COMPLETE { log::debug!("BAT completed"); *self = MouseState::Bat; MouseResult::Timeout(COMMAND_TIMEOUT) + } else if data == CMD_RESEND { + // Device asks us to resend the reset command (0xFF). + // Resend WITHOUT incrementing the retry counter — 0xFE is + // a normal protocol response, not a failure. + log::debug!("mouse requests resend during reset, resending 0xFF"); + match ps2.mouse_command_async(MouseCommand::Reset as u8) { + Ok(()) => MouseResult::Timeout(RESET_TIMEOUT), + Err(err) => { + log::error!("failed to resend mouse reset: {:?}", err); + self.reset(ps2) + } + } + } else if data == BAT_FAIL { + log::warn!("mouse BAT failed (0xFC)"); + self.reset(ps2) } else { log::warn!("unknown mouse response {:02X} after reset", data); self.reset(ps2) } } MouseState::Bat => { - if data == MouseId::Base as u8 { - // Enable intellimouse features + if data == CMD_RESEND { + // 0xFE after BAT is unusual — the device may be re-issuing + // BAT. Wait for the next byte (device ID or another BAT). + log::debug!("mouse resend (0xFE) during BAT, waiting"); + MouseResult::Timeout(COMMAND_TIMEOUT) + } else if data == MouseId::Base as u8 { log::debug!("BAT mouse id {:02X} (base)", data); self.identify_touchpad(ps2) } else if data == MouseId::Intellimouse1 as u8 { - // Extra packet already enabled log::debug!("BAT mouse id {:02X} (intellimouse)", data); self.enable_reporting(data, ps2) } else { @@ -320,10 +345,17 @@ impl MouseState { } } MouseState::DeviceId => { - if data == 0xFA { - // Command OK response - //TODO: handle this separately? + if data == CMD_ACK { MouseResult::Timeout(COMMAND_TIMEOUT) + } else if data == CMD_RESEND { + log::debug!("mouse resend during DeviceId, resending GetDeviceId"); + match ps2.mouse_command_async(MouseCommand::GetDeviceId as u8) { + Ok(()) => MouseResult::Timeout(COMMAND_TIMEOUT), + Err(err) => { + log::error!("failed to resend GetDeviceId: {:?}", err); + self.reset(ps2) + } + } } else if data == MouseId::Base as u8 || data == MouseId::Intellimouse1 as u8 { log::debug!("mouse id {:02X}", data); self.enable_reporting(data, ps2) @@ -333,10 +365,28 @@ impl MouseState { } } MouseState::EnableReporting { id } => { - log::debug!("mouse id {:02X} enable reporting {:02X}", id, data); - //TODO: handle response ok/error - *self = MouseState::Streaming { id }; - MouseResult::None + if data == CMD_ACK { + log::debug!("mouse id {:02X} reporting enabled", id); + *self = MouseState::Streaming { id }; + MouseResult::None + } else if data == CMD_RESEND { + log::debug!("mouse resend during EnableReporting, resending 0xF4"); + match ps2.mouse_command_async(MouseCommand::EnableReporting as u8) { + Ok(()) => MouseResult::Timeout(COMMAND_TIMEOUT), + Err(err) => { + log::error!("failed to resend EnableReporting: {:?}", err); + *self = MouseState::Streaming { id }; + MouseResult::None + } + } + } else { + log::warn!( + "unexpected mouse response {:02X} during enable reporting, streaming anyway", + data + ); + *self = MouseState::Streaming { id }; + MouseResult::None + } } MouseState::Streaming { id } => { MouseResult::Packet(data, id == MouseId::Intellimouse1 as u8) diff --git a/drivers/input/ps2d/src/state.rs b/drivers/input/ps2d/src/state.rs index 9018dc6b..da304e05 100644 --- a/drivers/input/ps2d/src/state.rs +++ b/drivers/input/ps2d/src/state.rs @@ -1,4 +1,4 @@ -use inputd::ProducerHandle; +use inputd::InputProducer; use log::{error, warn}; use orbclient::{ButtonEvent, KeyEvent, MouseEvent, MouseRelativeEvent, ScrollEvent}; use std::{ @@ -44,7 +44,8 @@ pub struct Ps2d { ps2: Ps2, vmmouse: bool, vmmouse_relative: bool, - input: ProducerHandle, + keyboard_input: InputProducer, + mouse_input: InputProducer, time_file: File, extended: bool, mouse_x: i32, @@ -59,9 +60,11 @@ pub struct Ps2d { } impl Ps2d { - pub fn new(input: ProducerHandle, time_file: File) -> Self { + pub fn new(keyboard_input: InputProducer, mouse_input: InputProducer, time_file: File) -> Self { let mut ps2 = Ps2::new(); - ps2.init().expect("failed to initialize"); + if let Err(err) = ps2.init() { + log::error!("ps2d: controller init failed: {:?}", err); + } // FIXME add an option for orbital to disable this when an app captures the mouse. let vmmouse_relative = false; @@ -77,7 +80,8 @@ impl Ps2d { ps2, vmmouse, vmmouse_relative, - input, + keyboard_input, + mouse_input, time_file, extended: false, mouse_x: 0, @@ -273,7 +277,7 @@ impl Ps2d { }; if scancode != 0 { - self.input + self.keyboard_input .write_event( KeyEvent { character: '\0', @@ -304,7 +308,7 @@ impl Ps2d { if self.vmmouse_relative { if dx != 0 || dy != 0 { - self.input + self.mouse_input .write_event( MouseRelativeEvent { dx: dx as i32, @@ -320,14 +324,14 @@ impl Ps2d { if x != self.mouse_x || y != self.mouse_y { self.mouse_x = x; self.mouse_y = y; - self.input + self.mouse_input .write_event(MouseEvent { x, y }.to_event()) .expect("ps2d: failed to write mouse event"); } }; if dz != 0 { - self.input + self.mouse_input .write_event( ScrollEvent { x: 0, @@ -348,7 +352,7 @@ impl Ps2d { self.mouse_left = left; self.mouse_middle = middle; self.mouse_right = right; - self.input + self.mouse_input .write_event( ButtonEvent { left, @@ -441,13 +445,13 @@ impl Ps2d { } if dx != 0 || dy != 0 { - self.input + self.mouse_input .write_event(MouseRelativeEvent { dx, dy }.to_event()) .expect("ps2d: failed to write mouse event"); } if dz != 0 { - self.input + self.mouse_input .write_event(ScrollEvent { x: 0, y: dz }.to_event()) .expect("ps2d: failed to write scroll event"); } @@ -462,7 +466,7 @@ impl Ps2d { self.mouse_left = left; self.mouse_middle = middle; self.mouse_right = right; - self.input + self.mouse_input .write_event( ButtonEvent { left, diff --git a/drivers/input/usbhidd/src/main.rs b/drivers/input/usbhidd/src/main.rs index 15c5b778..706c4008 100644 --- a/drivers/input/usbhidd/src/main.rs +++ b/drivers/input/usbhidd/src/main.rs @@ -1,7 +1,7 @@ use anyhow::{Context, Result}; use std::{env, thread, time}; -use inputd::ProducerHandle; +use inputd::InputProducer; use orbclient::KeyEvent as OrbKeyEvent; use rehid::{ report_desc::{ReportTy, REPORT_DESC_TY}, @@ -15,7 +15,7 @@ use xhcid_interface::{ mod reqs; -fn send_key_event(display: &mut ProducerHandle, usage_page: u16, usage: u16, pressed: bool) { +fn send_key_event(display: &mut InputProducer, usage_page: u16, usage: u16, pressed: bool) { let scancode = match usage_page { 0x07 => match usage { 0x04 => orbclient::K_A, @@ -272,7 +272,9 @@ fn main() -> Result<()> { let report_ty = ReportTy::Input; let report_id = 0; - let mut display = ProducerHandle::new().context("Failed to open input socket")?; + let producer_name = format!("usb-{}-if{}", port, interface_num); + let mut display = InputProducer::new_named_or_fallback(&producer_name) + .context("Failed to open input socket")?; let mut endpoint_opt = match endp_desc_opt { Some((endp_num, _endp_desc)) => match handle.open_endpoint(endp_num as u8) { Ok(ok) => Some(ok), diff --git a/drivers/inputd/src/lib.rs b/drivers/inputd/src/lib.rs index b68e8211..f07a411d 100644 --- a/drivers/inputd/src/lib.rs +++ b/drivers/inputd/src/lib.rs @@ -64,25 +64,53 @@ impl ConsumerHandle { let fd = self.0.as_raw_fd(); let written = libredox::call::fpath(fd as usize, &mut buffer)?; - assert!(written <= buffer.len()); - - let mut display_path = PathBuf::from( - std::str::from_utf8(&buffer[..written]) - .expect("init: display path UTF-8 check failed") - .to_owned(), - ); - display_path.set_file_name(format!( - "v2/{}", - display_path.file_name().unwrap().to_str().unwrap() - )); - let display_path = display_path.to_str().unwrap(); + if written > buffer.len() { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "inputd: display path exceeded buffer size", + )); + } + + let path_str = std::str::from_utf8(&buffer[..written]).map_err(|e| { + io::Error::new( + io::ErrorKind::InvalidData, + format!("inputd: display path is not valid UTF-8: {e}"), + ) + })?; + let mut display_path = PathBuf::from(path_str.to_owned()); + + let file_name = display_path + .file_name() + .and_then(|n| n.to_str()) + .ok_or_else(|| { + io::Error::new( + io::ErrorKind::InvalidData, + format!( + "inputd: display path has no valid file name: {}", + display_path.display() + ), + ) + })?; + display_path.set_file_name(format!("v2/{file_name}")); + let display_path_str = display_path.to_str().ok_or_else(|| { + io::Error::new( + io::ErrorKind::InvalidData, + format!( + "inputd: constructed display path is not valid UTF-8: {}", + display_path.display() + ), + ) + })?; let display_file = - libredox::call::open(display_path, (O_CLOEXEC | O_NONBLOCK | O_RDWR) as _, 0) + libredox::call::open(display_path_str, (O_CLOEXEC | O_NONBLOCK | O_RDWR) as _, 0) .map(|socket| unsafe { File::from_raw_fd(socket as RawFd) }) - .unwrap_or_else(|err| { - panic!("failed to open display {}: {}", display_path, err); - }); + .map_err(|err| { + io::Error::new( + io::ErrorKind::Other, + format!("inputd: failed to open display {display_path_str}: {err}"), + ) + })?; Ok(display_file) } @@ -152,8 +180,12 @@ impl DisplayHandle { if nread == 0 { Ok(None) + } else if nread != size_of::() { + Err(io::Error::new( + io::ErrorKind::InvalidData, + format!("inputd: partial vt event read: got {nread}, expected {}", size_of::()), + )) } else { - assert_eq!(nread, size_of::()); Ok(Some(event)) } } @@ -171,13 +203,11 @@ impl ControlHandle { Ok(Self(File::open(path)?)) } - /// Sent to Handle::Display pub fn activate_vt(&mut self, vt: usize) -> io::Result { let cmd = ControlEvent::from(VtActivate { vt }); self.0.write(unsafe { any_as_u8_slice(&cmd) }) } - /// Sent to Handle::Producer pub fn activate_keymap(&mut self, keymap: usize) -> io::Result { let cmd = ControlEvent::from(KeymapActivate { keymap }); self.0.write(unsafe { any_as_u8_slice(&cmd) }) @@ -209,3 +239,195 @@ impl ProducerHandle { Ok(()) } } + +pub struct NamedProducerHandle(File); + +impl NamedProducerHandle { + pub fn new(name: &str) -> io::Result { + let path = format!("/scheme/input/producer/{name}"); + Ok(Self(File::open(path)?)) + } + + pub fn write_event(&mut self, event: orbclient::Event) -> io::Result<()> { + self.0.write(&event)?; + Ok(()) + } +} + +/// Convenience wrapper that tries a named producer first, +/// falling back to the legacy anonymous producer on failure. +pub enum InputProducer { + Named(NamedProducerHandle), + Legacy(ProducerHandle), +} + +impl InputProducer { + /// Open a named producer (`/scheme/input/producer/{name}`). + /// If the named path is unavailable, fall back to the legacy + /// `/scheme/input/producer` path so the driver keeps working on + /// older inputd builds or degraded schemes. + pub fn new_named_or_fallback(name: &str) -> io::Result { + match NamedProducerHandle::new(name) { + Ok(named) => Ok(InputProducer::Named(named)), + Err(named_err) => { + log::debug!( + "inputd: named producer '{}' unavailable ({}), falling back to legacy", + name, + named_err + ); + ProducerHandle::new().map(InputProducer::Legacy) + } + } + } + + /// Open the legacy anonymous producer directly. + pub fn new_legacy() -> io::Result { + ProducerHandle::new().map(InputProducer::Legacy) + } + + pub fn write_event(&mut self, event: orbclient::Event) -> io::Result<()> { + match self { + InputProducer::Named(h) => h.write_event(event), + InputProducer::Legacy(h) => h.write_event(event), + } + } +} + +pub struct DeviceConsumerHandle(File); + +pub enum DeviceConsumerHandleEvent<'a> { + Events(&'a [Event]), +} + +impl DeviceConsumerHandle { + pub fn new(device_name: &str) -> io::Result { + let path = format!("/scheme/input/{device_name}"); + Ok(Self(File::open(path)?)) + } + + pub fn event_handle(&self) -> BorrowedFd<'_> { + self.0.as_fd() + } + + pub fn read_events<'a>( + &self, + events: &'a mut [Event], + ) -> io::Result> { + match read_to_slice(self.0.as_fd(), events) { + Ok(count) => Ok(DeviceConsumerHandleEvent::Events(&events[..count])), + Err(err) => Err(err.into()), + } + } +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct HotplugEventHeader { + pub kind: u32, + pub device_id: u32, + pub name_len: u32, + pub reserved: u32, +} + +#[derive(Debug, Clone)] +pub struct HotplugEvent { + pub kind: u32, + pub device_id: u32, + pub name: String, +} + +pub struct HotplugHandle { + file: File, + partial: Vec, +} + +impl HotplugHandle { + pub fn new() -> io::Result { + let file = File::open("/scheme/input/events")?; + Ok(Self { + file, + partial: Vec::new(), + }) + } + + pub fn event_handle(&self) -> BorrowedFd<'_> { + self.file.as_fd() + } + + pub fn read_event(&mut self) -> io::Result> { + let mut tmp = [0u8; 256]; + match self.file.read(&mut tmp) { + Ok(0) => {} + Ok(n) => self.partial.extend_from_slice(&tmp[..n]), + Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {} + Err(e) => return Err(e), + } + + if self.partial.len() < 16 { + return Ok(None); + } + + let header = HotplugEventHeader { + kind: u32::from_ne_bytes(self.partial[0..4].try_into().map_err(|_| { + io::Error::new(io::ErrorKind::InvalidData, "header parse failed") + })?), + device_id: u32::from_ne_bytes(self.partial[4..8].try_into().map_err(|_| { + io::Error::new(io::ErrorKind::InvalidData, "header parse failed") + })?), + name_len: u32::from_ne_bytes(self.partial[8..12].try_into().map_err(|_| { + io::Error::new(io::ErrorKind::InvalidData, "header parse failed") + })?), + reserved: 0, + }; + + let total_len = 16 + header.name_len as usize; + if self.partial.len() < total_len { + return Ok(None); + } + + let name = String::from_utf8(self.partial[16..total_len].to_vec()).map_err(|e| { + io::Error::new(io::ErrorKind::InvalidData, format!("invalid UTF-8: {e}")) + })?; + + self.partial.drain(..total_len); + + Ok(Some(HotplugEvent { + kind: header.kind, + device_id: header.device_id, + name, + })) + } +} + +pub const RESERVED_DEVICE_NAMES: &[&str] = &[ + "producer", + "consumer", + "consumer_bootlog", + "events", + "handle", + "handle_early", + "control", +]; + +pub struct InputDeviceLister; + +impl InputDeviceLister { + pub fn list() -> io::Result> { + let mut dir = std::fs::read_dir("/scheme/input/")?; + let mut devices = Vec::new(); + loop { + match dir.next() { + Some(Ok(entry)) => { + if let Some(name) = entry.file_name().to_str() { + if !RESERVED_DEVICE_NAMES.contains(&name) { + devices.push(name.to_owned()); + } + } + } + Some(Err(e)) => return Err(e), + None => break, + } + } + Ok(devices) + } +} diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs index 07aa943e..89018568 100644 --- a/drivers/inputd/src/main.rs +++ b/drivers/inputd/src/main.rs @@ -13,7 +13,7 @@ use core::mem::size_of; use std::borrow::Cow; -use std::collections::BTreeSet; +use std::collections::{BTreeMap, BTreeSet}; use std::mem::transmute; use std::ops::ControlFlow; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -26,8 +26,9 @@ use redox_scheme::{CallerCtx, OpenResult, Response, SignalBehavior, Socket}; use orbclient::{Event, EventOption}; use scheme_utils::{Blocking, FpathWriter, HandleMap}; +use syscall::dirent::{DirEntry, DirentBuf, DirentKind}; use syscall::schemev2::NewFdFlags; -use syscall::{Error as SysError, EventFlags, EACCES, EBADF, EEXIST, EINVAL}; +use syscall::{Error as SysError, EventFlags, EACCES, EBADF, EEXIST, EINVAL, ENOENT, ENOTDIR}; pub mod keymap; @@ -35,8 +36,57 @@ use keymap::KeymapKind; use crate::keymap::KeymapData; +const DEVICE_ADD: u32 = 1; +const DEVICE_REMOVE: u32 = 2; + +fn validate_producer_name(name: &str) -> Result<(), SysError> { + if name.is_empty() || name.contains('/') { + return Err(SysError::new(EINVAL)); + } + if inputd::RESERVED_DEVICE_NAMES.contains(&name) { + return Err(SysError::new(EINVAL)); + } + Ok(()) +} + +fn serialize_hotplug(kind: u32, device_id: u32, name: &str) -> Vec { + let name_bytes = name.as_bytes(); + let header = HotplugHeader { + kind, + device_id, + name_len: name_bytes.len() as u32, + _reserved: 0, + }; + let mut out = Vec::with_capacity(16 + name_bytes.len()); + out.extend_from_slice(&header.to_bytes()); + out.extend_from_slice(name_bytes); + out +} + +#[repr(C)] +struct HotplugHeader { + kind: u32, + device_id: u32, + name_len: u32, + _reserved: u32, +} + +impl HotplugHeader { + fn to_bytes(&self) -> [u8; 16] { + let mut buf = [0u8; 16]; + buf[0..4].copy_from_slice(&self.kind.to_ne_bytes()); + buf[4..8].copy_from_slice(&self.device_id.to_ne_bytes()); + buf[8..12].copy_from_slice(&self.name_len.to_ne_bytes()); + buf[12..16].copy_from_slice(&self._reserved.to_ne_bytes()); + buf + } +} + enum Handle { Producer, + NamedProducer { + name: String, + }, Consumer { events: EventFlags, pending: Vec, @@ -46,6 +96,17 @@ enum Handle { notified: bool, vt: usize, }, + DeviceConsumer { + device_name: String, + events: EventFlags, + pending: Vec, + notified: bool, + }, + HotplugEvents { + events: EventFlags, + pending: Vec, + notified: bool, + }, Display { events: EventFlags, pending: Vec, @@ -72,6 +133,9 @@ struct InputScheme { rshift: bool, has_new_events: bool, + + devices: BTreeMap, + next_device_id: AtomicUsize, } impl InputScheme { @@ -90,9 +154,28 @@ impl InputScheme { lshift: false, rshift: false, has_new_events: false, + + devices: BTreeMap::new(), + next_device_id: AtomicUsize::new(1), } } + fn emit_hotplug(&mut self, kind: u32, device_id: u32, name: &str) { + let record = serialize_hotplug(kind, device_id, name); + for handle in self.handles.values_mut() { + if let Handle::HotplugEvents { + pending, + notified, + .. + } = handle + { + pending.extend_from_slice(&record); + *notified = false; + } + } + self.has_new_events = true; + } + fn switch_vt(&mut self, new_active: usize) { if let Some(active_vt) = self.active_vt { if new_active == active_vt { @@ -146,6 +229,43 @@ impl InputScheme { self.active_keymap = KeymapData::new(new_active.into()); } + + fn deliver_to_legacy_consumers(&mut self, buf: &[u8]) { + if let Some(active_vt) = self.active_vt { + for handle in self.handles.values_mut() { + if let Handle::Consumer { + pending, + notified, + vt, + .. + } = handle + { + if *vt != active_vt { + continue; + } + pending.extend_from_slice(buf); + *notified = false; + } + } + } + } + + fn deliver_to_device_consumers(&mut self, name: &str, buf: &[u8]) { + for handle in self.handles.values_mut() { + if let Handle::DeviceConsumer { + device_name, + pending, + notified, + .. + } = handle + { + if device_name == name { + pending.extend_from_slice(buf); + *notified = false; + } + } + } + } } impl SchemeSync for InputScheme { @@ -170,7 +290,23 @@ impl SchemeSync for InputScheme { let command = path_parts.next().ok_or(SysError::new(EINVAL))?; let handle_ty = match command { - "producer" => Handle::Producer, + "producer" => { + if let Some(name) = path_parts.next() { + validate_producer_name(name)?; + if self.devices.contains_key(name) { + return Err(SysError::new(EEXIST)); + } + let device_id = self.next_device_id.fetch_add(1, Ordering::SeqCst) as u32; + self.devices.insert(name.to_owned(), device_id); + let handle = Handle::NamedProducer { + name: name.to_owned(), + }; + self.emit_hotplug(DEVICE_ADD, device_id, name); + handle + } else { + Handle::Producer + } + } "consumer" => { let vt = self.next_vt_id.fetch_add(1, Ordering::Relaxed); self.vts.insert(vt); @@ -253,9 +389,23 @@ impl SchemeSync for InputScheme { } "control" => Handle::Control, - _ => { - log::error!("invalid path '{path}'"); - return Err(SysError::new(EINVAL)); + "events" => Handle::HotplugEvents { + events: EventFlags::empty(), + pending: Vec::new(), + notified: false, + }, + + // dynamic device consumer: must be a currently registered device + name => { + if !self.devices.contains_key(name) { + return Err(SysError::new(ENOENT)); + } + Handle::DeviceConsumer { + device_name: name.to_owned(), + events: EventFlags::empty(), + pending: Vec::new(), + notified: false, + } } }; @@ -274,7 +424,7 @@ impl SchemeSync for InputScheme { let handle = self.handles.get(id)?; if let Handle::Consumer { vt, .. } = handle { - write!(w, "{vt}").unwrap(); + write!(w, "{vt}").map_err(|_| SysError::new(EINVAL))?; Ok(()) } else { Err(SysError::new(EINVAL)) @@ -282,6 +432,50 @@ impl SchemeSync for InputScheme { }) } + fn getdents<'buf>( + &mut self, + id: usize, + mut buf: DirentBuf<&'buf mut [u8]>, + opaque_offset: u64, + ) -> syscall::Result> { + let handle = self.handles.get(id)?; + if !matches!(handle, Handle::SchemeRoot) { + return Err(SysError::new(ENOTDIR)); + } + + let static_entries: &[&str] = &[ + "producer", + "consumer", + "consumer_bootlog", + "events", + "handle", + "handle_early", + "control", + ]; + + let device_names: Vec<&str> = self.devices.keys().map(|s| s.as_str()).collect(); + + let all_entries: Vec<(&str, DirentKind)> = static_entries + .iter() + .map(|&name| (name, DirentKind::Directory)) + .chain(device_names.iter().map(|&name| (name, DirentKind::Unspecified))) + .collect(); + + for (idx, (name, kind)) in all_entries + .iter() + .enumerate() + .skip(opaque_offset as usize) + { + buf.entry(DirEntry { + inode: 0, + next_opaque_id: idx as u64 + 1, + name, + kind: *kind, + })?; + } + Ok(buf) + } + fn read( &mut self, id: usize, @@ -313,6 +507,22 @@ impl SchemeSync for InputScheme { Ok(copy) } + Handle::DeviceConsumer { pending, .. } => { + let copy = core::cmp::min(pending.len(), buf.len()); + for (i, byte) in pending.drain(..copy).enumerate() { + buf[i] = byte; + } + Ok(copy) + } + + Handle::HotplugEvents { pending, .. } => { + let copy = core::cmp::min(pending.len(), buf.len()); + for (i, byte) in pending.drain(..copy).enumerate() { + buf[i] = byte; + } + Ok(copy) + } + Handle::Display { pending, .. } => { if buf.len() % size_of::() == 0 { let copy = core::cmp::min(pending.len(), buf.len() / size_of::()); @@ -334,6 +544,10 @@ impl SchemeSync for InputScheme { log::error!("producer tried to read"); return Err(SysError::new(EINVAL)); } + Handle::NamedProducer { .. } => { + log::error!("named producer tried to read"); + return Err(SysError::new(EINVAL)); + } Handle::Control => { log::error!("control tried to read"); return Err(SysError::new(EINVAL)); @@ -379,11 +593,20 @@ impl SchemeSync for InputScheme { log::error!("consumer tried to write"); return Err(SysError::new(EINVAL)); } + Handle::DeviceConsumer { .. } => { + log::error!("device consumer tried to write"); + return Err(SysError::new(EINVAL)); + } + Handle::HotplugEvents { .. } => { + log::error!("hotplug events tried to write"); + return Err(SysError::new(EINVAL)); + } Handle::Display { .. } => { log::error!("display tried to write"); return Err(SysError::new(EINVAL)); } Handle::Producer => {} + Handle::NamedProducer { .. } => {} Handle::SchemeRoot => return Err(SysError::new(EBADF)), } @@ -397,6 +620,11 @@ impl SchemeSync for InputScheme { buf.len() / size_of::(), ) }); + let producer_name = match self.handles.get(id)? { + Handle::NamedProducer { ref name } => Some(name.clone()), + Handle::Producer => None, + _ => return Err(SysError::new(EBADF)), + }; for i in 0..events.len() { let mut new_active_opt = None; @@ -437,38 +665,21 @@ impl SchemeSync for InputScheme { } } - let handle = self.handles.get_mut(id)?; - assert!(matches!(handle, Handle::Producer)); - - let buf = unsafe { + let serialized = unsafe { core::slice::from_raw_parts( (events.as_ptr()) as *const u8, events.len() * size_of::(), ) }; - if let Some(active_vt) = self.active_vt { - for handle in self.handles.values_mut() { - match handle { - Handle::Consumer { - pending, - notified, - vt, - .. - } => { - if *vt != active_vt { - continue; - } - - pending.extend_from_slice(buf); - *notified = false; - } - _ => continue, - } - } + if let Some(ref name) = producer_name { + self.deliver_to_device_consumers(name, serialized); } - Ok(buf.len()) + // named producers also feed the legacy path; legacy producers only feed legacy + self.deliver_to_legacy_consumers(serialized); + + Ok(serialized.len()) } fn fevent( @@ -487,6 +698,24 @@ impl SchemeSync for InputScheme { *notified = false; Ok(EventFlags::empty()) } + Handle::DeviceConsumer { + ref mut events, + ref mut notified, + .. + } => { + *events = flags; + *notified = false; + Ok(EventFlags::empty()) + } + Handle::HotplugEvents { + ref mut events, + ref mut notified, + .. + } => { + *events = flags; + *notified = false; + Ok(EventFlags::empty()) + } Handle::Display { ref mut events, ref mut notified, @@ -496,7 +725,7 @@ impl SchemeSync for InputScheme { *notified = false; Ok(EventFlags::empty()) } - Handle::Producer | Handle::Control => { + Handle::Producer | Handle::NamedProducer { .. } | Handle::Control => { log::error!("producer or control tried to use an event queue"); Err(SysError::new(EINVAL)) } @@ -505,8 +734,8 @@ impl SchemeSync for InputScheme { } fn on_close(&mut self, id: usize) { - match self.handles.remove(id).unwrap() { - Handle::Consumer { vt, .. } => { + match self.handles.remove(id) { + Some(Handle::Consumer { vt, .. }) => { self.vts.remove(&vt); if self.active_vt == Some(vt) { if let Some(&new_vt) = self.vts.last() { @@ -516,7 +745,15 @@ impl SchemeSync for InputScheme { } } } - _ => {} + Some(Handle::NamedProducer { name, .. }) => { + if let Some(device_id) = self.devices.remove(&name) { + self.emit_hotplug(DEVICE_REMOVE, device_id, &name); + } + } + Some(_) => {} + None => { + log::warn!("inputd: on_close called with unknown handle id {id}"); + } } } } @@ -564,6 +801,39 @@ fn deamon(daemon: daemon::SchemeDaemon) -> anyhow::Result<()> { *notified = true; } + Handle::DeviceConsumer { + events, + pending, + ref mut notified, + .. + } => { + if pending.is_empty() || *notified || !events.contains(EventFlags::EVENT_READ) { + continue; + } + + socket_file.write_response( + Response::post_fevent(*id, EventFlags::EVENT_READ.bits()), + SignalBehavior::Restart, + )?; + + *notified = true; + } + Handle::HotplugEvents { + events, + pending, + ref mut notified, + } => { + if pending.is_empty() || *notified || !events.contains(EventFlags::EVENT_READ) { + continue; + } + + socket_file.write_response( + Response::post_fevent(*id, EventFlags::EVENT_READ.bits()), + SignalBehavior::Restart, + )?; + + *notified = true; + } Handle::Display { events, pending, @@ -589,8 +859,11 @@ fn deamon(daemon: daemon::SchemeDaemon) -> anyhow::Result<()> { } fn daemon_runner(daemon: daemon::SchemeDaemon) -> ! { - deamon(daemon).unwrap(); - unreachable!(); + if let Err(err) = deamon(daemon) { + log::error!("inputd: scheme daemon failed: {err}"); + std::process::exit(1); + } + unreachable!() } const HELP: &str = r#" @@ -608,13 +881,26 @@ fn main() { match val.as_ref() { // Activates a VT. "-A" => { - let vt = args.next().unwrap().parse::().unwrap(); + let vt_str = args.next().unwrap_or_else(|| { + eprintln!("inputd: -A requires a VT number argument"); + std::process::exit(1); + }); + let vt = vt_str.parse::().unwrap_or_else(|_| { + eprintln!("inputd: invalid VT number: {vt_str}"); + std::process::exit(1); + }); - let mut handle = - inputd::ControlHandle::new().expect("inputd: failed to open control handle"); - handle - .activate_vt(vt) - .expect("inputd: failed to activate VT"); + let mut handle = match inputd::ControlHandle::new() { + Ok(h) => h, + Err(e) => { + eprintln!("inputd: failed to open control handle: {e}"); + std::process::exit(1); + } + }; + if let Err(e) = handle.activate_vt(vt) { + eprintln!("inputd: failed to activate VT {vt}: {e}"); + std::process::exit(1); + } } // Activates a keymap. "-K" => { @@ -630,11 +916,17 @@ fn main() { std::process::exit(1); }); - let mut handle = - inputd::ControlHandle::new().expect("inputd: failed to open control handle"); - handle - .activate_keymap(vt as usize) - .expect("inputd: failed to activate keymap"); + let mut handle = match inputd::ControlHandle::new() { + Ok(h) => h, + Err(e) => { + eprintln!("inputd: failed to open control handle: {e}"); + std::process::exit(1); + } + }; + if let Err(e) = handle.activate_keymap(vt as usize) { + eprintln!("inputd: failed to activate keymap: {e}"); + std::process::exit(1); + } } // List available keymaps "--keymaps" => { @@ -647,7 +939,10 @@ fn main() { println!("{}", HELP); } - _ => panic!("inputd: invalid argument: {}", val), + _ => { + eprintln!("inputd: invalid argument: {val}"); + std::process::exit(1); + } } } else { common::setup_logging( diff --git a/drivers/net/e1000d/src/device.rs b/drivers/net/e1000d/src/device.rs index 4c518f30..0e42d72b 100644 --- a/drivers/net/e1000d/src/device.rs +++ b/drivers/net/e1000d/src/device.rs @@ -3,7 +3,7 @@ use std::{cmp, mem, ptr, slice, thread, time}; use driver_network::NetworkAdapter; -use syscall::error::Result; +use syscall::error::{Error, Result, EIO}; use common::dma::Dma; @@ -207,11 +207,10 @@ impl NetworkAdapter for Intel8254x { } fn dma_array() -> Result<[Dma; N]> { - Ok((0..N) + let vec: Vec> = (0..N) .map(|_| Ok(unsafe { Dma::zeroed()?.assume_init() })) - .collect::>>()? - .try_into() - .unwrap_or_else(|_| unreachable!())) + .collect::>>()?; + vec.try_into().map_err(|_| Error::new(EIO)) } impl Intel8254x { pub unsafe fn new(base: usize) -> Result { diff --git a/drivers/net/e1000d/src/main.rs b/drivers/net/e1000d/src/main.rs index 373ea9b3..8ff57b33 100644 --- a/drivers/net/e1000d/src/main.rs +++ b/drivers/net/e1000d/src/main.rs @@ -1,5 +1,6 @@ use std::io::{Read, Write}; use std::os::unix::io::AsRawFd; +use std::process; use driver_network::NetworkScheme; use event::{user_data, EventQueue}; @@ -25,10 +26,13 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { common::file_level(), ); - let irq = pci_config - .func - .legacy_interrupt_line - .expect("e1000d: no legacy interrupts supported"); + let irq = match pci_config.func.legacy_interrupt_line { + Some(irq) => irq, + None => { + log::error!("e1000d: no legacy interrupts supported"); + process::exit(1); + } + }; log::info!("E1000 {}", pci_config.func.display()); @@ -38,7 +42,10 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { let mut scheme = NetworkScheme::new( move || unsafe { - device::Intel8254x::new(address).expect("e1000d: failed to allocate device") + device::Intel8254x::new(address).unwrap_or_else(|err| { + log::error!("e1000d: failed to allocate device: {err}"); + process::exit(1); + }) }, daemon, format!("network.{name}"), @@ -51,7 +58,10 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { } } - let event_queue = EventQueue::::new().expect("e1000d: failed to create event queue"); + let mut event_queue = EventQueue::::new().unwrap_or_else(|err| { + log::error!("e1000d: failed to create event queue: {err}"); + process::exit(1); + }); event_queue .subscribe( @@ -59,32 +69,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}"); + 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"); - - scheme.tick().unwrap(); + .unwrap_or_else(|err| { + log::error!("e1000d: failed to subscribe to scheme fd: {err}"); + process::exit(1); + }); + + libredox::call::setrens(0, 0).unwrap_or_else(|err| { + log::error!("e1000d: failed to enter null namespace: {err}"); + process::exit(1); + }); + + if let Err(err) = scheme.tick() { + log::error!("e1000d: failed initial scheme tick: {err}"); + process::exit(1); + } - for event in event_queue.map(|e| e.expect("e1000d: failed to get event")) { + loop { + let event = match event_queue.next() { + Some(Ok(event)) => event, + Some(Err(err)) => { + log::error!("e1000d: failed to get event: {err}"); + continue; + } + None => 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: {err}"); + continue; + } 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 write IRQ: {err}"); + continue; + } + + if let Err(err) = scheme.tick() { + log::error!("e1000d: failed to handle IRQ: {err}"); + } + } + } + Source::Scheme => { + if let Err(err) = scheme.tick() { + log::error!("e1000d: failed to handle scheme op: {err}"); } } - Source::Scheme => scheme.tick().expect("e1000d: failed to handle scheme op"), } } - unreachable!() + + process::exit(0); } diff --git a/drivers/net/ixgbed/Cargo.toml b/drivers/net/ixgbed/Cargo.toml index d97ff398..fcaf4b19 100644 --- a/drivers/net/ixgbed/Cargo.toml +++ b/drivers/net/ixgbed/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [dependencies] bitflags.workspace = true libredox.workspace = true +log.workspace = true redox_event.workspace = true redox_syscall.workspace = true diff --git a/drivers/net/ixgbed/src/device.rs b/drivers/net/ixgbed/src/device.rs index 0d59b46d..fc7c009f 100644 --- a/drivers/net/ixgbed/src/device.rs +++ b/drivers/net/ixgbed/src/device.rs @@ -3,7 +3,7 @@ use std::time::{Duration, Instant}; use std::{cmp, mem, ptr, slice, thread}; use driver_network::NetworkAdapter; -use syscall::error::Result; +use syscall::error::{Error, Result, EIO}; use common::dma::Dma; @@ -45,7 +45,12 @@ impl NetworkAdapter for Intel8259x { if (status & IXGBE_RXDADV_STAT_DD) != 0 { if (status & IXGBE_RXDADV_STAT_EOP) == 0 { - panic!("increase buffer size or decrease MTU") + log::error!("ixgbed: received fragmented packet, skipping descriptor"); + desc.read.pkt_addr = self.receive_buffer[self.receive_index].physical() as u64; + desc.read.hdr_addr = 0; + self.write_reg(IXGBE_RDT(0), self.receive_index as u32); + self.receive_index = wrap_ring(self.receive_index, self.receive_ring.len()); + return Ok(None); } let data = unsafe { @@ -132,13 +137,25 @@ impl Intel8259x { .map(|_| Ok(unsafe { Dma::zeroed()?.assume_init() })) .collect::>>()? .try_into() - .unwrap_or_else(|_| unreachable!()), + .map_err(|v: Vec<_>| { + log::error!( + "ixgbed: internal error: DMA buffer array conversion failed (got {} items, expected 32)", + v.len() + ); + Error::new(EIO) + })?, receive_ring: unsafe { Dma::zeroed()?.assume_init() }, transmit_buffer: (0..32) .map(|_| Ok(unsafe { Dma::zeroed()?.assume_init() })) .collect::>>()? .try_into() - .unwrap_or_else(|_| unreachable!()), + .map_err(|v: Vec<_>| { + log::error!( + "ixgbed: internal error: DMA buffer array conversion failed (got {} items, expected 32)", + v.len() + ); + Error::new(EIO) + })?, receive_index: 0, transmit_ring: unsafe { Dma::zeroed()?.assume_init() }, transmit_ring_free: 32, @@ -166,7 +183,7 @@ impl Intel8259x { if (status & IXGBE_RXDADV_STAT_DD) != 0 { if (status & IXGBE_RXDADV_STAT_EOP) == 0 { - panic!("increase buffer size or decrease MTU") + log::error!("ixgbed: received fragmented packet, buffer too small"); } return unsafe { desc.wb.upper.length as usize }; @@ -205,13 +222,8 @@ impl Intel8259x { self.mac_address = mac; } - /// Returns the register at `self.base` + `register`. - /// - /// # Panics - /// - /// Panics if `self.base` + `register` does not belong to the mapped memory of the PCIe device. fn read_reg(&self, register: u32) -> u32 { - assert!( + debug_assert!( register as usize <= self.size - 4 as usize, "MMIO access out of bounds" ); @@ -219,13 +231,8 @@ impl Intel8259x { unsafe { ptr::read_volatile((self.base + register as usize) as *mut u32) } } - /// Sets the register at `self.base` + `register`. - /// - /// # Panics - /// - /// Panics if `self.base` + `register` does not belong to the mapped memory of the PCIe device. fn write_reg(&self, register: u32, data: u32) -> u32 { - assert!( + debug_assert!( register as usize <= self.size - 4 as usize, "MMIO access out of bounds" ); @@ -279,7 +286,7 @@ impl Intel8259x { let mac = self.get_mac_addr(); - println!( + log::info!( " - MAC: {:>02X}:{:>02X}:{:>02X}:{:>02X}:{:>02X}:{:>02X}", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] ); @@ -438,13 +445,11 @@ impl Intel8259x { } /// Sets the rx queues` descriptors and enables the queues. - /// - /// # Panics - /// Panics if length of `self.receive_ring` is not a power of 2. fn start_rx_queue(&mut self, queue_id: u16) { - if self.receive_ring.len() & (self.receive_ring.len() - 1) != 0 { - panic!("number of receive queue entries must be a power of 2"); - } + debug_assert!( + self.receive_ring.len() & (self.receive_ring.len() - 1) == 0, + "number of receive queue entries must be a power of 2" + ); for i in 0..self.receive_ring.len() { self.receive_ring[i].read.pkt_addr = self.receive_buffer[i].physical() as u64; @@ -466,13 +471,11 @@ impl Intel8259x { } /// Enables the tx queues. - /// - /// # Panics - /// Panics if length of `self.transmit_ring` is not a power of 2. fn start_tx_queue(&mut self, queue_id: u16) { - if self.transmit_ring.len() & (self.transmit_ring.len() - 1) != 0 { - panic!("number of receive queue entries must be a power of 2"); - } + debug_assert!( + self.transmit_ring.len() & (self.transmit_ring.len() - 1) == 0, + "number of transmit queue entries must be a power of 2" + ); for i in 0..self.transmit_ring.len() { self.transmit_ring[i].read.buffer_addr = self.transmit_buffer[i].physical() as u64; @@ -506,14 +509,14 @@ impl Intel8259x { /// Waits for the link to come up. fn wait_for_link(&self) { - println!(" - waiting for link"); + log::info!(" - waiting for link"); let time = Instant::now(); let mut speed = self.get_link_speed(); while speed == 0 && time.elapsed().as_secs() < 10 { thread::sleep(Duration::from_millis(100)); speed = self.get_link_speed(); } - println!(" - link speed is {} Mbit/s", self.get_link_speed()); + log::info!(" - link speed is {} Mbit/s", self.get_link_speed()); } /// Enables or disables promisc mode of this device. diff --git a/drivers/net/ixgbed/src/main.rs b/drivers/net/ixgbed/src/main.rs index 4a6ce74d..855d339d 100644 --- a/drivers/net/ixgbed/src/main.rs +++ b/drivers/net/ixgbed/src/main.rs @@ -1,5 +1,6 @@ use std::io::{Read, Write}; use std::os::unix::io::AsRawFd; +use std::process; use driver_network::NetworkScheme; use event::{user_data, EventQueue}; @@ -19,12 +20,23 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { let mut name = pci_config.func.name(); name.push_str("_ixgbe"); - let irq = pci_config - .func - .legacy_interrupt_line - .expect("ixgbed: no legacy interrupts supported"); + common::setup_logging( + "net", + "pci", + &name, + common::output_level(), + common::file_level(), + ); + + let irq = match pci_config.func.legacy_interrupt_line { + Some(irq) => irq, + None => { + log::error!("ixgbed: no legacy interrupts supported"); + process::exit(1); + } + }; - println!(" + IXGBE {}", pci_config.func.display()); + log::info!("IXGBE {}", pci_config.func.display()); let mut irq_file = irq.irq_handle("ixgbed"); @@ -34,8 +46,10 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { let mut scheme = NetworkScheme::new( move || { - device::Intel8259x::new(address as usize, size) - .expect("ixgbed: failed to allocate device") + device::Intel8259x::new(address as usize, size).unwrap_or_else(|err| { + log::error!("ixgbed: failed to allocate device: {err}"); + process::exit(1); + }) }, daemon, format!("network.{name}"), @@ -48,41 +62,77 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { } } - let event_queue = EventQueue::::new().expect("ixgbed: Could not create event queue."); + let mut event_queue = EventQueue::::new().unwrap_or_else(|err| { + log::error!("ixgbed: failed to create event queue: {err}"); + process::exit(1); + }); + event_queue .subscribe( irq_file.as_raw_fd() as usize, Source::Irq, event::EventFlags::READ, ) - .unwrap(); + .unwrap_or_else(|err| { + log::error!("ixgbed: failed to subscribe to IRQ fd: {err}"); + 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| { + log::error!("ixgbed: failed to subscribe to scheme fd: {err}"); + process::exit(1); + }); + + libredox::call::setrens(0, 0).unwrap_or_else(|err| { + log::error!("ixgbed: failed to enter null namespace: {err}"); + process::exit(1); + }); + + if let Err(err) = scheme.tick() { + log::error!("ixgbed: failed initial scheme tick: {err}"); + process::exit(1); + } - scheme.tick().unwrap(); + loop { + let event = match event_queue.next() { + Some(Ok(event)) => event, + Some(Err(err)) => { + log::error!("ixgbed: failed to get event: {err}"); + continue; + } + None => break, + }; - for event in event_queue.map(|e| e.expect("ixgbed: failed to get next event")) { 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!("ixgbed: failed to read IRQ: {err}"); + continue; + } if scheme.adapter().irq() { - irq_file.write(&mut irq).unwrap(); - - scheme.tick().unwrap(); + if let Err(err) = irq_file.write(&mut irq) { + log::error!("ixgbed: failed to write IRQ: {err}"); + continue; + } + + if let Err(err) = scheme.tick() { + log::error!("ixgbed: failed to handle IRQ: {err}"); + } } } Source::Scheme => { - scheme.tick().unwrap(); + if let Err(err) = scheme.tick() { + log::error!("ixgbed: failed to handle scheme op: {err}"); + } } } } - unreachable!() + + process::exit(0); } diff --git a/drivers/net/rtl8139d/src/device.rs b/drivers/net/rtl8139d/src/device.rs index 37167ee2..d7428132 100644 --- a/drivers/net/rtl8139d/src/device.rs +++ b/drivers/net/rtl8139d/src/device.rs @@ -215,7 +215,7 @@ impl Rtl8139 { .map(|_| Ok(Dma::zeroed()?.assume_init())) .collect::>>()? .try_into() - .unwrap_or_else(|_| unreachable!()), + .map_err(|_| Error::new(EIO))?, transmit_i: 0, mac_address: [0; 6], }; diff --git a/drivers/net/rtl8139d/src/main.rs b/drivers/net/rtl8139d/src/main.rs index d470e814..64335a23 100644 --- a/drivers/net/rtl8139d/src/main.rs +++ b/drivers/net/rtl8139d/src/main.rs @@ -1,5 +1,6 @@ use std::io::{Read, Write}; use std::os::unix::io::AsRawFd; +use std::process; use driver_network::NetworkScheme; use event::{user_data, EventQueue}; @@ -32,7 +33,8 @@ fn map_bar(pcid_handle: &mut PciFunctionHandle) -> *mut u8 { other => log::warn!("BAR {} is {:?} instead of memory BAR", barnum, other), } } - panic!("rtl8139d: failed to find BAR"); + log::error!("rtl8139d: failed to find BAR"); + process::exit(1); } fn main() { @@ -61,7 +63,10 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { let mut scheme = NetworkScheme::new( move || unsafe { - device::Rtl8139::new(bar as usize).expect("rtl8139d: failed to allocate device") + device::Rtl8139::new(bar as usize).unwrap_or_else(|err| { + log::error!("rtl8139d: failed to allocate device: {err}"); + process::exit(1); + }) }, daemon, format!("network.{name}"), @@ -74,42 +79,76 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { } } - let event_queue = EventQueue::::new().expect("rtl8139d: Could not create event queue."); + let mut event_queue = EventQueue::::new().unwrap_or_else(|err| { + log::error!("rtl8139d: failed to create event queue: {err}"); + process::exit(1); + }); event_queue .subscribe( irq_file.irq_handle().as_raw_fd() as usize, Source::Irq, event::EventFlags::READ, ) - .unwrap(); + .unwrap_or_else(|err| { + log::error!("rtl8139d: failed to subscribe to IRQ fd: {err}"); + process::exit(1); + }); event_queue .subscribe( scheme.event_handle().raw(), Source::Scheme, event::EventFlags::READ, ) - .unwrap(); - - libredox::call::setrens(0, 0).expect("rtl8139d: failed to enter null namespace"); - - scheme.tick().unwrap(); + .unwrap_or_else(|err| { + log::error!("rtl8139d: failed to subscribe to scheme fd: {err}"); + process::exit(1); + }); + + libredox::call::setrens(0, 0).unwrap_or_else(|err| { + log::error!("rtl8139d: failed to enter null namespace: {err}"); + process::exit(1); + }); + + if let Err(err) = scheme.tick() { + log::error!("rtl8139d: failed initial scheme tick: {err}"); + process::exit(1); + } - for event in event_queue.map(|e| e.expect("rtl8139d: failed to get next event")) { + loop { + let event = match event_queue.next() { + Some(Ok(event)) => event, + Some(Err(err)) => { + log::error!("rtl8139d: failed to get next event: {err}"); + continue; + } + None => break, + }; match event.user_data { Source::Irq => { let mut irq = [0; 8]; - irq_file.irq_handle().read(&mut irq).unwrap(); + if let Err(err) = irq_file.irq_handle().read(&mut irq) { + log::error!("rtl8139d: failed to read IRQ: {err}"); + continue; + } //TODO: This may be causing spurious interrupts if unsafe { scheme.adapter_mut().irq() } { - irq_file.irq_handle().write(&mut irq).unwrap(); - - scheme.tick().unwrap(); + if let Err(err) = irq_file.irq_handle().write(&mut irq) { + log::error!("rtl8139d: failed to write IRQ: {err}"); + continue; + } + + if let Err(err) = scheme.tick() { + log::error!("rtl8139d: failed to handle IRQ tick: {err}"); + } } } Source::Scheme => { - scheme.tick().unwrap(); + if let Err(err) = scheme.tick() { + log::error!("rtl8139d: failed to handle scheme op: {err}"); + } } } } - unreachable!() + + process::exit(0); } diff --git a/drivers/net/rtl8168d/src/device.rs b/drivers/net/rtl8168d/src/device.rs index ae545ec4..7229a52d 100644 --- a/drivers/net/rtl8168d/src/device.rs +++ b/drivers/net/rtl8168d/src/device.rs @@ -177,7 +177,7 @@ impl Rtl8168 { .map(|_| Ok(Dma::zeroed()?.assume_init())) .collect::>>()? .try_into() - .unwrap_or_else(|_| unreachable!()), + .map_err(|_| Error::new(EIO))?, receive_ring: Dma::zeroed()?.assume_init(), receive_i: 0, @@ -185,7 +185,7 @@ impl Rtl8168 { .map(|_| Ok(Dma::zeroed()?.assume_init())) .collect::>>()? .try_into() - .unwrap_or_else(|_| unreachable!()), + .map_err(|_| Error::new(EIO))?, transmit_ring: Dma::zeroed()?.assume_init(), transmit_i: 0, transmit_buffer_h: [Dma::zeroed()?.assume_init()], diff --git a/drivers/net/rtl8168d/src/main.rs b/drivers/net/rtl8168d/src/main.rs index 1d9963a3..bd2fcb1a 100644 --- a/drivers/net/rtl8168d/src/main.rs +++ b/drivers/net/rtl8168d/src/main.rs @@ -1,5 +1,6 @@ use std::io::{Read, Write}; use std::os::unix::io::AsRawFd; +use std::process; use driver_network::NetworkScheme; use event::{user_data, EventQueue}; @@ -32,7 +33,8 @@ fn map_bar(pcid_handle: &mut PciFunctionHandle) -> *mut u8 { other => log::warn!("BAR {} is {:?} instead of memory BAR", barnum, other), } } - panic!("rtl8168d: failed to find BAR"); + log::error!("rtl8168d: failed to find BAR"); + process::exit(1); } fn main() { @@ -61,7 +63,10 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { let mut scheme = NetworkScheme::new( move || unsafe { - device::Rtl8168::new(bar as usize).expect("rtl8168d: failed to allocate device") + device::Rtl8168::new(bar as usize).unwrap_or_else(|err| { + log::error!("rtl8168d: failed to allocate device: {err}"); + process::exit(1); + }) }, daemon, format!("network.{name}"), @@ -74,42 +79,76 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { } } - let event_queue = EventQueue::::new().expect("rtl8168d: Could not create event queue."); + let mut event_queue = EventQueue::::new().unwrap_or_else(|err| { + log::error!("rtl8168d: failed to create event queue: {err}"); + process::exit(1); + }); event_queue .subscribe( irq_file.irq_handle().as_raw_fd() as usize, Source::Irq, event::EventFlags::READ, ) - .unwrap(); + .unwrap_or_else(|err| { + log::error!("rtl8168d: failed to subscribe to IRQ fd: {err}"); + process::exit(1); + }); event_queue .subscribe( scheme.event_handle().raw(), Source::Scheme, event::EventFlags::READ, ) - .unwrap(); - - libredox::call::setrens(0, 0).expect("rtl8168d: failed to enter null namespace"); - - scheme.tick().unwrap(); + .unwrap_or_else(|err| { + log::error!("rtl8168d: failed to subscribe to scheme fd: {err}"); + process::exit(1); + }); + + libredox::call::setrens(0, 0).unwrap_or_else(|err| { + log::error!("rtl8168d: failed to enter null namespace: {err}"); + process::exit(1); + }); + + if let Err(err) = scheme.tick() { + log::error!("rtl8168d: failed initial scheme tick: {err}"); + process::exit(1); + } - for event in event_queue.map(|e| e.expect("rtl8168d: failed to get next event")) { + loop { + let event = match event_queue.next() { + Some(Ok(event)) => event, + Some(Err(err)) => { + log::error!("rtl8168d: failed to get next event: {err}"); + continue; + } + None => break, + }; match event.user_data { Source::Irq => { let mut irq = [0; 8]; - irq_file.irq_handle().read(&mut irq).unwrap(); + if let Err(err) = irq_file.irq_handle().read(&mut irq) { + log::error!("rtl8168d: failed to read IRQ: {err}"); + continue; + } //TODO: This may be causing spurious interrupts if unsafe { scheme.adapter_mut().irq() } { - irq_file.irq_handle().write(&mut irq).unwrap(); - - scheme.tick().unwrap(); + if let Err(err) = irq_file.irq_handle().write(&mut irq) { + log::error!("rtl8168d: failed to write IRQ: {err}"); + continue; + } + + if let Err(err) = scheme.tick() { + log::error!("rtl8168d: failed to handle IRQ tick: {err}"); + } } } Source::Scheme => { - scheme.tick().unwrap(); + if let Err(err) = scheme.tick() { + log::error!("rtl8168d: failed to handle scheme op: {err}"); + } } } } - unreachable!() + + process::exit(0); } diff --git a/drivers/net/virtio-netd/src/main.rs b/drivers/net/virtio-netd/src/main.rs index 17d168ef..adbd1086 100644 --- a/drivers/net/virtio-netd/src/main.rs +++ b/drivers/net/virtio-netd/src/main.rs @@ -3,6 +3,7 @@ mod scheme; use std::fs::File; use std::io::{Read, Write}; use std::mem; +use std::process; use driver_network::NetworkScheme; use pcid_interface::PciFunctionHandle; @@ -31,8 +32,11 @@ fn main() { } fn daemon_runner(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { - deamon(daemon, pcid_handle).unwrap(); - unreachable!(); + deamon(daemon, pcid_handle).unwrap_or_else(|err| { + log::error!("virtio-netd: daemon failed: {err}"); + process::exit(1); + }); + process::exit(0); } fn deamon( @@ -52,7 +56,10 @@ 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 { + log::error!("virtio-netd: unexpected device ID {:#06x}, expected 0x1000", pci_config.func.full_device_id.device_id); + process::exit(1); + } log::info!("virtio-net: initiating startup sequence :^)"); let device = virtio_core::probe_device(&mut pcid_handle)?; @@ -84,7 +91,8 @@ fn deamon( device.transport.ack_driver_feature(VIRTIO_NET_F_MAC); mac } else { - unimplemented!() + log::error!("virtio-netd: device does not support MAC feature"); + return Err("virtio-netd: VIRTIO_NET_F_MAC not supported".into()); }; device.transport.finalize_features(); @@ -126,12 +134,23 @@ fn deamon( data: 0, })?; - libredox::call::setrens(0, 0).expect("virtio-netd: failed to enter null namespace"); + libredox::call::setrens(0, 0).unwrap_or_else(|err| { + log::error!("virtio-netd: failed to enter null namespace: {err}"); + process::exit(1); + }); - scheme.tick()?; + if let Err(err) = scheme.tick() { + log::error!("virtio-netd: failed initial scheme tick: {err}"); + process::exit(1); + } loop { - event_queue.read(&mut [0; mem::size_of::()])?; // Wait for event - scheme.tick()?; + if let Err(err) = event_queue.read(&mut [0; mem::size_of::()]) { + log::error!("virtio-netd: failed to read event: {err}"); + continue; + } + if let Err(err) = scheme.tick() { + log::error!("virtio-netd: failed to handle scheme event: {err}"); + } } } diff --git a/drivers/net/virtio-netd/src/scheme.rs b/drivers/net/virtio-netd/src/scheme.rs index 59b3b93e..d0acb2ba 100644 --- a/drivers/net/virtio-netd/src/scheme.rs +++ b/drivers/net/virtio-netd/src/scheme.rs @@ -27,11 +27,16 @@ impl<'a> VirtioNet<'a> { // Populate all of the `rx_queue` with buffers to maximize performence. let mut rx_buffers = vec![]; for i in 0..(rx.descriptor_len() as usize) { - rx_buffers.push(unsafe { - Dma::<[u8]>::zeroed_slice(MAX_BUFFER_LEN) - .unwrap() - .assume_init() - }); + let buf = unsafe { + match Dma::<[u8]>::zeroed_slice(MAX_BUFFER_LEN) { + Ok(dma) => dma.assume_init(), + Err(err) => { + log::error!("virtio-netd: failed to allocate rx buffer: {err}"); + continue; + } + } + }; + rx_buffers.push(buf); let chain = ChainBuilder::new() .chain(Buffer::new_unsized(&rx_buffers[i]).flags(DescriptorFlags::WRITE_ONLY)) diff --git a/drivers/pcid-spawner/src/main.rs b/drivers/pcid-spawner/src/main.rs index a968f4d4..bfff05c3 100644 --- a/drivers/pcid-spawner/src/main.rs +++ b/drivers/pcid-spawner/src/main.rs @@ -1,11 +1,41 @@ +use std::env; use std::fs; use std::process::Command; +use std::thread; +use std::time::Duration; use anyhow::{anyhow, Context, Result}; use pcid_interface::config::Config; use pcid_interface::PciFunctionHandle; +fn strict_usb_boot() -> bool { + matches!( + env::var("REDBEAR_STRICT_USB_BOOT") + .ok() + .as_deref() + .map(str::to_ascii_lowercase) + .as_deref(), + Some("1" | "true" | "yes" | "on") + ) +} + +fn should_detach_in_initfs(initfs: bool, class: u8, subclass: u8, strict_usb_boot: bool) -> bool { + if !initfs { + return false; + } + + if class == 0x01 { + return false; + } + + if strict_usb_boot && class == 0x0C && subclass == 0x03 { + return false; + } + + true +} + fn main() -> Result<()> { let mut args = pico_args::Arguments::from_env(); let initfs = args.contains("--initfs"); @@ -30,12 +59,33 @@ fn main() -> Result<()> { } let config: Config = toml::from_str(&config_data)?; + let strict_usb_boot = strict_usb_boot(); + + log::info!( + "pcid-spawner: starting (initfs={}, strict_usb_boot={})", + initfs, strict_usb_boot + ); + + let pci_dir = { + let mut attempts = 0u32; + loop { + match fs::read_dir("/scheme/pci") { + Ok(dir) => break dir, + Err(e) if e.raw_os_error() == Some(19) => { + attempts += 1; + if attempts > 50 { + return Err(e).context("pcid-spawner: /scheme/pci never appeared after 5 s"); + } + log::warn!( + "pcid-spawner: /scheme/pci not ready yet (ENODEV, attempt {}/50), waiting 100 ms", + attempts + ); + thread::sleep(Duration::from_millis(100)); + } + Err(e) => return Err(e).context("pcid-spawner: failed to read /scheme/pci"), + } + } + }; - for entry in fs::read_dir("/scheme/pci")? { + for entry in pci_dir { let entry = entry.context("failed to get entry")?; @@ -55,10 +90,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 +103,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 +121,105 @@ fn main() -> Result<()> { let mut command = Command::new(program); command.args(args); - log::info!("pcid-spawner: spawn {:?}", command); + 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; + } - handle.enable_device(); + let irq_info = handle.config().func.legacy_interrupt_line; + let irq_desc = match irq_info { + Some(irq) => format!("INTx#{irq}"), + None => "MSI/MSI-X only".to_string(), + }; + log::info!( + "pcid-spawner: {} enabled (interrupt: {}, driver: {:?})", + device_addr, + irq_desc, + driver.command, + ); 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 should_detach_in_initfs( + initfs, + full_device_id.class, + full_device_id.subclass, + strict_usb_boot, + ) { + log::warn!( + "pcid-spawner: detached initfs spawn for {} to avoid blocking early boot", + device_addr + ); + + let device_addr = device_addr.to_string(); + thread::spawn(move || { + #[allow(deprecated, reason = "we can't yet move this to init")] + 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 + ); + } + }); + } else { + log::info!( + "pcid-spawner: blocking on storage driver spawn for {} (class={:#04x})", + device_addr, + full_device_id.class + ); + #[allow(deprecated, reason = "we can't yet move this to init")] + 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 + ); + } else { + log::info!( + "pcid-spawner: storage driver ready for {}", + 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/cfg_access/fallback.rs b/drivers/pcid/src/cfg_access/fallback.rs index 671d17f7..ea8f69f8 100644 --- a/drivers/pcid/src/cfg_access/fallback.rs +++ b/drivers/pcid/src/cfg_access/fallback.rs @@ -33,7 +33,12 @@ impl Pci { "PCI: couldn't find or access PCIe extended configuration, \ and thus falling back to PCI 3.0 io ports" ); - common::acquire_port_io_rights().expect("pcid: failed to get IO port rights"); + common::acquire_port_io_rights() + .map_err(|err| { + log::error!("pcid: failed to get IO port rights: {err}"); + err + }) + .expect("pcid: IO port rights required for PCI 3.0 fallback"); } }); } @@ -61,8 +66,9 @@ impl ConfigRegionAccess for Pci { Self::set_iopl(); - let offset = - u8::try_from(offset).expect("offset too large for PCI 3.0 configuration space"); + let Ok(offset) = u8::try_from(offset) else { + return 0xFFFFFFFF; + }; let address = Self::address(address, offset); Pio::::new(0xCF8).write(address); @@ -74,8 +80,9 @@ impl ConfigRegionAccess for Pci { Self::set_iopl(); - let offset = - u8::try_from(offset).expect("offset too large for PCI 3.0 configuration space"); + let Ok(offset) = u8::try_from(offset) else { + return; + }; let address = Self::address(address, offset); Pio::::new(0xCF8).write(address); diff --git a/drivers/pcid/src/cfg_access/mod.rs b/drivers/pcid/src/cfg_access/mod.rs index c2552448..0fe215a6 100644 --- a/drivers/pcid/src/cfg_access/mod.rs +++ b/drivers/pcid/src/cfg_access/mod.rs @@ -38,42 +38,57 @@ fn locate_ecam_dtb( ) })?; - let address = node.reg().unwrap().next().unwrap().starting_address as u64; + let mut reg = node.reg().ok_or_else(|| { + io::Error::new(io::ErrorKind::NotFound, "pci-host-ecam-generic missing 'reg' property") + })?; + let address = reg.next().ok_or_else(|| { + io::Error::new(io::ErrorKind::NotFound, "pci-host-ecam-generic 'reg' has no entries") + })?.starting_address as u64; - let bus_range = node.property("bus-range").unwrap(); - assert_eq!(bus_range.value.len(), 8); - let start_bus = u32::from_be_bytes(<[u8; 4]>::try_from(&bus_range.value[0..4]).unwrap()); - let end_bus = u32::from_be_bytes(<[u8; 4]>::try_from(&bus_range.value[4..8]).unwrap()); + let bus_range = node.property("bus-range").ok_or_else(|| { + io::Error::new(io::ErrorKind::NotFound, "pci-host-ecam-generic missing 'bus-range' property") + })?; + if bus_range.value.len() != 8 { + return Err(io::Error::new(io::ErrorKind::InvalidData, "pci-host-ecam-generic 'bus-range' not 8 bytes")); + } + let start_bus = u32::from_be_bytes(<[u8; 4]>::try_from(&bus_range.value[0..4]).map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "bus-range start parse failed"))?); + let end_bus = u32::from_be_bytes(<[u8; 4]>::try_from(&bus_range.value[4..8]).map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "bus-range end parse failed"))?); - // address-cells == 3, size-cells == 2, interrupt-cells == 1 - let mut interrupt_map_data = node - .property("interrupt-map") - .unwrap() + let interrupt_map_prop = node.property("interrupt-map").ok_or_else(|| { + io::Error::new(io::ErrorKind::NotFound, "pci-host-ecam-generic missing 'interrupt-map' property") + })?; + let mut interrupt_map_data = interrupt_map_prop .value .chunks_exact(4) - .map(|x| u32::from_be_bytes(<[u8; 4]>::try_from(x).unwrap())); + .map(|x| u32::from_be_bytes(<[u8; 4]>::try_from(x).unwrap_or([0, 0, 0, 0]))); let mut interrupt_map = Vec::::new(); while let Ok([addr1, addr2, addr3, int1, phandle]) = interrupt_map_data.next_chunk::<5>() { - let parent = dt.find_phandle(phandle).unwrap(); - let parent_address_cells = u32::from_be_bytes( - parent.property("#address-cells").unwrap().value[..4] - .try_into() - .unwrap(), - ); + let Some(parent) = dt.find_phandle(phandle) else { + log::warn!("pcid: DTB interrupt-map references phandle {phandle} not found, skipping"); + continue; + }; + let parent_address_cells = match parent.property("#address-cells") { + Some(prop) => u32::from_be_bytes( + prop.value[..4] + .try_into() + .unwrap_or([0, 0, 0, 0]), + ), + None => 0, + }; match parent_address_cells { 0 => {} 1 => { - assert_eq!(interrupt_map_data.next().unwrap(), 0); + let _ = interrupt_map_data.next(); } 2 => { - assert_eq!(interrupt_map_data.next_chunk::<2>().unwrap(), [0, 0]); + let _ = interrupt_map_data.next_chunk::<2>(); } 3 => { - assert_eq!(interrupt_map_data.next_chunk::<3>().unwrap(), [0, 0, 0]); + let _ = interrupt_map_data.next_chunk::<3>(); } _ => break, }; - let parent_interrupt_cells = parent.interrupt_cells().unwrap(); + let parent_interrupt_cells = parent.interrupt_cells().unwrap_or(1); let parent_interrupt = match parent_interrupt_cells { 1 if let Some(a) = interrupt_map_data.next() => [a, 0, 0], 2 if let Ok([a, b]) = interrupt_map_data.next_chunk::<2>() => [a, b, 0], @@ -94,8 +109,8 @@ fn locate_ecam_dtb( let mut cells = interrupt_mask_node .value .chunks_exact(4) - .map(|x| u32::from_be_bytes(<[u8; 4]>::try_from(x).unwrap())); - cells.next_chunk::<4>().unwrap().to_owned() + .map(|x| u32::from_be_bytes(<[u8; 4]>::try_from(x).unwrap_or([0, 0, 0, 0]))); + cells.next_chunk::<4>().unwrap_or([u32::MAX, u32::MAX, u32::MAX, u32::MAX]).to_owned() } else { [u32::MAX, u32::MAX, u32::MAX, u32::MAX] }; @@ -104,8 +119,8 @@ fn locate_ecam_dtb( PcieAllocs(&[PcieAlloc { base_addr: address, seg_group_num: 0, - start_bus: start_bus.try_into().unwrap(), - end_bus: end_bus.try_into().unwrap(), + start_bus: start_bus.try_into().map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "start_bus overflow"))?, + end_bus: end_bus.try_into().map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "end_bus overflow"))?, _rsvd: [0; 4], }]), interrupt_map, @@ -165,7 +180,10 @@ impl Mcfg { // crashing. `as_encoded_bytes()` returns some superset // of ASCII, so directly comparing it with an ASCII name // is fine. - let table_filename = table_path.file_name().unwrap().as_encoded_bytes(); + let table_filename = match table_path.file_name() { + Some(name) => name.as_encoded_bytes(), + None => continue, + }; if table_filename.get(0..4) == Some(&MCFG_NAME) { let bytes = fs::read(table_path)?.into_boxed_slice(); match Mcfg::parse(&*bytes) { diff --git a/drivers/pcid/src/driver_handler.rs b/drivers/pcid/src/driver_handler.rs index f70a7f6d..64701f6c 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, }) @@ -230,10 +240,14 @@ impl<'a> DriverHandler<'a> { } info.set_message_info( message_addr, - message_addr_and_data - .data - .try_into() - .expect("pcid: MSI message data too big"), + match message_addr_and_data.data.try_into() { + Ok(d) => d, + Err(_) => { + return PcidClientResponse::Error( + PcidServerResponseError::InvalidBitPattern, + ) + } + }, self.pcie, ); } @@ -266,7 +280,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 +292,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..3a83bb4d 100644 --- a/drivers/pcid/src/driver_interface/bar.rs +++ b/drivers/pcid/src/driver_interface/bar.rs @@ -1,7 +1,38 @@ use std::convert::TryInto; +use std::fmt; +use std::process; 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 +61,88 @@ impl PciBar { } pub fn expect_port(&self) -> u16 { + match self.try_port() { + Ok(port) => port, + Err(err) => { + log::error!("{err}"); + process::exit(1); + } + } + } + + 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) { + match self.try_mem() { + Ok(result) => result, + Err(err) => { + log::error!("{err}"); + process::exit(1); + } + } + } + + 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..17c26c0c 100644 --- a/drivers/pcid/src/driver_interface/cap.rs +++ b/drivers/pcid/src/driver_interface/cap.rs @@ -1,14 +1,44 @@ use pci_types::capability::PciCapabilityAddress; use pci_types::ConfigRegionAccess; use serde::{Deserialize, Serialize}; +use std::fmt; +use std::process; #[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 { + match Self::try_parse(addr, access) { + Ok(cap) => cap, + Err(err) => { + log::error!("{err}"); + process::exit(1); + } + } + } + + 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 +47,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 +61,75 @@ 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() + .expect("mock config lock poisoned") + .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 + .expect("valid vendor capability should parse") + .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/config.rs b/drivers/pcid/src/driver_interface/config.rs index e148b26c..041f0ced 100644 --- a/drivers/pcid/src/driver_interface/config.rs +++ b/drivers/pcid/src/driver_interface/config.rs @@ -47,7 +47,13 @@ impl DriverConfig { let mut device_found = false; for (vendor, devices) in ids { let vendor_without_prefix = vendor.trim_start_matches("0x"); - let vendor = i64::from_str_radix(vendor_without_prefix, 16).unwrap() as u16; + let Ok(vendor_val) = i64::from_str_radix(vendor_without_prefix, 16) else { + log::warn!( + "invalid hex vendor ID '{vendor_without_prefix}' in driver config, skipping" + ); + continue; + }; + let vendor = vendor_val as u16; if vendor != id.vendor_id { continue; diff --git a/drivers/pcid/src/driver_interface/irq_helpers.rs b/drivers/pcid/src/driver_interface/irq_helpers.rs index 28ca077a..bff35650 100644 --- a/drivers/pcid/src/driver_interface/irq_helpers.rs +++ b/drivers/pcid/src/driver_interface/irq_helpers.rs @@ -7,6 +7,7 @@ use std::convert::TryFrom; use std::fs::{self, File}; use std::io::{self, prelude::*}; use std::num::NonZeroU8; +use std::process; use crate::driver_interface::msi::{MsiAddrAndData, MsixTableEntry}; @@ -24,11 +25,13 @@ pub fn read_bsp_apic_id() -> io::Result { buffer[0], buffer[1], buffer[2], buffer[3], ])) } else { - panic!( - "`/scheme/irq` scheme responded with {} bytes, expected {}", - bytes_read, - std::mem::size_of::() - ); + return Err(io::Error::new( + io::ErrorKind::InvalidData, + format!( + "`/scheme/irq` scheme responded with {bytes_read} bytes, expected {}", + std::mem::size_of::() + ), + )); }) .or(Err(io::Error::new( io::ErrorKind::InvalidData, @@ -83,7 +86,12 @@ pub fn allocate_aligned_interrupt_vectors( alignment: NonZeroU8, count: u8, ) -> io::Result)>> { - let cpu_id = u8::try_from(cpu_id).expect("usize cpu ids not implemented yet"); + let cpu_id = u8::try_from(cpu_id).map_err(|_| { + io::Error::new( + io::ErrorKind::InvalidInput, + format!("CPU id {cpu_id} too large for u8 (usize cpu ids not supported)"), + ) + })?; if count == 0 { return Ok(None); } @@ -163,7 +171,7 @@ pub fn allocate_aligned_interrupt_vectors( /// Allocate at most `count` interrupt vectors, which can start at any offset. Unless MSI is used /// and an entire aligned range of vectors is needed, this function should be used. pub fn allocate_interrupt_vectors(cpu_id: usize, count: u8) -> io::Result)>> { - allocate_aligned_interrupt_vectors(cpu_id, NonZeroU8::new(1).unwrap(), count) + allocate_aligned_interrupt_vectors(cpu_id, NonZeroU8::MIN, count) } /// Allocate a single interrupt vector, returning both the vector number (starting from 32 up to @@ -176,44 +184,66 @@ pub fn allocate_single_interrupt_vector(cpu_id: usize) -> io::Result return Err(err), }; assert_eq!(files.len(), 1); - Ok(Some((base, files.pop().unwrap()))) + let handle = files.pop().ok_or_else(|| { + io::Error::new( + io::ErrorKind::Other, + "allocate_interrupt_vectors returned empty file list despite count=1", + ) + })?; + Ok(Some((base, handle))) } #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -pub fn allocate_single_interrupt_vector_for_msi(cpu_id: usize) -> (MsiAddrAndData, File) { +pub fn try_allocate_single_interrupt_vector_for_msi( + cpu_id: usize, +) -> io::Result<(MsiAddrAndData, File)> { use crate::driver_interface::msi::x86 as x86_msix; - // FIXME for cpu_id >255 we need to use the IOMMU to use IRQ remapping - let lapic_id = u8::try_from(cpu_id).expect("CPU id couldn't fit inside u8"); + let lapic_id = u8::try_from(cpu_id).map_err(|_| { + io::Error::new( + io::ErrorKind::InvalidInput, + format!("CPU id {cpu_id} could not fit inside u8"), + ) + })?; let rh = false; let dm = false; let addr = x86_msix::message_address(lapic_id, rh, dm); - let (vector, interrupt_handle) = allocate_single_interrupt_vector(cpu_id) - .expect("failed to allocate interrupt vector") - .expect("no interrupt vectors left"); + let (vector, interrupt_handle) = allocate_single_interrupt_vector(cpu_id)? + .ok_or_else(|| io::Error::other("no interrupt vectors left"))?; let msg_data = x86_msix::message_data_edge_triggered(x86_msix::DeliveryMode::Fixed, vector); - ( + Ok(( MsiAddrAndData { addr, data: msg_data, }, interrupt_handle, - ) + )) } #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -pub fn allocate_first_msi_interrupt_on_bsp( +pub fn allocate_single_interrupt_vector_for_msi(cpu_id: usize) -> (MsiAddrAndData, File) { + match try_allocate_single_interrupt_vector_for_msi(cpu_id) { + Ok(result) => result, + Err(err) => { + log::error!("failed to allocate MSI interrupt vector: {err}"); + process::exit(1); + } + } +} + +#[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 +252,25 @@ 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 { + match try_allocate_first_msi_interrupt_on_bsp(pcid_handle) { + Ok(handle) => handle, + Err(err) => { + log::error!("failed to allocate first MSI interrupt on BSP: {err}"); + process::exit(1); + } + } } pub struct InterruptVector { @@ -234,6 +279,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 +344,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 +356,89 @@ 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 { + match try_pci_allocate_interrupt_vector(pcid_handle, driver) { + Ok(vec) => vec, + Err(err) => { + log::error!("{driver}: {err}"); + process::exit(1); + } + } +} + +// 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 { + match try_pci_allocate_interrupt_vector(pcid_handle, driver) { + Ok(vec) => vec, + Err(err) => { + log::error!("{driver}: {err}"); + process::exit(1); + } } } diff --git a/drivers/pcid/src/driver_interface/mod.rs b/drivers/pcid/src/driver_interface/mod.rs index bbc7304e..9d7172b9 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,15 +39,28 @@ 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 { + match self.try_irq_handle(driver) { + Ok(handle) => handle, + Err(err) => { + log::error!("{driver}: failed to open IRQ file: {err}"); + process::exit(1); + } } } } @@ -59,8 +72,10 @@ impl fmt::Display for LegacyInterruptLine { 1 => write!(f, "(phandle {}, {:?})", phandle, addr[0]), 2 => write!(f, "(phandle {}, {:?},{:?})", phandle, addr[0], addr[1]), 3 => write!(f, "(phandle {}, {:?})", phandle, addr), - _ => panic!( - "unexpected number of IRQ description cells for phandle {phandle}: {cells}" + _ => write!( + f, + "(phandle {}, invalid IRQ description cells: {cells})", + phandle, ), } } else { @@ -247,6 +262,7 @@ pub enum PcidClientRequest { pub enum PcidServerResponseError { NonexistentFeature(PciFeature), InvalidBitPattern, + UnrecognizedRequest, } #[derive(Debug, Serialize, Deserialize)] @@ -278,33 +294,51 @@ pub struct PciFunctionHandle { } fn send(w: &mut File, message: &T) { - let mut data = Vec::new(); - bincode::serialize_into(&mut data, message).expect("couldn't serialize pcid message"); - match w.write(&data) { - Ok(len) => assert_eq!(len, data.len()), + if let Err(err) = send_result(w, message) { + log::error!("pcid send failed: {err}"); + process::exit(1); + } +} +fn recv(r: &mut File) -> T { + match recv_result(r) { + Ok(value) => value, Err(err) => { - log::error!("writing pcid request failed: {err}"); + log::error!("pcid recv failed: {err}"); process::exit(1); } } } -fn recv(r: &mut File) -> T { - let mut length_bytes = [0u8; 8]; - if let Err(err) = r.read_exact(&mut length_bytes) { - log::error!("reading pcid response length failed: {err}"); - process::exit(1); + +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 { - panic!("pcid_interface: buffer too large"); + return Err(io::Error::new( + io::ErrorKind::InvalidData, + format!("pcid_interface: buffer too large ({length} bytes)"), + )); } let mut data = vec![0u8; length as usize]; - if let Err(err) = r.read_exact(&mut data) { - log::error!("reading pcid response failed: {err}"); - process::exit(1); - } - - bincode::deserialize_from(&data[..]).expect("couldn't deserialize pcid message") + r.read_exact(&mut data)?; + bincode::deserialize_from(&data[..]) + .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err)) } impl PciFunctionHandle { @@ -327,11 +361,14 @@ impl PciFunctionHandle { } pub fn connect_by_path(device_path: &Path) -> io::Result { - let channel_fd = libredox::call::open( - device_path.join("channel").to_str().unwrap(), - libredox::flag::O_RDWR, - 0, - )?; + let channel_path = device_path.join("channel"); + let channel_str = channel_path.to_str().ok_or_else(|| { + io::Error::new( + io::ErrorKind::InvalidData, + format!("device path contains invalid UTF-8: {}", device_path.display()), + ) + })?; + let channel_fd = libredox::call::open(channel_str, libredox::flag::O_RDWR, 0)?; Ok(Self::connect_common(channel_fd as RawFd)) } @@ -369,55 +406,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 +514,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 +565,25 @@ 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 { - ptr: NonNull::new(ptr.cast::()).expect("Mapping a BAR resulted in a nullptr"), + Ok(mapped_bar.insert(MappedBar { + ptr: NonNull::new(ptr.cast::()).ok_or_else(|| { + io::Error::new(io::ErrorKind::Other, "mapping a BAR resulted in a null pointer") + })?, 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..cd2fd701 100644 --- a/drivers/pcid/src/driver_interface/msi.rs +++ b/drivers/pcid/src/driver_interface/msi.rs @@ -1,6 +1,8 @@ use std::fmt; use std::ptr::NonNull; +use std::process; +use crate::driver_interface::bar::PciBarError; use crate::driver_interface::PciBar; use crate::PciFunctionHandle; @@ -33,9 +35,74 @@ 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, + }, + NullPointer, +} + +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}" + ), + MsixMapError::NullPointer => { + write!(f, "MSI-X BAR mapping resulted in null pointer") + }, + } + } +} + impl MsixInfo { pub unsafe fn map_and_mask_all(self, pcid_handle: &mut PciFunctionHandle) -> MappedMsixRegs { - self.validate(pcid_handle.config().func.bars); + match self.try_map_and_mask_all(pcid_handle) { + Ok(regs) => regs, + Err(err) => { + log::error!("{err}"); + process::exit(1); + } + } + } + + 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 +113,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::()) + .ok_or(MsixMapError::NullPointer)?, info: self, }; @@ -56,21 +124,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 +142,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 +192,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/main.rs b/drivers/pcid/src/main.rs index 61cd9a78..8840e141 100644 --- a/drivers/pcid/src/main.rs +++ b/drivers/pcid/src/main.rs @@ -12,6 +12,7 @@ use pci_types::{ }; use redox_scheme::scheme::register_sync_scheme; use scheme_utils::Blocking; +use syscall::{sendfd, SendFdFlags}; use crate::cfg_access::Pcie; use pcid_interface::{FullDeviceId, LegacyInterruptLine, PciBar, PciFunction, PciRom}; @@ -42,7 +43,15 @@ fn handle_parsed_header( continue; } match endpoint_header.bar(i, pcie) { - Some(TyBar::Io { port }) => bars[i as usize] = PciBar::Port(port.try_into().unwrap()), + Some(TyBar::Io { port }) => { + match u16::try_from(port) { + Ok(p) => bars[i as usize] = PciBar::Port(p), + Err(_) => { + warn!("pcid: BAR {} I/O port {:#x} out of u16 range, skipping", i, port); + bars[i as usize] = PciBar::None; + } + } + } Some(TyBar::Memory32 { address, size, @@ -251,7 +260,13 @@ fn daemon(daemon: daemon::Daemon) -> ! { info!("PCI SG-BS:DV.F VEND:DEVI CL.SC.IN.RV"); let mut scheme = scheme::PciScheme::new(pcie); - let socket = redox_scheme::Socket::create().expect("failed to open pci scheme socket"); + let socket = match redox_scheme::Socket::create() { + Ok(s) => s, + Err(err) => { + log::error!("pcid: failed to open pci scheme socket: {err}"); + std::process::exit(1); + } + }; let handler = Blocking::new(&socket, 16); { @@ -259,17 +274,23 @@ fn daemon(daemon: daemon::Daemon) -> ! { Ok(register_pci) => { let access_id = scheme.access(); - let access_fd = socket + let access_fd = match socket .create_this_scheme_fd(0, access_id, syscall::O_RDWR, 0) - .expect("failed to issue this resource"); - let access_bytes = access_fd.to_ne_bytes(); - let _ = register_pci - .call_wo( - &access_bytes, - syscall::CallFlags::WRITE | syscall::CallFlags::FD, - &[], - ) - .expect("failed to send pci_fd to acpid"); + { + Ok(fd) => fd, + Err(err) => { + warn!("pcid: failed to issue pci access resource to acpid: {err}. Running without ACPI integration."); + 0 + } + }; + if let Err(err) = sendfd( + register_pci.raw(), + access_fd as usize, + SendFdFlags::empty().bits(), + 0, + ) { + warn!("pcid: failed to send pci_fd to acpid: {err}. Running without ACPI integration."); + } } Err(err) => { if err.errno() == libredox::errno::ENODEV { @@ -302,16 +323,24 @@ fn daemon(daemon: daemon::Daemon) -> ! { ); } } - debug!("Enumeration complete, now starting pci scheme"); + info!( + "PCI enumeration complete: {} devices, {} buses", + scheme.tree.len(), + bus_nums.len() + ); - register_sync_scheme(&socket, "pci", &mut scheme) - .expect("failed to register pci scheme to namespace"); + if let Err(err) = register_sync_scheme(&socket, "pci", &mut scheme) { + log::error!("pcid: failed to register pci scheme to namespace: {err}"); + std::process::exit(1); + } let _ = daemon.ready(); - handler - .process_requests_blocking(scheme) - .expect("pcid: failed to process requests"); + if let Err(err) = handler.process_requests_blocking(scheme) { + log::error!("pcid: failed to process requests: {err}"); + std::process::exit(1); + } + loop {} } fn scan_device( @@ -350,16 +379,16 @@ fn scan_device( match header.header_type(pcie) { HeaderType::Endpoint => { - handle_parsed_header( - pcie, - tree, - EndpointHeader::from_header(header, pcie).unwrap(), - full_device_id, - ); + match EndpointHeader::from_header(header, pcie) { + Some(endpoint) => handle_parsed_header(pcie, tree, endpoint, full_device_id), + None => warn!("pcid: failed to parse endpoint header for {}.{}.{}", bus_num, dev_num, func_num), + } } HeaderType::PciPciBridge => { - let bridge_header = PciPciBridgeHeader::from_header(header, pcie).unwrap(); - bus_nums.push(bridge_header.secondary_bus_number(pcie)); + match PciPciBridgeHeader::from_header(header, pcie) { + Some(bridge) => bus_nums.push(bridge.secondary_bus_number(pcie)), + None => warn!("pcid: failed to parse PCI-PCI bridge header for {}.{}.{}", bus_num, dev_num, func_num), + } } ty => { warn!("pcid: unknown header type: {ty:?}"); 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/ahci/disk_ata.rs b/drivers/storage/ahcid/src/ahci/disk_ata.rs index 4f83c51d..7423603b 100644 --- a/drivers/storage/ahcid/src/ahci/disk_ata.rs +++ b/drivers/storage/ahcid/src/ahci/disk_ata.rs @@ -1,7 +1,7 @@ use std::convert::TryInto; use std::ptr; -use syscall::error::Result; +use syscall::error::{Error, Result, EIO}; use common::dma::Dma; @@ -39,7 +39,7 @@ impl DiskATA { .map(|_| Ok(unsafe { Dma::zeroed()?.assume_init() })) .collect::>>()? .try_into() - .unwrap_or_else(|_| unreachable!()); + .map_err(|_| Error::new(EIO))?; let mut fb = unsafe { Dma::zeroed()?.assume_init() }; let buf = unsafe { Dma::zeroed()?.assume_init() }; diff --git a/drivers/storage/ahcid/src/ahci/disk_atapi.rs b/drivers/storage/ahcid/src/ahci/disk_atapi.rs index a0e75c09..8fbdfbef 100644 --- a/drivers/storage/ahcid/src/ahci/disk_atapi.rs +++ b/drivers/storage/ahcid/src/ahci/disk_atapi.rs @@ -37,7 +37,7 @@ impl DiskATAPI { .map(|_| Ok(unsafe { Dma::zeroed()?.assume_init() })) .collect::>>()? .try_into() - .unwrap_or_else(|_| unreachable!()); + .map_err(|_| Error::new(EBADF))?; let mut fb = unsafe { Dma::zeroed()?.assume_init() }; let mut buf = unsafe { Dma::zeroed()?.assume_init() }; diff --git a/drivers/storage/ahcid/src/ahci/hba.rs b/drivers/storage/ahcid/src/ahci/hba.rs index bea8792c..11a3d4ae 100644 --- a/drivers/storage/ahcid/src/ahci/hba.rs +++ b/drivers/storage/ahcid/src/ahci/hba.rs @@ -178,7 +178,10 @@ impl HbaPort { clb: &mut Dma<[HbaCmdHeader; 32]>, ctbas: &mut [Dma; 32], ) -> Result { - let dest: Dma<[u16; 256]> = Dma::new([0; 256]).unwrap(); + let dest: Dma<[u16; 256]> = Dma::new([0; 256]).map_err(|err| { + error!("ahcid: failed to allocate DMA buffer: {err}"); + Error::new(EIO) + })?; let slot = self .ata_start(clb, ctbas, |cmdheader, cmdfis, prdt_entries, _acmd| { diff --git a/drivers/storage/ahcid/src/main.rs b/drivers/storage/ahcid/src/main.rs index 1f130a29..cccd2980 100644 --- a/drivers/storage/ahcid/src/main.rs +++ b/drivers/storage/ahcid/src/main.rs @@ -2,6 +2,7 @@ use std::io::{Read, Write}; use std::os::fd::AsRawFd; +use std::process; use std::usize; use common::io::Io; @@ -23,10 +24,13 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { let mut name = pci_config.func.name(); name.push_str("_ahci"); - let irq = pci_config - .func - .legacy_interrupt_line - .expect("ahcid: no legacy interrupts supported"); + let irq = match pci_config.func.legacy_interrupt_line { + Some(irq) => irq, + None => { + error!("ahcid: no legacy interrupts supported"); + process::exit(1); + } + }; common::setup_logging( "disk", @@ -57,46 +61,71 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { let mut irq_file = irq.irq_handle("ahcid"); 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 = RawEventQueue::new().unwrap_or_else(|err| { + error!("ahcid: failed to create event queue: {err}"); + process::exit(1); + }); - libredox::call::setrens(0, 0).expect("ahcid: failed to enter null namespace"); + libredox::call::setrens(0, 0).unwrap_or_else(|err| { + error!("ahcid: failed to enter null namespace: {err}"); + 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 event scheme socket: {err}"); + 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 event irq scheme: {err}"); + 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 get event: {err}"); + continue; + } + }; 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 event: {err}"); + } } 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() - { - let is = hba_mem.is.read(); - if is > 0 { - let pi = hba_mem.pi.read(); - let pi_is = pi & is; - for i in 0..hba_mem.ports.len() { - if pi_is & 1 << i > 0 { - let port = &mut hba_mem.ports[i]; - let is = port.is.read(); - port.is.write(is); - } + match irq_file.read(&mut irq) { + Ok(count) if count >= irq.len() => {} + Ok(_) => continue, + Err(err) => { + error!("ahcid: failed to read irq file: {err}"); + continue; + } + } + let is = hba_mem.is.read(); + if is > 0 { + let pi = hba_mem.pi.read(); + let pi_is = pi & is; + for i in 0..hba_mem.ports.len() { + if pi_is & 1 << i > 0 { + let port = &mut hba_mem.ports[i]; + let is = port.is.read(); + port.is.write(is); } - hba_mem.is.write(is); + } + 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 write irq file: {err}"); + continue; + } - FuturesExecutor.block_on(scheme.tick()).unwrap(); + if let Err(err) = FuturesExecutor.block_on(scheme.tick()) { + error!("ahcid: failed to handle IRQ tick: {err}"); } } } else { @@ -105,5 +134,5 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { } } - std::process::exit(0); + process::exit(0); } diff --git a/drivers/storage/ided/src/ide.rs b/drivers/storage/ided/src/ide.rs index 5faf3250..094e5889 100644 --- a/drivers/storage/ided/src/ide.rs +++ b/drivers/storage/ided/src/ide.rs @@ -184,10 +184,10 @@ impl Disk for AtaDisk { let block = start_block + (count as u64) / 512; //TODO: support other LBA modes - assert!(block < 0x1_0000_0000_0000); + debug_assert!(block < 0x1_0000_0000_0000); let sectors = (chunk.len() + 511) / 512; - assert!(sectors <= 128); + debug_assert!(sectors <= 128); log::trace!( "IDE read chan {} dev {} block {:#x} count {:#x}", @@ -205,7 +205,7 @@ impl Disk for AtaDisk { // Make PRDT EOT match chunk size for i in 0..sectors { chan.prdt[i] = PrdtEntry { - phys: (chan.buf.physical() + i * 512).try_into().unwrap(), + phys: (chan.buf.physical() + i * 512).try_into().map_err(|_| Error::new(EIO))?, size: 512, flags: if i + 1 == sectors { 1 << 15 // End of table @@ -216,7 +216,7 @@ impl Disk for AtaDisk { } // Set PRDT let prdt = chan.prdt.physical(); - chan.busmaster_prdt.write(prdt.try_into().unwrap()); + chan.busmaster_prdt.write(prdt.try_into().map_err(|_| Error::new(EIO))?); // Set to read chan.busmaster_command.writef(1 << 3, true); // Clear interrupt and error bits @@ -325,10 +325,10 @@ impl Disk for AtaDisk { let block = start_block + (count as u64) / 512; //TODO: support other LBA modes - assert!(block < 0x1_0000_0000_0000); + debug_assert!(block < 0x1_0000_0000_0000); let sectors = (chunk.len() + 511) / 512; - assert!(sectors <= 128); + debug_assert!(sectors <= 128); log::trace!( "IDE write chan {} dev {} block {:#x} count {:#x}", @@ -346,7 +346,7 @@ impl Disk for AtaDisk { // Make PRDT EOT match chunk size for i in 0..sectors { chan.prdt[i] = PrdtEntry { - phys: (chan.buf.physical() + i * 512).try_into().unwrap(), + phys: (chan.buf.physical() + i * 512).try_into().map_err(|_| Error::new(EIO))?, size: 512, flags: if i + 1 == sectors { 1 << 15 // End of table @@ -357,7 +357,7 @@ impl Disk for AtaDisk { } // Set PRDT let prdt = chan.prdt.physical(); - chan.busmaster_prdt.write(prdt.try_into().unwrap()); + chan.busmaster_prdt.write(prdt.try_into().map_err(|_| Error::new(EIO))?); // Set to write chan.busmaster_command.writef(1 << 3, false); // Clear interrupt and error bits diff --git a/drivers/storage/ided/src/main.rs b/drivers/storage/ided/src/main.rs index 4197217d..03174554 100644 --- a/drivers/storage/ided/src/main.rs +++ b/drivers/storage/ided/src/main.rs @@ -8,6 +8,7 @@ use std::{ fs::File, io::{Read, Write}, os::unix::io::{FromRawFd, RawFd}, + process, sync::{Arc, Mutex}, thread::{self, sleep}, time::Duration, @@ -45,17 +46,34 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { let busmaster_base = pci_config.func.bars[4].expect_port(); let (primary, primary_irq) = if pci_config.func.full_device_id.interface & 1 != 0 { - panic!("TODO: IDE primary channel is PCI native"); + error!("ided: IDE primary channel PCI native mode not supported"); + process::exit(1); } else { - (Channel::primary_compat(busmaster_base).unwrap(), 14) + ( + Channel::primary_compat(busmaster_base).unwrap_or_else(|err| { + error!("ided: failed to init primary channel: {err}"); + process::exit(1); + }), + 14, + ) }; let (secondary, secondary_irq) = if pci_config.func.full_device_id.interface & 1 != 0 { - panic!("TODO: IDE secondary channel is PCI native"); + error!("ided: IDE secondary channel PCI native mode not supported"); + process::exit(1); } else { - (Channel::secondary_compat(busmaster_base + 8).unwrap(), 15) + ( + Channel::secondary_compat(busmaster_base + 8).unwrap_or_else(|err| { + error!("ided: failed to init secondary channel: {err}"); + process::exit(1); + }), + 15, + ) }; - common::acquire_port_io_rights().expect("ided: failed to get I/O privilege"); + common::acquire_port_io_rights().unwrap_or_else(|err| { + error!("ided: failed to get I/O privilege: {err}"); + process::exit(1); + }); //TODO: move this to ide.rs? let chans = vec![ @@ -87,13 +105,13 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { for (chan_i, chan_lock) in chans.iter().enumerate() { let mut chan = chan_lock.lock().unwrap(); - println!(" - channel {}", chan_i); + log::info!(" - channel {}", chan_i); // Disable IRQs chan.control.write(2); for dev in 0..=1 { - println!(" - device {}", dev); + log::info!(" - device {}", dev); // Select device chan.device_select.write(0xA0 | (dev << 4)); @@ -105,7 +123,7 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { // Check if device exists if chan.status.read() == 0 { - println!(" not found"); + log::info!(" not found"); continue; } @@ -125,7 +143,7 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { //TODO: probe ATAPI if error { - println!(" error"); + log::info!(" error"); continue; } @@ -189,12 +207,12 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { 48 }; - println!(" Serial: {}", serial.trim()); - println!(" Firmware: {}", firmware.trim()); - println!(" Model: {}", model.trim()); - println!(" Size: {} MB", sectors / 2048); - println!(" DMA: {}", dma); - println!(" {}-bit LBA", lba_bits); + log::info!(" Serial: {}", serial.trim()); + log::info!(" Firmware: {}", firmware.trim()); + log::info!(" Model: {}", model.trim()); + log::info!(" Size: {} MB", sectors / 2048); + log::info!(" DMA: {}", dma); + log::info!(" {}-bit LBA", lba_bits); disks.push(AnyDisk::Ata(AtaDisk { chan: chan_lock.clone(), @@ -227,7 +245,10 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { flag::O_RDWR | flag::O_NONBLOCK, 0, ) - .expect("ided: failed to open irq file"); + .unwrap_or_else(|err| { + error!("ided: failed to open primary irq file: {err}"); + process::exit(1); + }); let mut primary_irq_file = unsafe { File::from_raw_fd(primary_irq_fd as RawFd) }; let secondary_irq_fd = libredox::call::open( @@ -235,70 +256,107 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { flag::O_RDWR | flag::O_NONBLOCK, 0, ) - .expect("ided: failed to open irq file"); + .unwrap_or_else(|err| { + error!("ided: failed to open secondary irq file: {err}"); + process::exit(1); + }); let mut secondary_irq_file = unsafe { File::from_raw_fd(secondary_irq_fd as RawFd) }; - let event_queue = RawEventQueue::new().expect("ided: failed to open event file"); + let event_queue = RawEventQueue::new().unwrap_or_else(|err| { + error!("ided: failed to open event file: {err}"); + process::exit(1); + }); - libredox::call::setrens(0, 0).expect("ided: failed to enter null namespace"); + libredox::call::setrens(0, 0).unwrap_or_else(|err| { + error!("ided: failed to enter null namespace: {err}"); + process::exit(1); + }); event_queue .subscribe(scheme.event_handle().raw(), 0, EventFlags::READ) - .expect("ided: failed to event disk scheme"); + .unwrap_or_else(|err| { + error!("ided: failed to event disk scheme: {err}"); + process::exit(1); + }); event_queue .subscribe(primary_irq_fd, 0, EventFlags::READ) - .expect("ided: failed to event irq scheme"); + .unwrap_or_else(|err| { + error!("ided: failed to event primary irq: {err}"); + process::exit(1); + }); event_queue .subscribe(secondary_irq_fd, 0, EventFlags::READ) - .expect("ided: failed to event irq scheme"); + .unwrap_or_else(|err| { + error!("ided: failed to event secondary irq: {err}"); + process::exit(1); + }); for event in event_queue { - let event = event.unwrap(); + let event = match event { + Ok(event) => event, + Err(err) => { + error!("ided: failed to get event: {err}"); + continue; + } + }; if event.fd == scheme.event_handle().raw() { - FuturesExecutor.block_on(scheme.tick()).unwrap(); + if let Err(err) = FuturesExecutor.block_on(scheme.tick()) { + error!("ided: failed to handle scheme event: {err}"); + } } else if event.fd == primary_irq_fd { let mut irq = [0; 8]; - if primary_irq_file - .read(&mut irq) - .expect("ided: failed to read irq file") - >= irq.len() - { - let _chan = chans[0].lock().unwrap(); - //TODO: check chan for irq + match primary_irq_file.read(&mut irq) { + Ok(count) if count >= irq.len() => {} + Ok(_) => continue, + Err(err) => { + error!("ided: failed to read primary irq file: {err}"); + continue; + } + } + let _chan = chans[0].lock().unwrap(); + //TODO: check chan for irq - primary_irq_file - .write(&irq) - .expect("ided: failed to write irq file"); + if let Err(err) = primary_irq_file.write(&irq) { + error!("ided: failed to write primary irq file: {err}"); + continue; + } - FuturesExecutor.block_on(scheme.tick()).unwrap(); + if let Err(err) = FuturesExecutor.block_on(scheme.tick()) { + error!("ided: failed to handle primary IRQ tick: {err}"); } } else if event.fd == secondary_irq_fd { let mut irq = [0; 8]; - if secondary_irq_file - .read(&mut irq) - .expect("ided: failed to read irq file") - >= irq.len() - { - let _chan = chans[1].lock().unwrap(); - //TODO: check chan for irq + match secondary_irq_file.read(&mut irq) { + Ok(count) if count >= irq.len() => {} + Ok(_) => continue, + Err(err) => { + error!("ided: failed to read secondary irq file: {err}"); + continue; + } + } + let _chan = chans[1].lock().unwrap(); + //TODO: check chan for irq - secondary_irq_file - .write(&irq) - .expect("ided: failed to write irq file"); + if let Err(err) = secondary_irq_file.write(&irq) { + error!("ided: failed to write secondary irq file: {err}"); + continue; + } - FuturesExecutor.block_on(scheme.tick()).unwrap(); + if let Err(err) = FuturesExecutor.block_on(scheme.tick()) { + error!("ided: failed to handle secondary IRQ tick: {err}"); } } else { error!("Unknown event {}", event.fd); } } - std::process::exit(0); + process::exit(0); } #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { - unimplemented!() + log::error!("ided: unsupported architecture"); + process::exit(1); } diff --git a/drivers/storage/nvmed/src/main.rs b/drivers/storage/nvmed/src/main.rs index beb1b689..3772f4e5 100644 --- a/drivers/storage/nvmed/src/main.rs +++ b/drivers/storage/nvmed/src/main.rs @@ -2,6 +2,7 @@ use std::cell::RefCell; use std::fs::File; use std::io::{self, Read, Write}; use std::os::fd::AsRawFd; +use std::process; use std::rc::Rc; use std::sync::Arc; use std::usize; @@ -22,7 +23,10 @@ struct NvmeDisk { impl Disk for NvmeDisk { fn block_size(&self) -> u32 { - self.ns.block_size.try_into().unwrap() + self.ns.block_size.try_into().unwrap_or_else(|_| { + log::error!("nvmed: block size {} does not fit in u32", self.ns.block_size); + process::exit(1); + }) } fn size(&self) -> u64 { @@ -79,26 +83,43 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { let interrupt_vector = irq_helpers::pci_allocate_interrupt_vector(&mut pcid_handle, "nvmed"); let iv = interrupt_vector.vector(); - let irq_handle = interrupt_vector.irq_handle().try_clone().unwrap(); + let irq_handle = interrupt_vector.irq_handle().try_clone().unwrap_or_else(|err| { + log::error!("nvmed: failed to clone IRQ handle: {err}"); + process::exit(1); + }); let mut nvme = Nvme::new(address.as_ptr() as usize, interrupt_vector, pcid_handle) - .expect("nvmed: failed to allocate driver data"); - - unsafe { nvme.init().expect("nvmed: failed to init") } + .unwrap_or_else(|err| { + log::error!("nvmed: failed to allocate driver data: {err}"); + process::exit(1); + }); + + unsafe { + nvme.init().unwrap_or_else(|err| { + log::error!("nvmed: failed to init: {err}"); + 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"); + .unwrap_or_else(|err| { + log::error!("nvmed: failed to open time handle: {err}"); + 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"); + time_arm(&mut time_handle, 5).unwrap_or_else(|err| { + log::error!("nvmed: failed to arm timer: {err}"); + 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 +127,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 on init"); + process::exit(1); + } } }); log::debug!("Initialized!"); @@ -134,7 +158,10 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { event::EventFlags::READ, )); - libredox::call::setrens(0, 0).expect("nvmed: failed to enter null namespace"); + libredox::call::setrens(0, 0).unwrap_or_else(|err| { + log::error!("nvmed: failed to enter null namespace: {err}"); + process::exit(1); + }); log::debug!("Starting to listen for scheme events"); @@ -150,5 +177,5 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { //TODO: destroy NVMe stuff - std::process::exit(0); + process::exit(0); } diff --git a/drivers/storage/nvmed/src/nvme/executor.rs b/drivers/storage/nvmed/src/nvme/executor.rs index 6242fa98..c1435e88 100644 --- a/drivers/storage/nvmed/src/nvme/executor.rs +++ b/drivers/storage/nvmed/src/nvme/executor.rs @@ -34,7 +34,12 @@ impl Hardware for NvmeHw { &VTABLE } fn current() -> std::rc::Rc> { - THE_EXECUTOR.with(|exec| Rc::clone(exec.borrow().as_ref().unwrap())) + THE_EXECUTOR.with(|exec| { + Rc::clone(exec.borrow().as_ref().unwrap_or_else(|| { + log::error!("nvmed: internal error: executor not initialized"); + std::process::exit(1); + })) + }) } fn try_submit( nvme: &Arc, diff --git a/drivers/storage/nvmed/src/nvme/identify.rs b/drivers/storage/nvmed/src/nvme/identify.rs index 05e5b9b2..b1b6e959 100644 --- a/drivers/storage/nvmed/src/nvme/identify.rs +++ b/drivers/storage/nvmed/src/nvme/identify.rs @@ -126,7 +126,7 @@ impl LbaFormat { 0b01 => RelativePerformance::Better, 0b10 => RelativePerformance::Good, 0b11 => RelativePerformance::Degraded, - _ => unreachable!(), + _ => RelativePerformance::Degraded, } } pub fn is_available(&self) -> bool { @@ -153,7 +153,14 @@ impl Nvme { /// Returns the serial number, model, and firmware, in that order. pub async fn identify_controller(&self) { // TODO: Use same buffer - let data: Dma = unsafe { Dma::zeroed().unwrap().assume_init() }; + let data: Dma = unsafe { + Dma::zeroed() + .map(|dma| dma.assume_init()) + .unwrap_or_else(|err| { + log::error!("nvmed: failed to allocate identify DMA: {err}"); + std::process::exit(1); + }) + }; // println!(" - Attempting to identify controller"); let comp = self @@ -182,7 +189,14 @@ impl Nvme { } pub async fn identify_namespace_list(&self, base: u32) -> Vec { // TODO: Use buffer - let data: Dma<[u32; 1024]> = unsafe { Dma::zeroed().unwrap().assume_init() }; + let data: Dma<[u32; 1024]> = unsafe { + Dma::zeroed() + .map(|dma| dma.assume_init()) + .unwrap_or_else(|err| { + log::error!("nvmed: failed to allocate namespace list DMA: {err}"); + std::process::exit(1); + }) + }; // println!(" - Attempting to retrieve namespace ID list"); let comp = self @@ -198,7 +212,14 @@ impl Nvme { } pub async fn identify_namespace(&self, nsid: u32) -> NvmeNamespace { //TODO: Use buffer - let data: Dma = unsafe { Dma::zeroed().unwrap().assume_init() }; + let data: Dma = unsafe { + Dma::zeroed() + .map(|dma| dma.assume_init()) + .unwrap_or_else(|err| { + log::error!("nvmed: failed to allocate namespace DMA: {err}"); + std::process::exit(1); + }) + }; log::debug!("Attempting to identify namespace {nsid}"); let comp = self @@ -216,7 +237,10 @@ impl Nvme { let block_size = data .formatted_lba_size() .lba_data_size() - .expect("nvmed: error: size outside 512-2^64 range"); + .unwrap_or_else(|| { + log::error!("nvmed: error: size outside 512-2^64 range"); + std::process::exit(1); + }); log::debug!("NVME block size: {}", block_size); NvmeNamespace { diff --git a/drivers/storage/nvmed/src/nvme/mod.rs b/drivers/storage/nvmed/src/nvme/mod.rs index 682ee933..90a25d5b 100644 --- a/drivers/storage/nvmed/src/nvme/mod.rs +++ b/drivers/storage/nvmed/src/nvme/mod.rs @@ -160,7 +160,15 @@ impl Nvme { } fn cur_thread_ctxt(&self) -> Arc> { // TODO: multi-threading - Arc::clone(self.thread_ctxts.read().get(&0).unwrap()) + Arc::clone( + self.thread_ctxts + .read() + .get(&0) + .unwrap_or_else(|| { + log::error!("nvmed: internal error: thread context 0 missing"); + std::process::exit(1); + }), + ) } pub unsafe fn submission_queue_tail(&self, qid: u16, tail: u16) { @@ -208,10 +216,22 @@ impl Nvme { } for (qid, iv) in self.cq_ivs.get_mut().iter_mut() { - let ctxt = thread_ctxts.get(&0).unwrap().lock(); + let ctxt = match thread_ctxts.get(&0) { + Some(c) => c.lock(), + None => { + log::error!("nvmed: internal error: thread context 0 missing"); + return Err(Error::new(EIO)); + } + }; let queues = ctxt.queues.borrow(); - let &(ref cq, ref sq) = queues.get(qid).unwrap(); + let (cq, sq) = match queues.get(qid) { + Some(pair) => pair, + None => { + log::error!("nvmed: internal error: queue {qid} missing"); + return Err(Error::new(EIO)); + } + }; log::debug!( "iv {iv} [cq {qid}: {:X}, {}] [sq {qid}: {:X}, {}]", cq.data.physical(), @@ -222,7 +242,13 @@ impl Nvme { } { - let main_ctxt = thread_ctxts.get(&0).unwrap().lock(); + let main_ctxt = match thread_ctxts.get(&0) { + Some(c) => c.lock(), + None => { + log::error!("nvmed: internal error: thread context 0 missing"); + return Err(Error::new(EIO)); + } + }; for (i, prp) in main_ctxt.buffer_prp.borrow_mut().iter_mut().enumerate() { *prp = (main_ctxt.buffer.borrow_mut().physical() + i * 4096) as u64; @@ -231,7 +257,13 @@ impl Nvme { let regs = self.regs.get_mut(); let mut queues = main_ctxt.queues.borrow_mut(); - let (asq, acq) = queues.get_mut(&0).unwrap(); + let (asq, acq) = match queues.get_mut(&0) { + Some(pair) => pair, + None => { + log::error!("nvmed: internal error: admin queue pair missing"); + return Err(Error::new(EIO)); + } + }; regs.aqa .write(((acq.data.len() as u32 - 1) << 16) | (asq.data.len() as u32 - 1)); regs.asq_low.write(asq.data.physical() as u32); @@ -281,14 +313,14 @@ impl Nvme { let vector = vector as u8; if masked { - assert_ne!( + debug_assert_ne!( to_clear & (1 << vector), (1 << vector), "nvmed: internal error: cannot both mask and set" ); to_mask |= 1 << vector; } else { - assert_ne!( + debug_assert_ne!( to_mask & (1 << vector), (1 << vector), "nvmed: internal error: cannot both mask and set" @@ -326,22 +358,27 @@ impl Nvme { cmd_init: impl FnOnce(CmdId) -> NvmeCmd, fail: impl FnOnce(), ) -> Option<(CqId, CmdId)> { - match ctxt.queues.borrow_mut().get_mut(&sq_id).unwrap() { - (sq, _cq) => { - if sq.is_full() { - fail(); - return None; - } - let cmd_id = sq.tail; - let tail = sq.submit_unchecked(cmd_init(cmd_id)); - - // TODO: Submit in bulk - unsafe { - self.submission_queue_tail(sq_id, tail); - } - Some((sq_id, cmd_id)) + let mut queues_ref = ctxt.queues.borrow_mut(); + let (sq, _cq) = match queues_ref.get_mut(&sq_id) { + Some(pair) => pair, + None => { + log::error!("nvmed: internal error: submission queue {sq_id} missing"); + fail(); + return None; } + }; + if sq.is_full() { + fail(); + return None; + } + let cmd_id = sq.tail; + let tail = sq.submit_unchecked(cmd_init(cmd_id)); + + // TODO: Submit in bulk + unsafe { + self.submission_queue_tail(sq_id, tail); } + Some((sq_id, cmd_id)) } pub async fn create_io_completion_queue( @@ -349,13 +386,19 @@ impl Nvme { io_cq_id: CqId, vector: Option, ) -> NvmeCompQueue { - let queue = NvmeCompQueue::new().expect("nvmed: failed to allocate I/O completion queue"); - - let len = u16::try_from(queue.data.len()) - .expect("nvmed: internal error: I/O CQ longer than 2^16 entries"); - let raw_len = len - .checked_sub(1) - .expect("nvmed: internal error: CQID 0 for I/O CQ"); + let queue = NvmeCompQueue::new().unwrap_or_else(|err| { + log::error!("nvmed: failed to allocate I/O completion queue: {err}"); + std::process::exit(1); + }); + + let len = u16::try_from(queue.data.len()).unwrap_or_else(|_| { + log::error!("nvmed: internal error: I/O CQ longer than 2^16 entries"); + std::process::exit(1); + }); + let raw_len = len.checked_sub(1).unwrap_or_else(|| { + log::error!("nvmed: internal error: CQID 0 for I/O CQ"); + std::process::exit(1); + }); let comp = self .submit_and_complete_admin_command(|cid| { @@ -370,22 +413,28 @@ impl Nvme { .await; /*match comp.status.specific { - 1 => panic!("invalid queue identifier"), - 2 => panic!("invalid queue size"), - 8 => panic!("invalid interrupt vector"), + 1 => { log::error!("nvmed: invalid queue identifier"); std::process::exit(1); } + 2 => { log::error!("nvmed: invalid queue size"); std::process::exit(1); } + 8 => { log::error!("nvmed: invalid interrupt vector"); std::process::exit(1); } _ => (), }*/ queue } pub async fn create_io_submission_queue(&self, io_sq_id: SqId, io_cq_id: CqId) -> NvmeCmdQueue { - let q = NvmeCmdQueue::new().expect("failed to create submission queue"); - - let len = u16::try_from(q.data.len()) - .expect("nvmed: internal error: I/O SQ longer than 2^16 entries"); - let raw_len = len - .checked_sub(1) - .expect("nvmed: internal error: SQID 0 for I/O SQ"); + let q = NvmeCmdQueue::new().unwrap_or_else(|err| { + log::error!("nvmed: failed to create submission queue: {err}"); + std::process::exit(1); + }); + + let len = u16::try_from(q.data.len()).unwrap_or_else(|_| { + log::error!("nvmed: internal error: I/O SQ longer than 2^16 entries"); + std::process::exit(1); + }); + let raw_len = len.checked_sub(1).unwrap_or_else(|| { + log::error!("nvmed: internal error: SQID 0 for I/O SQ"); + std::process::exit(1); + }); let comp = self .submit_and_complete_admin_command(|cid| { @@ -399,9 +448,9 @@ impl Nvme { }) .await; /*match comp.status.specific { - 0 => panic!("completion queue invalid"), - 1 => panic!("invalid queue identifier"), - 2 => panic!("invalid queue size"), + 0 => { log::error!("nvmed: completion queue invalid"); std::process::exit(1); } + 1 => { log::error!("nvmed: invalid queue identifier"); std::process::exit(1); } + 2 => { log::error!("nvmed: invalid queue size"); std::process::exit(1); } _ => (), }*/ @@ -431,7 +480,10 @@ impl Nvme { self.thread_ctxts .read() .get(&0) - .unwrap() + .unwrap_or_else(|| { + log::error!("nvmed: internal error: thread context 0 missing"); + std::process::exit(1); + }) .lock() .queues .borrow_mut() @@ -497,8 +549,8 @@ impl Nvme { for chunk in buf.chunks_mut(/* TODO: buf len */ 8192) { let blocks = (chunk.len() + block_size - 1) / block_size; - assert!(blocks > 0); - assert!(blocks <= 0x1_0000); + debug_assert!(blocks > 0); + debug_assert!(blocks <= 0x1_0000); self.namespace_rw(&*ctxt, namespace, lba, (blocks - 1) as u16, false) .await?; @@ -525,8 +577,8 @@ impl Nvme { for chunk in buf.chunks(/* TODO: buf len */ 8192) { let blocks = (chunk.len() + block_size - 1) / block_size; - assert!(blocks > 0); - assert!(blocks <= 0x1_0000); + debug_assert!(blocks > 0); + debug_assert!(blocks <= 0x1_0000); ctxt.buffer.borrow_mut()[..chunk.len()].copy_from_slice(chunk); diff --git a/drivers/storage/nvmed/src/nvme/queues.rs b/drivers/storage/nvmed/src/nvme/queues.rs index a3712aeb..438c905c 100644 --- a/drivers/storage/nvmed/src/nvme/queues.rs +++ b/drivers/storage/nvmed/src/nvme/queues.rs @@ -145,7 +145,7 @@ impl Status { 3 => Self::PathRelatedStatus(code), 4..=6 => Self::Rsvd(code), 7 => Self::Vendor(code), - _ => unreachable!(), + _ => Self::Vendor(code), } } } diff --git a/drivers/storage/virtio-blkd/src/main.rs b/drivers/storage/virtio-blkd/src/main.rs index d21236b3..2b777937 100644 --- a/drivers/storage/virtio-blkd/src/main.rs +++ b/drivers/storage/virtio-blkd/src/main.rs @@ -1,6 +1,7 @@ #![deny(trivial_numeric_casts, unused_allocation)] use std::collections::BTreeMap; +use std::process; use std::sync::{Arc, Weak}; use driver_block::DiskScheme; @@ -59,14 +60,23 @@ impl BlockDeviceConfig { T: Sized + TryFrom, >::Error: std::fmt::Debug, { - let transport = self.0.upgrade().unwrap(); + let transport = self.0.upgrade().unwrap_or_else(|| { + log::error!("virtio-blkd: transport handle dropped"); + process::exit(1); + }); let size = core::mem::size_of::() .try_into() - .expect("load_config: invalid size"); + .unwrap_or_else(|_| { + log::error!("virtio-blkd: load_config: invalid size"); + process::exit(1); + }); let value = transport.load_config(ty as u8, size); - T::try_from(value).unwrap() + T::try_from(value).unwrap_or_else(|_| { + log::error!("virtio-blkd: load_config: invalid config value"); + process::exit(1); + }) } /// Returns the capacity of the block device in bytes. @@ -103,8 +113,11 @@ fn main() { } fn daemon_runner(redox_daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { - daemon(redox_daemon, pcid_handle).unwrap(); - unreachable!(); + daemon(redox_daemon, pcid_handle).unwrap_or_else(|err| { + log::error!("virtio-blkd: daemon failed: {err}"); + process::exit(1); + }); + process::exit(0); } fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> anyhow::Result<()> { @@ -121,7 +134,10 @@ 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 { + log::error!("virtio-blkd: unexpected device ID {:#06x}, expected 0x1001", pci_config.func.full_device_id.device_id); + process::exit(1); + } log::info!("virtio-blk: initiating startup sequence :^)"); let device = virtio_core::probe_device(&mut pcid_handle)?; @@ -147,7 +163,10 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> anyhow: let scheme_name = format!("disk.{}", name); - let event_queue = event::EventQueue::new().unwrap(); + let mut event_queue = event::EventQueue::new().unwrap_or_else(|err| { + log::error!("virtio-blkd: failed to create event queue: {err}"); + process::exit(1); + }); event::user_data! { enum Event { @@ -162,7 +181,10 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> anyhow: &driver_block::FuturesExecutor, ); - libredox::call::setrens(0, 0).expect("nvmed: failed to enter null namespace"); + libredox::call::setrens(0, 0).unwrap_or_else(|err| { + log::error!("virtio-blkd: failed to enter null namespace: {err}"); + process::exit(1); + }); event_queue .subscribe( @@ -170,11 +192,26 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> anyhow: Event::Scheme, event::EventFlags::READ, ) - .unwrap(); - - for event in event_queue { - match event.unwrap().user_data { - Event::Scheme => futures::executor::block_on(scheme.tick()).unwrap(), + .unwrap_or_else(|err| { + log::error!("virtio-blkd: failed to subscribe to scheme events: {err}"); + process::exit(1); + }); + + loop { + let event = match event_queue.next() { + Some(Ok(event)) => event, + Some(Err(err)) => { + log::error!("virtio-blkd: failed to get event: {err}"); + continue; + } + None => break, + }; + match event.user_data { + Event::Scheme => { + if let Err(err) = futures::executor::block_on(scheme.tick()) { + log::error!("virtio-blkd: failed to handle scheme event: {err}"); + } + } } } diff --git a/drivers/storage/virtio-blkd/src/scheme.rs b/drivers/storage/virtio-blkd/src/scheme.rs index ec4ecf73..39fb24a8 100644 --- a/drivers/storage/virtio-blkd/src/scheme.rs +++ b/drivers/storage/virtio-blkd/src/scheme.rs @@ -15,19 +15,34 @@ trait BlkExtension { impl BlkExtension for Queue<'_> { async fn read(&self, block: u64, target: &mut [u8]) -> usize { - let req = Dma::new(BlockVirtRequest { + let req = match Dma::new(BlockVirtRequest { ty: BlockRequestTy::In, reserved: 0, sector: block, - }) - .unwrap(); + }) { + Ok(req) => req, + Err(err) => { + log::error!("virtio-blkd: failed to allocate read request DMA: {err}"); + return 0; + } + }; let result = unsafe { - Dma::<[u8]>::zeroed_slice(target.len()) - .unwrap() - .assume_init() + match Dma::<[u8]>::zeroed_slice(target.len()) { + Ok(dma) => dma.assume_init(), + Err(err) => { + log::error!("virtio-blkd: failed to allocate read buffer DMA: {err}"); + return 0; + } + } + }; + let status = match Dma::new(u8::MAX) { + Ok(s) => s, + Err(err) => { + log::error!("virtio-blkd: failed to allocate read status DMA: {err}"); + return 0; + } }; - let status = Dma::new(u8::MAX).unwrap(); let chain = ChainBuilder::new() .chain(Buffer::new(&req)) @@ -37,28 +52,46 @@ impl BlkExtension for Queue<'_> { // XXX: Subtract 1 because the of status byte. let written = self.send(chain).await as usize - 1; - assert_eq!(*status, 0); + if *status != 0 { + log::error!("virtio-blkd: read failed with status {}", *status); + return 0; + } target[..written].copy_from_slice(&result); written } async fn write(&self, block: u64, target: &[u8]) -> usize { - let req = Dma::new(BlockVirtRequest { + let req = match Dma::new(BlockVirtRequest { ty: BlockRequestTy::Out, reserved: 0, sector: block, - }) - .unwrap(); + }) { + Ok(req) => req, + Err(err) => { + log::error!("virtio-blkd: failed to allocate write request DMA: {err}"); + return 0; + } + }; let mut result = unsafe { - Dma::<[u8]>::zeroed_slice(target.len()) - .unwrap() - .assume_init() + match Dma::<[u8]>::zeroed_slice(target.len()) { + Ok(dma) => dma.assume_init(), + Err(err) => { + log::error!("virtio-blkd: failed to allocate write buffer DMA: {err}"); + return 0; + } + } }; result.copy_from_slice(target.as_ref()); - let status = Dma::new(u8::MAX).unwrap(); + let status = match Dma::new(u8::MAX) { + Ok(s) => s, + Err(err) => { + log::error!("virtio-blkd: failed to allocate write status DMA: {err}"); + return 0; + } + }; let chain = ChainBuilder::new() .chain(Buffer::new(&req)) @@ -67,7 +100,10 @@ impl BlkExtension for Queue<'_> { .build(); self.send(chain).await as usize; - assert_eq!(*status, 0); + if *status != 0 { + log::error!("virtio-blkd: write failed with status {}", *status); + return 0; + } target.len() } 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..0d2ec432 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, @@ -308,9 +365,97 @@ struct PortState { slot: u8, protocol_speed: &'static ProtocolSpeed, cfg_idx: Option, + active_ifaces: BTreeMap, // iface number → active alternate setting 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 +608,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 +939,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 +957,102 @@ 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, + active_ifaces: BTreeMap::new(), + 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 +1062,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 self.spawn_drivers(port_id) { - Ok(()) => (), - Err(err) => { - error!("Failed to spawn driver for port {}: `{}`", port_id, err) + 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)); } + + 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) + } } } @@ -1246,14 +1501,12 @@ impl Xhci { let drivers_usercfg: &DriversConfig = &DRIVERS_CONFIG; for ifdesc in config_desc.interface_descs.iter() { - //TODO: support alternate settings - // This is difficult because the device driver must know which alternate - // to use, but if alternates can have different classes, then a different - // device driver may be required for each alternate. For now, we will use - // only the default alternate setting (0) + // Only auto-spawn drivers for the default alternate setting (0). + // Non-default alternates are selected later by the device driver + // via SET_INTERFACE + configure_endpoints with specific alternate_setting. if ifdesc.alternate_setting != 0 { - warn!( - "ignoring port {} iface {} alternate {} class {}.{} proto {}", + debug!( + "skipping port {} iface {} alternate {} class {}.{} proto {} (non-default alternate)", port, ifdesc.number, ifdesc.alternate_setting, @@ -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 ca27b3fe..29437294 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; @@ -33,9 +34,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}; @@ -61,10 +62,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$") @@ -138,12 +145,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, } @@ -173,6 +183,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 @@ -188,6 +200,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 { @@ -210,6 +226,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) } @@ -236,6 +255,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(""), } } @@ -259,10 +284,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, @@ -290,10 +318,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, @@ -384,6 +415,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)?; @@ -392,6 +431,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)?; @@ -524,6 +567,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 { @@ -640,6 +716,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; @@ -710,6 +788,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)?; @@ -835,7 +915,10 @@ impl Xhci { port, usb::Setup::set_interface(interface_num, alternate_setting), ) - .await + .await?; + let mut port_state = self.port_states.get_mut(&port).ok_or(Error::new(EBADFD))?; + port_state.active_ifaces.insert(interface_num, alternate_setting); + Ok(()) } async fn reset_endpoint(&self, port_num: PortId, endp_num: u8, tsp: bool) -> Result<()> { @@ -950,35 +1033,114 @@ 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 = if let Some(iface_num) = req.interface_desc { + let alt = req.alternate_setting.unwrap_or(0); + config_desc + .interface_descs + .iter() + .filter(|if_desc| if_desc.number == iface_num && if_desc.alternate_setting == alt) + .flat_map(|if_desc| if_desc.endpoints.iter().copied()) + .collect::>() + } else { + config_desc + .interface_descs + .iter() + .filter(|if_desc| if_desc.alternate_setting == 0) + .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; @@ -989,74 +1151,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); - } + let mut staged_endpoint_states = BTreeMap::new(); + let mut endpoint_programs = Vec::new(); - for endp_idx in 0..endp_desc_count as u8 { - let endp_num = endp_idx + 1; - - let mut port_state = self.port_states.get_mut(&port).ok_or(Error::new(EBADFD))?; - let dev_desc = port_state.dev_desc.as_ref().unwrap(); - let endp_desc = port_state.get_endp_desc(endp_idx).ok_or_else(|| { - warn!("failed to find endpoint {}", endp_idx); - Error::new(EIO) - })?; - - let endp_num_xhc = Self::endp_num_to_dci(endp_num, endp_desc); + 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(); @@ -1078,20 +1188,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()?; @@ -1114,7 +1224,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))?; @@ -1127,15 +1237,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(); @@ -1145,68 +1253,205 @@ 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::>(); + + // 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); - //self.event_handler_finished(); + 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); + } + + 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); + } - handle_event_trb("CONFIGURE_ENDPOINT", &event_trb, &command_trb)?; + if self.consume_test_hook("fail_after_set_configuration") { + info!( + "xhcid: test hook injecting failure after SET_CONFIGURATION for port {}", + port + ); + self.rollback_configure_attempt( + port, + slot, + configure_snapshot, + &endpoint_snapshots, + "test hook fail_after_set_configuration", + ) + .await; + return Err(Error::new(EIO)); } - // Tell the device about this configuration. - self.set_configuration(port, configuration_value).await?; + { + let mut port_state = self.port_states.get_mut(&port).ok_or(Error::new(EBADFD))?; + port_state.cfg_idx = Some(configuration_value); + port_state.endpoint_states.retain(|endp_num, _| *endp_num == 0); + for (endp_num, endpoint_state) in staged_endpoint_states { + port_state.endpoint_states.insert(endp_num, endpoint_state); + } + if let Some(iface_num) = req.interface_desc { + let alt = req.alternate_setting.unwrap_or(0); + port_state.active_ifaces.insert(iface_num, alt); + } else if port_state.active_ifaces.is_empty() { + let default_iface_entries: Vec<(u8, u8)> = port_state + .dev_desc + .as_ref() + .and_then(|dd| dd.config_descs.iter().find(|cd| cd.configuration_value == configuration_value)) + .map(|cd| { + cd.interface_descs + .iter() + .filter(|if_desc| if_desc.alternate_setting == 0) + .map(|if_desc| (if_desc.number, 0u8)) + .collect() + }) + .unwrap_or_default(); + for (iface_num, alt) in default_iface_entries { + port_state.active_ifaces.insert(iface_num, alt); + } + } + } Ok(()) } @@ -1857,7 +2102,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 @@ -1894,6 +2139,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 @@ -2088,6 +2341,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 @@ -2156,6 +2433,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)? } @@ -2174,6 +2454,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); @@ -2204,7 +2490,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; } _ => {} @@ -2254,6 +2544,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 { @@ -2285,6 +2577,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 @@ -2324,6 +2620,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 => { @@ -2348,6 +2652,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))?; @@ -2398,6 +2750,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..c5b2767f 100644 --- a/drivers/virtio-core/src/arch/x86.rs +++ b/drivers/virtio-core/src/arch/x86.rs @@ -11,7 +11,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!(), + _ => { + log::warn!("virtio_core::enable_msix: expected MSI-X feature info"); + return Err(Error::Probe("unexpected PCI feature info for MSI-X")); + } }; let mut info = unsafe { msix_info.map_and_mask_all(pcid_handle) }; @@ -21,7 +24,10 @@ 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 destination_id = read_bsp_apic_id().map_err(|e| { + log::warn!("virtio_core::enable_msix: read_bsp_apic_id failed: {e}"); + Error::Probe("read_bsp_apic_id failed") + })?; let (msg_addr_and_data, interrupt_handle) = allocate_single_interrupt_vector_for_msi(destination_id); table_entry_pointer.write_addr_and_data(msg_addr_and_data); diff --git a/drivers/virtio-core/src/probe.rs b/drivers/virtio-core/src/probe.rs index 5631ef67..eaef1b96 100644 --- a/drivers/virtio-core/src/probe.rs +++ b/drivers/virtio-core/src/probe.rs @@ -31,16 +31,16 @@ pub const MSIX_PRIMARY_VECTOR: u16 = 0; /// before starting the device. /// * 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 { + log::warn!( + "virtio_core::probe_device: skipping non-virtio device (vendor ID {:#06x})", + pci_config.func.full_device_id.vendor_id + ); + return Err(Error::Probe("not a virtio device")); + } let mut common_addr = None; let mut notify_addr = None; @@ -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::Probe("BAR is not memory-mapped"))?; let address = unsafe { let addr = addr + capability.offset as usize; @@ -100,19 +102,23 @@ 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"); - - // 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" - ); + let common_addr = common_addr.ok_or(Error::InCapable(CfgType::Common))?; + let device_addr = device_addr.ok_or(Error::InCapable(CfgType::Device))?; + let (notify_addr, notify_multiplier) = + notify_addr.ok_or(Error::InCapable(CfgType::Notify))?; + + // The virtio specification explicitly allows a zero notify_off_multiplier, + // meaning all queues share the same notification address. Handle gracefully. + if notify_multiplier == 0 { + log::warn!( + "virtio_core::probe_device: device uses the same Queue Notify address for all queues" + ); + return Err(Error::Probe("zero notify_off_multiplier")); + } let common = unsafe { &mut *(common_addr as *mut CommonCfg) }; let device_space = unsafe { &mut *(device_addr as *mut u8) }; @@ -128,8 +134,10 @@ pub fn probe_device(pcid_handle: &mut PciFunctionHandle) -> Result Vec { - let last_buffer = self.buffers.last_mut().expect("virtio-core: empty chain"); - last_buffer.flags.remove(DescriptorFlags::NEXT); - + if let Some(last_buffer) = self.buffers.last_mut() { + last_buffer.flags.remove(DescriptorFlags::NEXT); + } self.buffers } } diff --git a/drivers/virtio-core/src/transport.rs b/drivers/virtio-core/src/transport.rs index d3445d2d..99972c95 100644 --- a/drivers/virtio-core/src/transport.rs +++ b/drivers/virtio-core/src/transport.rs @@ -19,6 +19,8 @@ pub enum Error { SyscallError(#[from] libredox::error::Error), #[error("the device is incapable of {0:?}")] InCapable(CfgType), + #[error("virtio probe: {0}")] + Probe(&'static str), } /// Returns the queue part sizes in bytes. @@ -59,14 +61,23 @@ pub fn spawn_irq_thread(irq_handle: &File, queue: &Arc>) { let queue_copy = queue.clone(); std::thread::spawn(move || { - let event_queue = RawEventQueue::new().unwrap(); + let event_queue = match RawEventQueue::new() { + Ok(eq) => eq, + Err(err) => { + log::error!("virtio-core: failed to create event queue for IRQ thread: {err}"); + return; + } + }; - event_queue - .subscribe(irq_fd as usize, 0, event::EventFlags::READ) - .unwrap(); + if let Err(err) = event_queue.subscribe(irq_fd as usize, 0, event::EventFlags::READ) { + log::error!("virtio-core: failed to subscribe to IRQ fd: {err}"); + return; + } - for _ in event_queue.map(Result::unwrap) { - // Wake up the tasks waiting on the queue. + for event_result in event_queue.map(|res| res) { + if event_result.is_err() { + break; + } for (_, task) in queue_copy.waker.lock().unwrap().iter() { task.wake_by_ref(); } @@ -604,7 +615,9 @@ impl Transport for StandardTransport<'_> { // 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); + if (confirm & DeviceStatusFlags::FEATURES_OK) != DeviceStatusFlags::FEATURES_OK { + log::error!("virtio-core: device rejected feature set (FEATURES_OK cleared after negotiation)"); + } } fn setup_config_notify(&self, vector: u16) { @@ -640,7 +653,10 @@ 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 { + log::error!("virtio-core: MSI-X vector {vector:#x} was not accepted by device for queue {queue_index}"); + return Err(Error::SyscallError(libredox::error::Error::new(libredox::errno::EIO))); + } // Enable the queue. common.queue_enable.set(1); @@ -685,7 +701,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 { + log::error!("virtio-core: MSI-X vector {:#x} was not accepted during reinit for queue {}", queue.vector, queue.queue_index); + } // Enable the queue. common.queue_enable.set(1); diff --git a/init.initfs.d/40_drivers.target b/init.initfs.d/40_drivers.target index 8ddb4795..029583a1 100644 --- a/init.initfs.d/40_drivers.target +++ b/init.initfs.d/40_drivers.target @@ -7,4 +7,5 @@ requires_weak = [ "40_bcm2835-sdhcid.service", "40_hwd.service", "40_pcid-spawner-initfs.service", + "41_acpid.service", ] diff --git a/init.initfs.d/40_hwd.service b/init.initfs.d/40_hwd.service index cba12dde..cf34a51b 100644 --- a/init.initfs.d/40_hwd.service +++ b/init.initfs.d/40_hwd.service @@ -1,6 +1,6 @@ [unit] description = "Hardware manager" -requires_weak = ["10_inputd.service", "10_lived.service", "20_graphics.target"] +requires_weak = ["10_inputd.service", "10_lived.service", "20_graphics.target", "41_acpid.service"] [service] cmd = "hwd" diff --git a/init.initfs.d/40_pcid-spawner-initfs.service b/init.initfs.d/40_pcid-spawner-initfs.service index 6945b9ea..ba1ee0bb 100644 --- a/init.initfs.d/40_pcid-spawner-initfs.service +++ b/init.initfs.d/40_pcid-spawner-initfs.service @@ -1,6 +1,6 @@ [unit] description = "PCI driver spawner" -requires_weak = ["10_inputd.service", "20_graphics.target", "40_hwd.service"] +requires_weak = ["10_inputd.service", "20_graphics.target", "40_hwd.service", "41_acpid.service"] [service] cmd = "pcid-spawner" diff --git a/init/src/main.rs b/init/src/main.rs index 5682cf44..ed436619 100644 --- a/init/src/main.rs +++ b/init/src/main.rs @@ -117,6 +117,8 @@ fn main() { let mut unit_store = UnitStore::new(); let mut scheduler = Scheduler::new(); + eprintln!("init: phase 1 — initfs boot"); + switch_root( &mut unit_store, &mut init_config, @@ -125,6 +127,7 @@ fn main() { ); // Start logd first such that we can pass /scheme/log as stdio to all other services + eprintln!("init: starting logd"); scheduler .schedule_start_and_report_errors(&mut unit_store, UnitId("00_logd.service".to_owned())); scheduler.step(&mut unit_store, &mut init_config); @@ -132,14 +135,18 @@ fn main() { eprintln!("init: failed to switch stdio to '/scheme/log': {err}"); } + eprintln!("init: starting runtime target"); let runtime_target = UnitId("00_runtime.target".to_owned()); scheduler.schedule_start_and_report_errors(&mut unit_store, runtime_target.clone()); unit_store.set_runtime_target(runtime_target); + eprintln!("init: starting initfs drivers target"); scheduler .schedule_start_and_report_errors(&mut unit_store, UnitId("90_initfs.target".to_owned())); scheduler.step(&mut unit_store, &mut init_config); + eprintln!("init: initfs drivers target step() complete"); + eprintln!("init: phase 2 — switchroot to /usr"); switch_root( &mut unit_store, &mut init_config, @@ -162,23 +169,64 @@ fn main() { .collect::>() .join(", ") ); - return; + Vec::new() } }; + eprintln!("init: scheduling {} rootfs units", entries.len()); for entry in entries { + let name = match entry.file_name().and_then(|n| n.to_str()) { + Some(name) => name, + None => { + eprintln!("init: skipping config entry with non-UTF-8 filename"); + continue; + } + }; scheduler.schedule_start_and_report_errors( &mut unit_store, - UnitId(entry.file_name().unwrap().to_str().unwrap().to_owned()), + UnitId(name.to_owned()), ); } }; scheduler.step(&mut unit_store, &mut init_config); + eprintln!("init: phase 3 — rootfs services started"); + + if let Err(err) = libredox::call::setrens(0, 0) { + eprintln!("init: failed to enter null namespace: {err}"); + } - libredox::call::setrens(0, 0).expect("init: failed to enter null namespace"); + eprintln!("init: boot complete — entering waitpid loop"); + + let mut respawn_map: BTreeMap = BTreeMap::new(); + for (unit_id, pid) in scheduler.respawn_pids { + respawn_map.insert(pid, unit_id); + } loop { let mut status = 0; - libredox::call::waitpid(0, &mut status, 0).unwrap(); + match libredox::call::waitpid(0, &mut status, 0) { + Ok(pid) => { + if let Some(unit_id) = respawn_map.remove(&(pid as u32)) { + eprintln!("init: respawning {} (pid {} exited)", unit_id.0, pid); + let mut resp_scheduler = Scheduler::new(); + resp_scheduler.schedule_start_and_report_errors( + &mut unit_store, + unit_id.clone(), + ); + resp_scheduler.step(&mut unit_store, &mut init_config); + for (uid, new_pid) in resp_scheduler.respawn_pids { + respawn_map.insert(new_pid, uid); + } + } + } + Err(err) => { + // EAGAIN is normal (no child exited yet). Other errors are + // unexpected but init must never crash — log and continue. + if err.errno() != syscall::EAGAIN { + eprintln!("init: waitpid error: {err}"); + } + std::thread::sleep(std::time::Duration::from_millis(100)); + } + } } } diff --git a/init/src/scheduler.rs b/init/src/scheduler.rs index d42a4e57..64e64e1e 100644 --- a/init/src/scheduler.rs +++ b/init/src/scheduler.rs @@ -5,6 +5,7 @@ use crate::unit::{Unit, UnitId, UnitKind, UnitStore}; pub struct Scheduler { pending: VecDeque, + pub respawn_pids: Vec<(UnitId, u32)>, } struct Job { @@ -20,6 +21,7 @@ impl Scheduler { pub fn new() -> Scheduler { Scheduler { pending: VecDeque::new(), + respawn_pids: Vec::new(), } } @@ -43,7 +45,10 @@ impl Scheduler { ) { let loaded_units = unit_store.load_units(unit_id.clone(), errors); for unit_id in loaded_units { - if !unit_store.unit(&unit_id).conditions_met() { + let Some(unit) = unit_store.unit(&unit_id) else { + continue; + }; + if !unit.conditions_met() { continue; } @@ -62,7 +67,10 @@ impl Scheduler { match job.kind { JobKind::Start => { - let unit = unit_store.unit_mut(&job.unit); + let Some(unit) = unit_store.unit_mut(&job.unit) else { + eprintln!("init: unit {} not found in store, skipping", job.unit.0); + continue 'a; + }; for dep in &unit.info.requires_weak { for pending_job in &self.pending { @@ -73,14 +81,17 @@ impl Scheduler { } } - run(unit, init_config); + let pid = run(unit, init_config); + if let Some(pid) = pid { + self.respawn_pids.push((job.unit.clone(), pid)); + } } } } } } -fn run(unit: &mut Unit, config: &mut InitConfig) { +fn run(unit: &mut Unit, config: &mut InitConfig) -> Option { match &unit.kind { UnitKind::LegacyScript { script } => { for cmd in script.clone() { @@ -92,25 +103,30 @@ fn run(unit: &mut Unit, config: &mut InitConfig) { } UnitKind::Service { service } => { if config.skip_cmd.contains(&service.cmd) { - eprintln!("Skipping '{} {}'", service.cmd, service.args.join(" ")); - return; + eprintln!("init: skipping {} {}", service.cmd, service.args.join(" ")); + return None; } - if config.log_debug { + eprintln!( + "init: starting {} ({})", + unit.info.description.as_ref().unwrap_or(&unit.id.0), + service.cmd, + ); + let pid = service.spawn(&config.envs); + if pid.is_some() { eprintln!( - "Starting {} ({})", + "init: started {} (pid {})", unit.info.description.as_ref().unwrap_or(&unit.id.0), - service.cmd, + pid.unwrap_or(0), ); } - service.spawn(&config.envs); + return pid; } UnitKind::Target {} => { - if config.log_debug { - eprintln!( - "Reached target {}", - unit.info.description.as_ref().unwrap_or(&unit.id.0), - ); - } + eprintln!( + "init: reached target {}", + unit.info.description.as_ref().unwrap_or(&unit.id.0), + ); } } + None } diff --git a/init/src/service.rs b/init/src/service.rs index ed0023e9..cc95d02b 100644 --- a/init/src/service.rs +++ b/init/src/service.rs @@ -22,6 +22,8 @@ pub struct Service { pub inherit_envs: BTreeSet, #[serde(rename = "type")] pub type_: ServiceType, + #[serde(default)] + pub respawn: bool, } #[derive(Clone, Debug, Default, Deserialize)] @@ -35,7 +37,7 @@ pub enum ServiceType { } impl Service { - pub fn spawn(&self, base_envs: &BTreeMap) { + pub fn spawn(&self, base_envs: &BTreeMap) -> Option { let mut command = Command::new(&self.cmd); command.args(self.args.iter().map(|arg| subst_env(arg))); command.env_clear(); @@ -46,20 +48,28 @@ impl Service { } command.envs(base_envs).envs(&self.envs); - let (mut read_pipe, write_pipe) = io::pipe().unwrap(); + let (mut read_pipe, write_pipe) = match io::pipe() { + Ok(pair) => pair, + Err(err) => { + eprintln!("init: failed to create readiness pipe for {:?}: {err}", command); + return None; + } + }; unsafe { pass_fd(&mut command, "INIT_NOTIFY", write_pipe.into()) }; let mut child = match command.spawn() { Ok(child) => child, Err(err) => { eprintln!("init: failed to execute {:?}: {}", command, err); - return; + return None; } }; match &self.type_ { ServiceType::Notify => match read_pipe.read_exact(&mut [0]) { - Ok(()) => {} + Ok(()) => { + eprintln!("init: {} ready (notify)", self.cmd); + } Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => { eprintln!("init: {command:?} exited without notifying readiness"); } @@ -81,23 +91,34 @@ impl Service { }) => continue, Ok(0) => { eprintln!("init: {command:?} exited without notifying readiness"); - return; + return None; } Ok(1) => break, Ok(n) => { eprintln!("init: incorrect amount of fds {n} returned"); - return; + return None; } Err(err) => { eprintln!("init: failed to wait for {command:?}: {err}"); - return; + return None; } } } - let current_namespace_fd = libredox::call::getns().expect("TODO"); - libredox::call::register_scheme_to_ns(current_namespace_fd, scheme, new_fd) - .expect("TODO"); + let current_namespace_fd = match libredox::call::getns() { + Ok(fd) => fd, + Err(err) => { + eprintln!("init: failed to get current namespace for {command:?}: {err}"); + return None; + } + }; + if let Err(err) = + libredox::call::register_scheme_to_ns(current_namespace_fd, scheme, new_fd) + { + eprintln!("init: failed to register scheme {scheme:?} for {command:?}: {err}"); + } else { + eprintln!("init: {} ready (scheme {})", self.cmd, scheme); + } } ServiceType::Oneshot => { drop(read_pipe); @@ -105,6 +126,8 @@ impl Service { Ok(exit_status) => { if !exit_status.success() { eprintln!("init: {command:?} failed with {exit_status}"); + } else { + eprintln!("init: {} done (oneshot)", self.cmd); } } Err(err) => { @@ -112,8 +135,13 @@ impl Service { } } } - ServiceType::OneshotAsync => {} + ServiceType::OneshotAsync => { + if self.respawn { + return Some(child.id()); + } + } } + None } } diff --git a/init/src/unit.rs b/init/src/unit.rs index 98053cb2..a58bfb96 100644 --- a/init/src/unit.rs +++ b/init/src/unit.rs @@ -23,8 +23,14 @@ impl UnitStore { } pub fn set_runtime_target(&mut self, unit_id: UnitId) { - assert!(self.runtime_target.is_none()); - assert!(self.units.contains_key(&unit_id)); + if self.runtime_target.is_some() { + eprintln!("init: runtime target already set, ignoring {}", unit_id.0); + return; + } + if !self.units.contains_key(&unit_id) { + eprintln!("init: runtime target {} not found in unit store", unit_id.0); + return; + } self.runtime_target = Some(unit_id); } @@ -85,8 +91,10 @@ impl UnitStore { let unit = self.load_single_unit(unit_id, errors); if let Some(unit) = unit { loaded_units.push(unit.clone()); - for dep in &self.unit(&unit).info.requires_weak { - pending_units.push(dep.clone()); + if let Some(u) = self.unit(&unit) { + for dep in &u.info.requires_weak { + pending_units.push(dep.clone()); + } } } } @@ -94,12 +102,12 @@ impl UnitStore { loaded_units } - pub fn unit(&self, unit: &UnitId) -> &Unit { - self.units.get(unit).unwrap() + pub fn unit(&self, unit: &UnitId) -> Option<&Unit> { + self.units.get(unit) } - pub fn unit_mut(&mut self, unit: &UnitId) -> &mut Unit { - self.units.get_mut(unit).unwrap() + pub fn unit_mut(&mut self, unit: &UnitId) -> Option<&mut Unit> { + self.units.get_mut(unit) } } @@ -180,7 +188,7 @@ impl Unit { ) -> io::Result { let config = fs::read_to_string(config_path)?; - let Some(ext) = config_path.extension().map(|ext| ext.to_str().unwrap()) else { + let Some(ext) = config_path.extension().and_then(|ext| ext.to_str()) else { let script = Script::from_str(&config, errors)?; return Ok(Unit { id, diff --git a/logd/src/main.rs b/logd/src/main.rs index 3636f1fa..559d8993 100644 --- a/logd/src/main.rs +++ b/logd/src/main.rs @@ -6,18 +6,30 @@ use crate::scheme::LogScheme; mod scheme; fn daemon(daemon: daemon::SchemeDaemon) -> ! { - let socket = Socket::create().expect("logd: failed to create log scheme"); + let socket = match Socket::create() { + Ok(s) => s, + Err(e) => { + eprintln!("logd: failed to create log scheme: {e}"); + std::process::exit(1); + } + }; let mut scheme = LogScheme::new(&socket); let handler = Blocking::new(&socket, 16); let _ = daemon.ready_sync_scheme(&socket, &mut scheme); - libredox::call::setrens(0, 0).expect("logd: failed to enter null namespace"); - - handler - .process_requests_blocking(scheme) - .expect("logd: failed to process requests"); + if let Err(e) = libredox::call::setrens(0, 0) { + eprintln!("logd: failed to enter null namespace: {e}"); + } + + match handler.process_requests_blocking(scheme) { + Ok(never) => match never {}, + Err(e) => { + eprintln!("logd: failed to process requests: {e}"); + std::process::exit(1); + } + } } fn main() { diff --git a/logd/src/scheme.rs b/logd/src/scheme.rs index 070de3d6..ef3e175c 100644 --- a/logd/src/scheme.rs +++ b/logd/src/scheme.rs @@ -22,7 +22,7 @@ pub enum LogHandle { pub struct LogScheme<'sock> { socket: &'sock Socket, - kernel_debug: File, + kernel_debug: Option, output_tx: Sender, handles: HandleMap, } @@ -34,12 +34,24 @@ enum OutputCmd { impl<'sock> LogScheme<'sock> { pub fn new(socket: &'sock Socket) -> Self { - let kernel_debug = OpenOptions::new() + let kernel_debug = match OpenOptions::new() .write(true) .open("/scheme/debug") - .unwrap(); + { + Ok(f) => Some(f), + Err(e) => { + eprintln!("logd: failed to open /scheme/debug: {e}"); + None + } + }; - let mut kernel_sys_log = std::fs::File::open("/scheme/sys/log").unwrap(); + let kernel_sys_log = match std::fs::File::open("/scheme/sys/log") { + Ok(f) => Some(f), + Err(e) => { + eprintln!("logd: failed to open /scheme/sys/log: {e}"); + None + } + }; let (output_tx, output_rx) = mpsc::channel::(); @@ -72,20 +84,28 @@ impl<'sock> LogScheme<'sock> { } }); - let output_tx2 = output_tx.clone(); - std::thread::spawn(move || { - let mut handle_buf = vec![]; - let mut buf = [0; 4096]; - buf[.."kernel: ".len()].copy_from_slice(b"kernel: "); - loop { - let n = kernel_sys_log.read(&mut buf["kernel: ".len()..]).unwrap(); - if n == 0 { - // FIXME currently possible as /scheme/log/kernel presents a snapshot of the log queue - break; + if let Some(mut kernel_sys_log) = kernel_sys_log { + let output_tx2 = output_tx.clone(); + std::thread::spawn(move || { + let mut handle_buf = vec![]; + let mut buf = [0; 4096]; + buf[.."kernel: ".len()].copy_from_slice(b"kernel: "); + loop { + let n = match kernel_sys_log.read(&mut buf["kernel: ".len()..]) { + Ok(n) => n, + Err(e) => { + eprintln!("logd: error reading kernel log: {e}"); + break; + } + }; + if n == 0 { + // FIXME currently possible as /scheme/log/kernel presents a snapshot of the log queue + break; + } + Self::write_logs(&output_tx2, &mut handle_buf, "kernel", &buf, None); } - Self::write_logs(&output_tx2, &mut handle_buf, "kernel", &buf, None); - } - }); + }); + } LogScheme { socket, @@ -120,9 +140,9 @@ impl<'sock> LogScheme<'sock> { let _ = kernel_debug.flush(); } - output_tx - .send(OutputCmd::Log(mem::take(handle_buf))) - .unwrap(); + if let Err(e) = output_tx.send(OutputCmd::Log(mem::take(handle_buf))) { + eprintln!("logd: failed to send log output: {e}"); + } } i += 1; @@ -196,7 +216,7 @@ impl<'sock> SchemeSync for LogScheme<'sock> { handle_buf, context, buf, - Some(&mut self.kernel_debug), + self.kernel_debug.as_mut(), ); Ok(buf.len()) @@ -217,7 +237,10 @@ impl<'sock> SchemeSync for LogScheme<'sock> { ) { return Err(e); } - self.output_tx.send(OutputCmd::AddSink(new_fd)).unwrap(); + if let Err(e) = self.output_tx.send(OutputCmd::AddSink(new_fd)) { + eprintln!("logd: failed to add log sink: {e}"); + return Err(Error::new(EIO)); + } Ok(1) } diff --git a/randd/src/main.rs b/randd/src/main.rs index d68dd732..5c330719 100644 --- a/randd/src/main.rs +++ b/randd/src/main.rs @@ -41,7 +41,11 @@ fn create_rdrand_seed() -> [u8; SEED_BYTES] { let mut have_seeded = false; #[cfg(target_arch = "x86_64")] { - if CpuId::new().get_feature_info().unwrap().has_rdrand() { + if CpuId::new() + .get_feature_info() + .map(|info| info.has_rdrand()) + .unwrap_or(false) + { for i in 0..SEED_BYTES / 8 { // We get 8 bytes at a time from rdrand instruction let rand: u64; @@ -81,7 +85,7 @@ fn create_rdrand_seed() -> [u8; SEED_BYTES] { } } // TODO integrate alternative entropy sources if !have_seeded { - println!("randd: Seeding failed, no entropy source. Random numbers on this platform are NOT SECURE"); + eprintln!("randd: no hardware entropy source, random numbers are NOT SECURE"); } rng } @@ -450,18 +454,32 @@ impl SchemeSync for RandScheme { } fn daemon(daemon: daemon::SchemeDaemon) -> ! { - let socket = Socket::create().expect("randd: failed to create rand scheme"); + let socket = match Socket::create() { + Ok(s) => s, + Err(e) => { + eprintln!("randd: failed to create rand scheme: {e}"); + std::process::exit(1); + } + }; let mut scheme = RandScheme::new(); - let handler = Blocking::new(&socket, 16); let _ = daemon.ready_sync_scheme(&socket, &mut scheme); - libredox::call::setrens(0, 0).expect("randd: failed to enter null namespace"); + if let Err(e) = libredox::call::setrens(0, 0) { + eprintln!("randd: failed to enter null namespace: {e}"); + } - handler - .process_requests_blocking(scheme) - .expect("randd: failed to process events from zero scheme"); + loop { + let handler = Blocking::new(&socket, 16); + match handler.process_requests_blocking(scheme) { + Ok(never) => never, + Err(e) => { + eprintln!("randd: error processing requests: {e}"); + scheme = RandScheme::new(); + } + } + } } fn main() { diff --git a/zerod/src/main.rs b/zerod/src/main.rs index c9bd1465..59f6b97c 100644 --- a/zerod/src/main.rs +++ b/zerod/src/main.rs @@ -5,6 +5,7 @@ use scheme_utils::Blocking; mod scheme; +#[derive(Clone, Copy)] enum Ty { Null, Zero, @@ -15,21 +16,36 @@ fn main() { } fn daemon(daemon: daemon::SchemeDaemon) -> ! { - let ty = match &*std::env::args().nth(1).unwrap() { - "null" => Ty::Null, - "zero" => Ty::Zero, - _ => panic!("needs to be called with either null or zero as argument"), + let ty = match std::env::args().nth(1).as_deref() { + Some("null") => Ty::Null, + Some("zero") | None => Ty::Zero, + Some(other) => { + eprintln!("zerod: unknown argument '{other}', use 'null' or 'zero'"); + std::process::exit(1); + } }; - let socket = Socket::create().expect("zerod: failed to create zero scheme"); + let socket = match Socket::create() { + Ok(s) => s, + Err(e) => { + eprintln!("zerod: failed to create zero scheme: {e}"); + std::process::exit(1); + } + }; let mut zero_scheme = ZeroScheme(ty); - let zero_handler = Blocking::new(&socket, 16); let _ = daemon.ready_sync_scheme(&socket, &mut zero_scheme); - libredox::call::setrens(0, 0).expect("zerod: failed to enter null namespace"); - - zero_handler - .process_requests_blocking(zero_scheme) - .expect("zerod: failed to process events from zero scheme"); + if let Err(e) = libredox::call::setrens(0, 0) { + eprintln!("zerod: failed to enter null namespace: {e}"); + } + + loop { + let zero_handler = Blocking::new(&socket, 16); + let scheme = ZeroScheme(ty); + match zero_handler.process_requests_blocking(scheme) { + Ok(never) => never, + Err(e) => eprintln!("zerod: error processing requests: {e}"), + } + } }