08bea46575
- Override 00_pcid-spawner.service to oneshot_async in redbear-legacy-base.toml: rootfs phase no longer blocks on PCI driver init; getty/login starts immediately. Confirmed working on both QEMU and bare metal (redbear-live-mini). - Clean up 00_base legacy script: remove dead notify ipcd/ptyd calls, keep sudo --daemon. - Add U3 named input producers: inputd supports per-device named producers with fan-out to both device-specific consumers and legacy VT consumers. Migrate ps2d and usbhidd to InputProducer trait. RESERVED_DEVICE_NAMES deduplicated. - Add Intel HDA audio driver phases A-D: ihdad error handling (37 fixes), audio quirks, codec path enumeration, mixer/volume control. - Add init service start/readiness logging (always visible, not debug-gated). - Update BOOT-PROCESS-ASSESSMENT.md: Phase 6 complete, boot procedure documented, validation matrix updated with confirmed boot status. - Update USB-IMPLEMENTATION-PLAN.md and INPUT-SCHEME-ENHANCEMENT.md for U2/U3 status.
16965 lines
624 KiB
Diff
16965 lines
624 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..4a2b9469 100644
|
|
--- a/drivers/hwd/src/main.rs
|
|
+++ b/drivers/hwd/src/main.rs
|
|
@@ -38,7 +38,7 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
|
//TODO: launch pcid based on backend information?
|
|
// Must launch after acpid but before probe calls /scheme/acpi/symbols
|
|
#[allow(deprecated, reason = "we can't yet move this to init")]
|
|
- daemon::Daemon::spawn(process::Command::new("pcid"));
|
|
+ let _ = daemon::Daemon::spawn(process::Command::new("pcid"));
|
|
|
|
daemon.ready();
|
|
|
|
diff --git a/drivers/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..56ae816a 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 {
|
|
@@ -304,14 +325,18 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
|
}
|
|
debug!("Enumeration complete, now starting pci scheme");
|
|
|
|
- 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 +375,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(®EX_PORT_DETACH, scheme, 0)?;
|
|
|
|
Ok(Self::DetachDevice(port_num))
|
|
+ } else if REGEX_PORT_SUSPEND.is_match(scheme) {
|
|
+ let port_num = get_port_id_from_regex(®EX_PORT_SUSPEND, scheme, 0)?;
|
|
+
|
|
+ Ok(Self::SuspendDevice(port_num))
|
|
+ } else if REGEX_PORT_RESUME.is_match(scheme) {
|
|
+ let port_num = get_port_id_from_regex(®EX_PORT_RESUME, scheme, 0)?;
|
|
+
|
|
+ Ok(Self::ResumeDevice(port_num))
|
|
} else if REGEX_PORT_DESCRIPTORS.is_match(scheme) {
|
|
let port_num = get_port_id_from_regex(®EX_PORT_DESCRIPTORS, scheme, 0)?;
|
|
|
|
@@ -392,6 +431,10 @@ impl SchemeParameters {
|
|
let port_num = get_port_id_from_regex(®EX_PORT_STATE, scheme, 0)?;
|
|
|
|
Ok(Self::PortState(port_num))
|
|
+ } else if REGEX_PORT_PM_STATE.is_match(scheme) {
|
|
+ let port_num = get_port_id_from_regex(®EX_PORT_PM_STATE, scheme, 0)?;
|
|
+
|
|
+ Ok(Self::PortPmState(port_num))
|
|
} else if REGEX_PORT_REQUEST.is_match(scheme) {
|
|
let port_num = get_port_id_from_regex(®EX_PORT_REQUEST, scheme, 0)?;
|
|
|
|
@@ -524,6 +567,39 @@ pub enum AnyDescriptor {
|
|
SuperSpeedPlusCompanion(usb::SuperSpeedPlusIsochCmpDescriptor),
|
|
}
|
|
|
|
+#[derive(Clone, Copy)]
|
|
+struct ConfigureContextSnapshot {
|
|
+ add_context: u32,
|
|
+ drop_context: u32,
|
|
+ control: u32,
|
|
+ slot_a: u32,
|
|
+ slot_b: u32,
|
|
+}
|
|
+
|
|
+#[derive(Clone, Copy)]
|
|
+struct EndpointContextSnapshot {
|
|
+ a: u32,
|
|
+ b: u32,
|
|
+ trl: u32,
|
|
+ trh: u32,
|
|
+ c: u32,
|
|
+}
|
|
+
|
|
+impl EndpointContextSnapshot {
|
|
+ fn capture_values(a: u32, b: u32, trl: u32, trh: u32, c: u32) -> Self {
|
|
+ Self { a, b, trl, trh, c }
|
|
+ }
|
|
+}
|
|
+
|
|
+struct EndpointProgram {
|
|
+ endp_num_xhc: u8,
|
|
+ a: u32,
|
|
+ b: u32,
|
|
+ trl: u32,
|
|
+ trh: u32,
|
|
+ c: u32,
|
|
+}
|
|
+
|
|
impl AnyDescriptor {
|
|
fn parse(bytes: &[u8]) -> Option<(Self, usize)> {
|
|
if bytes.len() < 2 {
|
|
@@ -640,6 +716,8 @@ impl<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}"),
|
|
+ }
|
|
+ }
|
|
}
|