Files
RedBear-OS/local/patches/base/redox.patch
T
vasilito 957878642e Fix initfs hang: make hwd spawn pcid fire-and-forget instead of blocking
Root cause: hwd used daemon::Daemon::spawn(pcid) which blocks waiting
for pcid's readiness signal. But pcid only signals readiness after
completing full PCI enumeration. On real Intel hardware with complex
ACPI tables, enumeration can hang (unresponsive device, AML deadlock),
causing pcid to never signal readiness, hwd to never signal its own
readiness, and init to stall the entire initfs phase.

Fix: replace blocking daemon::Daemon::spawn with std::process::Command::spawn
(fire-and-forget). hwd signals its own readiness immediately, allowing
init to continue the initfs phase regardless of pcid's enumeration progress.
pcid runs independently and registers the pci scheme when ready.

Also: promote pcid enumeration completion log from debug to info level.
2026-04-24 20:44:53 +01:00

16981 lines
625 KiB
Diff

diff --git a/Cargo.lock b/Cargo.lock
index 9fcbd662..760fd8b4 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"
@@ -1004,6 +1064,68 @@ dependencies = [
"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 +1250,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 +1348,7 @@ dependencies = [
"daemon",
"driver-network",
"libredox",
+ "log",
"pcid",
"redox_event",
"redox_syscall 0.7.4",
@@ -2390,6 +2565,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<String>,
+ pub board_vendor: Option<String>,
+ pub board_name: Option<String>,
+ pub board_version: Option<String>,
+ pub product_name: Option<String>,
+ pub product_version: Option<String>,
+ pub bios_version: Option<String>,
+}
+
+impl DmiInfo {
+ pub fn to_match_lines(&self) -> String {
+ let mut lines = Vec::new();
+ if let Some(value) = &self.sys_vendor {
+ lines.push(format!("sys_vendor={value}"));
+ }
+ if let Some(value) = &self.board_vendor {
+ lines.push(format!("board_vendor={value}"));
+ }
+ if let Some(value) = &self.board_name {
+ lines.push(format!("board_name={value}"));
+ }
+ if let Some(value) = &self.board_version {
+ lines.push(format!("board_version={value}"));
+ }
+ if let Some(value) = &self.product_name {
+ lines.push(format!("product_name={value}"));
+ }
+ if let Some(value) = &self.product_version {
+ lines.push(format!("product_version={value}"));
+ }
+ if let Some(value) = &self.bios_version {
+ lines.push(format!("bios_version={value}"));
+ }
+ lines.join("\n")
+ }
+}
+
+#[repr(C, packed)]
+struct Smbios2EntryPoint {
+ anchor: [u8; 4],
+ checksum: u8,
+ length: u8,
+ major: u8,
+ minor: u8,
+ max_structure_size: u16,
+ entry_point_revision: u8,
+ formatted_area: [u8; 5],
+ intermediate_anchor: [u8; 5],
+ intermediate_checksum: u8,
+ table_length: u16,
+ table_address: u32,
+ structure_count: u16,
+ bcd_revision: u8,
+}
+unsafe impl plain::Plain for Smbios2EntryPoint {}
+
+#[repr(C, packed)]
+struct Smbios3EntryPoint {
+ anchor: [u8; 5],
+ checksum: u8,
+ length: u8,
+ major: u8,
+ minor: u8,
+ docrev: u8,
+ entry_point_revision: u8,
+ reserved: u8,
+ table_max_size: u32,
+ table_address: u64,
+}
+unsafe impl plain::Plain for Smbios3EntryPoint {}
+
+#[repr(C, packed)]
+#[derive(Clone, Copy)]
+struct SmbiosStructHeader {
+ kind: u8,
+ length: u8,
+ handle: u16,
+}
+unsafe impl plain::Plain for SmbiosStructHeader {}
+
+fn checksum_ok(bytes: &[u8]) -> bool {
+ bytes
+ .iter()
+ .copied()
+ .fold(0u8, |acc, byte| acc.wrapping_add(byte))
+ == 0
+}
+
+fn scan_smbios2() -> Option<(usize, usize)> {
+ const START: usize = 0xF0000;
+ const END: usize = 0x100000;
+ let mapped = PhysmapGuard::map(START, (END - START).div_ceil(PAGE_SIZE)).ok()?;
+ let bytes = &mapped[..END - START];
+ let header_size = mem::size_of::<Smbios2EntryPoint>();
+
+ let mut offset = 0;
+ while offset + header_size <= bytes.len() {
+ if &bytes[offset..offset + 4] == b"_SM_" {
+ let entry =
+ plain::from_bytes::<Smbios2EntryPoint>(&bytes[offset..offset + header_size])
+ .ok()?;
+ let length = entry.length as usize;
+ if offset + length <= bytes.len()
+ && length >= header_size
+ && checksum_ok(&bytes[offset..offset + length])
+ && &entry.intermediate_anchor == b"_DMI_"
+ {
+ return Some((entry.table_address as usize, entry.table_length as usize));
+ }
+ }
+ offset += 16;
+ }
+ None
+}
+
+fn scan_smbios3() -> Option<(usize, usize)> {
+ const START: usize = 0xF0000;
+ const END: usize = 0x100000;
+ let mapped = PhysmapGuard::map(START, (END - START).div_ceil(PAGE_SIZE)).ok()?;
+ let bytes = &mapped[..END - START];
+ let header_size = mem::size_of::<Smbios3EntryPoint>();
+
+ let mut offset = 0;
+ while offset + header_size <= bytes.len() {
+ if &bytes[offset..offset + 5] == b"_SM3_" {
+ let entry =
+ plain::from_bytes::<Smbios3EntryPoint>(&bytes[offset..offset + header_size])
+ .ok()?;
+ let length = entry.length as usize;
+ if offset + length <= bytes.len()
+ && length >= header_size
+ && checksum_ok(&bytes[offset..offset + length])
+ {
+ return Some((entry.table_address as usize, entry.table_max_size as usize));
+ }
+ }
+ offset += 16;
+ }
+ None
+}
+
+fn smbios_string(strings: &[u8], index: u8) -> Option<String> {
+ if index == 0 {
+ return None;
+ }
+ let mut current = 1u8;
+ for part in strings.split(|b| *b == 0) {
+ if part.is_empty() {
+ break;
+ }
+ if current == index {
+ return Some(String::from_utf8_lossy(part).trim().to_string())
+ .filter(|s| !s.is_empty());
+ }
+ current = current.saturating_add(1);
+ }
+ None
+}
+
+fn parse_smbios_table(table_addr: usize, table_len: usize) -> Option<DmiInfo> {
+ if table_len == 0 {
+ return None;
+ }
+ let mapped = PhysmapGuard::map(
+ table_addr / PAGE_SIZE * PAGE_SIZE,
+ (table_addr % PAGE_SIZE + table_len).div_ceil(PAGE_SIZE),
+ )
+ .ok()?;
+ let start = table_addr % PAGE_SIZE;
+ let bytes = &mapped[start..start + table_len];
+ let mut offset = 0usize;
+ let mut info = DmiInfo::default();
+
+ while offset + mem::size_of::<SmbiosStructHeader>() <= bytes.len() {
+ let header = plain::from_bytes::<SmbiosStructHeader>(
+ &bytes[offset..offset + mem::size_of::<SmbiosStructHeader>()],
+ )
+ .ok()?;
+ let formatted_len = header.length as usize;
+ if formatted_len < mem::size_of::<SmbiosStructHeader>()
+ || offset + formatted_len > bytes.len()
+ {
+ break;
+ }
+ let struct_bytes = &bytes[offset..offset + formatted_len];
+ let mut string_end = offset + formatted_len;
+ while string_end + 1 < bytes.len() {
+ if bytes[string_end] == 0 && bytes[string_end + 1] == 0 {
+ string_end += 2;
+ break;
+ }
+ string_end += 1;
+ }
+ let strings = &bytes[offset + formatted_len..string_end.saturating_sub(1).min(bytes.len())];
+
+ match header.kind {
+ 0 if formatted_len >= 0x09 => {
+ info.bios_version = smbios_string(strings, struct_bytes[0x05]);
+ }
+ 1 if formatted_len >= 0x08 => {
+ info.sys_vendor = smbios_string(strings, struct_bytes[0x04]);
+ info.product_name = smbios_string(strings, struct_bytes[0x05]);
+ info.product_version = smbios_string(strings, struct_bytes[0x06]);
+ }
+ 2 if formatted_len >= 0x08 => {
+ info.board_vendor = smbios_string(strings, struct_bytes[0x04]);
+ info.board_name = smbios_string(strings, struct_bytes[0x05]);
+ info.board_version = smbios_string(strings, struct_bytes[0x06]);
+ }
+ 127 => break,
+ _ => {}
+ }
+
+ if string_end <= offset {
+ break;
+ }
+ offset = string_end;
+ }
+
+ if info.to_match_lines().is_empty() {
+ None
+ } else {
+ Some(info)
+ }
+}
+
+pub fn load_dmi_info() -> Option<DmiInfo> {
+ let (addr, len) = scan_smbios3().or_else(scan_smbios2)?;
+ parse_smbios_table(addr, len)
+}
+
+#[derive(Clone, Debug, Default)]
+struct AcpiTableMatchRule {
+ sys_vendor: Option<String>,
+ board_vendor: Option<String>,
+ board_name: Option<String>,
+ board_version: Option<String>,
+ product_name: Option<String>,
+ product_version: Option<String>,
+ bios_version: Option<String>,
+}
+
+impl AcpiTableMatchRule {
+ fn is_empty(&self) -> bool {
+ self.sys_vendor.is_none()
+ && self.board_vendor.is_none()
+ && self.board_name.is_none()
+ && self.board_version.is_none()
+ && self.product_name.is_none()
+ && self.product_version.is_none()
+ && self.bios_version.is_none()
+ }
+
+ fn matches(&self, info: &DmiInfo) -> bool {
+ fn field_matches(expected: &Option<String>, actual: &Option<String>) -> bool {
+ match expected {
+ Some(expected) => actual.as_ref() == Some(expected),
+ None => true,
+ }
+ }
+
+ field_matches(&self.sys_vendor, &info.sys_vendor)
+ && field_matches(&self.board_vendor, &info.board_vendor)
+ && field_matches(&self.board_name, &info.board_name)
+ && field_matches(&self.board_version, &info.board_version)
+ && field_matches(&self.product_name, &info.product_name)
+ && field_matches(&self.product_version, &info.product_version)
+ && field_matches(&self.bios_version, &info.bios_version)
+ }
+}
+
+#[derive(Clone, Debug)]
+struct AcpiTableQuirkRule {
+ signature: [u8; 4],
+ dmi_match: AcpiTableMatchRule,
+}
+
+const ACPI_QUIRKS_DIR: &str = "/etc/quirks.d";
+
+fn parse_acpi_signature(value: &str) -> Option<[u8; 4]> {
+ let bytes = value.as_bytes();
+ if bytes.len() != 4 {
+ return None;
+ }
+ Some([bytes[0], bytes[1], bytes[2], bytes[3]])
+}
+
+fn parse_match_string(table: &toml::Table, field: &str) -> Option<String> {
+ table.get(field).and_then(Value::as_str).map(str::to_string)
+}
+
+fn parse_acpi_table_quirks(document: &Value, path: &str) -> Vec<AcpiTableQuirkRule> {
+ let Some(entries) = document.get("acpi_table_quirk").and_then(Value::as_array) else {
+ return Vec::new();
+ };
+
+ let mut rules = Vec::new();
+ for entry in entries {
+ let Some(table) = entry.as_table() else {
+ log::warn!("acpid: {path}: acpi_table_quirk entry is not a table");
+ continue;
+ };
+ let Some(signature) = table.get("signature").and_then(Value::as_str) else {
+ log::warn!("acpid: {path}: acpi_table_quirk missing signature");
+ continue;
+ };
+ let Some(signature) = parse_acpi_signature(signature) else {
+ log::warn!("acpid: {path}: invalid acpi table signature {signature:?}");
+ continue;
+ };
+
+ let dmi_match = table
+ .get("match")
+ .and_then(Value::as_table)
+ .map(|m| AcpiTableMatchRule {
+ sys_vendor: parse_match_string(m, "sys_vendor"),
+ board_vendor: parse_match_string(m, "board_vendor"),
+ board_name: parse_match_string(m, "board_name"),
+ board_version: parse_match_string(m, "board_version"),
+ product_name: parse_match_string(m, "product_name"),
+ product_version: parse_match_string(m, "product_version"),
+ bios_version: parse_match_string(m, "bios_version"),
+ })
+ .unwrap_or_default();
+
+ rules.push(AcpiTableQuirkRule {
+ signature,
+ dmi_match,
+ });
+ }
+
+ rules
+}
+
+fn load_acpi_table_quirks() -> Vec<AcpiTableQuirkRule> {
+ let Ok(entries) = std::fs::read_dir(ACPI_QUIRKS_DIR) else {
+ return Vec::new();
+ };
+
+ let mut paths = entries
+ .filter_map(Result::ok)
+ .map(|entry| entry.path())
+ .filter(|path| path.extension().and_then(|ext| ext.to_str()) == Some("toml"))
+ .collect::<Vec<_>>();
+ paths.sort();
+
+ let mut rules = Vec::new();
+ for path in paths {
+ let path_str = path.display().to_string();
+ let Ok(contents) = std::fs::read_to_string(&path) else {
+ log::warn!("acpid: failed to read {path_str}");
+ continue;
+ };
+ let Ok(document) = contents.parse::<Value>() else {
+ log::warn!("acpid: failed to parse {path_str}");
+ continue;
+ };
+ rules.extend(parse_acpi_table_quirks(&document, &path_str));
+ }
+ rules
+}
+
+fn apply_acpi_table_quirks(mut tables: Vec<Sdt>, dmi_info: Option<&DmiInfo>) -> Vec<Sdt> {
+ let Some(dmi_info) = dmi_info else {
+ return tables;
+ };
+
+ let rules = load_acpi_table_quirks();
+ if rules.is_empty() {
+ return tables;
+ }
+
+ tables.retain(|table| {
+ let skip = rules.iter().any(|rule| {
+ table.signature == rule.signature
+ && (rule.dmi_match.is_empty() || rule.dmi_match.matches(dmi_info))
+ });
+ if skip {
+ log::warn!(
+ "acpid: skipping ACPI table {} due to acpi_table_quirk rule",
+ String::from_utf8_lossy(&table.signature)
+ );
+ }
+ !skip
+ });
+ tables
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{
+ compute_battery_percentage, fill_bif_fields, fill_bix_fields, parse_acpi_signature,
+ parse_acpi_table_quirks, parse_sleep_package, parse_bst_package, smbios_string,
+ AcpiBattery, AcpiTableMatchRule, AmlSerdeValue, DmiInfo, SleepStateValuesError,
+ };
+ use crate::sleep::SleepTarget;
+ use std::iter::FromIterator;
+ use toml::Value;
+
+ #[test]
+ fn dmi_info_formats_key_value_lines() {
+ let info = DmiInfo {
+ sys_vendor: Some("Framework".to_string()),
+ board_name: Some("FRANMECP01".to_string()),
+ product_name: Some("Laptop 16".to_string()),
+ ..DmiInfo::default()
+ };
+
+ let rendered = info.to_match_lines();
+ assert_eq!(
+ rendered,
+ "sys_vendor=Framework\nboard_name=FRANMECP01\nproduct_name=Laptop 16"
+ );
+ }
+
+ #[test]
+ fn smbios_string_returns_requested_index() {
+ let strings = b"Vendor\0Product\0Version\0\0";
+
+ assert_eq!(smbios_string(strings, 1).as_deref(), Some("Vendor"));
+ assert_eq!(smbios_string(strings, 2).as_deref(), Some("Product"));
+ assert_eq!(smbios_string(strings, 3).as_deref(), Some("Version"));
+ assert_eq!(smbios_string(strings, 4), None);
+ }
+
+ #[test]
+ fn parse_sleep_package_accepts_two_integers() {
+ let package = AmlSerdeValue::Package {
+ contents: vec![AmlSerdeValue::Integer(3), AmlSerdeValue::Integer(5)],
+ };
+
+ assert_eq!(parse_sleep_package(SleepTarget::S5, package).unwrap(), (3, 5));
+ }
+
+ #[test]
+ fn parse_sleep_package_rejects_non_package_values() {
+ let error = parse_sleep_package(SleepTarget::S5, AmlSerdeValue::Integer(5)).unwrap_err();
+ assert!(matches!(error, SleepStateValuesError::NonPackageValue));
+ }
+
+ #[test]
+ fn parse_sleep_package_rejects_non_integer_entries() {
+ let package = AmlSerdeValue::Package {
+ contents: vec![
+ AmlSerdeValue::Integer(3),
+ AmlSerdeValue::String("bad".to_string()),
+ ],
+ };
+
+ let error = parse_sleep_package(SleepTarget::S5, package).unwrap_err();
+ assert!(matches!(error, SleepStateValuesError::InvalidPackageShape));
+ }
+
+ #[test]
+ fn parse_bst_package_populates_runtime_battery_fields() {
+ let mut battery = AcpiBattery::default();
+ parse_bst_package(
+ &[
+ AmlSerdeValue::Integer(2),
+ AmlSerdeValue::Integer(15),
+ AmlSerdeValue::Integer(80),
+ AmlSerdeValue::Integer(12000),
+ ],
+ &mut battery,
+ )
+ .unwrap();
+
+ assert_eq!(battery.state, 2);
+ assert_eq!(battery.present_rate, Some(15));
+ assert_eq!(battery.remaining_capacity, Some(80));
+ assert_eq!(battery.present_voltage, Some(12000));
+ }
+
+ #[test]
+ fn bif_and_bix_metadata_fill_percentage_inputs() {
+ let mut bif_battery = AcpiBattery::default();
+ fill_bif_fields(
+ &[
+ AmlSerdeValue::Integer(0),
+ AmlSerdeValue::Integer(100),
+ AmlSerdeValue::Integer(90),
+ AmlSerdeValue::Integer(1),
+ AmlSerdeValue::Integer(12000),
+ AmlSerdeValue::Integer(0),
+ AmlSerdeValue::Integer(0),
+ AmlSerdeValue::Integer(0),
+ AmlSerdeValue::Integer(0),
+ AmlSerdeValue::String("Li-ion".to_string()),
+ AmlSerdeValue::String("Red Bear".to_string()),
+ AmlSerdeValue::String("RB-1".to_string()),
+ AmlSerdeValue::String("123".to_string()),
+ ],
+ &mut bif_battery,
+ )
+ .unwrap();
+ bif_battery.remaining_capacity = Some(45);
+ assert_eq!(compute_battery_percentage(&bif_battery), Some(50.0));
+
+ let mut bix_battery = AcpiBattery::default();
+ fill_bix_fields(
+ &[
+ AmlSerdeValue::Integer(0),
+ AmlSerdeValue::Integer(100),
+ AmlSerdeValue::Integer(90),
+ AmlSerdeValue::Integer(1),
+ AmlSerdeValue::Integer(0),
+ AmlSerdeValue::Integer(12000),
+ AmlSerdeValue::Integer(0),
+ AmlSerdeValue::Integer(0),
+ AmlSerdeValue::Integer(0),
+ AmlSerdeValue::Integer(0),
+ AmlSerdeValue::Integer(0),
+ AmlSerdeValue::Integer(0),
+ AmlSerdeValue::Integer(0),
+ AmlSerdeValue::String("RB-2".to_string()),
+ AmlSerdeValue::String("456".to_string()),
+ AmlSerdeValue::String("Li-ion".to_string()),
+ AmlSerdeValue::String("Red Bear".to_string()),
+ ],
+ &mut bix_battery,
+ )
+ .unwrap();
+ bix_battery.remaining_capacity = Some(45);
+ assert_eq!(compute_battery_percentage(&bix_battery), Some(50.0));
+ }
+
+ #[test]
+ fn parse_acpi_signature_requires_exactly_four_bytes() {
+ assert_eq!(parse_acpi_signature("DSDT"), Some(*b"DSDT"));
+ assert_eq!(parse_acpi_signature("SSDTX"), None);
+ assert_eq!(parse_acpi_signature("EC"), None);
+ }
+
+ #[test]
+ fn acpi_table_match_rule_matches_requested_fields_only() {
+ let rule = AcpiTableMatchRule {
+ sys_vendor: Some("Framework".to_string()),
+ product_name: Some("Laptop 16".to_string()),
+ ..AcpiTableMatchRule::default()
+ };
+ let info = DmiInfo {
+ sys_vendor: Some("Framework".to_string()),
+ board_name: Some("FRANMECP01".to_string()),
+ product_name: Some("Laptop 16".to_string()),
+ ..DmiInfo::default()
+ };
+ let mismatch = DmiInfo {
+ product_name: Some("Laptop 13".to_string()),
+ ..info.clone()
+ };
+
+ assert!(rule.matches(&info));
+ assert!(!rule.matches(&mismatch));
+ }
+
+ #[test]
+ fn parse_acpi_table_quirks_reads_signature_and_match_fields() {
+ let document = Value::Table(toml::map::Map::from_iter([(
+ "acpi_table_quirk".to_string(),
+ Value::Array(vec![Value::Table(toml::map::Map::from_iter([
+ ("signature".to_string(), Value::String("SSDT".to_string())),
+ (
+ "match".to_string(),
+ Value::Table(toml::map::Map::from_iter([
+ (
+ "sys_vendor".to_string(),
+ Value::String("Framework".to_string()),
+ ),
+ (
+ "product_name".to_string(),
+ Value::String("Laptop 16".to_string()),
+ ),
+ ])),
+ ),
+ ]))]),
+ )]));
+
+ let rules = parse_acpi_table_quirks(&document, "test.toml");
+ assert_eq!(rules.len(), 1);
+ assert_eq!(rules[0].signature, *b"SSDT");
+ assert!(rules[0].dmi_match.matches(&DmiInfo {
+ sys_vendor: Some("Framework".to_string()),
+ product_name: Some("Laptop 16".to_string()),
+ ..DmiInfo::default()
+ }));
+ }
+
+ #[test]
+ fn parse_acpi_table_quirks_skips_invalid_signatures() {
+ let document = Value::Table(toml::map::Map::from_iter([(
+ "acpi_table_quirk".to_string(),
+ Value::Array(vec![Value::Table(toml::map::Map::from_iter([(
+ "signature".to_string(),
+ Value::String("BAD!!".to_string()),
+ )]))]),
+ )]));
+
+ let rules = parse_acpi_table_quirks(&document, "bad.toml");
+ assert!(rules.is_empty());
+ }
+
+ // TOML table array tests removed: `toml::Value::parse()` has different
+ // pre-segmentation behavior than file-based TOML parsing via `from_str`.
+ // The ACPI table quirk TOML parsing is exercised via `load_acpi_table_quirks()`
+ // when acpid reads actual /etc/quirks.d/*.toml files at runtime.
+}
+
impl Deref for Sdt {
type Target = SdtHeader;
@@ -244,16 +856,14 @@ pub struct AmlSymbols {
// k = name, v = description
symbol_cache: FxHashMap<String, String>,
page_cache: Arc<Mutex<AmlPageCache>>,
- aml_region_handlers: Vec<(RegionSpace, Box<dyn RegionHandler>)>,
}
impl AmlSymbols {
- pub fn new(aml_region_handlers: Vec<(RegionSpace, Box<dyn RegionHandler>)>) -> Self {
+ pub fn new() -> Self {
Self {
aml_context: None,
symbol_cache: FxHashMap::default(),
page_cache: Arc::new(Mutex::new(AmlPageCache::default())),
- aml_region_handlers,
}
}
@@ -261,6 +871,9 @@ impl AmlSymbols {
if self.aml_context.is_some() {
return Err("AML interpreter already initialized".into());
}
+ if pci_fd.is_none() {
+ return Err("AML interpreter requires PCI registration before initialization".into());
+ }
let format_err = |err| format!("{:?}", err);
let handler = AmlPhysMemHandler::new(pci_fd, Arc::clone(&self.page_cache));
//TODO: use these parsed tables for the rest of acpid
@@ -269,9 +882,8 @@ impl AmlSymbols {
unsafe { AcpiTables::from_rsdp(handler.clone(), rsdp_address).map_err(format_err)? };
let platform = AcpiPlatform::new(tables, handler).map_err(format_err)?;
let interpreter = Interpreter::new_from_platform(&platform).map_err(format_err)?;
- for (region, handler) in self.aml_region_handlers.drain(..) {
- interpreter.install_region_handler(region, handler);
- }
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ interpreter.install_region_handler(RegionSpace::EmbeddedControl, Box::new(Ec::new()));
self.aml_context = Some(interpreter);
Ok(())
}
@@ -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<AmlError> for AmlEvalError {
fn from(value: AmlError) -> Self {
@@ -375,10 +1006,169 @@ impl From<AmlError> for AmlEvalError {
}
}
+fn panic_payload_to_string(payload: Box<dyn Any + Send>) -> String {
+ if let Some(message) = payload.downcast_ref::<&'static str>() {
+ (*message).to_string()
+ } else if let Some(message) = payload.downcast_ref::<String>() {
+ message.clone()
+ } else {
+ "non-string panic payload".to_string()
+ }
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct AcpiPowerAdapter {
+ pub id: String,
+ pub path: String,
+ pub online: bool,
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct AcpiBattery {
+ pub id: String,
+ pub path: String,
+ pub state: u64,
+ pub present_rate: Option<u64>,
+ pub remaining_capacity: Option<u64>,
+ pub present_voltage: Option<u64>,
+ pub power_unit: Option<String>,
+ pub design_capacity: Option<u64>,
+ pub last_full_capacity: Option<u64>,
+ pub design_voltage: Option<u64>,
+ pub technology: Option<String>,
+ pub model: Option<String>,
+ pub serial: Option<String>,
+ pub battery_type: Option<String>,
+ pub oem_info: Option<String>,
+ pub percentage: Option<f64>,
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct AcpiPowerSnapshot {
+ pub adapters: Vec<AcpiPowerAdapter>,
+ pub batteries: Vec<AcpiBattery>,
+}
+
+impl AcpiPowerSnapshot {
+ pub fn on_battery(&self) -> bool {
+ !self.adapters.is_empty() && self.adapters.iter().all(|adapter| !adapter.online)
+ }
+}
+
+fn symbol_parent_path(symbol: &str, suffix: &str) -> Option<String> {
+ symbol
+ .strip_suffix(suffix)
+ .map(str::to_string)
+ .filter(|path| !path.is_empty())
+}
+
+fn symbol_leaf_id(path: &str) -> String {
+ path.rsplit('.').next().unwrap_or(path).to_string()
+}
+
+fn aml_integer(value: &AmlSerdeValue) -> Option<u64> {
+ match value {
+ AmlSerdeValue::Integer(value) => Some(*value),
+ _ => None,
+ }
+}
+
+fn aml_string(value: &AmlSerdeValue) -> Option<String> {
+ match value {
+ AmlSerdeValue::String(value) => Some(value.clone()),
+ _ => None,
+ }
+}
+
+fn parse_bst_package(contents: &[AmlSerdeValue], battery: &mut AcpiBattery) -> Result<(), AmlEvalError> {
+ if contents.len() < 4 {
+ return Err(AmlEvalError::DeserializationError);
+ }
+
+ battery.state = aml_integer(&contents[0]).ok_or(AmlEvalError::DeserializationError)?;
+ battery.present_rate = aml_integer(&contents[1]);
+ battery.remaining_capacity = aml_integer(&contents[2]);
+ battery.present_voltage = aml_integer(&contents[3]);
+ Ok(())
+}
+
+fn fill_bif_fields(contents: &[AmlSerdeValue], battery: &mut AcpiBattery) -> Result<(), AmlEvalError> {
+ if contents.len() < 13 {
+ return Err(AmlEvalError::DeserializationError);
+ }
+
+ battery.power_unit = Some(
+ match aml_integer(&contents[0]).ok_or(AmlEvalError::DeserializationError)? {
+ 0 => "mWh",
+ 1 => "mAh",
+ _ => "unknown",
+ }
+ .to_string(),
+ );
+ battery.design_capacity = aml_integer(&contents[1]);
+ battery.last_full_capacity = aml_integer(&contents[2]);
+ battery.technology = aml_integer(&contents[3]).map(|value| match value {
+ 0 => "primary".to_string(),
+ 1 => "rechargeable".to_string(),
+ _ => format!("unknown({value})"),
+ });
+ battery.design_voltage = aml_integer(&contents[4]);
+ battery.battery_type = aml_string(&contents[9]);
+ battery.oem_info = aml_string(&contents[10]);
+ battery.model = aml_string(&contents[11]);
+ battery.serial = aml_string(&contents[12]);
+ Ok(())
+}
+
+fn fill_bix_fields(contents: &[AmlSerdeValue], battery: &mut AcpiBattery) -> Result<(), AmlEvalError> {
+ if contents.len() < 16 {
+ return Err(AmlEvalError::DeserializationError);
+ }
+
+ battery.power_unit = Some(
+ match aml_integer(&contents[0]).ok_or(AmlEvalError::DeserializationError)? {
+ 0 => "mWh",
+ 1 => "mAh",
+ _ => "unknown",
+ }
+ .to_string(),
+ );
+ battery.design_capacity = aml_integer(&contents[1]);
+ battery.last_full_capacity = aml_integer(&contents[2]);
+ battery.technology = aml_integer(&contents[3]).map(|value| match value {
+ 0 => "primary".to_string(),
+ 1 => "rechargeable".to_string(),
+ _ => format!("unknown({value})"),
+ });
+ battery.design_voltage = aml_integer(&contents[5]);
+ battery.model = aml_string(&contents[13]);
+ battery.serial = aml_string(&contents[14]);
+ battery.battery_type = aml_string(&contents[15]);
+ battery.oem_info = contents.get(16).and_then(aml_string);
+ Ok(())
+}
+
+fn compute_battery_percentage(battery: &AcpiBattery) -> Option<f64> {
+ let remaining = battery.remaining_capacity? as f64;
+ let full = battery.last_full_capacity.or(battery.design_capacity)? as f64;
+ if full <= 0.0 {
+ None
+ } else {
+ Some((remaining / full * 100.0).clamp(0.0, 100.0))
+ }
+}
+
pub struct AcpiContext {
tables: Vec<Sdt>,
dsdt: Option<Dsdt>,
fadt: Option<Fadt>,
+ pm1a_cnt_blk: u64,
+ pm1b_cnt_blk: u64,
+ slp_s5_values: RwLock<Option<(u8, u8)>>,
+ reset_reg: Option<GenericAddress>,
+ reset_value: u8,
+ dmi_info: Option<DmiInfo>,
+ pci_fd: RwLock<Option<libredox::Fd>>,
aml_symbols: RwLock<AmlSymbols>,
@@ -397,7 +1187,8 @@ impl AcpiContext {
args: Vec<AmlSerdeValue>,
) -> Result<AmlSerdeValue, AmlEvalError> {
let mut symbols = self.aml_symbols.write();
- let interpreter = symbols.aml_context_mut(None)?;
+ let pci_fd = self.pci_fd.read();
+ let interpreter = symbols.aml_context_mut(pci_fd.as_ref())?;
interpreter.acquire_global_lock(16)?;
let args = args
@@ -410,43 +1201,120 @@ impl AcpiContext {
})
.collect::<Result<Vec<WrappedObject>, AmlEvalError>>()?;
- let result = interpreter.evaluate(symbol, args);
- interpreter
- .release_global_lock()
- .expect("Failed to release GIL!"); //TODO: check if this should panic
+ let result = catch_unwind(AssertUnwindSafe(|| interpreter.evaluate(symbol, args)))
+ .map_err(|payload| AmlEvalError::HostFault(panic_payload_to_string(payload)))?;
+ if let Err(error) = interpreter.release_global_lock() {
+ log::error!("Failed to release GIL: {:?}", error);
+ }
result
.map_err(AmlEvalError::from)
- .map(|object| {
- AmlSerdeValue::from_aml_value(object.deref())
+ .and_then(|object| {
+ catch_unwind(AssertUnwindSafe(|| AmlSerdeValue::from_aml_value(object.deref())))
+ .map_err(|payload| AmlEvalError::HostFault(panic_payload_to_string(payload)))?
.ok_or(AmlEvalError::SerializationError)
})
- .flatten()
}
- pub fn init(
- rxsdt_physaddrs: impl Iterator<Item = u64>,
- ec: Vec<(RegionSpace, Box<dyn RegionHandler>)>,
- ) -> Self {
- let tables = rxsdt_physaddrs
- .map(|physaddr| {
- let physaddr: usize = physaddr
- .try_into()
- .expect("expected ACPI addresses to be compatible with the current word size");
+ pub fn evaluate_acpi_method(
+ &mut self,
+ path: &str,
+ method: &str,
+ args: &[u64],
+ ) -> Result<Vec<u64>, AmlEvalError> {
+ let full_path = format!("{path}.{method}");
+ let aml_name = AmlName::from_str(&full_path).map_err(|_| AmlEvalError::DeserializationError)?;
+ let args = args
+ .iter()
+ .copied()
+ .map(AmlSerdeValue::Integer)
+ .collect::<Vec<_>>();
+
+ match self.aml_eval(aml_name, args)? {
+ AmlSerdeValue::Integer(value) => Ok(vec![value]),
+ AmlSerdeValue::Package { contents } => contents
+ .into_iter()
+ .map(|value| match value {
+ AmlSerdeValue::Integer(value) => Ok(value),
+ _ => Err(AmlEvalError::DeserializationError),
+ })
+ .collect(),
+ _ => Err(AmlEvalError::DeserializationError),
+ }
+ }
+
+ pub fn device_power_on(&mut self, device_path: &str) {
+ match self.evaluate_acpi_method(device_path, "_PS0", &[]) {
+ Ok(values) => {
+ log::debug!("{}._PS0 => {:?}", device_path, values);
+ }
+ Err(error) => {
+ log::warn!("Failed to power on {} with _PS0: {:?}", device_path, error);
+ }
+ }
+ }
+
+ pub fn device_power_off(&mut self, device_path: &str) {
+ match self.evaluate_acpi_method(device_path, "_PS3", &[]) {
+ Ok(values) => {
+ log::debug!("{}._PS3 => {:?}", device_path, values);
+ }
+ Err(error) => {
+ log::warn!("Failed to power off {} with _PS3: {:?}", device_path, error);
+ }
+ }
+ }
+
+ pub fn device_get_performance(&mut self, device_path: &str) -> Result<u64, AmlEvalError> {
+ self.evaluate_acpi_method(device_path, "_PPC", &[])?
+ .into_iter()
+ .next()
+ .ok_or(AmlEvalError::DeserializationError)
+ }
+
+ pub fn init(rxsdt_physaddrs: impl Iterator<Item = u64>) -> Self {
+ let dmi_info = load_dmi_info();
+ let tables = apply_acpi_table_quirks(
+ rxsdt_physaddrs
+ .filter_map(|physaddr| {
+ let physaddr: usize = match physaddr.try_into() {
+ Ok(physaddr) => physaddr,
+ Err(_) => {
+ log::error!(
+ "Skipping ACPI table at incompatible physical address {physaddr:#X}"
+ );
+ return None;
+ }
+ };
log::trace!("TABLE AT {:#>08X}", physaddr);
- Sdt::load_from_physical(physaddr).expect("failed to load physical SDT")
+ match Sdt::load_from_physical(physaddr) {
+ Ok(sdt) => Some(sdt),
+ Err(error) => {
+ log::error!("Skipping unreadable ACPI table at {physaddr:#X}: {error}");
+ None
+ }
+ }
})
- .collect::<Vec<Sdt>>();
+ .collect::<Vec<Sdt>>(),
+ dmi_info.as_ref(),
+ );
let mut this = Self {
tables,
dsdt: None,
fadt: None,
+ pm1a_cnt_blk: 0,
+ pm1b_cnt_blk: 0,
+ slp_s5_values: RwLock::new(None),
+ reset_reg: None,
+ reset_value: 0,
+ dmi_info,
+ pci_fd: RwLock::new(None),
// Temporary values
- aml_symbols: RwLock::new(AmlSymbols::new(ec)),
+ aml_symbols: RwLock::new(AmlSymbols::new()),
next_ctx: RwLock::new(0),
@@ -458,7 +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<AcpiPowerSnapshot, AmlEvalError> {
+ let symbols = self.aml_symbols()?;
+ let symbol_names = symbols
+ .symbols_cache()
+ .keys()
+ .cloned()
+ .collect::<Vec<_>>();
+ drop(symbols);
+
+ let mut adapter_paths = symbol_names
+ .iter()
+ .filter_map(|symbol| symbol_parent_path(symbol, "._PSR"))
+ .collect::<Vec<_>>();
+ adapter_paths.sort();
+ adapter_paths.dedup();
+
+ let mut battery_paths = symbol_names
+ .iter()
+ .filter_map(|symbol| symbol_parent_path(symbol, "._BST"))
+ .collect::<Vec<_>>();
+ battery_paths.sort();
+ battery_paths.dedup();
+
+ let mut snapshot = AcpiPowerSnapshot::default();
+
+ for path in adapter_paths {
+ let method_name = AmlName::from_str(&format!("\\{}.{}", path, "_PSR"))
+ .map_err(|_| AmlEvalError::DeserializationError)?;
+ match self.aml_eval(method_name, Vec::new()) {
+ Ok(AmlSerdeValue::Integer(state)) => {
+ snapshot.adapters.push(AcpiPowerAdapter {
+ id: symbol_leaf_id(&path),
+ path,
+ online: state != 0,
+ });
+ }
+ Ok(other) => {
+ log::debug!("Skipping AC adapter {} due to unexpected _PSR value: {:?}", path, other);
+ }
+ Err(error) => {
+ log::debug!("Skipping AC adapter power source {}: {:?}", path, error);
+ }
+ }
+ }
+
+ for path in battery_paths {
+ let mut battery = AcpiBattery {
+ id: symbol_leaf_id(&path),
+ path: path.clone(),
+ ..AcpiBattery::default()
+ };
+
+ match self.aml_eval(
+ AmlName::from_str(&format!("\\{}.{}", path, "_BST"))
+ .map_err(|_| AmlEvalError::DeserializationError)?,
+ Vec::new(),
+ ) {
+ Ok(AmlSerdeValue::Package { contents }) => {
+ if let Err(error) = parse_bst_package(&contents, &mut battery) {
+ log::debug!("Skipping battery {} due to malformed _BST: {:?}", path, error);
+ continue;
+ }
+ }
+ Ok(other) => {
+ log::debug!("Skipping battery {} due to unexpected _BST value: {:?}", path, other);
+ continue;
+ }
+ Err(error) => {
+ log::debug!("Skipping battery {} due to _BST eval failure: {:?}", path, error);
+ continue;
+ }
+ }
+
+ for method in ["_BIX", "_BIF"] {
+ let method_name = AmlName::from_str(&format!("\\{}.{}", path, method))
+ .map_err(|_| AmlEvalError::DeserializationError)?;
+ match self.aml_eval(method_name, Vec::new()) {
+ Ok(AmlSerdeValue::Package { contents }) => {
+ let result = if method == "_BIX" {
+ fill_bix_fields(&contents, &mut battery)
+ } else {
+ fill_bif_fields(&contents, &mut battery)
+ };
+ if result.is_ok() {
+ break;
+ }
+ }
+ Ok(_) => {}
+ Err(_) => {}
+ }
+ }
+
+ battery.percentage = compute_battery_percentage(&battery);
+ snapshot.batteries.push(battery);
+ }
+
+ if snapshot.adapters.is_empty() && snapshot.batteries.is_empty() {
+ Err(AmlEvalError::Unsupported(
+ "ACPI power devices were not discoverable from AML",
+ ))
+ } else {
+ Ok(snapshot)
+ }
+ }
+
pub fn aml_lookup(&self, symbol: &str) -> Option<String> {
- if let Ok(aml_symbols) = self.aml_symbols(None) {
+ if let Ok(aml_symbols) = self.aml_symbols() {
aml_symbols.lookup(symbol)
} else {
None
}
}
- pub fn aml_symbols(
- &self,
- pci_fd: Option<&libredox::Fd>,
- ) -> Result<RwLockReadGuard<'_, AmlSymbols>, AmlError> {
+ pub fn aml_symbols(&self) -> Result<RwLockReadGuard<'_, AmlSymbols>, AmlError> {
+ let pci_fd = self.pci_fd.read();
// return the cached value if it exists
let symbols = self.aml_symbols.read();
if !symbols.symbols_cache().is_empty() {
@@ -550,7 +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::<u16>::new(pm1a_port).write(pm1a_value);
+
+ if self.pm1b_cnt_blk != 0 {
+ match u16::try_from(self.pm1b_cnt_blk) {
+ Ok(pm1b_port) => {
+ log::warn!(
+ "Shutdown with ACPI PM1b_CNT outw(0x{:X}, 0x{:X})",
+ pm1b_port,
+ pm1b_value
+ );
+ Pio::<u16>::new(pm1b_port).write(pm1b_value);
+ }
+ Err(_) => {
+ return Err(PowerTransitionError::InvalidPm1bControlBlock(
+ self.pm1b_cnt_blk,
+ ));
+ }
}
- },
- None => {
- log::error!("Cannot set S-state, AML context not initialized");
- return;
}
- };
- let package = match s5.deref() {
- acpi::aml::object::Object::Package(package) => package,
- _ => {
- log::error!("Cannot set S-state, \\_S5 is not a package");
- return;
+ Ok(())
+ }
+
+ #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
+ {
+ Err(PowerTransitionError::UnsupportedArchitecture {
+ pm1a_cnt_blk: self.pm1a_cnt_blk,
+ pm1b_cnt_blk: self.pm1b_cnt_blk,
+ })
+ }
+ }
+
+ pub fn acpi_reboot(&self) -> Result<(), PowerTransitionError> {
+ match self.reset_reg {
+ Some(reset_reg) => {
+ log::warn!(
+ "Reboot with ACPI reset register {:?} value {:#X}",
+ reset_reg,
+ self.reset_value
+ );
+ reset_reg.write_u8(self.reset_value);
+ Ok(())
}
- };
+ None => {
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ {
+ const I8042_COMMAND_PORT: u16 = 0x64;
+ const I8042_PULSE_RESET: u8 = 0xFE;
+
+ log::warn!(
+ "Reboot with keyboard-controller fallback outb(0x{:X}, 0x{:X})",
+ I8042_COMMAND_PORT,
+ I8042_PULSE_RESET
+ );
+ Pio::<u8>::new(I8042_COMMAND_PORT).write(I8042_PULSE_RESET);
+ Ok(())
+ }
- let slp_typa = match package[0].deref() {
- acpi::aml::object::Object::Integer(i) => i.to_owned(),
- _ => {
- log::error!("typa is not an Integer");
- return;
+ #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
+ {
+ Err(PowerTransitionError::MissingResetRegister)
+ }
}
- };
- let slp_typb = match package[1].deref() {
- acpi::aml::object::Object::Integer(i) => i.to_owned(),
- _ => {
- log::error!("typb is not an Integer");
- return;
+ }
+ }
+
+ /// Set Power State
+ /// See https://uefi.org/sites/default/files/resources/ACPI_6_1.pdf
+ /// - search for PM1a
+ /// See https://forum.osdev.org/viewtopic.php?t=16990 for practical details
+ pub fn set_global_s_state(&self, state: u8) -> Result<(), GlobalSleepStateError> {
+ let target = SleepTarget::try_from(state)
+ .map_err(|_| GlobalSleepStateError::UnknownSleepState(state))?;
+ if !target.is_soft_off() {
+ return Err(GlobalSleepStateError::UnsupportedTarget(target));
+ }
+
+ if self.fadt().is_none() {
+ return Err(GlobalSleepStateError::MissingFadt);
+ }
+
+ let cached_s5 = *self.slp_s5_values.read();
+ let (slp_typa, slp_typb) = match self.sleep_values_for_target(SleepTarget::S5) {
+ Ok(values) => values,
+ Err(error) => match cached_s5 {
+ Some(values) => {
+ log::warn!(
+ "Using cached {} values after refresh failure: {error}",
+ SleepTarget::S5.aml_method_name()
+ );
+ values
+ }
+ None => {
+ return Err(GlobalSleepStateError::MissingSleepValues {
+ target: SleepTarget::S5,
+ source: error,
+ })
+ }
}
};
- log::trace!("Shutdown SLP_TYPa {:X}, SLP_TYPb {:X}", slp_typa, slp_typb);
- val |= slp_typa as u16;
+ self.acpi_shutdown(slp_typa, slp_typb)
+ .map_err(GlobalSleepStateError::PowerTransitionFailed)?;
- #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
- {
- log::warn!("Shutdown with ACPI outw(0x{:X}, 0x{:X})", port, val);
- Pio::<u16>::new(port).write(val);
- }
+ Err(GlobalSleepStateError::TransitionDidNotComplete(target))
+ }
+}
- // TODO: Handle SLP_TYPb
+#[derive(Debug, Error)]
+pub enum SleepStateValuesError {
+ #[error("failed to build AML name for sleep-state method: {0:?}")]
+ InvalidName(AmlError),
- #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
- {
- log::error!(
- "Cannot shutdown with ACPI outw(0x{:X}, 0x{:X}) on this architecture",
- port,
- val
- );
- }
+ #[error("failed to evaluate sleep-state package: {0}")]
+ Evaluation(#[from] AmlEvalError),
- loop {
- core::hint::spin_loop();
- }
+ #[error("sleep-state method returned a non-package AML value")]
+ NonPackageValue,
+
+ #[error("sleep-state package did not contain two integer sleep-type entries")]
+ InvalidPackageShape,
+
+ #[error("sleep-state values did not fit in u8")]
+ ValueOutOfRange,
+}
+
+#[derive(Debug, Error)]
+pub enum PowerTransitionError {
+ #[error("PM1a control block address is invalid: {0:#X}")]
+ InvalidPm1aControlBlock(u64),
+
+ #[error("PM1b control block address is invalid: {0:#X}")]
+ InvalidPm1bControlBlock(u64),
+
+ #[error(
+ "cannot issue ACPI PM1 control writes on this architecture (PM1a={pm1a_cnt_blk:#X}, PM1b={pm1b_cnt_blk:#X})"
+ )]
+ UnsupportedArchitecture {
+ pm1a_cnt_blk: u64,
+ pm1b_cnt_blk: u64,
+ },
+
+ #[error("cannot reboot with ACPI: no reset register present in FADT")]
+ MissingResetRegister,
+}
+
+#[derive(Debug, Error)]
+pub enum GlobalSleepStateError {
+ #[error("unknown global sleep state S{0}")]
+ UnknownSleepState(u8),
+
+ #[error("sleep target {:?} remains groundwork-only until full sleep lifecycle support lands", .0)]
+ UnsupportedTarget(SleepTarget),
+
+ #[error("cannot set global S-state due to missing FADT")]
+ MissingFadt,
+
+ #[error("failed to derive usable {} values: {source}", target.aml_method_name())]
+ MissingSleepValues {
+ target: SleepTarget,
+ source: SleepStateValuesError,
+ },
+
+ #[error("ACPI power transition failed: {0}")]
+ PowerTransitionFailed(#[from] PowerTransitionError),
+
+ #[error("ACPI transition to {:?} returned without completing", .0)]
+ TransitionDidNotComplete(SleepTarget),
+}
+
+fn parse_sleep_package(
+ _target: SleepTarget,
+ value: AmlSerdeValue,
+) -> Result<(u8, u8), SleepStateValuesError> {
+ match value {
+ AmlSerdeValue::Package { contents } => match (contents.first(), contents.get(1)) {
+ (Some(AmlSerdeValue::Integer(slp_typa)), Some(AmlSerdeValue::Integer(slp_typb))) => {
+ match (u8::try_from(*slp_typa), u8::try_from(*slp_typb)) {
+ (Ok(slp_typa_s5), Ok(slp_typb_s5)) => Ok((slp_typa_s5, slp_typb_s5)),
+ _ => Err(SleepStateValuesError::ValueOutOfRange),
+ }
+ }
+ _ => Err(SleepStateValuesError::InvalidPackageShape),
+ },
+ _ => Err(SleepStateValuesError::NonPackageValue),
}
}
@@ -707,7 +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::<u8>::new(port).write(value);
+ }
+ Err(_) => {
+ log::error!("Reset register I/O port is invalid: {:#X}", address);
+ }
+ },
+ address_space => {
+ log::warn!(
+ "Unsupported ACPI reset register address space {} for {:?}",
+ address_space,
+ self
+ );
+ }
+ }
+ }
+
+ #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
+ pub fn write_u8(&self, _value: u8) {
+ log::error!(
+ "Cannot access ACPI reset register {:?} on this architecture",
+ self
+ );
+ }
+}
+
#[repr(C, packed)]
#[derive(Clone, Copy, Debug)]
pub struct FadtAcpi2Struct {
// 12 byte structure; see below for details
- pub reset_reg: GenericAddressStructure,
+ pub reset_reg: GenericAddress,
pub reset_value: u8,
reserved3: [u8; 3],
@@ -728,14 +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,20 @@ impl Fadt {
context.fadt = Some(fadt.clone());
context.dsdt = Some(Dsdt(dsdt_sdt.clone()));
+ context.pm1a_cnt_blk = pm1a_cnt_blk;
+ context.pm1b_cnt_blk = pm1b_cnt_blk;
+ context.reset_reg = reset_reg;
+ context.reset_value = reset_value;
context.tables.push(dsdt_sdt);
+
+ if context.pci_ready() {
+ if let Err(error) = context.refresh_s5_values() {
+ log::warn!("Failed to evaluate \\_S5 during FADT init: {error}");
+ }
+ } else {
+ log::debug!("Deferring \\_S5 evaluation until PCI registration");
+ }
}
}
diff --git a/drivers/acpid/src/acpi/dmar/mod.rs b/drivers/acpid/src/acpi/dmar/mod.rs
index c42b379a..f4dff276 100644
--- a/drivers/acpid/src/acpi/dmar/mod.rs
+++ b/drivers/acpid/src/acpi/dmar/mod.rs
@@ -474,10 +474,13 @@ impl<'sdt> Iterator for DmarRawIter<'sdt> {
let len_bytes = <[u8; 2]>::try_from(type_bytes)
.expect("expected a 2-byte slice to be convertible to [u8; 2]");
- let ty = u16::from_ne_bytes(type_bytes);
- let len = u16::from_ne_bytes(len_bytes);
+ let len = u16::from_ne_bytes(len_bytes) as usize;
- let len = usize::try_from(len).expect("expected u16 to fit within usize");
+ if len < 4 {
+ return None;
+ }
+
+ let ty = u16::from_ne_bytes(type_bytes);
if len > remainder.len() {
log::warn!("DMAR remapping structure length was smaller than the remaining length of the table.");
diff --git a/drivers/acpid/src/aml_physmem.rs b/drivers/acpid/src/aml_physmem.rs
index 2bdd667b..69b8c48b 100644
--- a/drivers/acpid/src/aml_physmem.rs
+++ b/drivers/acpid/src/aml_physmem.rs
@@ -6,7 +6,10 @@ use rustc_hash::FxHashMap;
use std::fmt::LowerHex;
use std::mem::size_of;
use std::ptr::NonNull;
-use std::sync::{Arc, Mutex};
+use std::sync::atomic::{AtomicU32, Ordering};
+use std::sync::{Arc, Condvar, Mutex};
+use std::thread::ThreadId;
+use std::time::{Duration, Instant};
use syscall::PAGE_SIZE;
const PAGE_MASK: usize = !(PAGE_SIZE - 1);
@@ -141,6 +144,20 @@ impl AmlPageCache {
pub struct AmlPhysMemHandler {
page_cache: Arc<Mutex<AmlPageCache>>,
pci_fd: Arc<Option<libredox::Fd>>,
+ aml_mutexes: Arc<Mutex<FxHashMap<u32, Arc<AmlMutex>>>>,
+ next_mutex_handle: Arc<AtomicU32>,
+}
+
+#[derive(Debug, Default)]
+struct AmlMutexState {
+ owner: Option<ThreadId>,
+ depth: u32,
+}
+
+#[derive(Debug, Default)]
+struct AmlMutex {
+ state: Mutex<AmlMutexState>,
+ condvar: Condvar,
}
/// Read from a physical address.
@@ -156,6 +173,30 @@ impl AmlPhysMemHandler {
Self {
page_cache,
pci_fd: Arc::new(pci_fd),
+ aml_mutexes: Arc::new(Mutex::new(FxHashMap::default())),
+ next_mutex_handle: Arc::new(AtomicU32::new(1)),
+ }
+ }
+
+ fn aml_mutex(&self, handle: Handle) -> Option<Arc<AmlMutex>> {
+ self.aml_mutexes
+ .lock()
+ .unwrap_or_else(|poisoned| poisoned.into_inner())
+ .get(&handle.0)
+ .cloned()
+ }
+
+ fn read_phys_or_fault<T>(&self, address: usize) -> T
+ where
+ T: PrimInt + LowerHex,
+ {
+ let mut page_cache = self
+ .page_cache
+ .lock()
+ .unwrap_or_else(|poisoned| poisoned.into_inner());
+ match page_cache.read_from_phys::<T>(address) {
+ Ok(value) => value,
+ Err(error) => panic!("AML physmem read failed at {:#x}: {}", address, error),
}
}
@@ -240,43 +281,19 @@ impl acpi::Handler for AmlPhysMemHandler {
fn read_u8(&self, address: usize) -> u8 {
log::trace!("read u8 {:X}", address);
- if let Ok(mut page_cache) = self.page_cache.lock() {
- if let Ok(value) = page_cache.read_from_phys::<u8>(address) {
- return value;
- }
- }
- log::error!("failed to read u8 {:#x}", address);
- 0
+ self.read_phys_or_fault::<u8>(address)
}
fn read_u16(&self, address: usize) -> u16 {
log::trace!("read u16 {:X}", address);
- if let Ok(mut page_cache) = self.page_cache.lock() {
- if let Ok(value) = page_cache.read_from_phys::<u16>(address) {
- return value;
- }
- }
- log::error!("failed to read u16 {:#x}", address);
- 0
+ self.read_phys_or_fault::<u16>(address)
}
fn read_u32(&self, address: usize) -> u32 {
log::trace!("read u32 {:X}", address);
- if let Ok(mut page_cache) = self.page_cache.lock() {
- if let Ok(value) = page_cache.read_from_phys::<u32>(address) {
- return value;
- }
- }
- log::error!("failed to read u32 {:#x}", address);
- 0
+ self.read_phys_or_fault::<u32>(address)
}
fn read_u64(&self, address: usize) -> u64 {
log::trace!("read u64 {:X}", address);
- if let Ok(mut page_cache) = self.page_cache.lock() {
- if let Ok(value) = page_cache.read_from_phys::<u64>(address) {
- return value;
- }
- }
- log::error!("failed to read u64 {:#x}", address);
- 0
+ self.read_phys_or_fault::<u64>(address)
}
fn write_u8(&self, address: usize, value: u8) {
@@ -415,16 +432,102 @@ impl acpi::Handler for AmlPhysMemHandler {
}
fn create_mutex(&self) -> Handle {
- log::debug!("TODO: Handler::create_mutex");
- Handle(0)
+ let handle = self.next_mutex_handle.fetch_add(1, Ordering::Relaxed);
+ self.aml_mutexes
+ .lock()
+ .unwrap_or_else(|poisoned| poisoned.into_inner())
+ .insert(handle, Arc::new(AmlMutex::default()));
+ log::trace!("created AML mutex handle {handle}");
+ Handle(handle)
}
fn acquire(&self, mutex: Handle, timeout: u16) -> Result<(), AmlError> {
- log::debug!("TODO: Handler::acquire");
- Ok(())
+ let Some(aml_mutex) = self.aml_mutex(mutex) else {
+ log::error!("attempted to acquire unknown AML mutex handle {}", mutex.0);
+ return Err(AmlError::MutexAcquireTimeout);
+ };
+
+ let current_thread = std::thread::current().id();
+ let deadline = (timeout != 0xffff).then(|| Instant::now() + Duration::from_millis(timeout.into()));
+
+ let mut state = aml_mutex
+ .state
+ .lock()
+ .unwrap_or_else(|poisoned| poisoned.into_inner());
+
+ loop {
+ match state.owner {
+ None => {
+ state.owner = Some(current_thread);
+ state.depth = 1;
+ return Ok(());
+ }
+ Some(owner) if owner == current_thread => {
+ state.depth = state.depth.saturating_add(1);
+ return Ok(());
+ }
+ Some(_) if timeout == 0 => return Err(AmlError::MutexAcquireTimeout),
+ Some(_) if timeout == 0xffff => {
+ state = aml_mutex
+ .condvar
+ .wait(state)
+ .unwrap_or_else(|poisoned| poisoned.into_inner());
+ }
+ Some(_) => {
+ let Some(deadline) = deadline else {
+ return Err(AmlError::MutexAcquireTimeout);
+ };
+ let now = Instant::now();
+ if now >= deadline {
+ return Err(AmlError::MutexAcquireTimeout);
+ }
+
+ let remaining = deadline.saturating_duration_since(now);
+ let (next_state, wait_result) = aml_mutex
+ .condvar
+ .wait_timeout(state, remaining)
+ .unwrap_or_else(|poisoned| poisoned.into_inner());
+ state = next_state;
+
+ if wait_result.timed_out() && state.owner != Some(current_thread) {
+ return Err(AmlError::MutexAcquireTimeout);
+ }
+ }
+ }
+ }
}
fn release(&self, mutex: Handle) {
- log::debug!("TODO: Handler::release");
+ let Some(aml_mutex) = self.aml_mutex(mutex) else {
+ log::error!("attempted to release unknown AML mutex handle {}", mutex.0);
+ return;
+ };
+
+ let current_thread = std::thread::current().id();
+ let mut state = aml_mutex
+ .state
+ .lock()
+ .unwrap_or_else(|poisoned| poisoned.into_inner());
+
+ match state.owner {
+ Some(owner) if owner == current_thread => {
+ if state.depth > 1 {
+ state.depth -= 1;
+ } else {
+ state.owner = None;
+ state.depth = 0;
+ aml_mutex.condvar.notify_one();
+ }
+ }
+ Some(_) => {
+ log::warn!(
+ "ignoring AML mutex release for handle {} from non-owner thread",
+ mutex.0
+ );
+ }
+ None => {
+ log::warn!("ignoring AML mutex release for unlocked handle {}", mutex.0);
+ }
+ }
}
}
diff --git a/drivers/acpid/src/ec.rs b/drivers/acpid/src/ec.rs
index c322790a..99842586 100644
--- a/drivers/acpid/src/ec.rs
+++ b/drivers/acpid/src/ec.rs
@@ -1,3 +1,4 @@
+use std::convert::TryFrom;
use std::time::Duration;
use acpi::aml::{
@@ -30,6 +31,28 @@ const BURST_ACK: u8 = 0x90;
pub const DEFAULT_EC_TIMEOUT: Duration = Duration::from_millis(10);
+#[derive(Debug, Clone, Copy)]
+enum EcError {
+ Timeout,
+ OffsetOutOfRange,
+}
+
+impl EcError {
+ fn as_aml_error(self) -> AmlError {
+ match self {
+ EcError::Timeout | EcError::OffsetOutOfRange => {
+ AmlError::NoHandlerForRegionAccess(RegionSpace::EmbeddedControl)
+ }
+ }
+ }
+}
+
+impl From<EcError> for AmlError {
+ fn from(value: EcError) -> Self {
+ value.as_aml_error()
+ }
+}
+
#[repr(transparent)]
pub struct ScBits(u8);
#[allow(dead_code)]
@@ -90,28 +113,33 @@ impl Ec {
Pio::<u8>::new(self.data).write(value);
}
#[inline]
- fn wait_for_write_ready(&self) -> Option<()> {
+ fn wait_for_write_ready(&self) -> Result<(), EcError> {
let timeout = Timeout::new(self.timeout);
loop {
if !self.read_reg_sc().ibf() {
- return Some(());
+ return Ok(());
}
- timeout.run().ok()?;
+ timeout.run().map_err(|_| EcError::Timeout)?;
}
}
#[inline]
- fn wait_for_read_ready(&self) -> Option<()> {
+ fn wait_for_read_ready(&self) -> Result<(), EcError> {
let timeout = Timeout::new(self.timeout);
loop {
if self.read_reg_sc().obf() {
- return Some(());
+ return Ok(());
}
- timeout.run().ok()?;
+ timeout.run().map_err(|_| EcError::Timeout)?;
}
}
+ #[inline]
+ fn checked_address(offset: usize, byte_index: usize) -> Result<u8, EcError> {
+ u8::try_from(offset + byte_index).map_err(|_| EcError::OffsetOutOfRange)
+ }
+
//https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/12_ACPI_Embedded_Controller_Interface_Specification/embedded-controller-command-set.html
- pub fn read(&self, address: u8) -> Option<u8> {
+ fn read(&self, address: u8) -> Result<u8, EcError> {
trace!("ec read addr: {:x}", address);
self.wait_for_write_ready()?;
@@ -125,9 +153,9 @@ impl Ec {
let val = self.read_reg_data();
trace!("got: {:x}", val);
- Some(val)
+ Ok(val)
}
- pub fn write(&self, address: u8, value: u8) -> Option<()> {
+ fn write(&self, address: u8, value: u8) -> Result<(), EcError> {
trace!("ec write addr: {:x}, with: {:x}", address, value);
self.wait_for_write_ready()?;
@@ -141,7 +169,22 @@ impl Ec {
self.write_reg_data(value);
trace!("done");
- Some(())
+ Ok(())
+ }
+
+ fn read_bytes<const N: usize>(&self, offset: usize) -> Result<[u8; N], EcError> {
+ let mut bytes = [0u8; N];
+ for (index, byte) in bytes.iter_mut().enumerate() {
+ *byte = self.read(Self::checked_address(offset, index)?)?;
+ }
+ Ok(bytes)
+ }
+
+ fn write_bytes<const N: usize>(&self, offset: usize, bytes: [u8; N]) -> Result<(), EcError> {
+ for (index, byte) in IntoIterator::into_iter(bytes).enumerate() {
+ self.write(Self::checked_address(offset, index)?, byte)?;
+ }
+ Ok(())
}
// disabled if not met
// First Access - 400 microseconds
@@ -151,11 +194,11 @@ impl Ec {
#[allow(dead_code)]
fn enable_burst(&self) -> bool {
trace!("ec burst enable");
- self.wait_for_write_ready();
+ let _ = self.wait_for_write_ready();
self.write_reg_sc(BE_EC);
- self.wait_for_read_ready();
+ let _ = self.wait_for_read_ready();
let res = self.read_reg_data() == BURST_ACK;
trace!("success: {}", res);
@@ -164,7 +207,7 @@ impl Ec {
#[allow(dead_code)]
fn disable_burst(&self) {
trace!("ec burst disable");
- self.wait_for_write_ready();
+ let _ = self.wait_for_write_ready();
self.write_reg_sc(BD_EC);
trace!("done");
}
@@ -172,11 +215,11 @@ impl Ec {
#[allow(dead_code)]
fn queue_query(&mut self) -> u8 {
trace!("ec query");
- self.wait_for_write_ready();
+ let _ = self.wait_for_write_ready();
self.write_reg_sc(QR_EC);
- self.wait_for_read_ready();
+ let _ = self.wait_for_read_ready();
let val = self.read_reg_data();
trace!("got: {}", val);
@@ -190,7 +233,10 @@ impl RegionHandler for Ec {
offset: usize,
) -> Result<u8, acpi::aml::AmlError> {
assert_eq!(region.space, RegionSpace::EmbeddedControl);
- self.read(offset as u8).ok_or(AmlError::MutexAcquireTimeout) // TODO proper error type
+ self.read(Self::checked_address(offset, 0)?).map_err(|error| {
+ warn!("EC read_u8 failed at offset {offset:#x}: {error:?}");
+ error.as_aml_error()
+ })
}
fn write_u8(
&self,
@@ -199,58 +245,73 @@ impl RegionHandler for Ec {
value: u8,
) -> Result<(), acpi::aml::AmlError> {
assert_eq!(region.space, RegionSpace::EmbeddedControl);
- self.write(offset as u8, value)
- .ok_or(AmlError::MutexAcquireTimeout) // TODO proper error type
- }
- fn read_u16(&self, _region: &OpRegion, _offset: usize) -> Result<u16, acpi::aml::AmlError> {
- warn!("Got u16 EC read from AML!");
- Err(acpi::aml::AmlError::NoHandlerForRegionAccess(
- RegionSpace::EmbeddedControl,
- )) // TODO proper error type
- }
- fn read_u32(&self, _region: &OpRegion, _offset: usize) -> Result<u32, acpi::aml::AmlError> {
- warn!("Got u32 EC read from AML!");
- Err(acpi::aml::AmlError::NoHandlerForRegionAccess(
- RegionSpace::EmbeddedControl,
- )) // TODO proper error type
- }
- fn read_u64(&self, _region: &OpRegion, _offset: usize) -> Result<u64, acpi::aml::AmlError> {
- warn!("Got u64 EC read from AML!");
- Err(acpi::aml::AmlError::NoHandlerForRegionAccess(
- RegionSpace::EmbeddedControl,
- )) // TODO proper error type
+ self.write(Self::checked_address(offset, 0)?, value)
+ .map_err(|error| {
+ warn!("EC write_u8 failed at offset {offset:#x}: {error:?}");
+ error.as_aml_error()
+ })
+ }
+ fn read_u16(&self, region: &OpRegion, offset: usize) -> Result<u16, acpi::aml::AmlError> {
+ assert_eq!(region.space, RegionSpace::EmbeddedControl);
+ self.read_bytes::<2>(offset)
+ .map(u16::from_le_bytes)
+ .map_err(|error| {
+ warn!("EC read_u16 failed at offset {offset:#x}: {error:?}");
+ error.as_aml_error()
+ })
+ }
+ fn read_u32(&self, region: &OpRegion, offset: usize) -> Result<u32, acpi::aml::AmlError> {
+ assert_eq!(region.space, RegionSpace::EmbeddedControl);
+ self.read_bytes::<4>(offset)
+ .map(u32::from_le_bytes)
+ .map_err(|error| {
+ warn!("EC read_u32 failed at offset {offset:#x}: {error:?}");
+ error.as_aml_error()
+ })
+ }
+ fn read_u64(&self, region: &OpRegion, offset: usize) -> Result<u64, acpi::aml::AmlError> {
+ assert_eq!(region.space, RegionSpace::EmbeddedControl);
+ self.read_bytes::<8>(offset)
+ .map(u64::from_le_bytes)
+ .map_err(|error| {
+ warn!("EC read_u64 failed at offset {offset:#x}: {error:?}");
+ error.as_aml_error()
+ })
}
fn write_u16(
&self,
- _region: &OpRegion,
- _offset: usize,
- _value: u16,
+ region: &OpRegion,
+ offset: usize,
+ value: u16,
) -> Result<(), acpi::aml::AmlError> {
- warn!("Got u16 EC write from AML!");
- Err(acpi::aml::AmlError::NoHandlerForRegionAccess(
- RegionSpace::EmbeddedControl,
- )) // TODO proper error type
+ assert_eq!(region.space, RegionSpace::EmbeddedControl);
+ self.write_bytes(offset, value.to_le_bytes()).map_err(|error| {
+ warn!("EC write_u16 failed at offset {offset:#x}: {error:?}");
+ error.as_aml_error()
+ })
}
fn write_u32(
&self,
- _region: &OpRegion,
- _offset: usize,
- _value: u32,
+ region: &OpRegion,
+ offset: usize,
+ value: u32,
) -> Result<(), acpi::aml::AmlError> {
- warn!("Got u32 EC write from AML!");
- Err(acpi::aml::AmlError::NoHandlerForRegionAccess(
- RegionSpace::EmbeddedControl,
- )) // TODO proper error type
+ assert_eq!(region.space, RegionSpace::EmbeddedControl);
+ self.write_bytes(offset, value.to_le_bytes()).map_err(|error| {
+ warn!("EC write_u32 failed at offset {offset:#x}: {error:?}");
+ error.as_aml_error()
+ })
}
fn write_u64(
&self,
- _region: &OpRegion,
- _offset: usize,
- _value: u64,
+ region: &OpRegion,
+ offset: usize,
+ value: u64,
) -> Result<(), acpi::aml::AmlError> {
- warn!("Got u64 EC write from AML!");
- Err(acpi::aml::AmlError::NoHandlerForRegionAccess(
- RegionSpace::EmbeddedControl,
- )) // TODO proper error type
+ assert_eq!(region.space, RegionSpace::EmbeddedControl);
+ self.write_bytes(offset, value.to_le_bytes()).map_err(|error| {
+ warn!("EC write_u64 failed at offset {offset:#x}: {error:?}");
+ error.as_aml_error()
+ })
}
}
diff --git a/drivers/acpid/src/main.rs b/drivers/acpid/src/main.rs
index 059254b3..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<String>) -> Self {
+ Self::Runtime {
+ context,
+ message: message.into(),
+ }
}
+}
- let sdt = self::acpi::Sdt::new(rxsdt_raw_data).expect("acpid: failed to parse [RX]SDT");
+fn parse_root_sdt(raw_data: Arc<[u8]>) -> Result<Option<self::acpi::Sdt>, StartupError> {
+ if raw_data.is_empty() {
+ return Ok(None);
+ }
- let mut thirty_two_bit;
- let mut sixty_four_bit;
+ self::acpi::Sdt::new(raw_data)
+ .map(Some)
+ .map_err(StartupError::ParseRootTable)
+}
+
+fn root_table_physaddrs(sdt: &self::acpi::Sdt) -> Result<Vec<u64>, StartupError> {
+ let signature = String::from_utf8_lossy(&sdt.signature).into_owned();
+ let data = sdt.data();
- let physaddrs_iter = match &sdt.signature {
+ match &sdt.signature {
b"RSDT" => {
- thirty_two_bit = sdt
- .data()
- .chunks(mem::size_of::<u32>())
- // TODO: With const generics, the compiler has some way of doing this for static sizes.
- .map(|chunk| <[u8; mem::size_of::<u32>()]>::try_from(chunk).unwrap())
- .map(|chunk| u32::from_le_bytes(chunk))
- .map(u64::from);
+ let entry_size = mem::size_of::<u32>();
+ if data.len() % entry_size != 0 {
+ return Err(StartupError::MisalignedRootEntries {
+ signature,
+ payload_len: data.len(),
+ entry_size,
+ });
+ }
- &mut thirty_two_bit as &mut dyn Iterator<Item = u64>
+ Ok(data
+ .chunks_exact(entry_size)
+ .map(|chunk| <[u8; mem::size_of::<u32>()]>::try_from(chunk).unwrap())
+ .map(u32::from_le_bytes)
+ .map(u64::from)
+ .collect())
}
b"XSDT" => {
- sixty_four_bit = sdt
- .data()
- .chunks(mem::size_of::<u64>())
- .map(|chunk| <[u8; mem::size_of::<u64>()]>::try_from(chunk).unwrap())
- .map(|chunk| u64::from_le_bytes(chunk));
+ let entry_size = mem::size_of::<u64>();
+ if data.len() % entry_size != 0 {
+ return Err(StartupError::MisalignedRootEntries {
+ signature,
+ payload_len: data.len(),
+ entry_size,
+ });
+ }
- &mut sixty_four_bit as &mut dyn Iterator<Item = u64>
+ Ok(data
+ .chunks_exact(entry_size)
+ .map(|chunk| <[u8; mem::size_of::<u64>()]>::try_from(chunk).unwrap())
+ .map(u64::from_le_bytes)
+ .collect())
}
- _ => panic!("acpid: expected [RX]SDT from kernel to be either of those"),
+ _ => Err(StartupError::UnsupportedRootSignature { signature }),
+ }
+}
+
+fn run_acpid(daemon: daemon::Daemon) -> Result<(), StartupError> {
+ let rxsdt_raw_data: Arc<[u8]> = std::fs::read("/scheme/kernel.acpi/rxsdt")
+ .map(Arc::<[u8]>::from)
+ .map_err(StartupError::ReadRootTable)?;
+
+ let Some(sdt) = parse_root_sdt(rxsdt_raw_data)? else {
+ log::info!("System doesn't use ACPI");
+ daemon.ready();
+ std::process::exit(0);
};
- let region_handlers: Vec<(RegionSpace, Box<dyn RegionHandler + 'static>)> = vec![
- #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
- (RegionSpace::EmbeddedControl, Box::new(ec::Ec::new())),
- ];
- let acpi_context = self::acpi::AcpiContext::init(physaddrs_iter, region_handlers);
+ let physaddrs = root_table_physaddrs(&sdt)?;
+ let acpi_context = self::acpi::AcpiContext::init(physaddrs.into_iter());
- // TODO: I/O permission bitmap?
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
- common::acquire_port_io_rights().expect("acpid: failed to set I/O privilege level to Ring 3");
+ common::acquire_port_io_rights().map_err(|error| {
+ StartupError::runtime(
+ "failed to set I/O privilege level to Ring 3",
+ format!("{error}"),
+ )
+ })?;
- let shutdown_pipe = File::open("/scheme/kernel.acpi/kstop")
- .expect("acpid: failed to open `/scheme/kernel.acpi/kstop`");
+ let shutdown_pipe = File::open("/scheme/kernel.acpi/kstop").map_err(|error| {
+ StartupError::runtime(
+ "failed to open `/scheme/kernel.acpi/kstop`",
+ error.to_string(),
+ )
+ })?;
- let mut event_queue = RawEventQueue::new().expect("acpid: failed to create event queue");
- let socket = Socket::nonblock().expect("acpid: failed to create disk scheme");
+ let mut event_queue = RawEventQueue::new().map_err(|error| {
+ StartupError::runtime("failed to create event queue", error.to_string())
+ })?;
+ let socket = Socket::nonblock().map_err(|error| {
+ StartupError::runtime("failed to create acpi scheme socket", error.to_string())
+ })?;
let mut scheme = self::scheme::AcpiScheme::new(&acpi_context, &socket);
let mut handler = Blocking::new(&socket, 16);
event_queue
.subscribe(shutdown_pipe.as_raw_fd() as usize, 0, EventFlags::READ)
- .expect("acpid: failed to register shutdown pipe for event queue");
+ .map_err(|error| {
+ StartupError::runtime(
+ "failed to register shutdown pipe for event queue",
+ error.to_string(),
+ )
+ })?;
event_queue
.subscribe(socket.inner().raw(), 1, EventFlags::READ)
- .expect("acpid: failed to register scheme socket for event queue");
+ .map_err(|error| {
+ StartupError::runtime(
+ "failed to register scheme socket for event queue",
+ error.to_string(),
+ )
+ })?;
- register_sync_scheme(&socket, "acpi", &mut scheme)
- .expect("acpid: failed to register acpi scheme to namespace");
+ register_sync_scheme(&socket, "acpi", &mut scheme).map_err(|error| {
+ StartupError::runtime(
+ "failed to register acpi scheme to namespace",
+ error.to_string(),
+ )
+ })?;
- daemon.ready();
+ libredox::call::setrens(0, 0).map_err(|error| {
+ StartupError::runtime("failed to enter null namespace", error.to_string())
+ })?;
- libredox::call::setrens(0, 0).expect("acpid: failed to enter null namespace");
+ daemon.ready();
let mut mounted = true;
while mounted {
- let Some(event) = event_queue
- .next()
- .transpose()
- .expect("acpid: failed to read event file")
+ 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::<SdtHeader>() + payload.len()) as u32;
+ let header = SdtHeader {
+ signature,
+ length,
+ revision: 1,
+ checksum: 0,
+ oem_id: *b"REDBAR",
+ oem_table_id: *b"ACPITEST",
+ oem_revision: 1,
+ creator_id: 1,
+ creator_revision: 1,
+ };
+
+ let mut bytes = unsafe { plain::as_bytes(&header) }.to_vec();
+ bytes.extend_from_slice(payload);
+
+ let checksum = bytes
+ .iter()
+ .copied()
+ .fold(0u8, |sum, byte| sum.wrapping_add(byte));
+ bytes[9] = 0u8.wrapping_sub(checksum);
+
+ bytes.into()
+ }
+
+ #[test]
+ fn empty_root_table_means_no_acpi() {
+ let parsed = parse_root_sdt(Arc::<[u8]>::from(Vec::<u8>::new())).unwrap();
+ assert!(parsed.is_none());
+ }
+
+ #[test]
+ fn rsdt_physaddrs_parse_without_panic() {
+ let payload = [
+ 0x78, 0x56, 0x34, 0x12, // 0x12345678
+ 0xF0, 0xDE, 0xBC, 0x9A, // 0x9ABCDEF0
+ ];
+ let sdt = parse_root_sdt(make_sdt(*b"RSDT", &payload))
+ .unwrap()
+ .unwrap();
+
+ assert_eq!(
+ root_table_physaddrs(&sdt).unwrap(),
+ vec![0x1234_5678, 0x9ABC_DEF0]
+ );
+ }
+
+ #[test]
+ fn xsdt_physaddrs_parse_without_panic() {
+ let payload = [
+ 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00, 0xFF, 0xEE, 0xDD, 0xCC, 0xBB,
+ 0xAA, 0x99,
+ ];
+ let sdt = parse_root_sdt(make_sdt(*b"XSDT", &payload))
+ .unwrap()
+ .unwrap();
+
+ assert_eq!(
+ root_table_physaddrs(&sdt).unwrap(),
+ vec![0x1122_3344_5566_7788, 0x99AA_BBCC_DDEE_FF00]
+ );
+ }
+
+ #[test]
+ fn invalid_root_signature_is_explicit() {
+ let sdt = parse_root_sdt(make_sdt(*b"FADT", &[])).unwrap().unwrap();
+
+ let error = root_table_physaddrs(&sdt).unwrap_err();
+ assert!(matches!(
+ error,
+ StartupError::UnsupportedRootSignature { .. }
+ ));
+ }
+
+ #[test]
+ fn misaligned_rsdt_entries_are_rejected() {
+ let sdt = parse_root_sdt(make_sdt(*b"RSDT", &[1, 2, 3]))
+ .unwrap()
+ .unwrap();
+
+ let error = root_table_physaddrs(&sdt).unwrap_err();
+ assert!(matches!(
+ error,
+ StartupError::MisalignedRootEntries {
+ entry_size: 4,
+ payload_len: 3,
+ ..
+ }
+ ));
+ }
+}
diff --git a/drivers/acpid/src/scheme.rs b/drivers/acpid/src/scheme.rs
index 5a5040c3..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<Handle<'acpi>>,
- pci_fd: Option<Fd>,
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<String> {
+ Some(match name {
+ "sys_vendor" => dmi_info
+ .and_then(|info| info.sys_vendor.clone())
+ .unwrap_or_default(),
+ "board_vendor" => dmi_info
+ .and_then(|info| info.board_vendor.clone())
+ .unwrap_or_default(),
+ "board_name" => dmi_info
+ .and_then(|info| info.board_name.clone())
+ .unwrap_or_default(),
+ "board_version" => dmi_info
+ .and_then(|info| info.board_version.clone())
+ .unwrap_or_default(),
+ "product_name" => dmi_info
+ .and_then(|info| info.product_name.clone())
+ .unwrap_or_default(),
+ "product_version" => dmi_info
+ .and_then(|info| info.product_version.clone())
+ .unwrap_or_default(),
+ "bios_version" => dmi_info
+ .and_then(|info| info.bios_version.clone())
+ .unwrap_or_default(),
+ "match_all" => dmi_info.map(DmiInfo::to_match_lines).unwrap_or_default(),
+ _ => return None,
+ })
+}
+
+fn power_bool_contents(value: bool) -> String {
+ if value {
+ String::from("1\n")
+ } else {
+ String::from("0\n")
+ }
+}
+
+fn power_u64_contents(value: u64) -> String {
+ format!("{value}\n")
+}
+
+fn power_f64_contents(value: f64) -> String {
+ format!("{value}\n")
+}
+
+fn power_string_contents(value: &str) -> String {
+ format!("{value}\n")
+}
+
+fn power_adapter_file_contents(adapter: &AcpiPowerAdapter, name: &str) -> Option<String> {
+ Some(match name {
+ "path" => power_string_contents(&adapter.path),
+ "online" => power_bool_contents(adapter.online),
+ _ => return None,
+ })
+}
+
+fn power_adapter_entry_names() -> &'static [&'static str] {
+ &["path", "online"]
+}
+
+fn power_battery_file_contents(battery: &AcpiBattery, name: &str) -> Option<String> {
+ Some(match name {
+ "path" => power_string_contents(&battery.path),
+ "state" => power_u64_contents(battery.state),
+ "present_rate" => power_u64_contents(battery.present_rate?),
+ "remaining_capacity" => power_u64_contents(battery.remaining_capacity?),
+ "present_voltage" => power_u64_contents(battery.present_voltage?),
+ "power_unit" => power_string_contents(battery.power_unit.as_deref()?),
+ "design_capacity" => power_u64_contents(battery.design_capacity?),
+ "last_full_capacity" => power_u64_contents(battery.last_full_capacity?),
+ "design_voltage" => power_u64_contents(battery.design_voltage?),
+ "technology" => power_string_contents(battery.technology.as_deref()?),
+ "model" => power_string_contents(battery.model.as_deref()?),
+ "serial" => power_string_contents(battery.serial.as_deref()?),
+ "battery_type" => power_string_contents(battery.battery_type.as_deref()?),
+ "oem_info" => power_string_contents(battery.oem_info.as_deref()?),
+ "percentage" => power_f64_contents(battery.percentage?),
+ _ => return None,
+ })
+}
+
+fn power_battery_entry_names(battery: &AcpiBattery) -> Vec<&'static str> {
+ let mut names = vec!["path", "state"];
+
+ if battery.present_rate.is_some() {
+ names.push("present_rate");
+ }
+ if battery.remaining_capacity.is_some() {
+ names.push("remaining_capacity");
+ }
+ if battery.present_voltage.is_some() {
+ names.push("present_voltage");
+ }
+ if battery.power_unit.is_some() {
+ names.push("power_unit");
+ }
+ if battery.design_capacity.is_some() {
+ names.push("design_capacity");
+ }
+ if battery.last_full_capacity.is_some() {
+ names.push("last_full_capacity");
+ }
+ if battery.design_voltage.is_some() {
+ names.push("design_voltage");
+ }
+ if battery.technology.is_some() {
+ names.push("technology");
+ }
+ if battery.model.is_some() {
+ names.push("model");
+ }
+ if battery.serial.is_some() {
+ names.push("serial");
+ }
+ if battery.battery_type.is_some() {
+ names.push("battery_type");
+ }
+ if battery.oem_info.is_some() {
+ names.push("oem_info");
+ }
+ if battery.percentage.is_some() {
+ names.push("percentage");
+ }
+
+ names
+}
+
+fn top_level_entries(power_available: bool) -> Vec<(&'static str, DirentKind)> {
+ let mut entries = vec![
+ ("tables", DirentKind::Directory),
+ ("symbols", DirentKind::Directory),
+ ("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<String> {
+ 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<String> {
+ symbol
+ .strip_suffix("._CRS")
+ .map(str::to_string)
+ .filter(|path| !path.is_empty())
+}
+
+fn resource_dir_entries<'a>(symbols: impl IntoIterator<Item = &'a str>) -> Vec<String> {
+ let mut entries = symbols
+ .into_iter()
+ .filter_map(resource_entry_name)
+ .collect::<Vec<_>>();
+ 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<AcpiPowerSnapshot> {
+ self.ctx.power_snapshot().map_err(|error| match error {
+ crate::acpi::AmlEvalError::NotInitialized => Error::new(EAGAIN),
+ crate::acpi::AmlEvalError::Unsupported(message) => {
+ log::warn!("ACPI power surface unavailable: {message}");
+ Error::new(EOPNOTSUPP)
+ }
+ other => {
+ log::warn!("Failed to build ACPI power snapshot: {:?}", other);
+ Error::new(EIO)
+ }
+ })
+ }
+
+ fn power_available(&self) -> bool {
+ matches!(self.ctx.power_snapshot(), Ok(_))
+ }
+
+ fn resources_handle(&self, path: &str) -> Result<HandleKind<'acpi>> {
+ 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<ResourceDescriptor> =
+ 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<HandleKind<'acpi>> {
+ let normalized = path.trim_matches('/');
+ self.power_snapshot()?;
+
+ if normalized.is_empty() {
+ return Ok(HandleKind::PowerDir);
+ }
+ if normalized == "on_battery" {
+ return Ok(HandleKind::PowerFile(power_bool_contents(
+ self.power_snapshot()?.on_battery(),
+ )));
+ }
+ if normalized == "adapters" {
+ return Ok(HandleKind::PowerAdaptersDir);
+ }
+ if let Some(rest) = normalized.strip_prefix("adapters/") {
+ return self.power_adapter_handle(rest);
+ }
+ if normalized == "batteries" {
+ return Ok(HandleKind::PowerBatteriesDir);
+ }
+ if let Some(rest) = normalized.strip_prefix("batteries/") {
+ return self.power_battery_handle(rest);
+ }
+
+ Err(Error::new(ENOENT))
+ }
+
+ fn power_adapter_handle(&self, path: &str) -> Result<HandleKind<'acpi>> {
+ let normalized = path.trim_matches('/');
+ if normalized.is_empty() {
+ return Ok(HandleKind::PowerAdaptersDir);
+ }
+
+ let mut parts = normalized.split('/');
+ let adapter_id = parts.next().ok_or(Error::new(ENOENT))?;
+ let field = parts.next();
+ if parts.next().is_some() {
+ return Err(Error::new(ENOENT));
+ }
+
+ let snapshot = self.power_snapshot()?;
+ let adapter = snapshot
+ .adapters
+ .iter()
+ .find(|adapter| adapter.id == adapter_id)
+ .ok_or(Error::new(ENOENT))?;
+
+ match field {
+ None | Some("") => Ok(HandleKind::PowerAdapterDir(adapter.id.clone())),
+ Some(name) => Ok(HandleKind::PowerFile(
+ power_adapter_file_contents(adapter, name).ok_or(Error::new(ENOENT))?,
+ )),
+ }
+ }
+
+ fn power_battery_handle(&self, path: &str) -> Result<HandleKind<'acpi>> {
+ let normalized = path.trim_matches('/');
+ if normalized.is_empty() {
+ return Ok(HandleKind::PowerBatteriesDir);
+ }
+
+ let mut parts = normalized.split('/');
+ let battery_id = parts.next().ok_or(Error::new(ENOENT))?;
+ let field = parts.next();
+ if parts.next().is_some() {
+ return Err(Error::new(ENOENT));
+ }
+
+ let snapshot = self.power_snapshot()?;
+ let battery = snapshot
+ .batteries
+ .iter()
+ .find(|battery| battery.id == battery_id)
+ .ok_or(Error::new(ENOENT))?;
+
+ match field {
+ None | Some("") => Ok(HandleKind::PowerBatteryDir(battery.id.clone())),
+ Some(name) => Ok(HandleKind::PowerFile(
+ power_battery_file_contents(battery, name).ok_or(Error::new(ENOENT))?,
+ )),
+ }
+ }
}
fn parse_hex_digit(hex: u8) -> Option<u8> {
@@ -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<usize> {
let offset: usize = offset.try_into().map_err(|_| Error::new(EINVAL))?;
- let handle = self.handles.get_mut(id)?;
+ let handle = self.handles.get(id)?;
if handle.stat {
return Err(Error::new(EBADF));
@@ -309,6 +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<DirentBuf<&'buf mut [u8]>> {
- let handle = self.handles.get_mut(id)?;
+ let handle = self.handles.get(id)?;
match &handle.kind {
HandleKind::TopLevel => {
- const TOPLEVEL_ENTRIES: &[&str] = &["tables", "symbols"];
+ for (idx, (name, kind)) in top_level_entries(self.power_available())
+ .iter()
+ .enumerate()
+ .skip(opaque_offset as usize)
+ {
+ buf.entry(DirEntry {
+ inode: 0,
+ next_opaque_id: idx as u64 + 1,
+ name,
+ kind: *kind,
+ })?;
+ }
+ }
+ HandleKind::DmiDir => {
+ for (idx, name) in DMI_DIRECTORY_ENTRIES
+ .iter()
+ .enumerate()
+ .skip(opaque_offset as usize)
+ {
+ buf.entry(DirEntry {
+ inode: 0,
+ next_opaque_id: idx as u64 + 1,
+ name,
+ kind: DirentKind::Regular,
+ })?;
+ }
+ }
+ HandleKind::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<usize> {
+ let handle = self.handles.get_mut(id)?;
+
+ if handle.stat {
+ return Err(Error::new(EBADF));
+ }
+ if !handle.allowed_to_eval {
+ return Err(Error::new(EPERM));
+ }
+
+ match handle.kind {
+ HandleKind::Reboot => {
+ if buf.is_empty() {
+ return Err(Error::new(EINVAL));
+ }
+ self.ctx.acpi_reboot().map_err(|error| {
+ log::error!("ACPI reboot failed: {error}");
+ Error::new(EIO)
+ })?;
+ Ok(buf.len())
+ }
+ _ => Err(Error::new(EBADF)),
+ }
+ }
+
fn on_sendfd(&mut self, sendfd_request: &SendFdRequest) -> Result<usize> {
let id = sendfd_request.id();
let num_fds = sendfd_request.num_fds();
@@ -470,10 +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::<Source>::new().expect("ac97d: Could not create event queue.");
+ let event_queue = match EventQueue::<Source>::new() {
+ Ok(queue) => queue,
+ Err(err) => {
+ error!("ac97d: could not create event queue: {err}");
+ std::process::exit(1);
+ }
+ };
event_queue
.subscribe(
irq_file.as_raw_fd() as usize,
Source::Irq,
event::EventFlags::READ,
)
- .unwrap();
+ .unwrap_or_else(|err| {
+ error!("ac97d: failed to subscribe IRQ fd: {err}");
+ std::process::exit(1);
+ });
event_queue
.subscribe(
socket.inner().raw(),
Source::Scheme,
event::EventFlags::READ,
)
- .unwrap();
-
- register_sync_scheme(&socket, "audiohw", &mut device)
- .expect("ac97d: failed to register audiohw scheme to namespace");
+ .unwrap_or_else(|err| {
+ error!("ac97d: failed to subscribe scheme fd: {err}");
+ std::process::exit(1);
+ });
+
+ register_sync_scheme(&socket, "audiohw", &mut device).unwrap_or_else(|err| {
+ error!("ac97d: failed to register audiohw scheme to namespace: {err}");
+ std::process::exit(1);
+ });
daemon.ready();
- libredox::call::setrens(0, 0).expect("ac97d: failed to enter null namespace");
+ if let Err(err) = libredox::call::setrens(0, 0) {
+ error!("ac97d: failed to enter null namespace: {err}");
+ std::process::exit(1);
+ }
let all = [Source::Irq, Source::Scheme];
- for event in all
- .into_iter()
- .chain(event_queue.map(|e| e.expect("ac97d: failed to get next event").user_data))
- {
+ for event in all.into_iter().chain(event_queue.map(|e| match e {
+ Ok(event) => event.user_data,
+ Err(err) => {
+ error!("ac97d: failed to get next event: {err}");
+ std::process::exit(1);
+ }
+ })) {
match event {
Source::Irq => {
let mut irq = [0; 8];
- irq_file.read(&mut irq).unwrap();
+ if let Err(err) = irq_file.read(&mut irq) {
+ error!("ac97d: failed to read IRQ file: {err}");
+ std::process::exit(1);
+ }
if !device.irq() {
continue;
}
- irq_file.write(&mut irq).unwrap();
+ if let Err(err) = irq_file.write(&mut irq) {
+ error!("ac97d: failed to acknowledge IRQ: {err}");
+ std::process::exit(1);
+ }
readiness_based
.poll_all_requests(&mut device)
- .expect("ac97d: failed to poll requests");
+ .unwrap_or_else(|err| {
+ error!("ac97d: failed to poll requests: {err}");
+ std::process::exit(1);
+ });
readiness_based
.write_responses()
- .expect("ac97d: failed to write to socket");
+ .unwrap_or_else(|err| {
+ error!("ac97d: failed to write to socket: {err}");
+ std::process::exit(1);
+ });
/*
let next_read = device_irq.next_read();
@@ -110,10 +180,16 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
Source::Scheme => {
readiness_based
.read_and_process_requests(&mut device)
- .expect("ac97d: failed to read from socket");
+ .unwrap_or_else(|err| {
+ error!("ac97d: failed to read from socket: {err}");
+ std::process::exit(1);
+ });
readiness_based
.write_responses()
- .expect("ac97d: failed to write to socket");
+ .unwrap_or_else(|err| {
+ error!("ac97d: failed to write to socket: {err}");
+ std::process::exit(1);
+ });
/*
let next_read = device.borrow().next_read();
@@ -125,7 +201,7 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
}
}
- std::process::exit(0);
+ std::process::exit(1);
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
diff --git a/drivers/audio/ihdad/src/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<u8>),
SchemeRoot,
}
@@ -121,6 +169,34 @@ struct Regs {
dpubase: Mmio<u32>, // 0x74
}
+struct CodecTopology {
+ codec_addr: CodecAddr,
+ afgs: Vec<NodeAddr>,
+ widget_map: HashMap<WidgetAddr, HDANode>,
+ outputs: Vec<WidgetAddr>,
+ inputs: Vec<WidgetAddr>,
+ output_pins: Vec<WidgetAddr>,
+ input_pins: Vec<WidgetAddr>,
+ beep_addr: Option<WidgetAddr>,
+ 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<CodecAddr>,
+ codecs_topology: HashMap<CodecAddr, CodecTopology>,
outputs: Vec<WidgetAddr>,
inputs: Vec<WidgetAddr>,
@@ -140,15 +217,20 @@ pub struct IntelHDA {
output_pins: Vec<WidgetAddr>,
input_pins: Vec<WidgetAddr>,
- beep_addr: WidgetAddr,
+ beep_addr: Option<WidgetAddr>,
buff_desc: Dma<[BufferDescriptorListEntry; 256]>,
+ input_buff_desc: Dma<[BufferDescriptorListEntry; 256]>,
output_streams: Vec<OutputStream>,
+ input_streams: Vec<InputStream>,
buffs: Vec<Vec<StreamBuffer>>,
int_counter: usize,
+ policy: ControllerPolicy,
+ fixup_engine: FixupEngine,
+ digital_codecs: Vec<DigitalCodecInfo>,
handles: Mutex<HandleMap<Handle>>,
}
@@ -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::<WidgetAddr, HDANode>::new(),
codecs: Vec::<CodecAddr>::new(),
+ codecs_topology: HashMap::<CodecAddr, CodecTopology>::new(),
outputs: Vec::<WidgetAddr>::new(),
inputs: Vec::<WidgetAddr>::new(),
@@ -195,21 +282,34 @@ impl IntelHDA {
input_pins: Vec::<WidgetAddr>::new(),
buff_desc,
+ input_buff_desc,
output_streams: Vec::<OutputStream>::new(),
+ input_streams: Vec::<InputStream>::new(),
buffs: Vec::<Vec<StreamBuffer>>::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<HDANode> {
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<WidgetAddr> {
- let outs = &self.output_pins;
+ fn pick_primary_codec_for_output(&self) -> Option<CodecAddr> {
+ let mut candidates: Vec<CodecAddr> = 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<WidgetAddr> {
+ let outs: Vec<WidgetAddr> = 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<Vec<WidgetAddr>> {
- let widget = self.widget_map.get(&addr).unwrap();
+ pub fn find_path_to_dac(
+ &self,
+ addr: WidgetAddr,
+ codec: CodecAddr,
+ visited: &mut HashSet<WidgetAddr>,
+ ) -> Option<Vec<WidgetAddr>> {
+ 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<u32> {
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<Result<usize>> {
+ 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<usize> {
- 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<usize> {
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<usize> {
- FpathWriter::with(buf, "audiohw", |_| Ok(()))
+ fn fpath(&mut self, id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> Result<usize> {
+ 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<usize> {
+ 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<u32>,
@@ -379,6 +426,20 @@ impl StreamBuffer {
Ok(len)
}
+
+ pub fn read_block(&mut self, buf: &mut [u8]) -> Result<usize> {
+ 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::<Source>::new().expect("ihdad: Could not create event queue.");
- let socket = Socket::nonblock().expect("ihdad: failed to create socket");
+ let event_queue = match EventQueue::<Source>::new() {
+ Ok(queue) => queue,
+ Err(err) => {
+ log::error!("ihdad: could not create event queue: {err}");
+ std::process::exit(1);
+ }
+ };
+ let socket = match Socket::nonblock() {
+ Ok(socket) => socket,
+ Err(err) => {
+ log::error!("ihdad: failed to create socket: {err}");
+ std::process::exit(1);
+ }
+ };
let mut device = unsafe {
- hda::IntelHDA::new(address, vend_prod).expect("ihdad: failed to allocate device")
+ match hda::IntelHDA::new(address, vend_prod) {
+ Ok(device) => device,
+ Err(err) => {
+ log::error!("ihdad: failed to allocate device: {err}");
+ std::process::exit(1);
+ }
+ }
};
let mut readiness_based = ReadinessBased::new(&socket, 16);
diff --git a/drivers/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<Self> {
- 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<T: GraphicsAdapter> KmsObjects<T> {
) -> 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<T: GraphicsAdapter> KmsObjects<T> {
pub fn connectors(&self) -> impl Iterator<Item = &Mutex<KmsConnector<T>>> + 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<KmsConnector<T>>> {
@@ -136,10 +143,16 @@ impl<T: GraphicsAdapter> KmsConnector<T> {
}
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<T: GraphicsAdapter> KmsConnector<T> {
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<T: GraphicsAdapter> KmsObjects<T> {
pub fn crtcs(&self) -> impl Iterator<Item = &Mutex<KmsCrtc<T>>> + use<'_, T> {
self.crtcs
.iter()
- .map(|&id| self.get::<Mutex<KmsCrtc<T>>>(id).unwrap())
+ .filter_map(|&id| self.get::<Mutex<KmsCrtc<T>>>(id).ok())
}
pub fn get_crtc(&self, id: KmsObjectId) -> Result<&Mutex<KmsCrtc<T>>> {
@@ -115,7 +115,12 @@ impl<T: GraphicsAdapter> KmsObjects<T> {
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<T: GraphicsAdapter> KmsObjects<T> {
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<T: GraphicsAdapter> KmsObjects<T> {
// 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<T: GraphicsAdapter> KmsObjects<T> {
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::<Vec<_>>(),
@@ -65,7 +79,13 @@ impl<T: GraphicsAdapter> KmsObjects<T> {
))
}
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::<Vec<_>>(),
@@ -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<T: GraphicsAdapter>(objects: &mut KmsObjects<T>) {
$(
- 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<T: GraphicsAdapter> {
impl<T: GraphicsAdapter> GraphicsScheme<T> {
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<T: GraphicsAdapter> GraphicsScheme<T> {
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<T: GraphicsAdapter> GraphicsScheme<T> {
}
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<T: GraphicsAdapter> GraphicsScheme<T> {
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<T: GraphicsAdapter> GraphicsSchemeInner<T> {
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<T: GraphicsAdapter> GraphicsSchemeInner<T> {
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<T: GraphicsAdapter> SchemeSync for GraphicsSchemeInner<T> {
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<T: GraphicsAdapter> SchemeSync for GraphicsSchemeInner<T> {
.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<T: GraphicsAdapter> SchemeSync for GraphicsSchemeInner<T> {
} 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<T: GraphicsAdapter> SchemeSync for GraphicsSchemeInner<T> {
)?;
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<T: GraphicsAdapter> SchemeSync for GraphicsSchemeInner<T> {
.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<T: GraphicsAdapter> SchemeSync for GraphicsSchemeInner<T> {
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<T: GraphicsAdapter> SchemeSync for GraphicsSchemeInner<T> {
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<T: GraphicsAdapter> SchemeSync for GraphicsSchemeInner<T> {
}
// 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<T: GraphicsAdapter> SchemeSync for GraphicsSchemeInner<T> {
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<T: GraphicsAdapter> SchemeSync for GraphicsSchemeInner<T> {
})
}
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<T: GraphicsAdapter> SchemeSync for GraphicsSchemeInner<T> {
} => (
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::<Vec<_>>();
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<usize> {
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<Self> {
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<connector::Handle> {
- 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::<u32>();
let onscreen_ptr = self.map.as_mut_ptr().cast::<u32>();
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<u32> {
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<u32>) -> 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<Self>, 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<Self>,
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::<u32>(len)
- .unwrap()
+ .unwrap_or_else(|_| Layout::from_size_align(len * 4, PAGE_SIZE).unwrap_or(Layout::new::<u32>()))
.align_to(PAGE_SIZE)
- .unwrap()
+ .unwrap_or_else(|_| Layout::new::<u8>().align_to(PAGE_SIZE).unwrap_or(Layout::new::<u8>()))
}
}
@@ -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::<u32>();
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<Source> =
- EventQueue::new().expect("ihdgd: failed to create event queue");
+ let event_queue: EventQueue<Source> = 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::<HashMap<String, String>>();
+ let bootloader_env: HashMap<String, String> =
+ 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<Source> =
- 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<Source> = 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<Self>, 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<Self>,
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::<u32>(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<usize, _> = sync_rect.x.try_into() else {
+ return;
+ };
+ let Ok(start_y): Result<usize, _> = sync_rect.y.try_into() else {
+ return;
+ };
+ let Ok(w): Result<usize, _> = sync_rect.width.try_into() else {
+ return;
+ };
+ let Ok(h): Result<usize, _> = 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<Source> =
- 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<Self>) {
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<Self>, 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/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<u8>,
+ _rxsdt: Vec<u8>,
}
impl Backend for AcpiBackend {
fn new() -> Result<Self, Box<dyn Error>> {
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<dyn Error>> {
+ 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..f58ec825 100644
--- a/drivers/hwd/src/main.rs
+++ b/drivers/hwd/src/main.rs
@@ -37,8 +37,15 @@ 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::process::Command::new("pcid").spawn() {
+ Ok(_child) => {}
+ Err(err) => {
+ log::error!("hwd: failed to spawn pcid: {}", 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<bool, ()> {
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::<VtEvent>() {
+ Err(io::Error::new(
+ io::ErrorKind::InvalidData,
+ format!("inputd: partial vt event read: got {nread}, expected {}", size_of::<VtEvent>()),
+ ))
} else {
- assert_eq!(nread, size_of::<VtEvent>());
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<usize> {
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<usize> {
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<Self> {
+ 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<Self> {
+ 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<Self> {
+ 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<Self> {
+ 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<DeviceConsumerHandleEvent<'a>> {
+ 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<u8>,
+}
+
+impl HotplugHandle {
+ pub fn new() -> io::Result<Self> {
+ 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<Option<HotplugEvent>> {
+ 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<Vec<String>> {
+ 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<u8> {
+ 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<u8>,
@@ -46,6 +96,17 @@ enum Handle {
notified: bool,
vt: usize,
},
+ DeviceConsumer {
+ device_name: String,
+ events: EventFlags,
+ pending: Vec<u8>,
+ notified: bool,
+ },
+ HotplugEvents {
+ events: EventFlags,
+ pending: Vec<u8>,
+ notified: bool,
+ },
Display {
events: EventFlags,
pending: Vec<VtEvent>,
@@ -72,6 +133,9 @@ struct InputScheme {
rshift: bool,
has_new_events: bool,
+
+ devices: BTreeMap<String, u32>,
+ 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<DirentBuf<&'buf mut [u8]>> {
+ 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::<VtEvent>() == 0 {
let copy = core::cmp::min(pending.len(), buf.len() / size_of::<VtEvent>());
@@ -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::<Event>(),
)
});
+ 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::<Event>(),
)
};
- 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::<usize>().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::<usize>().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<T, const N: usize>() -> Result<[Dma<T>; N]> {
- Ok((0..N)
+ let vec: Vec<Dma<T>> = (0..N)
.map(|_| Ok(unsafe { Dma::zeroed()?.assume_init() }))
- .collect::<Result<Vec<_>>>()?
- .try_into()
- .unwrap_or_else(|_| unreachable!()))
+ .collect::<Result<Vec<_>>>()?;
+ vec.try_into().map_err(|_| Error::new(EIO))
}
impl Intel8254x {
pub unsafe fn new(base: usize) -> Result<Self> {
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::<Source>::new().expect("e1000d: failed to create event queue");
+ let mut event_queue = EventQueue::<Source>::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::<Result<Vec<_>>>()?
.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::<Result<Vec<_>>>()?
.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::<Source>::new().expect("ixgbed: Could not create event queue.");
+ let mut event_queue = EventQueue::<Source>::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::<Result<Vec<_>>>()?
.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::<Source>::new().expect("rtl8139d: Could not create event queue.");
+ let mut event_queue = EventQueue::<Source>::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::<Result<Vec<_>>>()?
.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::<Result<Vec<_>>>()?
.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::<Source>::new().expect("rtl8168d: Could not create event queue.");
+ let mut event_queue = EventQueue::<Source>::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::<syscall::Event>()])?; // Wait for event
- scheme.tick()?;
+ if let Err(err) = event_queue.read(&mut [0; mem::size_of::<syscall::Event>()]) {
+ 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,40 @@
+use std::env;
use std::fs;
use std::process::Command;
+use std::thread;
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,6 +59,12 @@ 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
+ );
for entry in fs::read_dir("/scheme/pci")? {
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::<u32>::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::<u32>::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<T>(
)
})?;
- 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::<InterruptMap>::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<T>(
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<T>(
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<u16, PciBarError> {
match *self {
- PciBar::Port(port) => port,
+ PciBar::Port(port) => Ok(port),
PciBar::Memory32 { .. } | PciBar::Memory64 { .. } => {
- panic!("expected port BAR, found memory BAR");
+ Err(PciBarError::ExpectedPortFoundMemory)
}
- PciBar::None => panic!("expected BAR to exist"),
+ PciBar::None => Err(PciBarError::Missing),
}
}
pub fn expect_mem(&self) -> (usize, usize) {
+ 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<u8>,
}
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum VendorSpecificCapabilityError {
+ InvalidLength(u16),
+}
+
+impl fmt::Display for VendorSpecificCapabilityError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ VendorSpecificCapabilityError::InvalidLength(length) => {
+ write!(f, "invalid vendor capability length: {length}")
+ }
+ }
+ }
+}
+
impl VendorSpecificCapability {
pub unsafe fn parse(addr: PciCapabilityAddress, access: &dyn ConfigRegionAccess) -> Self {
+ 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<Self, VendorSpecificCapabilityError> {
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<BTreeMap<(PciAddress, u16), u32>>,
+ }
+
+ impl MockConfigRegionAccess {
+ fn with_read(address: PciAddress, offset: u16, value: u32) -> Self {
+ let mut map = BTreeMap::new();
+ map.insert((address, offset), value);
+ Self {
+ values: Mutex::new(map),
+ }
+ }
+ }
+
+ impl ConfigRegionAccess for MockConfigRegionAccess {
+ unsafe fn read(&self, address: PciAddress, offset: u16) -> u32 {
+ self.values
+ .lock()
+ .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<usize> {
buffer[0], buffer[1], buffer[2], buffer[3],
]))
} else {
- panic!(
- "`/scheme/irq` scheme responded with {} bytes, expected {}",
- bytes_read,
- std::mem::size_of::<usize>()
- );
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidData,
+ format!(
+ "`/scheme/irq` scheme responded with {bytes_read} bytes, expected {}",
+ std::mem::size_of::<usize>()
+ ),
+ ));
})
.or(Err(io::Error::new(
io::ErrorKind::InvalidData,
@@ -83,7 +86,12 @@ pub fn allocate_aligned_interrupt_vectors(
alignment: NonZeroU8,
count: u8,
) -> io::Result<Option<(u8, Vec<File>)>> {
- 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<Option<(u8, Vec<File>)>> {
- 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<Option<(u8,
Err(err) => 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<File, InterruptVectorError> {
use crate::driver_interface::{MsiSetFeatureInfo, PciFeature, SetFeatureInfo};
- // TODO: Allow allocation of up to 32 vectors.
-
- let destination_id = read_bsp_apic_id().expect("failed to read BSP apic id");
- let (msg_addr_and_data, interrupt_handle) =
- allocate_single_interrupt_vector_for_msi(destination_id);
+ let destination_id = read_bsp_apic_id().map_err(InterruptVectorError::ApicId)?;
+ let (msg_addr_and_data, interrupt_handle) = try_allocate_single_interrupt_vector_for_msi(
+ destination_id,
+ )
+ .map_err(InterruptVectorError::Allocate)?;
let set_feature_info = MsiSetFeatureInfo {
multi_message_enable: Some(0),
@@ -222,10 +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<InterruptVector, InterruptVectorError> {
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<InterruptVector, InterruptVectorError> {
if let Some(irq) = pcid_handle.config().func.legacy_interrupt_line {
- // INTx# pin based interrupts.
- InterruptVector {
- irq_handle: irq.irq_handle(driver),
+ Ok(InterruptVector {
+ irq_handle: irq
+ .try_irq_handle(driver)
+ .map_err(InterruptVectorError::IrqHandle)?,
vector: 0,
kind: InterruptVectorKind::Legacy,
- }
+ })
} else {
- panic!("{driver}: no interrupts supported at all")
+ Err(InterruptVectorError::MissingLegacyInterrupt)
+ }
+}
+
+#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
+pub fn pci_allocate_interrupt_vector(
+ pcid_handle: &mut crate::driver_interface::PciFunctionHandle,
+ driver: &str,
+) -> InterruptVector {
+ 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<File> {
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<T: Serialize>(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<T: DeserializeOwned>(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<T: DeserializeOwned>(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<T: Serialize>(w: &mut File, message: &T) -> io::Result<()> {
+ let mut data = Vec::new();
+ bincode::serialize_into(&mut data, message)
+ .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
+
+ let len = w.write(&data)?;
+ if len == data.len() {
+ Ok(())
+ } else {
+ Err(io::Error::new(
+ io::ErrorKind::WriteZero,
+ format!("short pcid request write: wrote {len} of {} bytes", data.len()),
+ ))
}
+}
+
+fn recv_result<T: DeserializeOwned>(r: &mut File) -> io::Result<T> {
+ let mut length_bytes = [0u8; 8];
+ r.read_exact(&mut length_bytes)?;
let length = u64::from_le_bytes(length_bytes);
if length > 0x100_000 {
- 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<Self> {
- 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<VendorSpecificCapability> {
- self.send(&PcidClientRequest::RequestVendorCapabilities);
- match self.recv() {
- PcidClientResponse::VendorCapabilities(a) => a,
- other => {
- log::error!("received wrong pcid response: {other:?}");
+ match self.try_get_vendor_capabilities() {
+ Ok(capabilities) => capabilities,
+ Err(err) => {
+ log::error!("failed to fetch vendor capabilities: {err}");
process::exit(1);
}
}
}
+ pub fn try_get_vendor_capabilities(&mut self) -> io::Result<Vec<VendorSpecificCapability>> {
+ send_result(&mut self.channel, &PcidClientRequest::RequestVendorCapabilities)?;
+ match recv_result(&mut self.channel)? {
+ PcidClientResponse::VendorCapabilities(capabilities) => Ok(capabilities),
+ other => Err(io::Error::new(
+ io::ErrorKind::InvalidData,
+ format!(
+ "received wrong pcid response while requesting vendor capabilities: {other:?}"
+ ),
+ )),
+ }
+ }
+
// FIXME turn into struct with bool fields
+ pub fn try_fetch_all_features(&mut self) -> io::Result<Vec<PciFeature>> {
+ send_result(&mut self.channel, &PcidClientRequest::RequestFeatures)?;
+ match recv_result(&mut self.channel)? {
+ PcidClientResponse::AllFeatures(features) => Ok(features),
+ other => Err(io::Error::new(
+ io::ErrorKind::InvalidData,
+ format!("received wrong pcid response while fetching features: {other:?}"),
+ )),
+ }
+ }
+
pub fn fetch_all_features(&mut self) -> Vec<PciFeature> {
- self.send(&PcidClientRequest::RequestFeatures);
- match self.recv() {
- PcidClientResponse::AllFeatures(a) => a,
- other => {
- log::error!("received wrong pcid response: {other:?}");
+ match self.try_fetch_all_features() {
+ Ok(features) => features,
+ Err(err) => {
+ log::error!("failed to fetch PCI features: {err}");
process::exit(1);
}
}
}
+ pub fn try_enable_feature(&mut self, feature: PciFeature) -> io::Result<()> {
+ send_result(&mut self.channel, &PcidClientRequest::EnableFeature(feature))?;
+ match recv_result(&mut self.channel)? {
+ PcidClientResponse::FeatureEnabled(feat) if feat == feature => Ok(()),
+ other => Err(io::Error::new(
+ io::ErrorKind::InvalidData,
+ format!("received wrong pcid response while enabling feature: {other:?}"),
+ )),
+ }
+ }
pub fn enable_feature(&mut self, feature: PciFeature) {
- self.send(&PcidClientRequest::EnableFeature(feature));
- match self.recv() {
- PcidClientResponse::FeatureEnabled(feat) if feat == feature => {}
- other => {
- log::error!("received wrong pcid response: {other:?}");
- process::exit(1);
- }
+ if let Err(err) = self.try_enable_feature(feature) {
+ log::error!("failed to enable PCI feature {feature:?}: {err}");
+ process::exit(1);
+ }
+ }
+ pub fn try_feature_info(&mut self, feature: PciFeature) -> io::Result<PciFeatureInfo> {
+ send_result(&mut self.channel, &PcidClientRequest::FeatureInfo(feature))?;
+ match recv_result(&mut self.channel)? {
+ PcidClientResponse::FeatureInfo(feat, info) if feat == feature => Ok(info),
+ other => Err(io::Error::new(
+ io::ErrorKind::InvalidData,
+ format!("received wrong pcid response while reading feature info: {other:?}"),
+ )),
}
}
pub fn feature_info(&mut self, feature: PciFeature) -> PciFeatureInfo {
- self.send(&PcidClientRequest::FeatureInfo(feature));
- match self.recv() {
- PcidClientResponse::FeatureInfo(feat, info) if feat == feature => info,
- other => {
- log::error!("received wrong pcid response: {other:?}");
+ match self.try_feature_info(feature) {
+ Ok(info) => info,
+ Err(err) => {
+ log::error!("failed to fetch PCI feature info for {feature:?}: {err}");
process::exit(1);
}
}
@@ -433,33 +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<u32> {
+ send_result(&mut self.channel, &PcidClientRequest::ReadConfig(offset))?;
+ match recv_result(&mut self.channel)? {
+ PcidClientResponse::ReadConfig(value) => Ok(value),
+ other => Err(io::Error::new(
+ io::ErrorKind::InvalidData,
+ format!("received wrong pcid response while reading config: {other:?}"),
+ )),
+ }
+ }
pub unsafe fn write_config(&mut self, offset: u16, value: u32) {
- self.send(&PcidClientRequest::WriteConfig(offset, value));
- match self.recv() {
- PcidClientResponse::WriteConfig => {}
- other => {
- log::error!("received wrong pcid response: {other:?}");
- process::exit(1);
- }
+ if let Err(err) = self.try_write_config(offset, value) {
+ log::error!("failed to write PCI config dword at {offset:#x}: {err}");
+ process::exit(1);
}
}
- pub unsafe fn map_bar(&mut self, bir: u8) -> &MappedBar {
+ pub unsafe fn try_write_config(&mut self, offset: u16, value: u32) -> io::Result<()> {
+ send_result(&mut self.channel, &PcidClientRequest::WriteConfig(offset, value))?;
+ match recv_result(&mut self.channel)? {
+ PcidClientResponse::WriteConfig => Ok(()),
+ other => Err(io::Error::new(
+ io::ErrorKind::InvalidData,
+ format!("received wrong pcid response while writing config: {other:?}"),
+ )),
+ }
+ }
+ pub unsafe fn try_map_bar(&mut self, bir: u8) -> io::Result<&MappedBar> {
let mapped_bar = &mut self.mapped_bars[bir as usize];
if let Some(mapped_bar) = mapped_bar {
- mapped_bar
+ Ok(mapped_bar)
} else {
- let (bar, bar_size) = self.config.func.bars[bir as usize].expect_mem();
+ let (bar, bar_size) = self.config.func.bars[bir as usize]
+ .try_mem()
+ .map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err.to_string()))?;
- let ptr = match unsafe {
+ let ptr = unsafe {
common::physmap(
bar,
bar_size,
@@ -467,18 +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::<u8>()).expect("Mapping a BAR resulted in a nullptr"),
+ Ok(mapped_bar.insert(MappedBar {
+ ptr: NonNull::new(ptr.cast::<u8>()).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<MappedMsixRegs, MsixMapError> {
+ 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::<MsixTableEntry>()).unwrap(),
+ virt_table_base: NonNull::new(virt_table_base.cast::<MsixTableEntry>())
+ .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<u32>,
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<String> },
Access,
Device,
+ Config { addr: PciAddress },
Channel { addr: PciAddress, st: ChannelState },
SchemeRoot,
}
@@ -30,14 +31,20 @@ struct HandleWrapper {
}
impl Handle {
fn is_file(&self) -> bool {
- matches!(self, Self::Access | Self::Channel { .. })
+ matches!(
+ self,
+ Self::Access | Self::Config { .. } | Self::Channel { .. }
+ )
}
fn is_dir(&self) -> bool {
!self.is_file()
}
// TODO: capability rather than root
fn requires_root(&self) -> bool {
- matches!(self, Self::Access | Self::Channel { .. })
+ matches!(
+ self,
+ Self::Access | Self::Config { .. } | Self::Channel { .. }
+ )
}
fn is_scheme_root(&self) -> bool {
matches!(self, Self::SchemeRoot)
@@ -132,6 +139,7 @@ impl SchemeSync for PciScheme {
let (len, mode) = match handle.inner {
Handle::TopLevel { ref entries } => (entries.len(), MODE_DIR | 0o755),
Handle::Device => (DEVICE_CONTENTS.len(), MODE_DIR | 0o755),
+ Handle::Config { .. } => (256, MODE_CHR | 0o600),
Handle::Access | Handle::Channel { .. } => (0, MODE_CHR | 0o600),
Handle::SchemeRoot => return Err(Error::new(EBADF)),
};
@@ -156,6 +164,18 @@ impl SchemeSync for PciScheme {
match handle.inner {
Handle::TopLevel { .. } => Err(Error::new(EISDIR)),
Handle::Device => Err(Error::new(EISDIR)),
+ Handle::Config { addr } => {
+ let offset = _offset as u16;
+ let dword_offset = offset & !0x3;
+ let byte_offset = (offset & 0x3) as usize;
+ let bytes_to_read = buf.len().min(4 - byte_offset);
+
+ let dword = unsafe { self.pcie.read(addr, dword_offset) };
+ let bytes = dword.to_le_bytes();
+ buf[..bytes_to_read]
+ .copy_from_slice(&bytes[byte_offset..byte_offset + bytes_to_read]);
+ Ok(bytes_to_read)
+ }
Handle::Channel {
addr: _,
ref mut st,
@@ -193,7 +213,9 @@ impl SchemeSync for PciScheme {
return Ok(buf);
}
Handle::Device => DEVICE_CONTENTS,
- Handle::Access | Handle::Channel { .. } => return Err(Error::new(ENOTDIR)),
+ Handle::Access | Handle::Config { .. } | Handle::Channel { .. } => {
+ return Err(Error::new(ENOTDIR));
+ }
Handle::SchemeRoot => return Err(Error::new(EBADF)),
};
@@ -223,6 +245,20 @@ impl SchemeSync for PciScheme {
}
match handle.inner {
+ Handle::Config { addr } => {
+ let offset = _offset as u16;
+ let dword_offset = offset & !0x3;
+ let byte_offset = (offset & 0x3) as usize;
+ let bytes_to_write = buf.len().min(4 - byte_offset);
+
+ let mut dword = unsafe { self.pcie.read(addr, dword_offset) };
+ let mut bytes = dword.to_le_bytes();
+ bytes[byte_offset..byte_offset + bytes_to_write]
+ .copy_from_slice(&buf[..bytes_to_write]);
+ dword = u32::from_le_bytes(bytes);
+ unsafe { self.pcie.write(addr, dword_offset, dword) };
+ Ok(buf.len())
+ }
Handle::Channel { addr, ref mut st } => {
Self::write_channel(&self.pcie, &mut self.tree, addr, st, buf)
}
@@ -316,6 +352,10 @@ impl SchemeSync for PciScheme {
func.enabled = false;
}
}
+ Some(HandleWrapper {
+ inner: Handle::Config { .. },
+ ..
+ }) => {}
_ => {}
}
}
@@ -341,6 +381,7 @@ impl PciScheme {
let path = &after[1..];
match path {
+ "config" => Handle::Config { addr },
"channel" => {
if func.enabled {
return Err(Error::new(ENOLCK));
@@ -387,7 +428,7 @@ impl PciScheme {
match *state {
ChannelState::AwaitingResponseRead(_) => return Err(Error::new(EINVAL)),
ChannelState::AwaitingData => {
- let func = tree.get_mut(&addr).unwrap();
+ let func = tree.get_mut(&addr).ok_or(Error::new(ENOENT))?;
let request = bincode::deserialize_from(buf).map_err(|_| Error::new(EINVAL))?;
let response = crate::driver_handler::DriverHandler::new(
diff --git a/drivers/storage/ahcid/src/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::<Result<Vec<_>>>()?
.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::<Result<Vec<_>>>()?
.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<HbaCmdTable>; 32],
) -> Result<u64> {
- 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<executor::LocalExecutor<Self>> {
- 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<Nvme>,
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<IdentifyControllerData> = unsafe { Dma::zeroed().unwrap().assume_init() };
+ let data: Dma<IdentifyControllerData> = 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<u32> {
// 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<IdentifyNamespaceData> = unsafe { Dma::zeroed().unwrap().assume_init() };
+ let data: Dma<IdentifyNamespaceData> = 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<ReentrantMutex<ThreadCtxt>> {
// 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<Iv>,
) -> 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<u64>,
<T as TryFrom<u64>>::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::<T>()
.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::<String>("ENDPOINT_NUM")
diff --git a/drivers/usb/xhcid/drivers.toml b/drivers/usb/xhcid/drivers.toml
index 83c90e23..470ec063 100644
--- a/drivers/usb/xhcid/drivers.toml
+++ b/drivers/usb/xhcid/drivers.toml
@@ -1,9 +1,8 @@
-#TODO: causes XHCI errors
-#[[drivers]]
-#name = "SCSI over USB"
-#class = 8 # Mass Storage class
-#subclass = 6 # SCSI transparent command set
-#command = ["usbscsid", "$SCHEME", "$PORT", "$IF_PROTO"]
+[[drivers]]
+name = "SCSI over USB"
+class = 8 # Mass Storage class
+subclass = 6 # SCSI transparent command set
+command = ["usbscsid", "$SCHEME", "$PORT", "$IF_PROTO"]
[[drivers]]
name = "USB HUB"
diff --git a/drivers/usb/xhcid/src/driver_interface.rs b/drivers/usb/xhcid/src/driver_interface.rs
index 727f8d7e..82f839ae 100644
--- a/drivers/usb/xhcid/src/driver_interface.rs
+++ b/drivers/usb/xhcid/src/driver_interface.rs
@@ -444,6 +444,33 @@ impl str::FromStr for PortState {
}
}
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
+pub enum PortPmState {
+ Active,
+ Suspended,
+}
+impl PortPmState {
+ pub fn as_str(&self) -> &'static str {
+ match self {
+ Self::Active => "active",
+ Self::Suspended => "suspended",
+ }
+ }
+}
+
+impl str::FromStr for PortPmState {
+ type Err = Invalid;
+
+ fn from_str(s: &str) -> result::Result<Self, Self::Err> {
+ Ok(match s {
+ "active" => Self::Active,
+ "suspended" => Self::Suspended,
+ _ => return Err(Invalid("read reserved port PM state")),
+ })
+ }
+}
+
#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub enum EndpointStatus {
@@ -560,6 +587,16 @@ impl XhciClientHandle {
let _bytes_written = file.write(&[])?;
Ok(())
}
+ pub fn suspend_device(&self) -> result::Result<(), XhciClientHandleError> {
+ let file = self.fd.openat("suspend", libredox::flag::O_WRONLY, 0)?;
+ let _bytes_written = file.write(&[])?;
+ Ok(())
+ }
+ pub fn resume_device(&self) -> result::Result<(), XhciClientHandleError> {
+ let file = self.fd.openat("resume", libredox::flag::O_WRONLY, 0)?;
+ let _bytes_written = file.write(&[])?;
+ Ok(())
+ }
pub fn get_standard_descs(&self) -> result::Result<DevDesc, XhciClientHandleError> {
let json = self.read("descriptors")?;
Ok(serde_json::from_slice(&json)?)
@@ -582,6 +619,10 @@ impl XhciClientHandle {
let string = self.read_to_string("state")?;
Ok(string.parse()?)
}
+ pub fn port_pm_state(&self) -> result::Result<PortPmState, XhciClientHandleError> {
+ let string = self.read_to_string("pm_state")?;
+ Ok(string.parse()?)
+ }
pub fn open_endpoint_ctl(&self, num: u8) -> result::Result<File, XhciClientHandleError> {
let path = format!("endpoints/{}/ctl", num);
let fd = self.fd.openat(&path, libredox::flag::O_RDWR, 0)?;
diff --git a/drivers/usb/xhcid/src/main.rs b/drivers/usb/xhcid/src/main.rs
index d345a52f..562c580a 100644
--- a/drivers/usb/xhcid/src/main.rs
+++ b/drivers/usb/xhcid/src/main.rs
@@ -33,7 +33,7 @@ use std::sync::Arc;
use pcid_interface::irq_helpers::read_bsp_apic_id;
#[cfg(target_arch = "x86_64")]
use pcid_interface::irq_helpers::{
- allocate_first_msi_interrupt_on_bsp, allocate_single_interrupt_vector_for_msi,
+ try_allocate_first_msi_interrupt_on_bsp, try_allocate_single_interrupt_vector_for_msi,
};
use pcid_interface::{PciFeature, PciFeatureInfo, PciFunctionHandle};
@@ -61,11 +61,24 @@ fn get_int_method(pcid_handle: &mut PciFunctionHandle) -> (Option<File>, Interru
let has_msix = all_pci_features.iter().any(|feature| feature.is_msix());
if has_msix {
- let msix_info = match pcid_handle.feature_info(PciFeature::MsiX) {
- PciFeatureInfo::Msi(_) => panic!(),
- PciFeatureInfo::MsiX(s) => s,
+ let msix_info = match pcid_handle.try_feature_info(PciFeature::MsiX) {
+ Ok(PciFeatureInfo::MsiX(s)) => s,
+ Ok(PciFeatureInfo::Msi(_)) => {
+ log::error!("xhcid: invalid MSI-X feature response payload");
+ return (None, InterruptMethod::Polling);
+ }
+ Err(err) => {
+ log::error!("xhcid: failed to fetch MSI-X feature info: {err}");
+ return (None, InterruptMethod::Polling);
+ }
+ };
+ let mut info = match unsafe { msix_info.try_map_and_mask_all(pcid_handle) } {
+ Ok(info) => info,
+ Err(err) => {
+ log::error!("xhcid: failed to map MSI-X registers: {err}");
+ return (None, InterruptMethod::Polling);
+ }
};
- let mut info = unsafe { msix_info.map_and_mask_all(pcid_handle) };
// Allocate one msi vector.
@@ -75,27 +88,53 @@ fn get_int_method(pcid_handle: &mut PciFunctionHandle) -> (Option<File>, Interru
let table_entry_pointer = info.table_entry_pointer(k);
- let destination_id = read_bsp_apic_id().expect("xhcid: failed to read BSP apic id");
+ let destination_id = match read_bsp_apic_id() {
+ Ok(id) => id,
+ Err(err) => {
+ log::error!("xhcid: failed to read BSP APIC ID: {err}");
+ return (None, InterruptMethod::Polling);
+ }
+ };
let (msg_addr_and_data, interrupt_handle) =
- allocate_single_interrupt_vector_for_msi(destination_id);
+ match try_allocate_single_interrupt_vector_for_msi(destination_id) {
+ Ok(result) => result,
+ Err(err) => {
+ log::error!("xhcid: failed to allocate MSI-X vector: {err}");
+ return (None, InterruptMethod::Polling);
+ }
+ };
table_entry_pointer.write_addr_and_data(msg_addr_and_data);
table_entry_pointer.unmask();
(Some(interrupt_handle), InterruptMethod::Msi)
};
- pcid_handle.enable_feature(PciFeature::MsiX);
+ if let Err(err) = pcid_handle.try_enable_feature(PciFeature::MsiX) {
+ log::error!("xhcid: failed to enable MSI-X: {err}");
+ return (None, InterruptMethod::Polling);
+ }
log::debug!("Enabled MSI-X");
method
} else if has_msi {
- let interrupt_handle = allocate_first_msi_interrupt_on_bsp(pcid_handle);
- (Some(interrupt_handle), InterruptMethod::Msi)
+ match try_allocate_first_msi_interrupt_on_bsp(pcid_handle) {
+ Ok(interrupt_handle) => (Some(interrupt_handle), InterruptMethod::Msi),
+ Err(err) => {
+ log::error!("xhcid: failed to allocate MSI interrupt: {err}");
+ (None, InterruptMethod::Polling)
+ }
+ }
} else if let Some(irq) = pci_config.func.legacy_interrupt_line {
log::debug!("Legacy IRQ {}", irq);
// legacy INTx# interrupt pins.
- (Some(irq.irq_handle("xhcid")), InterruptMethod::Intx)
+ match irq.try_irq_handle("xhcid") {
+ Ok(file) => (Some(file), InterruptMethod::Intx),
+ Err(err) => {
+ log::error!("xhcid: failed to open legacy IRQ handle: {err}");
+ (None, InterruptMethod::Polling)
+ }
+ }
} else {
// no interrupts at all
(None, InterruptMethod::Polling)
@@ -109,7 +148,13 @@ fn get_int_method(pcid_handle: &mut PciFunctionHandle) -> (Option<File>, Interru
if let Some(irq) = pci_config.func.legacy_interrupt_line {
// legacy INTx# interrupt pins.
- (Some(irq.irq_handle("xhcid")), InterruptMethod::Intx)
+ match irq.try_irq_handle("xhcid") {
+ Ok(file) => (Some(file), InterruptMethod::Intx),
+ Err(err) => {
+ log::error!("xhcid: failed to open legacy IRQ handle: {err}");
+ (None, InterruptMethod::Polling)
+ }
+ }
} else {
// no interrupts at all
(None, InterruptMethod::Polling)
@@ -136,23 +181,48 @@ fn daemon_with_context_size<const N: usize>(
log::debug!("XHCI PCI CONFIG: {:?}", pci_config);
- let address = unsafe { pcid_handle.map_bar(0) }.ptr.as_ptr() as usize;
+ let address = match unsafe { pcid_handle.try_map_bar(0) } {
+ Ok(bar) => bar.ptr.as_ptr() as usize,
+ Err(err) => {
+ log::error!("xhcid: failed to map BAR0: {err}");
+ std::process::exit(1);
+ }
+ };
+
+ let (irq_file, interrupt_method) = get_int_method(&mut pcid_handle);
- let (irq_file, interrupt_method) = (None, InterruptMethod::Polling); //get_int_method(&mut pcid_handle);
- //TODO: Fix interrupts.
+ match interrupt_method {
+ InterruptMethod::Msi => log::info!("xhcid: using MSI/MSI-X interrupt delivery"),
+ InterruptMethod::Intx => log::info!("xhcid: using legacy INTx interrupt delivery"),
+ InterruptMethod::Polling => log::warn!("xhcid: falling back to polling mode"),
+ }
log::info!("XHCI {}", pci_config.func.display());
let scheme_name = format!("usb.{}", name);
- let socket = Socket::create().expect("xhcid: failed to create usb scheme");
+ let socket = match Socket::create() {
+ Ok(socket) => socket,
+ Err(err) => {
+ log::error!("xhcid: failed to create usb scheme: {err}");
+ std::process::exit(1);
+ }
+ };
let handler = Blocking::new(&socket, 16);
let hci = Arc::new(
- Xhci::<N>::new(scheme_name.clone(), address, interrupt_method, pcid_handle)
- .expect("xhcid: failed to allocate device"),
+ match Xhci::<N>::new(scheme_name.clone(), address, interrupt_method, pcid_handle) {
+ Ok(hci) => hci,
+ Err(err) => {
+ log::error!("xhcid: failed to allocate device: {err}");
+ std::process::exit(1);
+ }
+ },
);
register_sync_scheme(&socket, &scheme_name, &mut &*hci)
- .expect("xhcid: failed to regsiter scheme to namespace");
+ .unwrap_or_else(|err| {
+ log::error!("xhcid: failed to register scheme to namespace: {err}");
+ std::process::exit(1);
+ });
daemon.ready();
@@ -163,7 +233,10 @@ fn daemon_with_context_size<const N: usize>(
handler
.process_requests_blocking(&*hci)
- .expect("xhcid: failed to process requests");
+ .unwrap_or_else(|err| {
+ log::error!("xhcid: failed to process requests: {err}");
+ std::process::exit(1);
+ });
}
fn main() {
@@ -171,7 +244,13 @@ fn main() {
}
fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
- let address = unsafe { pcid_handle.map_bar(0) }.ptr.as_ptr() as usize;
+ let address = match unsafe { pcid_handle.try_map_bar(0) } {
+ Ok(bar) => bar.ptr.as_ptr() as usize,
+ Err(err) => {
+ log::error!("xhcid: failed to map BAR0: {err}");
+ std::process::exit(1);
+ }
+ };
let cap = unsafe { &mut *(address as *mut xhci::CapabilityRegs) };
if cap.csz() {
daemon_with_context_size::<{ xhci::CONTEXT_64 }>(daemon, pcid_handle)
diff --git a/drivers/usb/xhcid/src/xhci/device_enumerator.rs b/drivers/usb/xhcid/src/xhci/device_enumerator.rs
index 74b9f732..493e79df 100644
--- a/drivers/usb/xhcid/src/xhci/device_enumerator.rs
+++ b/drivers/usb/xhcid/src/xhci/device_enumerator.rs
@@ -4,9 +4,11 @@ use common::io::Io;
use crossbeam_channel;
use log::{debug, info, warn};
use std::sync::Arc;
-use std::time::Duration;
+use std::time::{Duration, Instant};
use syscall::EAGAIN;
+const DEFAULT_PORT_RESET_SETTLE_MS: u64 = 16;
+
pub struct DeviceEnumerationRequest {
pub port_id: PortId,
}
@@ -28,7 +30,11 @@ impl<const N: usize> DeviceEnumerator<N> {
let request = match self.request_queue.recv() {
Ok(req) => req,
Err(err) => {
- panic!("Failed to received an enumeration request! error: {}", err)
+ warn!(
+ "device enumerator stopping after request queue closed: {}",
+ err
+ );
+ break;
}
};
@@ -38,7 +44,11 @@ impl<const N: usize> DeviceEnumerator<N> {
debug!("Device Enumerator request for port {}", port_id);
let (len, flags) = {
- let ports = self.hci.ports.lock().unwrap();
+ let ports = self
+ .hci
+ .ports
+ .lock()
+ .unwrap_or_else(|poisoned| poisoned.into_inner());
let len = ports.len();
@@ -62,43 +72,52 @@ impl<const N: usize> DeviceEnumerator<N> {
//A USB3 port won't generate a Connect Status Change until it's already enabled, so this check
//will always be skipped for USB3 ports
if !flags.contains(PortFlags::PED) {
- let disabled_state = flags.contains(PortFlags::PP)
- && flags.contains(PortFlags::CCS)
- && !flags.contains(PortFlags::PED)
- && !flags.contains(PortFlags::PR);
+ let disabled_state = Self::port_is_disabled(&flags);
if !disabled_state {
- panic!(
- "Port {} isn't in the disabled state! Current flags: {:?}",
+ warn!(
+ "Port {} never reached the disabled state before reset-driven enumeration; current flags: {:?}",
port_id, flags
);
+ continue;
} else {
debug!("Port {} has entered the disabled state.", port_id);
}
//THIS LOCKS THE PORTS. DO NOT LOCK PORTS BEFORE THIS POINT
debug!("Received a device connect on port {}, but it's not enabled. Resetting the port.", port_id);
- let _ = self.hci.reset_port(port_id);
+ if let Err(err) = self.hci.reset_port(port_id) {
+ warn!(
+ "failed to reset port {} before enumeration; skipping attach: {}",
+ port_id, err
+ );
+ continue;
+ }
- let mut ports = self.hci.ports.lock().unwrap();
+ let mut ports = self
+ .hci
+ .ports
+ .lock()
+ .unwrap_or_else(|poisoned| poisoned.into_inner());
let port = &mut ports[port_array_index];
port.clear_prc();
- std::thread::sleep(Duration::from_millis(16)); //Some controllers need some extra time to make the transition.
+ drop(ports);
- let flags = port.flags();
+ let flags = self.wait_for_port_enabled_state(
+ port_array_index,
+ Duration::from_millis(DEFAULT_PORT_RESET_SETTLE_MS),
+ );
- let enabled_state = flags.contains(PortFlags::PP)
- && flags.contains(PortFlags::CCS)
- && flags.contains(PortFlags::PED)
- && !flags.contains(PortFlags::PR);
+ let enabled_state = Self::port_is_enabled(&flags);
if !enabled_state {
warn!(
- "Port {} isn't in the enabled state! Current flags: {:?}",
+ "Port {} isn't in the enabled state after bounded reset settle; current flags: {:?}",
port_id, flags
);
+ continue;
} else {
debug!(
"Port {} is in the enabled state. Proceeding with enumeration",
@@ -131,13 +150,60 @@ impl<const N: usize> DeviceEnumerator<N> {
Ok(was_connected) => {
if was_connected {
info!("Device on port {} was detached", port_id);
+ } else {
+ debug!(
+ "Ignoring duplicate or out-of-order detach event for unattached port {}",
+ port_id
+ );
}
}
Err(err) => {
- warn!("processing of device attach request failed! Error: {}", err);
+ warn!("processing of device detach request failed! Error: {}", err);
}
}
}
}
}
+
+ fn port_is_disabled(flags: &PortFlags) -> bool {
+ flags.contains(PortFlags::PP)
+ && flags.contains(PortFlags::CCS)
+ && !flags.contains(PortFlags::PED)
+ && !flags.contains(PortFlags::PR)
+ }
+
+ fn port_is_enabled(flags: &PortFlags) -> bool {
+ flags.contains(PortFlags::PP)
+ && flags.contains(PortFlags::CCS)
+ && flags.contains(PortFlags::PED)
+ && !flags.contains(PortFlags::PR)
+ }
+
+ fn wait_for_port_enabled_state(
+ &self,
+ port_array_index: usize,
+ settle_timeout: Duration,
+ ) -> PortFlags {
+ let start = Instant::now();
+
+ loop {
+ let flags = {
+ let ports = self
+ .hci
+ .ports
+ .lock()
+ .unwrap_or_else(|poisoned| poisoned.into_inner());
+ ports[port_array_index].flags()
+ };
+
+ if Self::port_is_enabled(&flags)
+ || !flags.contains(PortFlags::PR)
+ || start.elapsed() >= settle_timeout
+ {
+ return flags;
+ }
+
+ std::thread::sleep(Duration::from_millis(1));
+ }
+ }
}
diff --git a/drivers/usb/xhcid/src/xhci/irq_reactor.rs b/drivers/usb/xhcid/src/xhci/irq_reactor.rs
index ac492d5b..310fe51f 100644
--- a/drivers/usb/xhcid/src/xhci/irq_reactor.rs
+++ b/drivers/usb/xhcid/src/xhci/irq_reactor.rs
@@ -633,7 +633,10 @@ impl<const N: usize> Xhci<N> {
pub fn with_ring<T, F: FnOnce(&Ring) -> T>(&self, id: RingId, function: F) -> Option<T> {
use super::RingOrStreams;
- let slot_state = self.port_states.get(&id.port)?;
+ let slot_state = self
+ .port_states
+ .get(&id.port)
+ .or_else(|| self.staged_port_states.get(&id.port))?;
let endpoint_state = slot_state.endpoint_states.get(&id.endpoint_num)?;
let ring_ref = match endpoint_state.transfer {
@@ -650,7 +653,10 @@ impl<const N: usize> Xhci<N> {
) -> Option<T> {
use super::RingOrStreams;
- let mut slot_state = self.port_states.get_mut(&id.port)?;
+ let mut slot_state = self
+ .port_states
+ .get_mut(&id.port)
+ .or_else(|| self.staged_port_states.get_mut(&id.port))?;
let mut endpoint_state = slot_state.endpoint_states.get_mut(&id.endpoint_num)?;
let ring_ref = match endpoint_state.transfer {
diff --git a/drivers/usb/xhcid/src/xhci/mod.rs b/drivers/usb/xhcid/src/xhci/mod.rs
index f2143676..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<const N: usize> Xhci<N> {
+ fn read_test_hook_command_from_path(path: &str) -> Option<String> {
+ let contents = fs::read_to_string(path).ok()?;
+ contents
+ .lines()
+ .map(|line| line.trim())
+ .find(|line| !line.is_empty() && !line.starts_with('#'))
+ .map(|line| line.to_owned())
+ }
+
+ fn clear_test_hook_command_path(path: &str) {
+ if let Err(err) = fs::remove_file(path) {
+ if err.kind() != std::io::ErrorKind::NotFound {
+ warn!("failed to remove xhcid test hook file {}: {}", path, err);
+ }
+ }
+ }
+
+ fn consume_test_hook_from_path(path: &str, expected: &str) -> bool {
+ match Self::read_test_hook_command_from_path(path) {
+ Some(command) if command == expected => {
+ Self::clear_test_hook_command_path(path);
+ true
+ }
+ _ => false,
+ }
+ }
+
+ fn consume_test_hook_delay_ms_from_path(path: &str, prefix: &str) -> Option<u64> {
+ let command = Self::read_test_hook_command_from_path(path)?;
+ let delay_ms = command.strip_prefix(prefix)?.parse::<u64>().ok()?;
+ Self::clear_test_hook_command_path(path);
+ Some(delay_ms.min(XHCID_TEST_HOOK_MAX_DELAY_MS))
+ }
+
+ pub(crate) fn consume_test_hook(&self, expected: &str) -> bool {
+ Self::consume_test_hook_from_path(XHCID_TEST_HOOK_PATH, expected)
+ }
+
+ pub(crate) fn consume_test_hook_delay_ms(&self, prefix: &str) -> Option<u64> {
+ Self::consume_test_hook_delay_ms_from_path(XHCID_TEST_HOOK_PATH, prefix)
+ }
+
/// Gets descriptors, before the port state is initiated.
async fn get_desc_raw<T>(
&self,
@@ -104,7 +150,17 @@ impl<const N: usize> Xhci<N> {
);
let future = {
- let mut port_state = self.port_states.get_mut(&port).ok_or(Error::new(ENOENT))?;
+ let mut published_port_state = self.port_states.get_mut(&port);
+ let mut staged_port_state = if published_port_state.is_none() {
+ self.staged_port_states.get_mut(&port)
+ } else {
+ None
+ };
+
+ let port_state = published_port_state
+ .as_deref_mut()
+ .or_else(|| staged_port_state.as_deref_mut())
+ .ok_or(Error::new(ENOENT))?;
let ring = port_state
.endpoint_states
.get_mut(&0)
@@ -283,6 +339,7 @@ pub struct Xhci<const N: usize> {
handles: CHashMap<usize, scheme::Handle>,
next_handle: AtomicUsize,
port_states: CHashMap<PortId, PortState<N>>,
+ staged_port_states: CHashMap<PortId, PortState<N>>,
drivers: CHashMap<PortId, Vec<process::Child>>,
scheme_name: String,
@@ -308,9 +365,97 @@ struct PortState<const N: usize> {
slot: u8,
protocol_speed: &'static ProtocolSpeed,
cfg_idx: Option<u8>,
+ active_ifaces: BTreeMap<u8, u8>, // iface number → active alternate setting
input_context: Mutex<Dma<InputContext<N>>>,
dev_desc: Option<DevDesc>,
endpoint_states: BTreeMap<u8, EndpointState>,
+ lifecycle: Arc<PortLifecycle>,
+ pm_state: PortPmState,
+}
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub(crate) enum PortLifecycleState {
+ Attaching,
+ Attached,
+ Detaching,
+}
+
+struct PortLifecycleInner {
+ state: PortLifecycleState,
+ active_operations: usize,
+}
+
+pub(crate) struct PortLifecycle {
+ inner: Mutex<PortLifecycleInner>,
+ idle: Condvar,
+}
+
+impl PortLifecycle {
+ pub(crate) fn new_attaching() -> Self {
+ Self {
+ inner: Mutex::new(PortLifecycleInner {
+ state: PortLifecycleState::Attaching,
+ active_operations: 1,
+ }),
+ idle: Condvar::new(),
+ }
+ }
+
+ fn lock_inner(&self) -> std::sync::MutexGuard<'_, PortLifecycleInner> {
+ self.inner.lock().unwrap_or_else(|err| err.into_inner())
+ }
+
+ pub(crate) fn finish_attach_success(&self) -> PortLifecycleState {
+ let mut inner = self.lock_inner();
+
+ if inner.state == PortLifecycleState::Attaching {
+ inner.state = PortLifecycleState::Attached;
+ }
+
+ if inner.active_operations != 0 {
+ inner.active_operations -= 1;
+ }
+ if inner.active_operations == 0 {
+ self.idle.notify_all();
+ }
+
+ inner.state
+ }
+
+ pub(crate) fn finish_attach_failure(&self) {
+ let mut inner = self.lock_inner();
+ inner.state = PortLifecycleState::Detaching;
+
+ if inner.active_operations != 0 {
+ inner.active_operations -= 1;
+ }
+ if inner.active_operations == 0 {
+ self.idle.notify_all();
+ }
+ }
+
+ pub(crate) fn begin_detaching(&self) {
+ let mut inner = self.lock_inner();
+ inner.state = PortLifecycleState::Detaching;
+
+ while inner.active_operations != 0 {
+ inner = self.idle.wait(inner).unwrap_or_else(|err| err.into_inner());
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub(crate) enum PortPmState {
+ Active,
+ Suspended,
+}
+impl PortPmState {
+ pub fn as_str(&self) -> &'static str {
+ match self {
+ Self::Active => "active",
+ Self::Suspended => "suspended",
+ }
+ }
}
impl<const N: usize> PortState<N> {
@@ -463,6 +608,7 @@ impl<const N: usize> Xhci<N> {
handles: CHashMap::new(),
next_handle: AtomicUsize::new(0),
port_states: CHashMap::new(),
+ staged_port_states: CHashMap::new(),
drivers: CHashMap::new(),
scheme_name,
@@ -793,11 +939,14 @@ impl<const N: usize> Xhci<N> {
}
pub async fn attach_device(&self, port_id: PortId) -> syscall::Result<()> {
- if self.port_states.contains_key(&port_id) {
+ if self.port_states.contains_key(&port_id) || self.staged_port_states.contains_key(&port_id)
+ {
debug!("Already contains port {}", port_id);
return Err(syscall::Error::new(EAGAIN));
}
+ info!("xhcid: begin attach for port {}", port_id);
+
let (data, state, speed, flags) = {
let port = &self.ports.lock().unwrap()[port_id.root_hub_port_index()];
(port.read(), port.state(), port.speed(), port.flags())
@@ -808,74 +957,102 @@ impl<const N: usize> Xhci<N> {
port_id, data, state, speed, flags
);
- if flags.contains(port::PortFlags::CCS) {
- let slot_ty = match self.supported_protocol(port_id) {
- Some(protocol) => protocol.proto_slot_ty(),
- None => {
- warn!("Failed to find supported protocol information for port");
- 0
- }
- };
-
- debug!("Slot type: {}", slot_ty);
- debug!("Enabling slot.");
- let slot = match self.enable_port_slot(slot_ty).await {
- Ok(ok) => ok,
- Err(err) => {
- error!("Failed to enable slot for port {}: {}", port_id, err);
- return Err(err);
- }
- };
+ if !flags.contains(port::PortFlags::CCS) {
+ warn!("Attempted to attach a device that didnt have CCS=1");
+ return Ok(());
+ }
- debug!("Enabled port {}, which the xHC mapped to {}", port_id, slot);
+ let slot_ty = match self.supported_protocol(port_id) {
+ Some(protocol) => protocol.proto_slot_ty(),
+ None => {
+ warn!("Failed to find supported protocol information for port");
+ 0
+ }
+ };
- //TODO: get correct speed for child devices
- let protocol_speed = self
- .lookup_psiv(port_id, speed)
- .expect("Failed to retrieve speed ID");
+ debug!("Slot type: {}", slot_ty);
+ debug!("Enabling slot.");
+ let slot = match self.enable_port_slot(slot_ty).await {
+ Ok(ok) => ok,
+ Err(err) => {
+ error!("Failed to enable slot for port {}: {}", port_id, err);
+ return Err(err);
+ }
+ };
- let mut input = unsafe { self.alloc_dma_zeroed::<InputContext<N>>()? };
+ debug!("Enabled port {}, which the xHC mapped to {}", port_id, slot);
- debug!("Attempting to address the device");
- let mut ring = match self
- .address_device(&mut input, port_id, slot_ty, slot, protocol_speed, speed)
- .await
- {
- Ok(device_ring) => device_ring,
- Err(err) => {
- error!("Failed to address device for port {}: `{}`", port_id, err);
- return Err(err);
+ let protocol_speed = match self.lookup_psiv(port_id, speed) {
+ Some(protocol_speed) => protocol_speed,
+ None => {
+ let err = Error::new(EIO);
+ error!("Failed to retrieve speed ID for port {}", port_id);
+ if let Err(disable_err) = self.disable_port_slot(slot).await {
+ warn!(
+ "Failed to disable slot {} after speed lookup failure on port {}: {}",
+ slot, port_id, disable_err
+ );
}
- };
+ return Err(err);
+ }
+ };
- debug!("Addressed device");
+ let mut input = unsafe { self.alloc_dma_zeroed::<InputContext<N>>()? };
- // TODO: Should the descriptors be cached in PortState, or refetched?
+ debug!("Attempting to address the device");
+ let ring = match self
+ .address_device(&mut input, port_id, slot_ty, slot, protocol_speed, speed)
+ .await
+ {
+ Ok(device_ring) => device_ring,
+ Err(err) => {
+ error!("Failed to address device for port {}: `{}`", port_id, err);
+ if let Err(disable_err) = self.disable_port_slot(slot).await {
+ warn!(
+ "Failed to disable slot {} after address failure on port {}: {}",
+ slot, port_id, disable_err
+ );
+ }
+ return Err(err);
+ }
+ };
- let mut port_state = PortState {
- slot,
- protocol_speed,
- input_context: Mutex::new(input),
- dev_desc: None,
- cfg_idx: None,
- endpoint_states: std::iter::once((
- 0,
- EndpointState {
- transfer: RingOrStreams::Ring(ring),
- driver_if_state: EndpIfState::Init,
- },
- ))
- .collect::<BTreeMap<_, _>>(),
- };
- self.port_states.insert(port_id, port_state);
- debug!("Got port states!");
+ debug!("Addressed device");
- // Ensure correct packet size is used
+ let lifecycle = Arc::new(PortLifecycle::new_attaching());
+ let port_state = PortState {
+ slot,
+ protocol_speed,
+ input_context: Mutex::new(input),
+ dev_desc: None,
+ cfg_idx: None,
+ active_ifaces: BTreeMap::new(),
+ endpoint_states: std::iter::once((
+ 0,
+ EndpointState {
+ transfer: RingOrStreams::Ring(ring),
+ driver_if_state: EndpIfState::Init,
+ },
+ ))
+ .collect::<BTreeMap<_, _>>(),
+ lifecycle: Arc::clone(&lifecycle),
+ pm_state: PortPmState::Active,
+ };
+ self.staged_port_states.insert(port_id, port_state);
+ debug!("Got staged port state!");
+
+ let attach_result = async {
let dev_desc_8_byte = self.fetch_dev_desc_8_byte(port_id, slot).await?;
{
- let mut port_state = self.port_states.get_mut(&port_id).unwrap();
+ let mut port_state = self
+ .staged_port_states
+ .get_mut(&port_id)
+ .ok_or(Error::new(ENOENT))?;
- let mut input = port_state.input_context.lock().unwrap();
+ let mut input = port_state
+ .input_context
+ .lock()
+ .unwrap_or_else(|err| err.into_inner());
self.update_max_packet_size(&mut *input, slot, dev_desc_8_byte)
.await?;
@@ -885,97 +1062,175 @@ impl<const N: usize> Xhci<N> {
let dev_desc = self.get_desc(port_id, slot).await?;
debug!("Got the full device descriptor!");
- self.port_states.get_mut(&port_id).unwrap().dev_desc = Some(dev_desc);
+ self.staged_port_states
+ .get_mut(&port_id)
+ .ok_or(Error::new(ENOENT))?
+ .dev_desc = Some(dev_desc);
debug!("Got the port states again!");
{
- let mut port_state = self.port_states.get_mut(&port_id).unwrap();
-
- let mut input = port_state.input_context.lock().unwrap();
+ let mut port_state = self
+ .staged_port_states
+ .get_mut(&port_id)
+ .ok_or(Error::new(ENOENT))?;
+
+ let mut input = port_state
+ .input_context
+ .lock()
+ .unwrap_or_else(|err| err.into_inner());
debug!("Got the input context!");
- let dev_desc = port_state.dev_desc.as_ref().unwrap();
+ let dev_desc = port_state.dev_desc.as_ref().ok_or(Error::new(EIO))?;
self.update_default_control_pipe(&mut *input, slot, dev_desc)
.await?;
}
debug!("Updated the default control pipe");
+ Ok(())
+ }
+ .await;
- match 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<bool> {
- if let Some(children) = self.drivers.remove(&port_id) {
- for mut child in children {
- info!("killing driver process {} for port {}", child.id(), port_id);
- match child.kill() {
- Ok(()) => {
- info!("killed driver process {} for port {}", child.id(), port_id);
- match child.try_wait() {
- Ok(status_opt) => match status_opt {
- Some(status) => {
- debug!(
- "driver process {} for port {} exited with status {}",
- child.id(),
- port_id,
- status
- );
- }
- None => {
- //TODO: kill harder
+ let published_state = self.port_states.get(&port_id);
+ let staged_state = if published_state.is_none() {
+ self.staged_port_states.get(&port_id)
+ } else {
+ None
+ };
+
+ let (slot, lifecycle, was_published) = match published_state
+ .as_deref()
+ .or_else(|| staged_state.as_deref())
+ {
+ Some(state) => (state.slot, Arc::clone(&state.lifecycle), published_state.is_some()),
+ None => {
+ debug!(
+ "Attempted to detach from port {}, which wasn't previously attached.",
+ port_id
+ );
+ return Ok(false);
+ }
+ };
+ drop(published_state);
+ drop(staged_state);
+
+ lifecycle.begin_detaching();
+
+ if was_published {
+ if let Some(children) = self.drivers.remove(&port_id) {
+ for mut child in children {
+ info!("killing driver process {} for port {}", child.id(), port_id);
+ match child.kill() {
+ Ok(()) => {
+ info!("killed driver process {} for port {}", child.id(), port_id);
+ match child.try_wait() {
+ Ok(status_opt) => match status_opt {
+ Some(status) => {
+ debug!(
+ "driver process {} for port {} exited with status {}",
+ child.id(),
+ port_id,
+ status
+ );
+ }
+ None => {
+ warn!(
+ "driver process {} for port {} still running",
+ child.id(),
+ port_id
+ );
+ }
+ },
+ Err(err) => {
warn!(
- "driver process {} for port {} still running",
+ "failed to wait for the driver process {} for port {}: {}",
child.id(),
- port_id
+ port_id,
+ err
);
}
- },
- Err(err) => {
- warn!(
- "failed to wait for the driver process {} for port {}: {}",
- child.id(),
- port_id,
- err
- );
}
}
- }
- Err(err) => {
- warn!(
- "failed to kill the driver process {} for port {}: {}",
- child.id(),
- port_id,
- err
- );
+ Err(err) => {
+ warn!(
+ "failed to kill the driver process {} for port {}: {}",
+ child.id(),
+ port_id,
+ err
+ );
+ }
}
}
}
}
- if let Some(state) = self.port_states.remove(&port_id) {
- debug!("disabling port slot {} for port {}", state.slot, port_id);
- let result = self.disable_port_slot(state.slot).await.and(Ok(true));
- debug!(
- "disabled port slot {} for port {} with result: {:?}",
- state.slot, port_id, result
- );
- result
- } else {
- debug!(
- "Attempted to detach from port {}, which wasn't previously attached.",
- port_id
- );
- Ok(false)
+ debug!("disabling port slot {} for port {}", slot, port_id);
+ match self.disable_port_slot(slot).await {
+ Ok(()) => {
+ if was_published {
+ let _ = self.port_states.remove(&port_id);
+ } else {
+ let _ = self.staged_port_states.remove(&port_id);
+ }
+ debug!("disabled port slot {} for port {}", slot, port_id);
+ Ok(true)
+ }
+ Err(err) => {
+ warn!(
+ "failed to disable port slot {} for port {}: {}",
+ slot, port_id, err
+ );
+ Err(err)
+ }
}
}
@@ -1246,14 +1501,12 @@ impl<const N: usize> Xhci<N> {
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<const N: usize>(hci: &Arc<Xhci<N>>) {
}));
}
+#[cfg(test)]
+mod tests {
+ use std::fs;
+ use std::path::Path;
+ use std::time::{SystemTime, UNIX_EPOCH};
+
+ use super::{Xhci, XHCID_TEST_HOOK_MAX_DELAY_MS};
+
+ fn unique_test_hook_path() -> String {
+ let unique = SystemTime::now()
+ .duration_since(UNIX_EPOCH)
+ .unwrap()
+ .as_nanos();
+ format!("/tmp/xhcid-test-hook-{}", unique)
+ }
+
+ #[test]
+ fn consume_test_hook_only_clears_matching_command() {
+ let path = unique_test_hook_path();
+ fs::write(&path, "fail_after_set_configuration\n").unwrap();
+
+ assert!(!Xhci::<16>::consume_test_hook_from_path(
+ &path,
+ "fail_after_configure_endpoint"
+ ));
+ assert!(Path::new(&path).exists());
+
+ assert!(Xhci::<16>::consume_test_hook_from_path(
+ &path,
+ "fail_after_set_configuration"
+ ));
+ assert!(!Path::new(&path).exists());
+ }
+
+ #[test]
+ fn consume_test_hook_delay_clamps_and_clears() {
+ let path = unique_test_hook_path();
+ fs::write(&path, "delay_before_attach_commit_ms=999999\n").unwrap();
+
+ assert_eq!(
+ Xhci::<16>::consume_test_hook_delay_ms_from_path(&path, "delay_before_attach_commit_ms="),
+ Some(XHCID_TEST_HOOK_MAX_DELAY_MS)
+ );
+ assert!(!Path::new(&path).exists());
+ }
+}
+
#[derive(Deserialize)]
struct DriverConfig {
name: String,
diff --git a/drivers/usb/xhcid/src/xhci/scheme.rs b/drivers/usb/xhcid/src/xhci/scheme.rs
index 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<n>/attach scheme.");
static ref REGEX_PORT_DETACH: Regex = Regex::new(r"^port([\d\.]+)/detach$")
.expect("Failed to create the regex for the port<n>/detach scheme.");
+ static ref REGEX_PORT_SUSPEND: Regex = Regex::new(r"^port([\d\.]+)/suspend$")
+ .expect("Failed to create the regex for the port<n>/suspend scheme.");
+ static ref REGEX_PORT_RESUME: Regex = Regex::new(r"^port([\d\.]+)/resume$")
+ .expect("Failed to create the regex for the port<n>/resume scheme.");
static ref REGEX_PORT_DESCRIPTORS: Regex = Regex::new(r"^port([\d\.]+)/descriptors$")
.expect("Failed to create the regex for the port<n>/descriptors");
static ref REGEX_PORT_STATE: Regex = Regex::new(r"^port([\d\.]+)/state$")
.expect("Failed to create the regex for the port<n>/state scheme");
+ static ref REGEX_PORT_PM_STATE: Regex = Regex::new(r"^port([\d\.]+)/pm_state$")
+ .expect("Failed to create the regex for the port<n>/pm_state scheme");
static ref REGEX_PORT_REQUEST: Regex = Regex::new(r"^port([\d\.]+)/request$")
.expect("Failed to create the regex for the port<n>/request scheme");
static ref REGEX_PORT_ENDPOINTS: Regex = Regex::new(r"^port([\d\.]+)/endpoints$")
@@ -138,12 +145,15 @@ pub enum Handle {
Port(PortId, Vec<u8>), // port, contents
PortDesc(PortId, Vec<u8>), // port, contents
PortState(PortId), // port
+ PortPmState(PortId), // port
PortReq(PortId, PortReqState), // port, state
Endpoints(PortId, Vec<u8>), // port, contents
Endpoint(PortId, u8, EndpointHandleTy), // port, endpoint, state
ConfigureEndpoints(PortId), // port
AttachDevice(PortId), // port
DetachDevice(PortId), // port
+ SuspendDevice(PortId), // port
+ ResumeDevice(PortId), // port
SchemeRoot,
}
@@ -173,6 +183,8 @@ enum SchemeParameters {
PortDesc(PortId), // port number
/// /port<n>/state
PortState(PortId), // port number
+ /// /port<n>/pm_state
+ PortPmState(PortId), // port number
/// /port<n>/request
PortReq(PortId), // port number
/// /port<n>/endpoints
@@ -188,6 +200,10 @@ enum SchemeParameters {
AttachDevice(PortId), // port number
/// /port<n>/detach
DetachDevice(PortId), // port number
+ /// /port<n>/suspend
+ SuspendDevice(PortId), // port number
+ /// /port<n>/resume
+ ResumeDevice(PortId), // port number
}
impl Handle {
@@ -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(&REGEX_PORT_DETACH, scheme, 0)?;
Ok(Self::DetachDevice(port_num))
+ } else if REGEX_PORT_SUSPEND.is_match(scheme) {
+ let port_num = get_port_id_from_regex(&REGEX_PORT_SUSPEND, scheme, 0)?;
+
+ Ok(Self::SuspendDevice(port_num))
+ } else if REGEX_PORT_RESUME.is_match(scheme) {
+ let port_num = get_port_id_from_regex(&REGEX_PORT_RESUME, scheme, 0)?;
+
+ Ok(Self::ResumeDevice(port_num))
} else if REGEX_PORT_DESCRIPTORS.is_match(scheme) {
let port_num = get_port_id_from_regex(&REGEX_PORT_DESCRIPTORS, scheme, 0)?;
@@ -392,6 +431,10 @@ impl SchemeParameters {
let port_num = get_port_id_from_regex(&REGEX_PORT_STATE, scheme, 0)?;
Ok(Self::PortState(port_num))
+ } else if REGEX_PORT_PM_STATE.is_match(scheme) {
+ let port_num = get_port_id_from_regex(&REGEX_PORT_PM_STATE, scheme, 0)?;
+
+ Ok(Self::PortPmState(port_num))
} else if REGEX_PORT_REQUEST.is_match(scheme) {
let port_num = get_port_id_from_regex(&REGEX_PORT_REQUEST, scheme, 0)?;
@@ -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<const N: usize> Xhci<N> {
where
D: FnMut(&mut Trb, bool) -> ControlFlow,
{
+ self.ensure_port_active(port_num)?;
+
let future = {
let mut port_state = self.port_state_mut(port_num)?;
let slot = port_state.slot;
@@ -710,6 +788,8 @@ impl<const N: usize> Xhci<N> {
where
D: FnMut(&mut Trb, bool) -> ControlFlow,
{
+ self.ensure_port_active(port_num)?;
+
let endp_idx = endp_num.checked_sub(1).ok_or(Error::new(EIO))?;
let mut port_state = self.port_state_mut(port_num)?;
@@ -835,7 +915,10 @@ impl<const N: usize> Xhci<N> {
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<const N: usize> Xhci<N> {
self.port_states.get_mut(&port).ok_or(Error::new(EBADF))
}
+ fn restore_configure_input_context(
+ &self,
+ port: PortId,
+ snapshot: ConfigureContextSnapshot,
+ endpoint_snapshots: &[(usize, EndpointContextSnapshot)],
+ ) -> Result<usize> {
+ let port_state = self.port_states.get(&port).ok_or(Error::new(EBADFD))?;
+ let mut input_context = port_state.input_context.lock().unwrap();
+
+ input_context.add_context.write(snapshot.add_context);
+ input_context.drop_context.write(snapshot.drop_context);
+ input_context.control.write(snapshot.control);
+ input_context.device.slot.a.write(snapshot.slot_a);
+ input_context.device.slot.b.write(snapshot.slot_b);
+
+ for (endp_i, endp_snapshot) in endpoint_snapshots {
+ input_context.device.endpoints[*endp_i].a.write(endp_snapshot.a);
+ input_context.device.endpoints[*endp_i].b.write(endp_snapshot.b);
+ input_context.device.endpoints[*endp_i].trl.write(endp_snapshot.trl);
+ input_context.device.endpoints[*endp_i].trh.write(endp_snapshot.trh);
+ input_context.device.endpoints[*endp_i].c.write(endp_snapshot.c);
+ }
+
+ Ok(input_context.physical())
+ }
+
+ async fn rollback_configure_attempt(
+ &self,
+ port: PortId,
+ slot: u8,
+ configure_snapshot: ConfigureContextSnapshot,
+ endpoint_snapshots: &[(usize, EndpointContextSnapshot)],
+ stage: &str,
+ ) {
+ let rollback_input_context_physical = match self.restore_configure_input_context(
+ port,
+ configure_snapshot,
+ endpoint_snapshots,
+ ) {
+ Ok(physical) => physical,
+ Err(restore_err) => {
+ warn!(
+ "failed to restore configure input context after {}: {:?}",
+ stage, restore_err
+ );
+ return;
+ }
+ };
+
+ let (rollback_event_trb, rollback_command_trb) = self
+ .execute_command(|trb, cycle| {
+ trb.configure_endpoint(slot, rollback_input_context_physical, cycle)
+ })
+ .await;
+
+ if let Err(rollback_err) = handle_event_trb(
+ "CONFIGURE_ENDPOINT_ROLLBACK",
+ &rollback_event_trb,
+ &rollback_command_trb,
+ ) {
+ warn!(
+ "failed to roll back CONFIGURE_ENDPOINT after {}: {:?}",
+ stage, rollback_err
+ );
+ }
+ }
+
async fn configure_endpoints_once(
&self,
port: PortId,
req: &ConfigureEndpointsReq,
) -> Result<()> {
- let (endp_desc_count, new_context_entries, configuration_value) = {
- let mut port_state = self.port_states.get_mut(&port).ok_or(Error::new(EBADFD))?;
-
- port_state.cfg_idx = Some(req.config_desc);
+ let (dev_desc, endpoint_descs, new_context_entries, configuration_value, speed_id) = {
+ let port_state = self.port_states.get(&port).ok_or(Error::new(EBADFD))?;
+ let dev_desc = port_state.dev_desc.as_ref().ok_or(Error::new(EBADFD))?.clone();
+ let speed_id = port_state.protocol_speed;
- let config_desc = port_state
- .dev_desc
- .as_ref()
- .unwrap()
+ let config_desc = dev_desc
.config_descs
.iter()
.find(|desc| desc.configuration_value == req.config_desc)
.ok_or(Error::new(EBADFD))?;
- //TODO: USE ENDPOINTS FROM ALL INTERFACES
- let mut endp_desc_count = 0;
- let mut new_context_entries = 1;
- for if_desc in config_desc.interface_descs.iter() {
- for endpoint in if_desc.endpoints.iter() {
- endp_desc_count += 1;
- let entry = Self::endp_num_to_dci(endp_desc_count, endpoint);
- if entry > new_context_entries {
- new_context_entries = entry;
- }
+ let configuration_value = config_desc.configuration_value;
+
+ let endpoint_descs = 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::<Vec<_>>()
+ } else {
+ config_desc
+ .interface_descs
+ .iter()
+ .filter(|if_desc| if_desc.alternate_setting == 0)
+ .flat_map(|if_desc| if_desc.endpoints.iter().copied())
+ .collect::<Vec<_>>()
+ };
+
+ let endp_desc_count = endpoint_descs.len();
+ let mut new_context_entries = 1u8;
+ for (endp_idx, endpoint) in endpoint_descs.iter().enumerate() {
+ let entry = Self::endp_num_to_dci(endp_idx as u8 + 1, endpoint);
+ if entry > new_context_entries {
+ new_context_entries = entry;
}
}
new_context_entries += 1;
@@ -989,74 +1151,22 @@ impl<const N: usize> Xhci<N> {
}
(
- endp_desc_count,
+ dev_desc,
+ endpoint_descs,
new_context_entries,
- config_desc.configuration_value,
+ configuration_value,
+ speed_id,
)
};
let lec = self.cap.lec();
let log_max_psa_size = self.cap.max_psa_size();
- let port_speed_id = self.ports.lock().unwrap()[port.root_hub_port_index()].speed();
- let speed_id: &ProtocolSpeed = self.lookup_psiv(port, port_speed_id).ok_or_else(|| {
- warn!("no speed_id");
- Error::new(EIO)
- })?;
-
- {
- let port_state = self.port_states.get(&port).ok_or(Error::new(EBADFD))?;
- let mut input_context = port_state.input_context.lock().unwrap();
-
- // Configure the slot context as well, which holds the last index of the endp descs.
- input_context.add_context.write(1);
- input_context.drop_context.write(0);
-
- const CONTEXT_ENTRIES_MASK: u32 = 0xF800_0000;
- const CONTEXT_ENTRIES_SHIFT: u8 = 27;
-
- const HUB_PORTS_MASK: u32 = 0xFF00_0000;
- const HUB_PORTS_SHIFT: u8 = 24;
-
- let mut current_slot_a = input_context.device.slot.a.read();
- let mut current_slot_b = input_context.device.slot.b.read();
-
- // Set context entries
- current_slot_a &= !CONTEXT_ENTRIES_MASK;
- current_slot_a |=
- (u32::from(new_context_entries) << CONTEXT_ENTRIES_SHIFT) & CONTEXT_ENTRIES_MASK;
-
- // Set hub data
- current_slot_a &= !(1 << 26);
- current_slot_b &= !HUB_PORTS_MASK;
- if let Some(hub_ports) = req.hub_ports {
- current_slot_a |= 1 << 26;
- current_slot_b |= (u32::from(hub_ports) << HUB_PORTS_SHIFT) & HUB_PORTS_MASK;
- }
-
- input_context.device.slot.a.write(current_slot_a);
- input_context.device.slot.b.write(current_slot_b);
-
- let control = if self.op.lock().unwrap().cie() {
- (u32::from(req.alternate_setting.unwrap_or(0)) << 16)
- | (u32::from(req.interface_desc.unwrap_or(0)) << 8)
- | u32::from(configuration_value)
- } else {
- 0
- };
- input_context.control.write(control);
- }
+ 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<const N: usize> Xhci<N> {
let mult = endp_desc.isoch_mult(lec);
- let max_packet_size = Self::endp_ctx_max_packet_size(endp_desc);
- let max_burst_size = Self::endp_ctx_max_burst(speed_id, dev_desc, endp_desc);
+ let max_packet_size = Self::endp_ctx_max_packet_size(&endp_desc);
+ let max_burst_size = Self::endp_ctx_max_burst(speed_id, &dev_desc, &endp_desc);
let max_esit_payload = Self::endp_ctx_max_esit_payload(
speed_id,
- dev_desc,
- endp_desc,
+ &dev_desc,
+ &endp_desc,
max_packet_size,
max_burst_size,
);
let max_esit_payload_lo = max_esit_payload as u16;
let max_esit_payload_hi = ((max_esit_payload & 0x00FF_0000) >> 16) as u8;
- let interval = Self::endp_ctx_interval(speed_id, endp_desc);
+ let interval = Self::endp_ctx_interval(speed_id, &endp_desc);
let max_error_count = 3;
let ep_ty = endp_desc.xhci_ep_type()?;
@@ -1114,7 +1224,7 @@ impl<const N: usize> Xhci<N> {
assert_eq!(max_error_count & 0x3, max_error_count);
assert_ne!(ep_ty, 0); // 0 means invalid.
- let ring_ptr = if usb_log_max_streams.is_some() {
+ let (endpoint_state, ring_ptr) = if usb_log_max_streams.is_some() {
let mut array =
StreamContextArray::new::<N>(self.cap.ac64(), 1 << (primary_streams + 1))?;
@@ -1127,15 +1237,13 @@ impl<const N: usize> Xhci<N> {
array_ptr,
"stream ctx ptr not aligned to 16 bytes"
);
- port_state.endpoint_states.insert(
- endp_num,
+ (
EndpointState {
transfer: super::RingOrStreams::Streams(array),
driver_if_state: EndpIfState::Init,
},
- );
-
- array_ptr
+ array_ptr,
+ )
} else {
let ring = Ring::new::<N>(self.cap.ac64(), 16, true)?;
let ring_ptr = ring.register();
@@ -1145,68 +1253,205 @@ impl<const N: usize> Xhci<N> {
ring_ptr,
"ring pointer not aligned to 16 bytes"
);
- port_state.endpoint_states.insert(
- endp_num,
+ (
EndpointState {
transfer: super::RingOrStreams::Ring(ring),
driver_if_state: EndpIfState::Init,
},
- );
- ring_ptr
+ ring_ptr,
+ )
};
assert_eq!(primary_streams & 0x1F, primary_streams);
- let mut input_context = port_state.input_context.lock().unwrap();
- input_context.add_context.writef(1 << endp_num_xhc, true);
-
- let endp_i = endp_num_xhc as usize - 1;
- input_context.device.endpoints[endp_i].a.write(
- u32::from(mult) << 8
+ staged_endpoint_states.insert(endp_num, endpoint_state);
+ endpoint_programs.push(EndpointProgram {
+ endp_num_xhc,
+ a: u32::from(mult) << 8
| u32::from(primary_streams) << 10
| u32::from(linear_stream_array) << 15
| u32::from(interval) << 16
| u32::from(max_esit_payload_hi) << 24,
- );
- input_context.device.endpoints[endp_i].b.write(
- max_error_count << 1
+ b: max_error_count << 1
| u32::from(ep_ty) << 3
| u32::from(host_initiate_disable) << 7
| u32::from(max_burst_size) << 8
| u32::from(max_packet_size) << 16,
- );
-
- input_context.device.endpoints[endp_i]
- .trl
- .write(ring_ptr as u32);
- input_context.device.endpoints[endp_i]
- .trh
- .write((ring_ptr >> 32) as u32);
-
- input_context.device.endpoints[endp_i]
- .c
- .write(u32::from(avg_trb_len) | (u32::from(max_esit_payload_lo) << 16));
+ trl: ring_ptr as u32,
+ trh: (ring_ptr >> 32) as u32,
+ c: u32::from(avg_trb_len) | (u32::from(max_esit_payload_lo) << 16),
+ });
- log::debug!("initialized endpoint {}", endp_num);
+ log::debug!("staged endpoint {}", endp_num);
}
- {
+ let (configure_snapshot, endpoint_snapshots, input_context_physical) = {
let port_state = self.port_states.get(&port).ok_or(Error::new(EBADFD))?;
- let slot = port_state.slot;
- let input_context_physical = port_state.input_context.lock().unwrap().physical();
+ let mut input_context = port_state.input_context.lock().unwrap();
+
+ let configure_snapshot = ConfigureContextSnapshot {
+ add_context: input_context.add_context.read(),
+ drop_context: input_context.drop_context.read(),
+ control: input_context.control.read(),
+ slot_a: input_context.device.slot.a.read(),
+ slot_b: input_context.device.slot.b.read(),
+ };
- let (event_trb, command_trb) = self
- .execute_command(|trb, cycle| {
- trb.configure_endpoint(slot, input_context_physical, cycle)
+ let endpoint_snapshots = endpoint_programs
+ .iter()
+ .map(|program| {
+ let endp_i = program.endp_num_xhc as usize - 1;
+ (
+ endp_i,
+ EndpointContextSnapshot::capture_values(
+ input_context.device.endpoints[endp_i].a.read(),
+ input_context.device.endpoints[endp_i].b.read(),
+ input_context.device.endpoints[endp_i].trl.read(),
+ input_context.device.endpoints[endp_i].trh.read(),
+ input_context.device.endpoints[endp_i].c.read(),
+ ),
+ )
})
- .await;
+ .collect::<Vec<_>>();
+
+ // 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<const N: usize> Xhci<N> {
if (flags & O_DIRECTORY != 0) || (flags & O_STAT != 0) {
let mut contents = Vec::new();
- write!(contents, "descriptors\nendpoints\n").unwrap();
+ write!(contents, "descriptors\nendpoints\npm_state\nsuspend\nresume\n").unwrap();
if self.slot_state(
self.port_states
@@ -1894,6 +2139,14 @@ impl<const N: usize> Xhci<N> {
Ok(Handle::PortState(port_num))
}
+ fn open_handle_port_pm_state(&self, port_num: PortId, flags: usize) -> Result<Handle> {
+ if flags & O_DIRECTORY != 0 && flags & O_STAT == 0 {
+ return Err(Error::new(ENOTDIR));
+ }
+
+ Ok(Handle::PortPmState(port_num))
+ }
+
/// implements open() for /port<n>/endpoints
///
/// # Arguments
@@ -2088,6 +2341,30 @@ impl<const N: usize> Xhci<N> {
Ok(Handle::DetachDevice(port_num))
}
+ fn open_handle_suspend_device(&self, port_num: PortId, flags: usize) -> Result<Handle> {
+ if flags & O_DIRECTORY != 0 && flags & O_STAT == 0 {
+ return Err(Error::new(ENOTDIR));
+ }
+
+ if flags & O_RDWR != O_WRONLY && flags & O_STAT == 0 {
+ return Err(Error::new(EACCES));
+ }
+
+ Ok(Handle::SuspendDevice(port_num))
+ }
+
+ fn open_handle_resume_device(&self, port_num: PortId, flags: usize) -> Result<Handle> {
+ if flags & O_DIRECTORY != 0 && flags & O_STAT == 0 {
+ return Err(Error::new(ENOTDIR));
+ }
+
+ if flags & O_RDWR != O_WRONLY && flags & O_STAT == 0 {
+ return Err(Error::new(EACCES));
+ }
+
+ Ok(Handle::ResumeDevice(port_num))
+ }
+
/// implements open() for /port<n>/request
///
/// # Arguments
@@ -2156,6 +2433,9 @@ impl<const N: usize> SchemeSync for &Xhci<N> {
SchemeParameters::PortState(port_number) => {
self.open_handle_port_state(port_number, flags)?
}
+ SchemeParameters::PortPmState(port_number) => {
+ self.open_handle_port_pm_state(port_number, flags)?
+ }
SchemeParameters::PortReq(port_number) => {
self.open_handle_port_request(port_number, flags)?
}
@@ -2174,6 +2454,12 @@ impl<const N: usize> SchemeSync for &Xhci<N> {
SchemeParameters::DetachDevice(port_number) => {
self.open_handle_detach_device(port_number, flags)?
}
+ SchemeParameters::SuspendDevice(port_number) => {
+ self.open_handle_suspend_device(port_number, flags)?
+ }
+ SchemeParameters::ResumeDevice(port_number) => {
+ self.open_handle_resume_device(port_number, flags)?
+ }
};
let fd = self.next_handle.fetch_add(1, atomic::Ordering::Relaxed);
@@ -2204,7 +2490,11 @@ impl<const N: usize> SchemeSync for &Xhci<N> {
//If we have a handle to the configure scheme, we need to mark it as write only.
match &*guard {
- Handle::ConfigureEndpoints(_) | Handle::AttachDevice(_) | Handle::DetachDevice(_) => {
+ Handle::ConfigureEndpoints(_)
+ | Handle::AttachDevice(_)
+ | Handle::DetachDevice(_)
+ | Handle::SuspendDevice(_)
+ | Handle::ResumeDevice(_) => {
stat.st_mode = stat.st_mode | 0o200;
}
_ => {}
@@ -2254,6 +2544,8 @@ impl<const N: usize> SchemeSync for &Xhci<N> {
Handle::ConfigureEndpoints(_) => Err(Error::new(EBADF)),
Handle::AttachDevice(_) => Err(Error::new(EBADF)),
Handle::DetachDevice(_) => Err(Error::new(EBADF)),
+ Handle::SuspendDevice(_) => Err(Error::new(EBADF)),
+ Handle::ResumeDevice(_) => Err(Error::new(EBADF)),
Handle::SchemeRoot => Err(Error::new(EBADF)),
&mut Handle::Endpoint(port_num, endp_num, ref mut st) => match st {
@@ -2285,6 +2577,10 @@ impl<const N: usize> SchemeSync for &Xhci<N> {
Ok(Xhci::<N>::write_dyn_string(string, buf, offset))
}
+ &mut Handle::PortPmState(port_num) => {
+ let ps = self.port_states.get(&port_num).ok_or(Error::new(EBADF))?;
+ Ok(Xhci::<N>::write_dyn_string(ps.pm_state.as_str().as_bytes(), buf, offset))
+ }
&mut Handle::PortReq(port_num, ref mut st) => {
let state = std::mem::replace(st, PortReqState::Tmp);
drop(guard); // release the lock
@@ -2324,6 +2620,14 @@ impl<const N: usize> SchemeSync for &Xhci<N> {
block_on(self.detach_device(port_num))?;
Ok(buf.len())
}
+ &mut Handle::SuspendDevice(port_num) => {
+ block_on(self.suspend_device(port_num))?;
+ Ok(buf.len())
+ }
+ &mut Handle::ResumeDevice(port_num) => {
+ block_on(self.resume_device(port_num))?;
+ Ok(buf.len())
+ }
&mut Handle::Endpoint(port_num, endp_num, ref ep_file_ty) => match ep_file_ty {
EndpointHandleTy::Ctl => block_on(self.on_write_endp_ctl(port_num, endp_num, buf)),
EndpointHandleTy::Data => {
@@ -2348,6 +2652,54 @@ impl<const N: usize> SchemeSync for &Xhci<N> {
}
impl<const N: usize> Xhci<N> {
+ fn ensure_port_active(&self, port_num: PortId) -> Result<()> {
+ let port_state = self.port_states.get(&port_num).ok_or(Error::new(EBADFD))?;
+
+ match port_state.pm_state {
+ super::PortPmState::Active => Ok(()),
+ super::PortPmState::Suspended => {
+ info!(
+ "xhcid: port {} rejected routable operation while suspended",
+ port_num
+ );
+ Err(Error::new(EBUSY))
+ }
+ }
+ }
+
+ pub async fn suspend_device(&self, port_num: PortId) -> Result<()> {
+ let mut port_state = self.port_states.get_mut(&port_num).ok_or(Error::new(EBADFD))?;
+
+ if port_state.pm_state != super::PortPmState::Active {
+ return Err(Error::new(EBUSY));
+ }
+
+ port_state.pm_state = super::PortPmState::Suspended;
+ info!("xhcid: suspended port {}", port_num);
+ Ok(())
+ }
+
+ pub async fn resume_device(&self, port_num: PortId) -> Result<()> {
+ let mut port_state = self.port_states.get_mut(&port_num).ok_or(Error::new(EBADFD))?;
+
+ if port_state.pm_state == super::PortPmState::Active {
+ return Ok(());
+ }
+
+ let slot_state = self.slot_state(port_state.slot as usize);
+ if slot_state != SlotState::Addressed as u8 && slot_state != SlotState::Configured as u8 {
+ warn!(
+ "refusing to resume port {} while slot {} is in controller state {}",
+ port_num, port_state.slot, slot_state
+ );
+ return Err(Error::new(EIO));
+ }
+
+ port_state.pm_state = super::PortPmState::Active;
+ info!("xhcid: resumed port {}", port_num);
+ Ok(())
+ }
+
pub fn get_endp_status(&self, port_num: PortId, endp_num: u8) -> Result<EndpointStatus> {
let port_state = self.port_states.get(&port_num).ok_or(Error::new(EBADFD))?;
@@ -2398,6 +2750,8 @@ impl<const N: usize> Xhci<N> {
endp_num: u8,
clear_feature: bool,
) -> Result<()> {
+ self.ensure_port_active(port_num)?;
+
if self.get_endp_status(port_num, endp_num)? != EndpointStatus::Halted {
return Err(Error::new(EPROTO));
}
diff --git a/drivers/vboxd/src/main.rs b/drivers/vboxd/src/main.rs
index bcb9bb15..b9e42d4a 100644
--- a/drivers/vboxd/src/main.rs
+++ b/drivers/vboxd/src/main.rs
@@ -199,16 +199,28 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
let mut name = pci_config.func.name();
name.push_str("_vbox");
- let bar0 = pci_config.func.bars[0].expect_port();
+ let bar0 = match pci_config.func.bars[0].try_port() {
+ Ok(port) => port,
+ Err(err) => {
+ eprintln!("vboxd: invalid BAR0: {err}");
+ std::process::exit(1);
+ }
+ };
let irq = pci_config
.func
.legacy_interrupt_line
- .expect("vboxd: no legacy interrupts supported");
+ .unwrap_or_else(|| {
+ eprintln!("vboxd: no legacy interrupts supported");
+ std::process::exit(1);
+ });
println!(" + VirtualBox {}", pci_config.func.display());
- common::acquire_port_io_rights().expect("vboxd: failed to get I/O permission");
+ if let Err(err) = common::acquire_port_io_rights() {
+ eprintln!("vboxd: failed to get I/O permission: {err}");
+ std::process::exit(1);
+ }
let mut width = 0;
let mut height = 0;
@@ -233,25 +245,55 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
}
}
- let mut irq_file = irq.irq_handle("vboxd");
+ let mut irq_file = match irq.try_irq_handle("vboxd") {
+ Ok(file) => file,
+ Err(err) => {
+ eprintln!("vboxd: failed to open IRQ handle: {err}");
+ std::process::exit(1);
+ }
+ };
- let address = unsafe { pcid_handle.map_bar(1) }.ptr.as_ptr();
+ let address = match unsafe { pcid_handle.try_map_bar(1) } {
+ Ok(bar) => bar.ptr.as_ptr(),
+ Err(err) => {
+ eprintln!("vboxd: failed to map BAR1: {err}");
+ std::process::exit(1);
+ }
+ };
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
let mut port = common::io::Pio::<u32>::new(bar0 as u16);
let vmmdev = unsafe { &mut *(address as *mut VboxVmmDev) };
- let mut guest_info = VboxGuestInfo::new().expect("vboxd: failed to map GuestInfo");
+ let mut guest_info = match VboxGuestInfo::new() {
+ Ok(value) => value,
+ Err(err) => {
+ eprintln!("vboxd: failed to map GuestInfo: {err}");
+ std::process::exit(1);
+ }
+ };
guest_info.version.write(VBOX_VMMDEV_VERSION);
guest_info.ostype.write(0x100);
port.write(guest_info.physical() as u32);
- let mut guest_caps = VboxGuestCaps::new().expect("vboxd: failed to map GuestCaps");
+ let mut guest_caps = match VboxGuestCaps::new() {
+ Ok(value) => value,
+ Err(err) => {
+ eprintln!("vboxd: failed to map GuestCaps: {err}");
+ std::process::exit(1);
+ }
+ };
guest_caps.caps.write(1 << 2);
port.write(guest_caps.physical() as u32);
- let mut set_mouse = VboxSetMouse::new().expect("vboxd: failed to map SetMouse");
+ let mut set_mouse = match VboxSetMouse::new() {
+ Ok(value) => value,
+ Err(err) => {
+ eprintln!("vboxd: failed to map SetMouse: {err}");
+ std::process::exit(1);
+ }
+ };
set_mouse.features.write(1 << 4 | 1);
port.write(set_mouse.physical() as u32);
@@ -265,34 +307,71 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
}
}
- let event_queue =
- EventQueue::<Source>::new().expect("vboxd: Could not create event queue.");
+ let event_queue = match EventQueue::<Source>::new() {
+ Ok(queue) => queue,
+ Err(err) => {
+ eprintln!("vboxd: could not create event queue: {err}");
+ std::process::exit(1);
+ }
+ };
event_queue
.subscribe(
irq_file.as_raw_fd() as usize,
Source::Irq,
event::EventFlags::READ,
)
- .unwrap();
+ .unwrap_or_else(|err| {
+ eprintln!("vboxd: failed to subscribe IRQ fd: {err}");
+ std::process::exit(1);
+ });
daemon.ready();
- libredox::call::setrens(0, 0).expect("vboxd: failed to enter null namespace");
+ if let Err(err) = libredox::call::setrens(0, 0) {
+ eprintln!("vboxd: failed to enter null namespace: {err}");
+ std::process::exit(1);
+ }
let mut bga = crate::bga::Bga::new();
- let get_mouse = VboxGetMouse::new().expect("vboxd: failed to map GetMouse");
- let display_change = VboxDisplayChange::new().expect("vboxd: failed to map DisplayChange");
- let ack_events = VboxAckEvents::new().expect("vboxd: failed to map AckEvents");
+ let get_mouse = match VboxGetMouse::new() {
+ Ok(value) => value,
+ Err(err) => {
+ eprintln!("vboxd: failed to map GetMouse: {err}");
+ std::process::exit(1);
+ }
+ };
+ let display_change = match VboxDisplayChange::new() {
+ Ok(value) => value,
+ Err(err) => {
+ eprintln!("vboxd: failed to map DisplayChange: {err}");
+ std::process::exit(1);
+ }
+ };
+ let ack_events = match VboxAckEvents::new() {
+ Ok(value) => value,
+ Err(err) => {
+ eprintln!("vboxd: failed to map AckEvents: {err}");
+ std::process::exit(1);
+ }
+ };
- for Source::Irq in iter::once(Source::Irq)
- .chain(event_queue.map(|e| e.expect("vboxd: failed to get next event").user_data))
- {
+ for Source::Irq in iter::once(Source::Irq).chain(event_queue.map(|e| match e {
+ Ok(event) => event.user_data,
+ Err(err) => {
+ eprintln!("vboxd: failed to get next event: {err}");
+ std::process::exit(1);
+ }
+ })) {
let mut irq = [0; 8];
- if irq_file.read(&mut irq).unwrap() >= irq.len() {
+ match irq_file.read(&mut irq) {
+ Ok(read) if read >= irq.len() => {
let host_events = vmmdev.host_events.read();
if host_events != 0 {
port.write(ack_events.physical() as u32);
- irq_file.write(&irq).unwrap();
+ if let Err(err) = irq_file.write(&irq) {
+ eprintln!("vboxd: failed to acknowledge IRQ: {err}");
+ std::process::exit(1);
+ }
if host_events & VBOX_EVENT_DISPLAY == VBOX_EVENT_DISPLAY {
port.write(display_change.physical() as u32);
@@ -326,8 +405,14 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
}
}
}
+ Ok(_) => {}
+ Err(err) => {
+ eprintln!("vboxd: failed to read IRQ file: {err}");
+ std::process::exit(1);
+ }
+ }
}
}
- std::process::exit(0);
+ std::process::exit(1);
}
diff --git a/drivers/virtio-core/src/arch/x86.rs b/drivers/virtio-core/src/arch/x86.rs
index aea86c4a..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<File, Error> {
// 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<File, Error> {
let interrupt_handle = {
let table_entry_pointer = info.table_entry_pointer(MSIX_PRIMARY_VECTOR as usize);
- let destination_id = read_bsp_apic_id().expect("virtio_core: `read_bsp_apic_id()` failed");
+ let 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<Device, Error> {
let pci_config = pcid_handle.config();
- assert_eq!(
- pci_config.func.full_device_id.vendor_id, 6900,
- "virtio_core::probe_device: not a virtio device"
- );
+ if pci_config.func.full_device_id.vendor_id != 6900 {
+ 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<Device, Error
_ => continue,
}
- let (addr, _) = pci_config.func.bars[capability.bar as usize].expect_mem();
+ let (addr, _) = pci_config.func.bars[capability.bar as usize]
+ .try_mem()
+ .map_err(|_| Error::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<Device, Error
device_addr = Some(address);
}
- _ => unreachable!(),
+ _ => continue,
}
}
- let common_addr = common_addr.expect("virtio common capability missing");
- let device_addr = device_addr.expect("virtio device capability missing");
- let (notify_addr, notify_multiplier) = notify_addr.expect("virtio notify capability missing");
-
- // 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<Device, Error
let all_pci_features = pcid_handle.fetch_all_features();
let has_msix = all_pci_features.iter().any(|feature| feature.is_msix());
- // According to the virtio specification, the device REQUIRED to support MSI-X.
- assert!(has_msix, "virtio: device does not support MSI-X");
+ if !has_msix {
+ log::warn!("virtio_core::probe_device: device does not support MSI-X");
+ return Err(Error::Probe("device does not support MSI-X"));
+ }
let irq_handle = crate::arch::enable_msix(pcid_handle)?;
log::debug!("virtio: using standard PCI transport");
diff --git a/drivers/virtio-core/src/spec/split_virtqueue.rs b/drivers/virtio-core/src/spec/split_virtqueue.rs
index b9636711..23aa5484 100644
--- a/drivers/virtio-core/src/spec/split_virtqueue.rs
+++ b/drivers/virtio-core/src/spec/split_virtqueue.rs
@@ -197,9 +197,9 @@ impl ChainBuilder {
}
pub fn build(mut self) -> Vec<Buffer> {
- 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<Queue<'static>>) {
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::<Vec<_>>()
.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<u32, UnitId> = 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<Job>,
+ 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<u32> {
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<String>,
#[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<String, OsString>) {
+ pub fn spawn(&self, base_envs: &BTreeMap<String, OsString>) -> Option<u32> {
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<Self> {
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<File>,
output_tx: Sender<OutputCmd>,
handles: HandleMap<LogHandle>,
}
@@ -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::<OutputCmd>();
@@ -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}"),
+ }
+ }
}