fix: absorb redundant base daemon and driver patches

Consolidate ~30 absorbed base patches into surviving carriers. Add
new init service files, driver sources, and network/storage modules
for the base recipe. Move absorbed patches to local/patches/base/absorbed/.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-05-11 10:09:25 +01:00
parent 9f4ad484a4
commit 76b75d80b4
105 changed files with 6633 additions and 34000 deletions
@@ -1,364 +0,0 @@
diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs
--- a/drivers/acpid/src/acpi.rs
+++ b/drivers/acpid/src/acpi.rs
@@ -387,6 +387,12 @@
tables: Vec<Sdt>,
dsdt: Option<Dsdt>,
fadt: Option<Fadt>,
+ pm1a_cnt_blk: u64,
+ pm1b_cnt_blk: u64,
+ slp_typa_s5: u8,
+ slp_typb_s5: u8,
+ reset_reg: Option<GenericAddress>,
+ reset_value: u8,
aml_symbols: RwLock<AmlSymbols>,
@@ -452,6 +458,12 @@
tables,
dsdt: None,
fadt: None,
+ pm1a_cnt_blk: 0,
+ pm1b_cnt_blk: 0,
+ slp_typa_s5: 0,
+ slp_typb_s5: 0,
+ reset_reg: None,
+ reset_value: 0,
// Temporary values
aml_symbols: RwLock::new(AmlSymbols::new(ec)),
@@ -575,6 +587,67 @@
aml_symbols.symbol_cache = FxHashMap::default();
}
+ pub fn acpi_shutdown(&self) {
+ let pm1a_value = (u16::from(self.slp_typa_s5) << 10) | 0x2000;
+ let pm1b_value = (u16::from(self.slp_typb_s5) << 10) | 0x2000;
+
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ {
+ let Ok(pm1a_port) = u16::try_from(self.pm1a_cnt_blk) else {
+ log::error!("PM1a_CNT_BLK address is invalid: {:#X}", self.pm1a_cnt_blk);
+ 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(_) => {
+ log::error!("PM1b_CNT_BLK address is invalid: {:#X}", self.pm1b_cnt_blk);
+ }
+ }
+ }
+ }
+
+ #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
+ {
+ log::error!(
+ "Cannot shutdown with ACPI PM1_CNT writes on this architecture (PM1a={:#X}, PM1b={:#X})",
+ self.pm1a_cnt_blk,
+ self.pm1b_cnt_blk
+ );
+ }
+ }
+
+ pub fn acpi_reboot(&self) {
+ 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);
+ }
+ None => {
+ log::error!("Cannot reboot with ACPI: no reset register present in FADT");
+ }
+ }
+ }
+
/// Set Power State
/// See https://uefi.org/sites/default/files/resources/ACPI_6_1.pdf
/// - search for PM1a
@@ -583,83 +656,13 @@
if state != 5 {
return;
}
- let fadt = match self.fadt() {
- Some(fadt) => fadt,
- None => {
- log::error!("Cannot set global S-state due to missing FADT.");
- return;
- }
- };
-
- let port = fadt.pm1a_control_block as u16;
- let mut val = 1 << 13;
-
- let aml_symbols = self.aml_symbols.read();
-
- 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;
- }
- };
-
- 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;
- }
- },
- 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;
- }
- };
-
- let slp_typa = match package[0].deref() {
- acpi::aml::object::Object::Integer(i) => i.to_owned(),
- _ => {
- log::error!("typa is not an Integer");
- return;
- }
- };
- let slp_typb = match package[1].deref() {
- acpi::aml::object::Object::Integer(i) => i.to_owned(),
- _ => {
- log::error!("typb is not an Integer");
- return;
- }
- };
-
- log::trace!("Shutdown SLP_TYPa {:X}, SLP_TYPb {:X}", slp_typa, slp_typb);
- val |= slp_typa as u16;
-
- #[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);
- }
-
- // TODO: Handle SLP_TYPb
-
- #[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
- );
- }
+
+ if self.fadt().is_none() {
+ log::error!("Cannot set global S-state due to missing FADT.");
+ return;
+ }
+
+ self.acpi_shutdown();
loop {
core::hint::spin_loop();
@@ -720,7 +723,7 @@
#[repr(C, packed)]
#[derive(Clone, Copy, Debug, Default)]
-pub struct GenericAddressStructure {
+pub struct GenericAddress {
address_space: u8,
bit_width: u8,
bit_offset: u8,
@@ -728,11 +731,67 @@
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) {
+ match self.address_space {
+ 0 => {
+ let Ok(address) = usize::try_from(self.address) else {
+ log::error!("Reset register physical address is invalid: {:#X}", self.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(self.address) {
+ Ok(port) => {
+ Pio::<u8>::new(port).write(value);
+ }
+ Err(_) => {
+ log::error!("Reset register I/O port is invalid: {:#X}", self.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],
@@ -741,14 +800,14 @@
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 {}
@@ -806,9 +865,25 @@
None => usize::try_from(fadt.dsdt).expect("expected any given u32 to fit within usize"),
};
+ 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),
+ };
+
log::debug!("FACP at {:X}", { dsdt_ptr });
-
- let dsdt_sdt = match Sdt::load_from_physical(fadt.dsdt as usize) {
+ 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);
@@ -816,8 +891,46 @@
}
};
+ let (slp_typa_s5, slp_typb_s5) = match AmlName::from_str("\\_S5") {
+ Ok(s5_name) => match context.aml_eval(s5_name, Vec::new()) {
+ Ok(AmlSerdeValue::Package { contents }) => match (contents.get(0), 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)) => (slp_typa_s5, slp_typb_s5),
+ _ => {
+ log::warn!("\\_S5 values do not fit in u8: {:?}", contents);
+ (0, 0)
+ }
+ }
+ }
+ _ => {
+ log::warn!("\\_S5 package did not contain two integers: {:?}", contents);
+ (0, 0)
+ }
+ },
+ Ok(value) => {
+ log::warn!("\\_S5 returned unexpected AML value: {:?}", value);
+ (0, 0)
+ }
+ Err(error) => {
+ log::warn!("Failed to evaluate \\_S5: {:?}", error);
+ (0, 0)
+ }
+ },
+ Err(error) => {
+ log::warn!("Could not build AmlName for \\_S5: {:?}", error);
+ (0, 0)
+ }
+ };
+
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.slp_typa_s5 = slp_typa_s5;
+ context.slp_typb_s5 = slp_typb_s5;
+ context.reset_reg = reset_reg;
+ context.reset_value = reset_value;
context.tables.push(dsdt_sdt);
}
@@ -1,13 +1,9 @@
diff --git a/bootstrap/Cargo.toml b/bootstrap/Cargo.toml
index 82120c21..be1f8326 100644
--- a/bootstrap/Cargo.toml
+++ b/bootstrap/Cargo.toml
@@ -6,6 +6,8 @@ authors = ["4lDO2 <4lDO2@protonmail.com>"]
edition = "2024"
@@ -7,5 +7,3 @@ edition = "2024"
license = "MIT"
+[workspace]
+
-[workspace]
-
[dependencies]
hashbrown = { version = "0.15", default-features = false, features = [
"inline-more",
@@ -1,655 +0,0 @@
diff --git a/daemon/src/lib.rs b/daemon/src/lib.rs
index 9f507221..a0ba9d88 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}"),
+ )),
}
}
}
diff --git a/drivers/audio/ac97d/src/main.rs b/drivers/audio/ac97d/src/main.rs
index ffa8a94b..4e381e48 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,35 @@ 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");
+ std::process::exit(1);
+ }
+ };
+ let bar1 = match pci_config.func.bars[1].try_port() {
+ Ok(port) => port,
+ Err(err) => {
+ error!("ac97d: invalid BAR1");
+ std::process::exit(1);
+ }
+ };
+ let bar1 = match pci_config.func.bars[1].try_port() {
+ Ok(port) => port,
+ Err(err) => {
+ error!("ac97d: invalid BAR1");
+ 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 +63,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");
+ 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 +101,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 +187,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 +208,7 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
}
}
- std::process::exit(0);
+ std::process::exit(1);
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
diff --git a/drivers/audio/ihdad/src/main.rs b/drivers/audio/ihdad/src/main.rs
index 31a2add7..4e455066 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");
+ 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");
+ std::process::exit(1);
+ }
+ };
{
let vend_prod: u32 = ((pci_config.func.full_device_id.vendor_id as u32) << 16)
@@ -53,11 +63,28 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
}
}
- let event_queue =
- EventQueue::<Source>::new().expect("ihdad: Could not create event queue.");
- let socket = Socket::nonblock().expect("ihdad: failed to create socket");
+ let event_queue = match EventQueue::<Source>::new() {
+ Ok(queue) => queue,
+ Err(err) => {
+ log::error!("ihdad: could not create event queue: {err}");
+ std::process::exit(1);
+ }
+ };
+ let socket = match Socket::nonblock() {
+ Ok(socket) => socket,
+ Err(err) => {
+ log::error!("ihdad: failed to create socket: {err}");
+ std::process::exit(1);
+ }
+ };
let mut device = unsafe {
- hda::IntelHDA::new(address, vend_prod).expect("ihdad: failed to allocate device")
+ match hda::IntelHDA::new(address, vend_prod) {
+ Ok(device) => device,
+ Err(err) => {
+ log::error!("ihdad: failed to allocate device: {err}");
+ std::process::exit(1);
+ }
+ }
};
let mut readiness_based = ReadinessBased::new(&socket, 16);
diff --git a/drivers/graphics/ihdgd/src/main.rs b/drivers/graphics/ihdgd/src/main.rs
index a8b6cc60..d855d108 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");
+ 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/pcid/src/driver_interface/bar.rs b/drivers/pcid/src/driver_interface/bar.rs
index b2c1d35b..d333cd53 100644
--- a/drivers/pcid/src/driver_interface/bar.rs
+++ b/drivers/pcid/src/driver_interface/bar.rs
@@ -1,4 +1,5 @@
use std::convert::TryInto;
+use std::fmt;
use serde::{Deserialize, Serialize};
@@ -12,6 +13,21 @@ pub enum PciBar {
Port(u16),
}
+#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
+pub enum PciBarError {
+ WrongType,
+ Missing,
+}
+
+impl fmt::Display for PciBarError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ PciBarError::WrongType => write!(f, "wrong BAR type"),
+ PciBarError::Missing => write!(f, "BAR not present"),
+ }
+ }
+}
+
impl PciBar {
pub fn display(&self) -> String {
match self {
@@ -29,27 +45,33 @@ impl PciBar {
}
}
- pub fn expect_port(&self) -> u16 {
+ pub fn try_port(&self) -> Result<u16, PciBarError> {
match *self {
- PciBar::Port(port) => port,
- PciBar::Memory32 { .. } | PciBar::Memory64 { .. } => {
- panic!("expected port BAR, found memory BAR");
- }
- PciBar::None => panic!("expected BAR to exist"),
+ PciBar::Port(port) => Ok(port),
+ PciBar::Memory32 { .. } | PciBar::Memory64 { .. } => Err(PciBarError::WrongType),
+ PciBar::None => Err(PciBarError::Missing),
}
}
- pub fn expect_mem(&self) -> (usize, usize) {
+ 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 } => (
+ PciBar::Memory32 { addr, size } => Ok((addr as usize, size as usize)),
+ PciBar::Memory64 { addr, size } => Ok((
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::Port(_) => Err(PciBarError::WrongType),
+ PciBar::None => Err(PciBarError::Missing),
}
}
+
+ pub fn expect_port(&self) -> u16 {
+ self.try_port().expect("expected port BAR")
+ }
+
+ pub fn expect_mem(&self) -> (usize, usize) {
+ self.try_mem().expect("expected memory BAR")
+ }
}
diff --git a/drivers/pcid/src/driver_interface/irq_helpers.rs b/drivers/pcid/src/driver_interface/irq_helpers.rs
index 28ca077a..d0c7042e 100644
--- a/drivers/pcid/src/driver_interface/irq_helpers.rs
+++ b/drivers/pcid/src/driver_interface/irq_helpers.rs
@@ -61,6 +61,14 @@ pub fn cpu_ids() -> io::Result<impl Iterator<Item = io::Result<usize>> + 'static
)
}
+/// Allocate a single interrupt vector. Returns the InterruptVector on success.
+pub fn try_pci_allocate_interrupt_vector(
+ pcid_handle: &mut crate::driver_interface::PciFunctionHandle,
+ driver: &str,
+) -> Result<InterruptVector, ()> {
+ Ok(pci_allocate_interrupt_vector(pcid_handle, driver))
+}
+
/// Allocate multiple interrupt vectors, from the IDT of the specified processor, returning the
/// start vector and the IRQ handles.
///
diff --git a/drivers/pcid/src/driver_interface/mod.rs b/drivers/pcid/src/driver_interface/mod.rs
index bbc7304e..a77d79ec 100644
--- a/drivers/pcid/src/driver_interface/mod.rs
+++ b/drivers/pcid/src/driver_interface/mod.rs
@@ -29,6 +29,10 @@ pub struct LegacyInterruptLine {
}
impl LegacyInterruptLine {
+ /// Get an IRQ handle for this interrupt line.
+ pub fn try_irq_handle(self, driver: &str) -> Result<File, ()> {
+ Ok(self.irq_handle(driver))
+ }
/// Get an IRQ handle for this interrupt line.
pub fn irq_handle(self, driver: &str) -> File {
if let Some((phandle, addr, cells)) = self.phandled {
@@ -452,6 +456,9 @@ impl PciFunctionHandle {
}
}
}
+ pub unsafe fn try_map_bar(&mut self, bir: u8) -> Result<&MappedBar, ()> {
+ Ok(self.map_bar(bir))
+ }
pub unsafe fn map_bar(&mut self, bir: u8) -> &MappedBar {
let mapped_bar = &mut self.mapped_bars[bir as usize];
if let Some(mapped_bar) = mapped_bar {
diff --git a/drivers/virtio-core/src/transport.rs b/drivers/virtio-core/src/transport.rs
index d3445d2d..2a316557 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("probe: {0}")]
+ Probe(&'static str),
}
/// Returns the queue part sizes in bytes.
@@ -1,7 +1,15 @@
diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs
--- a/drivers/inputd/src/main.rs
+++ b/drivers/inputd/src/main.rs
@@ -35,6 +35,9 @@
@@ -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};
@@ -37,6 +37,9 @@
enum Handle {
Producer,
@@ -11,19 +19,18 @@ diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs
Consumer {
events: EventFlags,
pending: Vec<u8>,
@@ -58,9 +61,11 @@
@@ -60,8 +63,10 @@
struct InputScheme {
handles: BTreeMap<usize, Handle>,
handles: HandleMap<Handle>,
+ devices: BTreeMap<String, u32>,
next_id: AtomicUsize,
next_vt_id: AtomicUsize,
+ next_device_id: AtomicUsize,
display: Option<String>,
vts: BTreeSet<usize>,
@@ -73,13 +78,30 @@
@@ -74,12 +79,29 @@
has_new_events: bool,
}
@@ -45,10 +52,9 @@ diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs
impl InputScheme {
fn new() -> Self {
Self {
handles: BTreeMap::new(),
handles: HandleMap::new(),
+ devices: BTreeMap::new(),
next_id: AtomicUsize::new(0),
next_vt_id: AtomicUsize::new(2), // VT 1 is reserved for the bootlog
+ next_device_id: AtomicUsize::new(0),
@@ -101,7 +107,7 @@ diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs
}
fn switch_vt(&mut self, new_active: usize) {
@@ -175,7 +237,13 @@
@@ -170,7 +232,13 @@
let command = path_parts.next().ok_or(SysError::new(EINVAL))?;
let handle_ty = match command {
@@ -116,7 +122,7 @@ diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs
"consumer" => {
let vt = self.next_vt_id.fetch_add(1, Ordering::Relaxed);
self.vts.insert(vt);
@@ -338,7 +406,7 @@
@@ -330,7 +398,7 @@
}
}
@@ -125,18 +131,18 @@ diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs
log::error!("producer tried to read");
return Err(SysError::new(EINVAL));
}
@@ -360,9 +428,7 @@
@@ -352,9 +420,7 @@
) -> syscall::Result<usize> {
self.has_new_events = true;
- let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?;
- let handle = self.handles.get_mut(id)?;
-
- match handle {
+ let producer_kind = match self.handles.get(&id).ok_or(SysError::new(EINVAL))? {
+ let producer_kind = match self.handles.get(id)? {
Handle::Control => {
if buf.len() != size_of::<ControlEvent>() {
log::error!("control tried to write incorrectly sized command");
@@ -391,9 +457,10 @@
@@ -383,9 +449,10 @@
log::error!("display tried to write");
return Err(SysError::new(EINVAL));
}
@@ -149,17 +155,17 @@ diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs
if buf.len() == 1 && buf[0] > 0xf4 {
return Ok(1);
@@ -445,9 +512,6 @@
@@ -437,9 +504,6 @@
}
}
- let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?;
- let handle = self.handles.get_mut(id)?;
- assert!(matches!(handle, Handle::Producer));
-
let buf = unsafe {
core::slice::from_raw_parts(
(events.as_ptr()) as *const u8,
@@ -455,26 +519,11 @@
@@ -447,26 +511,11 @@
)
};
@@ -191,7 +197,7 @@ diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs
Ok(buf.len())
}
@@ -506,7 +555,7 @@
@@ -496,7 +545,7 @@
*notified = false;
Ok(EventFlags::empty())
}
@@ -200,62 +206,20 @@ diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs
log::error!("producer or control tried to use an event queue");
Err(SysError::new(EINVAL))
}
@@ -515,9 +564,15 @@
@@ -505,7 +554,15 @@
}
fn on_close(&mut self, id: usize) {
- let handle = self.handles.remove(&id).unwrap();
+ let Some(handle) = self.handles.remove(&id) else {
- match self.handles.remove(id).unwrap() {
+ let Some(handle) = self.handles.remove(id) else {
+ log::warn!("received close for unknown input handle {id}");
+ return;
+ };
match handle {
+
+ match handle {
+ Handle::NamedProducer { name } => {
+ self.devices.remove(&name);
+ }
Handle::Consumer { vt, .. } => {
self.vts.remove(&vt);
if self.active_vt == Some(vt) {
diff --git a/drivers/inputd/src/lib.rs b/drivers/inputd/src/lib.rs
--- a/drivers/inputd/src/lib.rs
+++ b/drivers/inputd/src/lib.rs
@@ -197,6 +197,38 @@
pub vt: usize,
}
+/// Handle for opening a named producer on the input scheme.
+/// Opens /scheme/input/producer/{name}
+pub struct NamedProducerHandle {
+ fd: File,
+}
+
+impl NamedProducerHandle {
+ pub fn new(name: &str) -> io::Result<Self> {
+ if name.is_empty() {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "input producer name must not be empty",
+ ));
+ }
+
+ if name.contains('/') {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "input producer name must not contain '/'",
+ ));
+ }
+
+ let path = format!("/scheme/input/producer/{name}");
+ File::open(path).map(|fd| Self { fd })
+ }
+
+ pub fn write_event(&mut self, event: &orbclient::Event) -> io::Result<()> {
+ self.fd.write(unsafe { any_as_u8_slice(event) })?;
+ Ok(())
+ }
+}
+
pub struct ProducerHandle(File);
impl ProducerHandle {
@@ -1,16 +1,15 @@
diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs
--- a/drivers/inputd/src/main.rs
+++ b/drivers/inputd/src/main.rs
@@ -17,7 +17,7 @@
use std::mem::transmute;
@@ -18,7 +18,7 @@
use std::ops::ControlFlow;
use std::sync::atomic::{AtomicUsize, Ordering};
-use inputd::{ControlEvent, VtEvent, VtEventKind};
+use inputd::{ControlEvent, HotplugEventHeader, VtEvent, VtEventKind};
use libredox::errno::ESTALE;
use redox_scheme::scheme::{SchemeState, SchemeSync};
@@ -47,6 +47,17 @@
use redox_scheme::scheme::SchemeSync;
@@ -49,6 +49,17 @@
notified: bool,
vt: usize,
},
@@ -28,7 +27,7 @@ diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs
Display {
events: EventFlags,
pending: Vec<VtEvent>,
@@ -88,6 +99,9 @@
@@ -89,6 +100,9 @@
"control",
];
@@ -47,7 +46,7 @@ diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs
return Err(SysError::new(EINVAL));
}
@@ -126,11 +140,57 @@
@@ -126,9 +140,55 @@
let device_id = self.next_device_id.fetch_add(1, Ordering::SeqCst) as u32;
self.devices.insert(name.to_owned(), device_id);
@@ -55,8 +54,8 @@ diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs
Ok(Handle::NamedProducer {
name: name.to_owned(),
})
}
+ }
+
+ fn drain_pending_bytes(pending: &mut Vec<u8>, buf: &mut [u8]) -> usize {
+ let copy = core::cmp::min(pending.len(), buf.len());
+
@@ -100,11 +99,9 @@ diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs
+
+ self.has_new_events = true;
+ Ok(())
+ }
+
}
fn route_legacy_consumer_events(&mut self, buf: &[u8]) {
if let Some(active_vt) = self.active_vt {
for handle in self.handles.values_mut() {
@@ -150,9 +210,21 @@
}
}
@@ -130,7 +127,7 @@ diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs
}
fn switch_vt(&mut self, new_active: usize) {
@@ -324,7 +396,24 @@
@@ -319,7 +391,24 @@
is_earlyfb: command == "handle_early",
}
}
@@ -155,7 +152,7 @@ diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs
_ => {
log::error!("invalid path '{path}'");
@@ -380,13 +469,11 @@
@@ -372,13 +461,11 @@
return Err(SysError::new(ESTALE));
}
@@ -164,27 +161,28 @@ diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs
- for (i, byte) in pending.drain(..copy).enumerate() {
- buf[i] = byte;
- }
-
- Ok(copy)
+ Ok(Self::drain_pending_bytes(pending, buf))
+ }
- Ok(copy)
+
+ Handle::DeviceConsumer { pending, .. } | Handle::HotplugEvents { pending, .. } => {
+ Ok(Self::drain_pending_bytes(pending, buf))
}
Handle::Display { pending, .. } => {
@@ -453,6 +540,10 @@
@@ -443,6 +530,10 @@
Handle::Consumer { .. } => {
log::error!("consumer tried to write");
return Err(SysError::new(EINVAL));
}
+ Handle::DeviceConsumer { .. } | Handle::HotplugEvents { .. } => {
+ log::error!("consumer or hotplug handle tried to write");
+ return Err(SysError::new(EINVAL));
+ }
Handle::Display { .. } => {
log::error!("display tried to write");
+ Handle::DeviceConsumer { .. } | Handle::HotplugEvents { .. } => {
+ log::error!("consumer or hotplug handle tried to write");
return Err(SysError::new(EINVAL));
@@ -541,6 +632,16 @@
}
Handle::Display { .. } => {
@@ -531,6 +622,16 @@
ref mut events,
ref mut notified,
..
@@ -201,7 +199,7 @@ diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs
} => {
*events = flags;
*notified = false;
@@ -571,7 +672,12 @@
@@ -561,7 +662,12 @@
match handle {
Handle::NamedProducer { name } => {
@@ -215,14 +213,10 @@ diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs
}
Handle::Consumer { vt, .. } => {
self.vts.remove(&vt);
@@ -658,6 +764,28 @@
socket_file.write_response(
Response::post_fevent(*id, EventFlags::EVENT_READ.bits()),
SignalBehavior::Restart,
+ )?;
+
+ *notified = true;
+ }
@@ -639,6 +745,28 @@
*notified = true;
}
+ Handle::DeviceConsumer {
+ events,
+ pending,
@@ -241,210 +235,10 @@ diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs
+ socket_file.write_response(
+ Response::post_fevent(*id, EventFlags::EVENT_READ.bits()),
+ SignalBehavior::Restart,
)?;
*notified = true;
diff --git a/drivers/inputd/src/lib.rs b/drivers/inputd/src/lib.rs
--- a/drivers/inputd/src/lib.rs
+++ b/drivers/inputd/src/lib.rs
@@ -1,5 +1,5 @@
use std::fs::{File, OpenOptions};
-use std::io::{self, Read, Write};
+use std::io::{self, ErrorKind, Read, Write};
use std::mem::size_of;
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, RawFd};
use std::os::unix::fs::OpenOptionsExt;
@@ -31,6 +31,24 @@
slice::from_raw_parts_mut((p as *mut T) as *mut u8, size_of::<T>())
}
+fn validate_input_name(kind: &str, name: &str) -> io::Result<()> {
+ if name.is_empty() {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ format!("input {kind} name must not be empty"),
+ ));
+ }
+ )?;
+
+ if name.contains('/') {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ format!("input {kind} name must not contain '/'"),
+ ));
+ }
+
+ Ok(())
+}
+
pub struct ConsumerHandle(File);
pub enum ConsumerHandleEvent<'a> {
@@ -197,6 +215,22 @@
pub vt: usize,
}
+#[derive(Debug, Clone, Copy)]
+#[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,
+}
+
/// Handle for opening a named producer on the input scheme.
/// Opens /scheme/input/producer/{name}
pub struct NamedProducerHandle {
@@ -205,19 +239,7 @@
impl NamedProducerHandle {
pub fn new(name: &str) -> io::Result<Self> {
- if name.is_empty() {
- return Err(io::Error::new(
- io::ErrorKind::InvalidInput,
- "input producer name must not be empty",
- ));
- }
-
- if name.contains('/') {
- return Err(io::Error::new(
- io::ErrorKind::InvalidInput,
- "input producer name must not contain '/'",
- ));
- }
+ validate_input_name("producer", name)?;
let path = format!("/scheme/input/producer/{name}");
File::open(path).map(|fd| Self { fd })
@@ -229,6 +251,124 @@
}
}
+pub struct DeviceConsumerHandle {
+ fd: File,
+}
+
+impl DeviceConsumerHandle {
+ pub fn new(device_name: &str) -> io::Result<Self> {
+ validate_input_name("device", device_name)?;
+
+ let fd = OpenOptions::new()
+ .read(true)
+ .custom_flags(O_NONBLOCK as i32)
+ .open(format!("/scheme/input/{device_name}"))?;
+
+ Ok(Self { fd })
+ }
+
+ pub fn event_handle(&self) -> BorrowedFd<'_> {
+ self.fd.as_fd()
+ }
+
+ pub fn read_event(&mut self) -> io::Result<Option<orbclient::Event>> {
+ let mut raw = [0_u8; size_of::<orbclient::Event>()];
+
+ match self.fd.read(&mut raw) {
+ Ok(0) => Ok(None),
+ Ok(read) => {
+ assert_eq!(read, raw.len());
+ Ok(Some(unsafe {
+ core::ptr::read_unaligned(raw.as_ptr().cast::<orbclient::Event>())
+ }))
+ }
+ Err(err) if err.kind() == ErrorKind::WouldBlock => Ok(None),
+ Err(err) => Err(err),
+ }
+ }
+}
+
+pub struct HotplugHandle {
+ fd: File,
+ partial: Vec<u8>,
+}
+
+impl HotplugHandle {
+ pub fn new() -> io::Result<Self> {
+ let fd = OpenOptions::new()
+ .read(true)
+ .custom_flags(O_NONBLOCK as i32)
+ .open("/scheme/input/events")?;
+
+ Ok(Self {
+ fd,
+ partial: Vec::new(),
+ })
+ }
+
+ pub fn event_handle(&self) -> BorrowedFd<'_> {
+ self.fd.as_fd()
+ }
+
+ pub fn read_event(&mut self) -> io::Result<Option<HotplugEvent>> {
+ let mut buf = [0_u8; 1024];
+
+ loop {
+ if let Some(event) = Self::try_parse_event(&mut self.partial)? {
+ return Ok(Some(event));
+ }
+
+ match self.fd.read(&mut buf) {
+ Ok(0) => return Ok(None),
+ Ok(read) => self.partial.extend_from_slice(&buf[..read]),
+ Err(err) if err.kind() == ErrorKind::WouldBlock => return Ok(None),
+ Err(err) => return Err(err),
+ }
+ }
+ }
+
+ fn try_parse_event(partial: &mut Vec<u8>) -> io::Result<Option<HotplugEvent>> {
+ if partial.len() < size_of::<HotplugEventHeader>() {
+ return Ok(None);
+ }
+
+ let header =
+ unsafe { core::ptr::read_unaligned(partial.as_ptr().cast::<HotplugEventHeader>()) };
+ let name_len = usize::try_from(header.name_len).map_err(|_| {
+ io::Error::new(
+ io::ErrorKind::InvalidData,
+ "invalid input hotplug name length",
+ )
+ })?;
+ let total_len = size_of::<HotplugEventHeader>()
+ .checked_add(name_len)
+ .ok_or_else(|| {
+ io::Error::new(io::ErrorKind::InvalidData, "input hotplug event too large")
+ })?;
+
+ if partial.len() < total_len {
+ return Ok(None);
+ }
+
+ let name = std::str::from_utf8(&partial[size_of::<HotplugEventHeader>()..total_len])
+ .map_err(|_| {
+ io::Error::new(
+ io::ErrorKind::InvalidData,
+ "input hotplug name is not UTF-8",
+ )
+ })?
+ .to_owned();
+
+ partial.drain(..total_len);
+
+ Ok(Some(HotplugEvent {
+ kind: header.kind,
+ device_id: header.device_id,
+ name,
+ }))
+ }
+}
+
pub struct ProducerHandle(File);
impl ProducerHandle {
+ *notified = true;
+ }
_ => {}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,719 +0,0 @@
diff --git a/drivers/acpid/src/scheme.rs b/drivers/acpid/src/scheme.rs
index 5a5040c3..7070e8b9 100644
--- a/drivers/acpid/src/scheme.rs
+++ b/drivers/acpid/src/scheme.rs
@@ -2,7 +2,6 @@ use acpi::aml::namespace::AmlName;
use amlserde::aml_serde_name::to_aml_format;
use amlserde::AmlSerdeValue;
use core::str;
-use libredox::Fd;
use parking_lot::RwLockReadGuard;
use redox_scheme::scheme::SchemeSync;
use redox_scheme::{CallerCtx, OpenResult, SendFdRequest, Socket};
@@ -16,17 +15,19 @@ use syscall::FobtainFdFlags;
use syscall::data::Stat;
use syscall::error::{Error, Result};
-use syscall::error::{EACCES, EBADF, EBADFD, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR};
+use syscall::error::{EACCES, EAGAIN, EBADF, EBADFD, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR, EOPNOTSUPP};
use syscall::flag::{MODE_DIR, MODE_FILE};
use syscall::flag::{O_ACCMODE, O_DIRECTORY, O_RDONLY, O_STAT, O_SYMLINK};
use syscall::{EOVERFLOW, EPERM};
-use crate::acpi::{AcpiContext, AmlSymbols, SdtSignature};
+use crate::acpi::{
+ AcpiBattery, AcpiContext, AcpiPowerAdapter, AcpiPowerSnapshot, AmlSymbols, DmiInfo,
+ SdtSignature,
+};
pub struct AcpiScheme<'acpi, 'sock> {
ctx: &'acpi AcpiContext,
handles: HandleMap<Handle<'acpi>>,
- pci_fd: Option<Fd>,
socket: &'sock Socket,
}
@@ -41,10 +42,170 @@ enum HandleKind<'a> {
Table(SdtSignature),
Symbols(RwLockReadGuard<'a, AmlSymbols>),
Symbol { name: String, description: String },
+ Reboot,
+ DmiDir,
+ Dmi(String),
+ PowerDir,
+ PowerAdaptersDir,
+ PowerAdapterDir(String),
+ PowerBatteriesDir,
+ PowerBatteryDir(String),
+ PowerFile(String),
SchemeRoot,
RegisterPci,
}
+const DMI_DIRECTORY_ENTRIES: &[&str] = &[
+ "sys_vendor",
+ "board_vendor",
+ "board_name",
+ "board_version",
+ "product_name",
+ "product_version",
+ "bios_version",
+ "match_all",
+];
+
+fn dmi_contents(dmi_info: Option<&DmiInfo>, name: &str) -> Option<String> {
+ Some(match name {
+ "sys_vendor" => dmi_info
+ .and_then(|info| info.sys_vendor.clone())
+ .unwrap_or_default(),
+ "board_vendor" => dmi_info
+ .and_then(|info| info.board_vendor.clone())
+ .unwrap_or_default(),
+ "board_name" => dmi_info
+ .and_then(|info| info.board_name.clone())
+ .unwrap_or_default(),
+ "board_version" => dmi_info
+ .and_then(|info| info.board_version.clone())
+ .unwrap_or_default(),
+ "product_name" => dmi_info
+ .and_then(|info| info.product_name.clone())
+ .unwrap_or_default(),
+ "product_version" => dmi_info
+ .and_then(|info| info.product_version.clone())
+ .unwrap_or_default(),
+ "bios_version" => dmi_info
+ .and_then(|info| info.bios_version.clone())
+ .unwrap_or_default(),
+ "match_all" => dmi_info.map(DmiInfo::to_match_lines).unwrap_or_default(),
+ _ => return None,
+ })
+}
+
+fn power_bool_contents(value: bool) -> String {
+ if value {
+ String::from("1\n")
+ } else {
+ String::from("0\n")
+ }
+}
+
+fn power_u64_contents(value: u64) -> String {
+ format!("{value}\n")
+}
+
+fn power_f64_contents(value: f64) -> String {
+ format!("{value}\n")
+}
+
+fn power_string_contents(value: &str) -> String {
+ format!("{value}\n")
+}
+
+fn power_adapter_file_contents(adapter: &AcpiPowerAdapter, name: &str) -> Option<String> {
+ Some(match name {
+ "path" => power_string_contents(&adapter.path),
+ "online" => power_bool_contents(adapter.online),
+ _ => return None,
+ })
+}
+
+fn power_adapter_entry_names() -> &'static [&'static str] {
+ &["path", "online"]
+}
+
+fn power_battery_file_contents(battery: &AcpiBattery, name: &str) -> Option<String> {
+ Some(match name {
+ "path" => power_string_contents(&battery.path),
+ "state" => power_u64_contents(battery.state),
+ "present_rate" => power_u64_contents(battery.present_rate?),
+ "remaining_capacity" => power_u64_contents(battery.remaining_capacity?),
+ "present_voltage" => power_u64_contents(battery.present_voltage?),
+ "power_unit" => power_string_contents(battery.power_unit.as_deref()?),
+ "design_capacity" => power_u64_contents(battery.design_capacity?),
+ "last_full_capacity" => power_u64_contents(battery.last_full_capacity?),
+ "design_voltage" => power_u64_contents(battery.design_voltage?),
+ "technology" => power_string_contents(battery.technology.as_deref()?),
+ "model" => power_string_contents(battery.model.as_deref()?),
+ "serial" => power_string_contents(battery.serial.as_deref()?),
+ "battery_type" => power_string_contents(battery.battery_type.as_deref()?),
+ "oem_info" => power_string_contents(battery.oem_info.as_deref()?),
+ "percentage" => power_f64_contents(battery.percentage?),
+ _ => return None,
+ })
+}
+
+fn power_battery_entry_names(battery: &AcpiBattery) -> Vec<&'static str> {
+ let mut names = vec!["path", "state"];
+
+ if battery.present_rate.is_some() {
+ names.push("present_rate");
+ }
+ if battery.remaining_capacity.is_some() {
+ names.push("remaining_capacity");
+ }
+ if battery.present_voltage.is_some() {
+ names.push("present_voltage");
+ }
+ if battery.power_unit.is_some() {
+ names.push("power_unit");
+ }
+ if battery.design_capacity.is_some() {
+ names.push("design_capacity");
+ }
+ if battery.last_full_capacity.is_some() {
+ names.push("last_full_capacity");
+ }
+ if battery.design_voltage.is_some() {
+ names.push("design_voltage");
+ }
+ if battery.technology.is_some() {
+ names.push("technology");
+ }
+ if battery.model.is_some() {
+ names.push("model");
+ }
+ if battery.serial.is_some() {
+ names.push("serial");
+ }
+ if battery.battery_type.is_some() {
+ names.push("battery_type");
+ }
+ if battery.oem_info.is_some() {
+ names.push("oem_info");
+ }
+ if battery.percentage.is_some() {
+ names.push("percentage");
+ }
+
+ names
+}
+
+fn top_level_entries(power_available: bool) -> Vec<(&'static str, DirentKind)> {
+ let mut entries = vec![
+ ("tables", DirentKind::Directory),
+ ("symbols", DirentKind::Directory),
+ ("dmi", DirentKind::Directory),
+ ("reboot", DirentKind::Regular),
+ ];
+ if power_available {
+ entries.push(("power", DirentKind::Directory));
+ }
+ entries
+}
+
impl HandleKind<'_> {
fn is_dir(&self) -> bool {
match self {
@@ -53,6 +214,15 @@ impl HandleKind<'_> {
Self::Table(_) => false,
Self::Symbols(_) => true,
Self::Symbol { .. } => false,
+ Self::Reboot => false,
+ Self::DmiDir => true,
+ Self::Dmi(_) => false,
+ Self::PowerDir => true,
+ Self::PowerAdaptersDir => true,
+ Self::PowerAdapterDir(_) => true,
+ Self::PowerBatteriesDir => true,
+ Self::PowerBatteryDir(_) => true,
+ Self::PowerFile(_) => false,
Self::SchemeRoot => false,
Self::RegisterPci => false,
}
@@ -65,8 +235,19 @@ impl HandleKind<'_> {
.ok_or(Error::new(EBADFD))?
.length(),
Self::Symbol { description, .. } => description.len(),
+ Self::Reboot => 0,
+ Self::Dmi(contents) => contents.len(),
+ Self::PowerFile(contents) => contents.len(),
// Directories
- Self::TopLevel | Self::Symbols(_) | Self::Tables => 0,
+ Self::TopLevel
+ | Self::Symbols(_)
+ | Self::Tables
+ | Self::DmiDir
+ | Self::PowerDir
+ | Self::PowerAdaptersDir
+ | Self::PowerAdapterDir(_)
+ | Self::PowerBatteriesDir
+ | Self::PowerBatteryDir(_) => 0,
Self::SchemeRoot | Self::RegisterPci => return Err(Error::new(EBADF)),
})
}
@@ -77,10 +258,111 @@ impl<'acpi, 'sock> AcpiScheme<'acpi, 'sock> {
Self {
ctx,
handles: HandleMap::new(),
- pci_fd: None,
socket,
}
}
+
+ fn power_snapshot(&self) -> Result<AcpiPowerSnapshot> {
+ self.ctx.power_snapshot().map_err(|error| match error {
+ crate::acpi::AmlEvalError::NotInitialized => Error::new(EAGAIN),
+ crate::acpi::AmlEvalError::Unsupported(message) => {
+ log::warn!("ACPI power surface unavailable: {message}");
+ Error::new(EOPNOTSUPP)
+ }
+ other => {
+ log::warn!("Failed to build ACPI power snapshot: {:?}", other);
+ Error::new(EIO)
+ }
+ })
+ }
+
+ fn power_available(&self) -> bool {
+ matches!(self.ctx.power_snapshot(), Ok(_))
+ }
+
+ fn power_handle(&self, path: &str) -> Result<HandleKind<'acpi>> {
+ let normalized = path.trim_matches('/');
+ self.power_snapshot()?;
+
+ if normalized.is_empty() {
+ return Ok(HandleKind::PowerDir);
+ }
+ if normalized == "on_battery" {
+ return Ok(HandleKind::PowerFile(power_bool_contents(
+ self.power_snapshot()?.on_battery(),
+ )));
+ }
+ if normalized == "adapters" {
+ return Ok(HandleKind::PowerAdaptersDir);
+ }
+ if let Some(rest) = normalized.strip_prefix("adapters/") {
+ return self.power_adapter_handle(rest);
+ }
+ if normalized == "batteries" {
+ return Ok(HandleKind::PowerBatteriesDir);
+ }
+ if let Some(rest) = normalized.strip_prefix("batteries/") {
+ return self.power_battery_handle(rest);
+ }
+
+ Err(Error::new(ENOENT))
+ }
+
+ fn power_adapter_handle(&self, path: &str) -> Result<HandleKind<'acpi>> {
+ let normalized = path.trim_matches('/');
+ if normalized.is_empty() {
+ return Ok(HandleKind::PowerAdaptersDir);
+ }
+
+ let mut parts = normalized.split('/');
+ let adapter_id = parts.next().ok_or(Error::new(ENOENT))?;
+ let field = parts.next();
+ if parts.next().is_some() {
+ return Err(Error::new(ENOENT));
+ }
+
+ let snapshot = self.power_snapshot()?;
+ let adapter = snapshot
+ .adapters
+ .iter()
+ .find(|adapter| adapter.id == adapter_id)
+ .ok_or(Error::new(ENOENT))?;
+
+ match field {
+ None | Some("") => Ok(HandleKind::PowerAdapterDir(adapter.id.clone())),
+ Some(name) => Ok(HandleKind::PowerFile(
+ power_adapter_file_contents(adapter, name).ok_or(Error::new(ENOENT))?,
+ )),
+ }
+ }
+
+ fn power_battery_handle(&self, path: &str) -> Result<HandleKind<'acpi>> {
+ let normalized = path.trim_matches('/');
+ if normalized.is_empty() {
+ return Ok(HandleKind::PowerBatteriesDir);
+ }
+
+ let mut parts = normalized.split('/');
+ let battery_id = parts.next().ok_or(Error::new(ENOENT))?;
+ let field = parts.next();
+ if parts.next().is_some() {
+ return Err(Error::new(ENOENT));
+ }
+
+ let snapshot = self.power_snapshot()?;
+ let battery = snapshot
+ .batteries
+ .iter()
+ .find(|battery| battery.id == battery_id)
+ .ok_or(Error::new(ENOENT))?;
+
+ match field {
+ None | Some("") => Ok(HandleKind::PowerBatteryDir(battery.id.clone())),
+ Some(name) => Ok(HandleKind::PowerFile(
+ power_battery_file_contents(battery, name).ok_or(Error::new(ENOENT))?,
+ )),
+ }
+ }
}
fn parse_hex_digit(hex: u8) -> Option<u8> {
@@ -184,9 +466,9 @@ impl SchemeSync for AcpiScheme<'_, '_> {
HandleKind::SchemeRoot => {
// TODO: arrayvec
let components = {
- let mut v = arrayvec::ArrayVec::<&str, 3>::new();
+ let mut v = arrayvec::ArrayVec::<&str, 4>::new();
let it = path.split('/');
- for component in it.take(3) {
+ for component in it.take(4) {
v.push(component);
}
@@ -195,6 +477,25 @@ impl SchemeSync for AcpiScheme<'_, '_> {
match &*components {
[""] => HandleKind::TopLevel,
+ ["reboot"] => HandleKind::Reboot,
+ ["dmi"] => {
+ if flag_dir || flag_stat || path.ends_with('/') {
+ HandleKind::DmiDir
+ } else {
+ HandleKind::Dmi(
+ dmi_contents(self.ctx.dmi_info(), "match_all")
+ .expect("match_all should always resolve"),
+ )
+ }
+ }
+ ["dmi", ""] => HandleKind::DmiDir,
+ ["dmi", field] => HandleKind::Dmi(
+ dmi_contents(self.ctx.dmi_info(), field).ok_or(Error::new(ENOENT))?,
+ ),
+ ["power"] => self.power_handle("")?,
+ ["power", tail] => self.power_handle(tail)?,
+ ["power", a, b] => self.power_handle(&format!("{a}/{b}"))?,
+ ["power", a, b, c] => self.power_handle(&format!("{a}/{b}/{c}"))?,
["register_pci"] => HandleKind::RegisterPci,
["tables"] => HandleKind::Tables,
@@ -204,7 +505,11 @@ impl SchemeSync for AcpiScheme<'_, '_> {
}
["symbols"] => {
- if let Ok(aml_symbols) = self.ctx.aml_symbols(self.pci_fd.as_ref()) {
+ if !self.ctx.pci_ready() {
+ log::warn!("Deferring AML symbol scan until PCI registration is ready");
+ return Err(Error::new(EAGAIN));
+ }
+ if let Ok(aml_symbols) = self.ctx.aml_symbols() {
HandleKind::Symbols(aml_symbols)
} else {
return Err(Error::new(EIO));
@@ -212,6 +517,12 @@ impl SchemeSync for AcpiScheme<'_, '_> {
}
["symbols", symbol] => {
+ if !self.ctx.pci_ready() {
+ log::warn!(
+ "Deferring AML symbol lookup for {symbol} until PCI registration is ready"
+ );
+ return Err(Error::new(EAGAIN));
+ }
if let Some(description) = self.ctx.aml_lookup(symbol) {
HandleKind::Symbol {
name: (*symbol).to_owned(),
@@ -225,6 +536,15 @@ impl SchemeSync for AcpiScheme<'_, '_> {
_ => return Err(Error::new(ENOENT)),
}
}
+ HandleKind::DmiDir => {
+ if path.is_empty() {
+ HandleKind::DmiDir
+ } else {
+ HandleKind::Dmi(
+ dmi_contents(self.ctx.dmi_info(), path).ok_or(Error::new(ENOENT))?,
+ )
+ }
+ }
HandleKind::Symbols(ref aml_symbols) => {
if let Some(description) = aml_symbols.lookup(path) {
HandleKind::Symbol {
@@ -235,6 +555,23 @@ impl SchemeSync for AcpiScheme<'_, '_> {
return Err(Error::new(ENOENT));
}
}
+ HandleKind::PowerDir => self.power_handle(path)?,
+ HandleKind::PowerAdaptersDir => self.power_adapter_handle(path)?,
+ HandleKind::PowerAdapterDir(ref adapter_id) => {
+ if path.is_empty() {
+ HandleKind::PowerAdapterDir(adapter_id.clone())
+ } else {
+ self.power_adapter_handle(&format!("{adapter_id}/{path}"))?
+ }
+ }
+ HandleKind::PowerBatteriesDir => self.power_battery_handle(path)?,
+ HandleKind::PowerBatteryDir(ref battery_id) => {
+ if path.is_empty() {
+ HandleKind::PowerBatteryDir(battery_id.clone())
+ } else {
+ self.power_battery_handle(&format!("{battery_id}/{path}"))?
+ }
+ }
_ => return Err(Error::new(EACCES)),
};
@@ -296,7 +633,7 @@ impl SchemeSync for AcpiScheme<'_, '_> {
) -> Result<usize> {
let offset: usize = offset.try_into().map_err(|_| Error::new(EINVAL))?;
- let handle = self.handles.get_mut(id)?;
+ let handle = self.handles.get(id)?;
if handle.stat {
return Err(Error::new(EBADF));
@@ -309,6 +646,8 @@ impl SchemeSync for AcpiScheme<'_, '_> {
.ok_or(Error::new(EBADFD))?
.as_slice(),
HandleKind::Symbol { description, .. } => description.as_bytes(),
+ HandleKind::Dmi(contents) => contents.as_bytes(),
+ HandleKind::PowerFile(contents) => contents.as_bytes(),
_ => return Err(Error::new(EINVAL)),
};
@@ -328,13 +667,82 @@ impl SchemeSync for AcpiScheme<'_, '_> {
mut buf: DirentBuf<&'buf mut [u8]>,
opaque_offset: u64,
) -> Result<DirentBuf<&'buf mut [u8]>> {
- let handle = self.handles.get_mut(id)?;
+ let handle = self.handles.get(id)?;
match &handle.kind {
HandleKind::TopLevel => {
- const TOPLEVEL_ENTRIES: &[&str] = &["tables", "symbols"];
+ for (idx, (name, kind)) in top_level_entries(self.power_available())
+ .iter()
+ .enumerate()
+ .skip(opaque_offset as usize)
+ {
+ buf.entry(DirEntry {
+ inode: 0,
+ next_opaque_id: idx as u64 + 1,
+ name,
+ kind: *kind,
+ })?;
+ }
+ }
+ HandleKind::DmiDir => {
+ for (idx, name) in DMI_DIRECTORY_ENTRIES
+ .iter()
+ .enumerate()
+ .skip(opaque_offset as usize)
+ {
+ buf.entry(DirEntry {
+ inode: 0,
+ next_opaque_id: idx as u64 + 1,
+ name,
+ kind: DirentKind::Regular,
+ })?;
+ }
+ }
+ HandleKind::PowerDir => {
+ const POWER_ROOT_ENTRIES: &[(&str, DirentKind)] = &[
+ ("on_battery", DirentKind::Regular),
+ ("adapters", DirentKind::Directory),
+ ("batteries", DirentKind::Directory),
+ ];
+
+ for (idx, (name, kind)) in POWER_ROOT_ENTRIES
+ .iter()
+ .enumerate()
+ .skip(opaque_offset as usize)
+ {
+ buf.entry(DirEntry {
+ inode: 0,
+ next_opaque_id: idx as u64 + 1,
+ name,
+ kind: *kind,
+ })?;
+ }
+ }
+ HandleKind::PowerAdaptersDir => {
+ let snapshot = self.power_snapshot()?;
+ for (idx, adapter) in snapshot
+ .adapters
+ .iter()
+ .enumerate()
+ .skip(opaque_offset as usize)
+ {
+ buf.entry(DirEntry {
+ inode: 0,
+ next_opaque_id: idx as u64 + 1,
+ name: adapter.id.as_str(),
+ kind: DirentKind::Directory,
+ })?;
+ }
+ }
+ HandleKind::PowerAdapterDir(adapter_id) => {
+ let snapshot = self.power_snapshot()?;
+ let _adapter = snapshot
+ .adapters
+ .iter()
+ .find(|adapter| adapter.id == *adapter_id)
+ .ok_or(Error::new(EIO))?;
- for (idx, name) in TOPLEVEL_ENTRIES
+ for (idx, name) in power_adapter_entry_names()
.iter()
.enumerate()
.skip(opaque_offset as usize)
@@ -343,10 +751,44 @@ impl SchemeSync for AcpiScheme<'_, '_> {
inode: 0,
next_opaque_id: idx as u64 + 1,
name,
+ kind: DirentKind::Regular,
+ })?;
+ }
+ }
+ HandleKind::PowerBatteriesDir => {
+ let snapshot = self.power_snapshot()?;
+ for (idx, battery) in snapshot
+ .batteries
+ .iter()
+ .enumerate()
+ .skip(opaque_offset as usize)
+ {
+ buf.entry(DirEntry {
+ inode: 0,
+ next_opaque_id: idx as u64 + 1,
+ name: battery.id.as_str(),
kind: DirentKind::Directory,
})?;
}
}
+ HandleKind::PowerBatteryDir(battery_id) => {
+ let snapshot = self.power_snapshot()?;
+ let battery = snapshot
+ .batteries
+ .iter()
+ .find(|battery| battery.id == *battery_id)
+ .ok_or(Error::new(EIO))?;
+ let entry_names = power_battery_entry_names(battery);
+
+ for (idx, name) in entry_names.iter().enumerate().skip(opaque_offset as usize) {
+ buf.entry(DirEntry {
+ inode: 0,
+ next_opaque_id: idx as u64 + 1,
+ name,
+ kind: DirentKind::Regular,
+ })?;
+ }
+ }
HandleKind::Symbols(aml_symbols) => {
for (idx, (symbol_name, _value)) in aml_symbols
.symbols_cache()
@@ -444,6 +886,38 @@ impl SchemeSync for AcpiScheme<'_, '_> {
Ok(result_len)
}
+ fn write(
+ &mut self,
+ id: usize,
+ buf: &[u8],
+ _offset: u64,
+ _flags: u32,
+ _ctx: &CallerCtx,
+ ) -> Result<usize> {
+ let handle = self.handles.get_mut(id)?;
+
+ if handle.stat {
+ return Err(Error::new(EBADF));
+ }
+ if !handle.allowed_to_eval {
+ return Err(Error::new(EPERM));
+ }
+
+ match handle.kind {
+ HandleKind::Reboot => {
+ if buf.is_empty() {
+ return Err(Error::new(EINVAL));
+ }
+ self.ctx.acpi_reboot().map_err(|error| {
+ log::error!("ACPI reboot failed: {error}");
+ Error::new(EIO)
+ })?;
+ Ok(buf.len())
+ }
+ _ => Err(Error::new(EBADF)),
+ }
+ }
+
fn on_sendfd(&mut self, sendfd_request: &SendFdRequest) -> Result<usize> {
let id = sendfd_request.id();
let num_fds = sendfd_request.num_fds();
@@ -470,10 +944,8 @@ impl SchemeSync for AcpiScheme<'_, '_> {
}
let new_fd = libredox::Fd::new(new_fd);
- if self.pci_fd.is_some() {
+ if self.ctx.register_pci_fd(new_fd).is_err() {
return Err(Error::new(EINVAL));
- } else {
- self.pci_fd = Some(new_fd);
}
Ok(num_fds)
@@ -483,3 +955,58 @@ impl SchemeSync for AcpiScheme<'_, '_> {
self.handles.remove(id);
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::{dmi_contents, top_level_entries};
+ use crate::acpi::DmiInfo;
+ use syscall::dirent::DirentKind;
+
+ #[test]
+ fn dmi_contents_exposes_individual_fields_and_match_all() {
+ let dmi_info = DmiInfo {
+ sys_vendor: Some("Framework".to_string()),
+ board_name: Some("FRANMECP01".to_string()),
+ product_name: Some("Laptop 16".to_string()),
+ ..DmiInfo::default()
+ };
+
+ assert_eq!(
+ dmi_contents(Some(&dmi_info), "sys_vendor").as_deref(),
+ Some("Framework")
+ );
+ assert_eq!(
+ dmi_contents(Some(&dmi_info), "board_name").as_deref(),
+ Some("FRANMECP01")
+ );
+ assert_eq!(
+ dmi_contents(Some(&dmi_info), "product_name").as_deref(),
+ Some("Laptop 16")
+ );
+ assert_eq!(
+ dmi_contents(Some(&dmi_info), "match_all").as_deref(),
+ Some("sys_vendor=Framework\nboard_name=FRANMECP01\nproduct_name=Laptop 16")
+ );
+ assert_eq!(dmi_contents(None, "bios_version").as_deref(), Some(""));
+ assert_eq!(dmi_contents(Some(&dmi_info), "unknown"), None);
+ }
+
+ #[test]
+ fn top_level_entries_always_include_reboot() {
+ let entries = top_level_entries(false);
+ assert!(entries.iter().any(|(name, kind)| {
+ *name == "reboot" && matches!(kind, DirentKind::Regular)
+ }));
+ }
+
+ #[test]
+ fn top_level_entries_only_include_power_when_available() {
+ let hidden = top_level_entries(false);
+ let visible = top_level_entries(true);
+
+ assert!(!hidden.iter().any(|(name, _)| *name == "power"));
+ assert!(visible.iter().any(|(name, kind)| {
+ *name == "power" && matches!(kind, DirentKind::Directory)
+ }));
+ }
+}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+27 -38
View File
@@ -1,61 +1,50 @@
diff --git a/drivers/pcid/src/scheme.rs b/drivers/pcid/src/scheme.rs
index ce55b33f..c06bdec4 100644
--- a/drivers/pcid/src/scheme.rs
+++ b/drivers/pcid/src/scheme.rs
@@ -21,6 +21,10 @@ enum Handle {
Access,
Device,
@@ -20,6 +20,7 @@ pub struct PciScheme {
Channel { addr: PciAddress, st: ChannelState },
+ // Uevent surface for hotplug consumers. Opening uevent returns an object
+ // from which device add/remove events can be read. Since pcid currently
+ // only scans at startup, this surface is ready for hotplug polling consumers.
+ Uevent,
// Uevent surface for hotplug consumers. pcid currently only scans at
// startup, so the surface exists for polling readers and will return no
// data until hotplug support grows real events.
Uevent,
SchemeRoot,
/// Represents an open handle to a device's bind endpoint
Bind { addr: PciAddress },
@@ -34,6 +38,6 @@ struct HandleWrapper {
}
}
@@ -33,7 +34,7 @@ impl Handle {
fn is_file(&self) -> bool {
- matches!(self, Self::Access | Self::Channel { .. } | Self::Bind { .. })
+ matches!(self, Self::Access | Self::Channel { .. } | Self::Bind { .. } | Self::Uevent)
matches!(
self,
- Self::Access | Self::Config { .. } | Self::Channel { .. }
+ Self::Access | Self::Config { .. } | Self::Channel { .. } | Self::Uevent
)
}
fn is_dir(&self) -> bool {
!self.is_file()
@@ -96,6 +100,8 @@ impl SchemeSync for PciScheme {
}
} else if path == "access" {
@@ -106,6 +107,8 @@ impl SchemeSync for PciScheme {
Handle::Access
+ } else if path == "uevent" {
+ Handle::Uevent
} else if path == "uevent" {
Handle::Uevent
} else {
let idx = path.find('/').unwrap_or(path.len());
let (addr_str, after) = path.split_at(idx);
@@ -140,5 +146,6 @@ impl SchemeSync for PciScheme {
Handle::Device => (DEVICE_CONTENTS.len(), MODE_DIR | 0o755),
Handle::Access | Handle::Channel { .. } | Handle::Bind { .. } => (0, MODE_CHR | 0o600),
@@ -142,6 +145,7 @@ impl SchemeSync for PciScheme {
Handle::Access | Handle::Channel { .. } => (0, MODE_CHR | 0o600),
+ Handle::Uevent => (0, MODE_CHR | 0o644),
Handle::SchemeRoot => return Err(Error::new(EBADF)),
};
stat.st_size = len as u64;
@@ -164,7 +171,13 @@ impl SchemeSync for PciScheme {
Handle::Channel {
addr: _,
@@ -180,6 +184,10 @@ impl SchemeSync for PciScheme {
ref mut st,
} => Self::read_channel(st, buf),
+ Handle::Uevent => {
+ // Uevent surface is ready for hotplug polling consumers.
+ // pcid currently only scans at startup, so return empty (EAGAIN would indicate no data available).
+ // Consumers can poll and re-read to check for new events.
+ // The surface is intentionally present before pcid gains real
+ // hotplug event delivery so consumers can open/poll it
+ // consistently across boot/runtime environments.
+ Ok(0)
+ }
Handle::SchemeRoot | Handle::Bind { .. } => Err(Error::new(EBADF)),
Handle::SchemeRoot => Err(Error::new(EBADF)),
_ => Err(Error::new(EBADF)),
}
@@ -199,6 +212,6 @@ impl SchemeSync for PciScheme {
}
@@ -222,7 +230,7 @@ impl SchemeSync for PciScheme {
Handle::Device => DEVICE_CONTENTS,
- Handle::Access | Handle::Channel { .. } | Handle::Bind { .. } => return Err(Error::new(ENOTDIR)),
+ Handle::Access | Handle::Channel { .. } | Handle::Bind { .. } | Handle::Uevent => return Err(Error::new(ENOTDIR)),
- Handle::Access | Handle::Config { .. } | Handle::Channel { .. } => {
+ Handle::Access | Handle::Config { .. } | Handle::Channel { .. } | Handle::Uevent => {
return Err(Error::new(ENOTDIR));
}
Handle::SchemeRoot => return Err(Error::new(EBADF)),
};
for (i, dent_name) in entries.iter().enumerate().skip(offset) {
File diff suppressed because it is too large Load Diff
@@ -1,942 +0,0 @@
diff --git a/drivers/usb/xhcid/src/xhci/mod.rs b/drivers/usb/xhcid/src/xhci/mod.rs
index f2143676..74126f67 100644
--- a/drivers/usb/xhcid/src/xhci/mod.rs
+++ b/drivers/usb/xhcid/src/xhci/mod.rs
@@ -311,6 +311,22 @@ struct PortState<const N: usize> {
input_context: Mutex<Dma<InputContext<N>>>,
dev_desc: Option<DevDesc>,
endpoint_states: BTreeMap<u8, EndpointState>,
+ quirks: crate::usb_quirks::UsbQuirkFlags,
+ pm_state: PortPmState,
+}
+
+#[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> {
@@ -809,6 +825,7 @@ impl<const N: usize> Xhci<N> {
);
if flags.contains(port::PortFlags::CCS) {
+ let early_quirks = crate::usb_quirks::lookup_usb_quirks_early(port_id);
let slot_ty = match self.supported_protocol(port_id) {
Some(protocol) => protocol.proto_slot_ty(),
None => {
@@ -838,7 +855,15 @@ impl<const N: usize> Xhci<N> {
debug!("Attempting to address the device");
let mut ring = match self
- .address_device(&mut input, port_id, slot_ty, slot, protocol_speed, speed)
+ .address_device(
+ &mut input,
+ port_id,
+ slot_ty,
+ slot,
+ protocol_speed,
+ speed,
+ early_quirks,
+ )
.await
{
Ok(device_ring) => device_ring,
@@ -866,6 +891,8 @@ impl<const N: usize> Xhci<N> {
},
))
.collect::<BTreeMap<_, _>>(),
+ quirks: early_quirks,
+ pm_state: PortPmState::Active,
};
self.port_states.insert(port_id, port_state);
debug!("Got port states!");
@@ -884,8 +911,14 @@ impl<const N: usize> Xhci<N> {
debug!("Got the 8 byte dev descriptor: {:X?}", dev_desc_8_byte);
let dev_desc = self.get_desc(port_id, slot).await?;
+ let quirks = early_quirks
+ | crate::usb_quirks::lookup_usb_quirks(dev_desc.vendor, dev_desc.product);
debug!("Got the full device descriptor!");
- self.port_states.get_mut(&port_id).unwrap().dev_desc = Some(dev_desc);
+ {
+ let mut port_state = self.port_states.get_mut(&port_id).unwrap();
+ port_state.quirks = quirks;
+ port_state.dev_desc = Some(dev_desc);
+ }
debug!("Got the port states again!");
{
@@ -1052,6 +1085,7 @@ impl<const N: usize> Xhci<N> {
slot: u8,
protocol_speed: &ProtocolSpeed,
speed: u8,
+ quirks: crate::usb_quirks::UsbQuirkFlags,
) -> Result<Ring> {
// Collect MTT, parent port number, parent slot ID
let mut mtt = false;
@@ -1162,11 +1196,16 @@ impl<const N: usize> Xhci<N> {
let input_context_physical = input_context.physical();
- let (event_trb, _) = self
- .execute_command(|trb, cycle| {
- trb.address_device(slot, input_context_physical, false, cycle)
- })
- .await;
+ let address_timeout = if quirks.contains(crate::usb_quirks::UsbQuirkFlags::SHORT_SET_ADDR_TIMEOUT)
+ {
+ Timeout::from_millis(100)
+ } else {
+ Timeout::from_secs(1)
+ };
+
+ let (event_trb, _) = self.execute_command_with_timeout(address_timeout, |trb, cycle| {
+ trb.address_device(slot, input_context_physical, false, cycle)
+ })?;
if event_trb.completion_code() != TrbCompletionCode::Success as u8 {
error!(
diff --git a/drivers/usb/xhcid/src/xhci/scheme.rs b/drivers/usb/xhcid/src/xhci/scheme.rs
index f2d439a4..dfe9fdec 100644
--- a/drivers/usb/xhcid/src/xhci/scheme.rs
+++ b/drivers/usb/xhcid/src/xhci/scheme.rs
@@ -24,6 +24,7 @@ use std::{cmp, fmt, io, mem, str};
use common::dma::Dma;
use futures::executor::block_on;
+use futures::FutureExt;
use log::{debug, error, info, trace, warn};
use redox_scheme::scheme::SchemeSync;
use smallvec::SmallVec;
@@ -32,9 +33,9 @@ use common::io::Io;
use redox_scheme::{CallerCtx, OpenResult};
use syscall::schemev2::NewFdFlags;
use syscall::{
- Error, Result, Stat, EACCES, EBADF, EBADFD, EBADMSG, EINVAL, EIO, EISDIR, ENOENT, ENOSYS,
- ENOTDIR, EOPNOTSUPP, EPROTO, ESPIPE, MODE_CHR, MODE_DIR, MODE_FILE, O_DIRECTORY, O_RDWR,
- O_STAT, O_WRONLY, SEEK_CUR, SEEK_END, SEEK_SET,
+ Error, Result, Stat, EACCES, EBADF, EBADFD, EBADMSG, EBUSY, EINVAL, EIO, EISDIR, ENOENT,
+ ENOSYS, ENOTDIR, EOPNOTSUPP, EPROTO, ESPIPE, MODE_CHR, MODE_DIR, MODE_FILE, O_DIRECTORY,
+ O_RDWR, O_STAT, O_WRONLY, SEEK_CUR, SEEK_END, SEEK_SET,
};
use super::{port, usb};
@@ -60,10 +61,16 @@ lazy_static! {
.expect("Failed to create the regex for the port<n>/attach scheme.");
static ref REGEX_PORT_DETACH: Regex = Regex::new(r"^port([\d\.]+)/detach$")
.expect("Failed to create the regex for the port<n>/detach scheme.");
+ static ref REGEX_PORT_SUSPEND: Regex = Regex::new(r"^port([\d\.]+)/suspend$")
+ .expect("Failed to create the regex for the port<n>/suspend scheme.");
+ static ref REGEX_PORT_RESUME: Regex = Regex::new(r"^port([\d\.]+)/resume$")
+ .expect("Failed to create the regex for the port<n>/resume scheme.");
static ref REGEX_PORT_DESCRIPTORS: Regex = Regex::new(r"^port([\d\.]+)/descriptors$")
.expect("Failed to create the regex for the port<n>/descriptors");
static ref REGEX_PORT_STATE: Regex = Regex::new(r"^port([\d\.]+)/state$")
.expect("Failed to create the regex for the port<n>/state scheme");
+ static ref REGEX_PORT_PM_STATE: Regex = Regex::new(r"^port([\d\.]+)/pm_state$")
+ .expect("Failed to create the regex for the port<n>/pm_state scheme");
static ref REGEX_PORT_REQUEST: Regex = Regex::new(r"^port([\d\.]+)/request$")
.expect("Failed to create the regex for the port<n>/request scheme");
static ref REGEX_PORT_ENDPOINTS: Regex = Regex::new(r"^port([\d\.]+)/endpoints$")
@@ -137,12 +144,15 @@ pub enum Handle {
Port(PortId, Vec<u8>), // port, contents
PortDesc(PortId, Vec<u8>), // port, contents
PortState(PortId), // port
+ PortPmState(PortId), // port
PortReq(PortId, PortReqState), // port, state
Endpoints(PortId, Vec<u8>), // port, contents
Endpoint(PortId, u8, EndpointHandleTy), // port, endpoint, state
ConfigureEndpoints(PortId), // port
AttachDevice(PortId), // port
DetachDevice(PortId), // port
+ SuspendDevice(PortId), // port
+ ResumeDevice(PortId), // port
SchemeRoot,
}
@@ -172,6 +182,8 @@ enum SchemeParameters {
PortDesc(PortId), // port number
/// /port<n>/state
PortState(PortId), // port number
+ /// /port<n>/pm_state
+ PortPmState(PortId), // port number
/// /port<n>/request
PortReq(PortId), // port number
/// /port<n>/endpoints
@@ -187,6 +199,10 @@ enum SchemeParameters {
AttachDevice(PortId), // port number
/// /port<n>/detach
DetachDevice(PortId), // port number
+ /// /port<n>/suspend
+ SuspendDevice(PortId), // port number
+ /// /port<n>/resume
+ ResumeDevice(PortId), // port number
}
impl Handle {
@@ -209,6 +225,9 @@ impl Handle {
Handle::PortState(port_num) => {
format!("port{}/state", port_num)
}
+ Handle::PortPmState(port_num) => {
+ format!("port{}/pm_state", port_num)
+ }
Handle::PortReq(port_num, _) => {
format!("port{}/request", port_num)
}
@@ -235,6 +254,12 @@ impl Handle {
Handle::DetachDevice(port_num) => {
format!("port{}/detach", port_num)
}
+ Handle::SuspendDevice(port_num) => {
+ format!("port{}/suspend", port_num)
+ }
+ Handle::ResumeDevice(port_num) => {
+ format!("port{}/resume", port_num)
+ }
Handle::SchemeRoot => String::from(""),
}
}
@@ -258,10 +283,13 @@ impl Handle {
&Handle::PortReq(_, PortReqState::Tmp) => unreachable!(),
&Handle::PortReq(_, PortReqState::TmpSetup(_)) => unreachable!(),
&Handle::PortState(_) => HandleType::Character,
+ &Handle::PortPmState(_) => HandleType::Character,
&Handle::PortReq(_, _) => HandleType::Character,
&Handle::ConfigureEndpoints(_) => HandleType::Character,
&Handle::AttachDevice(_) => HandleType::Character,
&Handle::DetachDevice(_) => HandleType::Character,
+ &Handle::SuspendDevice(_) => HandleType::Character,
+ &Handle::ResumeDevice(_) => HandleType::Character,
&Handle::Endpoint(_, _, ref st) => match st {
EndpointHandleTy::Data => HandleType::Character,
EndpointHandleTy::Ctl => HandleType::Character,
@@ -289,10 +317,13 @@ impl Handle {
&Handle::PortReq(_, PortReqState::Tmp) => None,
&Handle::PortReq(_, PortReqState::TmpSetup(_)) => None,
&Handle::PortState(_) => None,
+ &Handle::PortPmState(_) => None,
&Handle::PortReq(_, _) => None,
&Handle::ConfigureEndpoints(_) => None,
&Handle::AttachDevice(_) => None,
&Handle::DetachDevice(_) => None,
+ &Handle::SuspendDevice(_) => None,
+ &Handle::ResumeDevice(_) => None,
&Handle::Endpoint(_, _, ref st) => match st {
EndpointHandleTy::Data => None,
EndpointHandleTy::Ctl => None,
@@ -383,6 +414,14 @@ impl SchemeParameters {
let port_num = get_port_id_from_regex(&REGEX_PORT_DETACH, scheme, 0)?;
Ok(Self::DetachDevice(port_num))
+ } else if REGEX_PORT_SUSPEND.is_match(scheme) {
+ let port_num = get_port_id_from_regex(&REGEX_PORT_SUSPEND, scheme, 0)?;
+
+ Ok(Self::SuspendDevice(port_num))
+ } else if REGEX_PORT_RESUME.is_match(scheme) {
+ let port_num = get_port_id_from_regex(&REGEX_PORT_RESUME, scheme, 0)?;
+
+ Ok(Self::ResumeDevice(port_num))
} else if REGEX_PORT_DESCRIPTORS.is_match(scheme) {
let port_num = get_port_id_from_regex(&REGEX_PORT_DESCRIPTORS, scheme, 0)?;
@@ -391,6 +430,10 @@ impl SchemeParameters {
let port_num = get_port_id_from_regex(&REGEX_PORT_STATE, scheme, 0)?;
Ok(Self::PortState(port_num))
+ } else if REGEX_PORT_PM_STATE.is_match(scheme) {
+ let port_num = get_port_id_from_regex(&REGEX_PORT_PM_STATE, scheme, 0)?;
+
+ Ok(Self::PortPmState(port_num))
} else if REGEX_PORT_REQUEST.is_match(scheme) {
let port_num = get_port_id_from_regex(&REGEX_PORT_REQUEST, scheme, 0)?;
@@ -564,15 +607,22 @@ impl<const N: usize> Xhci<N> {
endps: impl IntoIterator<Item = EndpDesc>,
hid_descs: impl IntoIterator<Item = HidDesc>,
lang_id: u16,
+ quirks: crate::usb_quirks::UsbQuirkFlags,
) -> Result<IfDesc> {
Ok(IfDesc {
alternate_setting: desc.alternate_setting,
class: desc.class,
interface_str: if desc.interface_str > 0 {
- Some(
+ if quirks.contains(crate::usb_quirks::UsbQuirkFlags::BAD_DESCRIPTOR) {
self.fetch_string_desc(port_id, slot, desc.interface_str, lang_id)
- .await?,
- )
+ .await
+ .ok()
+ } else {
+ Some(
+ self.fetch_string_desc(port_id, slot, desc.interface_str, lang_id)
+ .await?,
+ )
+ }
} else {
None
},
@@ -628,6 +678,53 @@ impl<const N: usize> Xhci<N> {
(event_trb, command_trb)
}
+ pub fn execute_command_with_timeout<F: FnOnce(&mut Trb, bool)>(
+ &self,
+ timeout: common::timeout::Timeout,
+ f: F,
+ ) -> Result<(Trb, Trb)> {
+ if self.interrupt_is_pending(0) {
+ debug!("The EHB bit is already set!");
+ }
+
+ let next_event = {
+ let mut command_ring = self.cmd.lock().unwrap();
+ let (cmd_index, cycle) = (command_ring.next_index(), command_ring.cycle);
+
+ debug!("Sending command with cycle bit {}", cycle as u8);
+
+ {
+ let command_trb = &mut command_ring.trbs[cmd_index];
+ f(command_trb, cycle);
+ }
+
+ let command_trb = &command_ring.trbs[cmd_index];
+ self.next_command_completion_event_trb(
+ &*command_ring,
+ command_trb,
+ EventDoorbell::new(self, 0, 0),
+ )
+ };
+
+ let mut next_event = Box::pin(next_event);
+
+ loop {
+ if let Some(trbs) = next_event.as_mut().now_or_never() {
+ let event_trb = trbs.event_trb;
+ let command_trb = trbs.src_trb.ok_or(Error::new(EIO))?;
+
+ assert_eq!(
+ event_trb.trb_type(),
+ TrbType::CommandCompletion as u8,
+ "The IRQ reactor (or the xHC) gave an invalid event TRB"
+ );
+
+ return Ok((event_trb, command_trb));
+ }
+
+ timeout.run().map_err(|()| Error::new(EIO))?;
+ }
+ }
pub async fn execute_control_transfer<D>(
&self,
port_num: PortId,
@@ -639,6 +736,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;
@@ -690,6 +789,20 @@ impl<const N: usize> Xhci<N> {
handle_transfer_event_trb("CONTROL_TRANSFER", &event_trb, &status_trb)?;
+ let delay_ctrl_msg = self
+ .port_states
+ .get(&port_num)
+ .map(|port_state| {
+ port_state
+ .quirks
+ .contains(crate::usb_quirks::UsbQuirkFlags::DELAY_CTRL_MSG)
+ })
+ .unwrap_or(false);
+
+ if delay_ctrl_msg {
+ std::thread::sleep(std::time::Duration::from_millis(20));
+ }
+
//self.event_handler_finished();
Ok(event_trb)
@@ -709,6 +822,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)?;
@@ -785,7 +900,29 @@ impl<const N: usize> Xhci<N> {
let event_trb = trbs.event_trb;
let transfer_trb = trbs.src_trb.ok_or(Error::new(EIO))?;
- handle_transfer_event_trb("EXECUTE_TRANSFER", &event_trb, &transfer_trb)?;
+ if let Err(err) = handle_transfer_event_trb("EXECUTE_TRANSFER", &event_trb, &transfer_trb)
+ {
+ let need_reset = self
+ .port_states
+ .get(&port_num)
+ .map(|port_state| {
+ port_state
+ .quirks
+ .contains(crate::usb_quirks::UsbQuirkFlags::NEED_RESET)
+ })
+ .unwrap_or(false);
+
+ if need_reset {
+ if let Err(reset_err) = self.reset_device_slot(port_num).await {
+ error!(
+ "EXECUTE_TRANSFER reset recovery failed for port {}: {}",
+ port_num, reset_err
+ );
+ }
+ }
+
+ return Err(err);
+ }
// FIXME: EDTLA if event data was set
if event_trb.completion_code() != TrbCompletionCode::ShortPacket as u8
@@ -861,6 +998,21 @@ impl<const N: usize> Xhci<N> {
handle_event_trb("RESET_ENDPOINT", &event_trb, &command_trb)
}
+ async fn reset_device_slot(&self, port_num: PortId) -> Result<()> {
+ let slot = self
+ .port_states
+ .get(&port_num)
+ .ok_or(Error::new(EBADF))?
+ .slot;
+
+ let (event_trb, command_trb) = self
+ .execute_command(|trb, cycle| {
+ trb.reset_device(slot, cycle);
+ })
+ .await;
+
+ handle_event_trb("RESET_DEVICE", &event_trb, &command_trb)
+ }
fn endp_ctx_interval(speed_id: &ProtocolSpeed, endp_desc: &EndpDesc) -> u8 {
/// Logarithmic (base 2) 125 µs periods per millisecond.
@@ -1205,7 +1357,19 @@ impl<const N: usize> Xhci<N> {
}
// Tell the device about this configuration.
- self.set_configuration(port, configuration_value).await?;
+ let skip_set_configuration = self
+ .port_states
+ .get(&port)
+ .map(|port_state| {
+ port_state
+ .quirks
+ .contains(crate::usb_quirks::UsbQuirkFlags::NO_SET_CONFIG)
+ })
+ .unwrap_or(false);
+
+ if !skip_set_configuration {
+ self.set_configuration(port, configuration_value).await?;
+ }
Ok(())
}
@@ -1234,8 +1398,20 @@ impl<const N: usize> Xhci<N> {
if let Some(interface_num) = req.interface_desc {
if let Some(alternate_setting) = req.alternate_setting {
- self.set_interface(port, interface_num, alternate_setting)
- .await?;
+ let skip_set_interface = self
+ .port_states
+ .get(&port)
+ .map(|port_state| {
+ port_state
+ .quirks
+ .contains(crate::usb_quirks::UsbQuirkFlags::NO_SET_INTF)
+ })
+ .unwrap_or(false);
+
+ if !skip_set_interface {
+ self.set_interface(port, interface_num, alternate_setting)
+ .await?;
+ }
}
}
@@ -1453,52 +1629,109 @@ impl<const N: usize> Xhci<N> {
let raw_dd = self.fetch_dev_desc(port_id, slot).await?;
log::debug!("port {} slot {} desc {:X?}", port_id, slot, raw_dd);
+ let vendor = raw_dd.vendor;
+ let product = raw_dd.product;
+ let quirks = crate::usb_quirks::lookup_usb_quirks(vendor, product);
+ if !quirks.is_empty() {
+ log::info!(
+ "port {}: USB quirks for {:04x}:{:04x}: {:?}",
+ port_id, vendor, product, quirks
+ );
+ }
+
// Only fetch language IDs if we need to. Some devices will fail to return this descriptor
//TODO: also check configurations and interfaces for defined strings?
+ let bad_descriptor = quirks.contains(crate::usb_quirks::UsbQuirkFlags::BAD_DESCRIPTOR);
+
let lang_id =
- if raw_dd.manufacturer_str > 0 || raw_dd.product_str > 0 || raw_dd.serial_str > 0 {
- let lang_ids = self.fetch_lang_ids_desc(port_id, slot).await?;
- // Prefer US English, but fall back to first language ID, or zero
- let en_us_id = 0x409;
- if lang_ids.contains(&en_us_id) {
- en_us_id
- } else {
- match lang_ids.first() {
- Some(some) => *some,
- None => 0,
+ if !quirks.contains(crate::usb_quirks::UsbQuirkFlags::NO_STRING_FETCH)
+ && (raw_dd.manufacturer_str > 0
+ || raw_dd.product_str > 0
+ || raw_dd.serial_str > 0)
+ {
+ match self.fetch_lang_ids_desc(port_id, slot).await {
+ Ok(lang_ids) => {
+ // Prefer US English, but fall back to first language ID, or zero
+ let en_us_id = 0x409;
+ if lang_ids.contains(&en_us_id) {
+ en_us_id
+ } else {
+ match lang_ids.first() {
+ Some(some) => *some,
+ None => 0,
+ }
+ }
+ }
+ Err(err) if bad_descriptor => {
+ log::warn!(
+ "port {} slot {}: failed to fetch language IDs with BAD_DESCRIPTOR set: {}",
+ port_id,
+ slot,
+ err
+ );
+ 0
}
+ Err(err) => return Err(err),
}
} else {
0
};
log::debug!("port {} using language ID 0x{:04x}", port_id, lang_id);
- let (manufacturer_str, product_str, serial_str) = (
- if raw_dd.manufacturer_str > 0 {
- Some(
- self.fetch_string_desc(port_id, slot, raw_dd.manufacturer_str, lang_id)
- .await?,
- )
- } else {
- None
- },
- if raw_dd.product_str > 0 {
- Some(
- self.fetch_string_desc(port_id, slot, raw_dd.product_str, lang_id)
- .await?,
- )
+ let (manufacturer_str, product_str, serial_str) =
+ if quirks.contains(crate::usb_quirks::UsbQuirkFlags::NO_STRING_FETCH) {
+ (None, None, None)
} else {
- None
- },
- if raw_dd.serial_str > 0 {
- Some(
- self.fetch_string_desc(port_id, slot, raw_dd.serial_str, lang_id)
- .await?,
+ (
+ if raw_dd.manufacturer_str > 0 {
+ if bad_descriptor {
+ self.fetch_string_desc(port_id, slot, raw_dd.manufacturer_str, lang_id)
+ .await
+ .ok()
+ } else {
+ Some(
+ self.fetch_string_desc(
+ port_id,
+ slot,
+ raw_dd.manufacturer_str,
+ lang_id,
+ )
+ .await?,
+ )
+ }
+ } else {
+ None
+ },
+ if raw_dd.product_str > 0 {
+ if bad_descriptor {
+ self.fetch_string_desc(port_id, slot, raw_dd.product_str, lang_id)
+ .await
+ .ok()
+ } else {
+ Some(
+ self.fetch_string_desc(port_id, slot, raw_dd.product_str, lang_id)
+ .await?,
+ )
+ }
+ } else {
+ None
+ },
+ if raw_dd.serial_str > 0 {
+ if bad_descriptor {
+ self.fetch_string_desc(port_id, slot, raw_dd.serial_str, lang_id)
+ .await
+ .ok()
+ } else {
+ Some(
+ self.fetch_string_desc(port_id, slot, raw_dd.serial_str, lang_id)
+ .await?,
+ )
+ }
+ } else {
+ None
+ },
)
- } else {
- None
- },
- );
+ };
log::debug!(
"manufacturer {:?} product {:?} serial {:?}",
manufacturer_str,
@@ -1508,14 +1741,39 @@ impl<const N: usize> Xhci<N> {
//TODO let (bos_desc, bos_data) = self.fetch_bos_desc(port_id, slot).await?;
- let supports_superspeed = false;
- //TODO usb::bos_capability_descs(bos_desc, &bos_data).any(|desc| desc.is_superspeed());
- let supports_superspeedplus = false;
- //TODO usb::bos_capability_descs(bos_desc, &bos_data).any(|desc| desc.is_superspeedplus());
+ let (supports_superspeed, supports_superspeedplus) =
+ if quirks.contains(crate::usb_quirks::UsbQuirkFlags::NO_BOS) {
+ (false, false)
+ } else {
+ match self.fetch_bos_desc(port_id, slot).await {
+ Ok((bos_desc, bos_data)) => (
+ usb::bos_capability_descs(bos_desc, &bos_data)
+ .any(|desc| desc.is_superspeed()),
+ usb::bos_capability_descs(bos_desc, &bos_data)
+ .any(|desc| desc.is_superspeedplus()),
+ ),
+ Err(err) => {
+ log::debug!(
+ "port {} slot {}: failed to fetch BOS descriptor: {}",
+ port_id,
+ slot,
+ err
+ );
+ (false, false)
+ }
+ }
+ };
let mut config_descs = SmallVec::new();
- for index in 0..raw_dd.configurations {
+ let configuration_indices: Vec<u8> =
+ if quirks.contains(crate::usb_quirks::UsbQuirkFlags::FORCE_ONE_CONFIG) {
+ vec![0]
+ } else {
+ (0..raw_dd.configurations).collect()
+ };
+
+ for index in configuration_indices {
debug!("Fetching the config descriptor at index {}", index);
let (desc, data) = self.fetch_config_desc(port_id, slot, index).await?;
log::debug!(
@@ -1541,6 +1799,12 @@ impl<const N: usize> Xhci<N> {
let mut iter = descriptors.into_iter().peekable();
while let Some(item) = iter.next() {
+ if quirks.contains(crate::usb_quirks::UsbQuirkFlags::HONOR_BNUMINTERFACES)
+ && interface_descs.len() >= desc.interfaces as usize
+ {
+ break;
+ }
+
if let AnyDescriptor::Interface(idesc) = item {
let mut endpoints = SmallVec::<[EndpDesc; 4]>::new();
let mut hid_descs = SmallVec::<[HidDesc; 1]>::new();
@@ -1554,6 +1818,9 @@ impl<const N: usize> Xhci<N> {
}
Some(unexpected) => {
log::warn!("expected endpoint, got {:X?}", unexpected);
+ if bad_descriptor {
+ continue;
+ }
break;
}
None => break,
@@ -1578,8 +1845,16 @@ impl<const N: usize> Xhci<N> {
}
interface_descs.push(
- self.new_if_desc(port_id, slot, idesc, endpoints, hid_descs, lang_id)
- .await?,
+ self.new_if_desc(
+ port_id,
+ slot,
+ idesc,
+ endpoints,
+ hid_descs,
+ lang_id,
+ quirks,
+ )
+ .await?,
);
} else {
log::warn!("expected interface, got {:?}", item);
@@ -1590,11 +1865,20 @@ impl<const N: usize> Xhci<N> {
config_descs.push(ConfDesc {
kind: desc.kind,
- configuration: if desc.configuration_str > 0 {
- Some(
+ configuration: if quirks.contains(crate::usb_quirks::UsbQuirkFlags::NO_STRING_FETCH)
+ {
+ None
+ } else if desc.configuration_str > 0 {
+ if bad_descriptor {
self.fetch_string_desc(port_id, slot, desc.configuration_str, lang_id)
- .await?,
- )
+ .await
+ .ok()
+ } else {
+ Some(
+ self.fetch_string_desc(port_id, slot, desc.configuration_str, lang_id)
+ .await?,
+ )
+ }
} else {
None
},
@@ -1856,7 +2140,7 @@ impl<const N: usize> Xhci<N> {
if (flags & O_DIRECTORY != 0) || (flags & O_STAT != 0) {
let mut contents = Vec::new();
- write!(contents, "descriptors\nendpoints\n").unwrap();
+ write!(contents, "descriptors\nendpoints\npm_state\nsuspend\nresume\n").unwrap();
if self.slot_state(
self.port_states
@@ -1893,6 +2177,14 @@ impl<const N: usize> Xhci<N> {
Ok(Handle::PortState(port_num))
}
+ fn open_handle_port_pm_state(&self, port_num: PortId, flags: usize) -> Result<Handle> {
+ if flags & O_DIRECTORY != 0 && flags & O_STAT == 0 {
+ return Err(Error::new(ENOTDIR));
+ }
+
+ Ok(Handle::PortPmState(port_num))
+ }
+
/// implements open() for /port<n>/endpoints
///
/// # Arguments
@@ -2087,6 +2379,30 @@ impl<const N: usize> Xhci<N> {
Ok(Handle::DetachDevice(port_num))
}
+ fn open_handle_suspend_device(&self, port_num: PortId, flags: usize) -> Result<Handle> {
+ if flags & O_DIRECTORY != 0 && flags & O_STAT == 0 {
+ return Err(Error::new(ENOTDIR));
+ }
+
+ if flags & O_RDWR != O_WRONLY && flags & O_STAT == 0 {
+ return Err(Error::new(EACCES));
+ }
+
+ Ok(Handle::SuspendDevice(port_num))
+ }
+
+ fn open_handle_resume_device(&self, port_num: PortId, flags: usize) -> Result<Handle> {
+ if flags & O_DIRECTORY != 0 && flags & O_STAT == 0 {
+ return Err(Error::new(ENOTDIR));
+ }
+
+ if flags & O_RDWR != O_WRONLY && flags & O_STAT == 0 {
+ return Err(Error::new(EACCES));
+ }
+
+ Ok(Handle::ResumeDevice(port_num))
+ }
+
/// implements open() for /port<n>/request
///
/// # Arguments
@@ -2155,6 +2471,9 @@ impl<const N: usize> SchemeSync for &Xhci<N> {
SchemeParameters::PortState(port_number) => {
self.open_handle_port_state(port_number, flags)?
}
+ SchemeParameters::PortPmState(port_number) => {
+ self.open_handle_port_pm_state(port_number, flags)?
+ }
SchemeParameters::PortReq(port_number) => {
self.open_handle_port_request(port_number, flags)?
}
@@ -2173,6 +2492,12 @@ impl<const N: usize> SchemeSync for &Xhci<N> {
SchemeParameters::DetachDevice(port_number) => {
self.open_handle_detach_device(port_number, flags)?
}
+ SchemeParameters::SuspendDevice(port_number) => {
+ self.open_handle_suspend_device(port_number, flags)?
+ }
+ SchemeParameters::ResumeDevice(port_number) => {
+ self.open_handle_resume_device(port_number, flags)?
+ }
};
let fd = self.next_handle.fetch_add(1, atomic::Ordering::Relaxed);
@@ -2203,7 +2528,11 @@ impl<const N: usize> SchemeSync for &Xhci<N> {
//If we have a handle to the configure scheme, we need to mark it as write only.
match &*guard {
- Handle::ConfigureEndpoints(_) | Handle::AttachDevice(_) | Handle::DetachDevice(_) => {
+ Handle::ConfigureEndpoints(_)
+ | Handle::AttachDevice(_)
+ | Handle::DetachDevice(_)
+ | Handle::SuspendDevice(_)
+ | Handle::ResumeDevice(_) => {
stat.st_mode = stat.st_mode | 0o200;
}
_ => {}
@@ -2263,6 +2592,8 @@ impl<const N: usize> SchemeSync for &Xhci<N> {
Handle::ConfigureEndpoints(_) => Err(Error::new(EBADF)),
Handle::AttachDevice(_) => Err(Error::new(EBADF)),
Handle::DetachDevice(_) => Err(Error::new(EBADF)),
+ Handle::SuspendDevice(_) => Err(Error::new(EBADF)),
+ Handle::ResumeDevice(_) => Err(Error::new(EBADF)),
Handle::SchemeRoot => Err(Error::new(EBADF)),
&mut Handle::Endpoint(port_num, endp_num, ref mut st) => match st {
@@ -2294,6 +2625,10 @@ impl<const N: usize> SchemeSync for &Xhci<N> {
Ok(Xhci::<N>::write_dyn_string(string, buf, offset))
}
+ &mut Handle::PortPmState(port_num) => {
+ let ps = self.port_states.get(&port_num).ok_or(Error::new(EBADF))?;
+ Ok(Xhci::<N>::write_dyn_string(ps.pm_state.as_str().as_bytes(), buf, offset))
+ }
&mut Handle::PortReq(port_num, ref mut st) => {
let state = std::mem::replace(st, PortReqState::Tmp);
drop(guard); // release the lock
@@ -2333,6 +2668,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 => {
@@ -2356,6 +2699,38 @@ impl<const N: usize> Xhci<N> {
self.handles.remove(&fd);
}
+ fn ensure_port_active(&self, port_num: PortId) -> Result<()> {
+ let pm_state = self
+ .port_states
+ .get(&port_num)
+ .ok_or(Error::new(EBADFD))?
+ .pm_state;
+ match pm_state {
+ super::PortPmState::Active => Ok(()),
+ super::PortPmState::Suspended => 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
+ .quirks
+ .contains(crate::usb_quirks::UsbQuirkFlags::NO_SUSPEND)
+ {
+ return Err(Error::new(EOPNOTSUPP));
+ }
+
+ port_state.pm_state = super::PortPmState::Suspended;
+ 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))?;
+ port_state.pm_state = super::PortPmState::Active;
+ 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))?;
@@ -2406,6 +2781,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));
}
@@ -2562,6 +2939,7 @@ impl<const N: usize> Xhci<N> {
},
XhciEndpCtlReq::Reset { no_clear_feature } => match ep_if_state {
EndpIfState::Init => {
+ self.ensure_port_active(port_num)?;
self.on_req_reset_device(port_num, endp_num, !no_clear_feature)
.await?
}
@@ -2571,6 +2949,7 @@ impl<const N: usize> Xhci<N> {
},
XhciEndpCtlReq::Transfer { direction, count } => match ep_if_state {
state @ EndpIfState::Init => {
+ self.ensure_port_active(port_num)?;
if direction == XhciEndpCtlDirection::NoData {
// Yield the result directly because no bytes have to be sent or received
// beforehand.
@@ -2631,6 +3010,8 @@ impl<const N: usize> Xhci<N> {
endp_num: u8,
buf: &[u8],
) -> Result<usize> {
+ self.ensure_port_active(port_num)?;
+
let mut port_state = self
.port_states
.get_mut(&port_num)
@@ -2732,6 +3113,8 @@ impl<const N: usize> Xhci<N> {
endp_num: u8,
buf: &mut [u8],
) -> Result<usize> {
+ self.ensure_port_active(port_num)?;
+
let mut port_state = self
.port_states
.get_mut(&port_num)
@@ -1,20 +1,21 @@
diff --git a/drivers/usb/xhcid/src/xhci/mod.rs b/drivers/usb/xhcid/src/xhci/mod.rs
index f1c6d08e..a3f2e15c 100644
--- a/drivers/usb/xhcid/src/xhci/mod.rs
+++ b/drivers/usb/xhcid/src/xhci/mod.rs
@@ -904,6 +904,7 @@ impl<const N: usize> Xhci<N> {
match self.spawn_drivers(port_id) {
Ok(()) => {
info!("xhcid: uevent add device usb/{}", port_id.root_hub_port_num());
+ // NOTE: driver-manager hotplug loop detects new USB devices via this log
@@ -1102,7 +1102,9 @@
}
Err(err) => {
error!("Failed to spawn driver for port {}: `{}`", port_id, err)
@@ -974,6 +975,7 @@ impl<const N: usize> Xhci<N> {
info!("xhcid: uevent remove device usb/{}", port_id.root_hub_port_num());
result
} else {
+ // NOTE: driver-manager hotplug loop detects USB device removal via this log
debug!(
"Attempted to detach from port {}, which wasn't previously attached.",
port_id
match self.spawn_drivers(port_id) {
- Ok(()) => (),
+ Ok(()) => {
+ info!("xhcid: uevent add device usb/{}", port_id.root_hub_port_num());
+ }
Err(err) => {
error!("Failed to spawn driver for port {}: `{}`", port_id, err)
}
@@ -1189,6 +1191,7 @@
Ok(()) => {
let _ = self.port_states.remove(&port_id);
debug!("disabled port slot {} for port {}", slot, port_id);
+ info!("xhcid: uevent remove device usb/{}", port_id.root_hub_port_num());
Ok(true)
}
Err(err) => {
@@ -1,287 +0,0 @@
# P2-ac97d-ihdad-main.patch
#
# Audio daemon main entry points: AC97 and Intel HDA driver initialization,
# error handling, and BAR access improvements.
#
# Covers:
# - ac97d/src/main.rs: BAR access, error handling, codec initialization
# - ihdad/src/main.rs: error handling, device initialization
#
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,8 +201,8 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
}
}
- std::process::exit(0);
+ std::process::exit(1);
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
diff --git a/drivers/audio/ihdad/src/main.rs b/drivers/audio/ihdad/src/main.rs
index 31a2add7..11d80133 100755
--- a/drivers/audio/ihdad/src/main.rs
+++ b/drivers/audio/ihdad/src/main.rs
@@ -6,7 +6,7 @@ use std::os::unix::io::AsRawFd;
use std::usize;
use event::{user_data, EventQueue};
-use pcid_interface::irq_helpers::pci_allocate_interrupt_vector;
+use pcid_interface::irq_helpers::try_pci_allocate_interrupt_vector;
use pcid_interface::PciFunctionHandle;
pub mod hda;
@@ -38,9 +38,19 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
log::info!("IHDA {}", pci_config.func.display());
+ if let Err(err) = pci_config.func.bars[0].try_mem() {
+ log::error!("ihdad: invalid BAR0: {err}");
+ std::process::exit(1);
+ }
let address = unsafe { pcid_handle.map_bar(0) }.ptr.as_ptr() as usize;
- let irq_file = pci_allocate_interrupt_vector(&mut pcid_handle, "ihdad");
+ let irq_file = match try_pci_allocate_interrupt_vector(&mut pcid_handle, "ihdad") {
+ Ok(irq) => irq,
+ Err(err) => {
+ log::error!("ihdad: failed to allocate interrupt vector: {err}");
+ std::process::exit(1);
+ }
+ };
{
let vend_prod: u32 = ((pci_config.func.full_device_id.vendor_id as u32) << 16)
@@ -53,11 +63,28 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
}
}
- let event_queue =
- EventQueue::<Source>::new().expect("ihdad: Could not create event queue.");
- let socket = Socket::nonblock().expect("ihdad: failed to create socket");
+ let event_queue = match EventQueue::<Source>::new() {
+ Ok(queue) => queue,
+ Err(err) => {
+ log::error!("ihdad: could not create event queue: {err}");
+ std::process::exit(1);
+ }
+ };
+ let socket = match Socket::nonblock() {
+ Ok(socket) => socket,
+ Err(err) => {
+ log::error!("ihdad: failed to create socket: {err}");
+ std::process::exit(1);
+ }
+ };
let mut device = unsafe {
- hda::IntelHDA::new(address, vend_prod).expect("ihdad: failed to allocate device")
+ match hda::IntelHDA::new(address, vend_prod) {
+ Ok(device) => device,
+ Err(err) => {
+ log::error!("ihdad: failed to allocate device: {err}");
+ std::process::exit(1);
+ }
+ }
};
let mut readiness_based = ReadinessBased::new(&socket, 16);
@@ -1,31 +0,0 @@
Defer AML initialization until PCI registration completes.
When acpid starts before pcid has registered the PCI fd, AML
initialization fails with a misleading ERROR-level message. This is
expected on every boot because the service ordering requires acpid to
start before pcid-spawner. The AML interpreter initializes successfully
after pcid registers via /scheme/acpi/register_pci.
Changes:
- aml_context_mut(): log at DEBUG instead of ERROR when PCI fd is None
(expected pre-registration state, not a fault)
- Fadt::init(): skip \\_S5 evaluation when PCI is not yet registered,
since refresh_s5_values() is retried in register_pci_fd() after PCI
registration completes
diff -urN a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs
--- a/drivers/acpid/src/acpi.rs
+++ b/drivers/acpid/src/acpi.rs
@@ -896,7 +896,11 @@
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);
+ }
}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+6 -170
View File
@@ -1,7 +1,6 @@
diff --git a/drivers/hwd/src/backend/acpi.rs b/drivers/hwd/src/backend/acpi.rs
--- a/drivers/hwd/src/backend/acpi.rs
+++ b/drivers/hwd/src/backend/acpi.rs
@@ -1,27 +1,36 @@
@@ -1,26 +1,30 @@
use amlserde::{AmlSerde, AmlSerdeValue};
-use std::{error::Error, fs, process::Command};
+use std::{error::Error, fs};
@@ -35,10 +34,7 @@ diff --git a/drivers/hwd/src/backend/acpi.rs b/drivers/hwd/src/backend/acpi.rs
- 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) =>
+ {
+ 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(());
+ }
@@ -47,10 +43,10 @@ diff --git a/drivers/hwd/src/backend/acpi.rs b/drivers/hwd/src/backend/acpi.rs
// TODO: Reimplement with getdents?
let symbols_fd = libredox::Fd::open(
"/scheme/acpi/symbols",
@@ -100,13 +109,104 @@
@@ -83,6 +87,7 @@ impl Backend for AcpiBackend {
"PNP0C0F" => "PCI interrupt link",
"PNP0C50" => "I2C HID",
"PNP0F13" => "PS/2 port for PS/2-style mouse",
"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" => {
@@ -121,9 +117,9 @@ diff --git a/drivers/hwd/src/backend/acpi.rs b/drivers/hwd/src/backend/acpi.rs
+ | "INTC1055"
+ | "INTC1050"
+ | "INTC1051"
+ | "INTC1080"
+ | "INT1080"
+ | "INTC1081"
+ | "INTC1082"
+ | "INT1082"
+ ) || is_elan_touchpad_id(id)
+ || is_cypress_touchpad_id(id)
+ || is_synaptics_rmi_id(id)
@@ -151,163 +147,3 @@ diff --git a/drivers/hwd/src/backend/acpi.rs b/drivers/hwd/src/backend/acpi.rs
+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/pcid-spawner/src/main.rs b/drivers/pcid-spawner/src/main.rs
--- 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,7 @@
}
let config: Config = toml::from_str(&config_data)?;
+ let strict_usb_boot = strict_usb_boot();
for entry in fs::read_dir("/scheme/pci")? {
let entry = entry.context("failed to get entry")?;
@@ -87,15 +117,71 @@
log::info!("pcid-spawner: spawn {:?}", command);
+ let device_addr = handle.config().func.addr;
+
handle.enable_device();
let channel_fd = handle.into_inner_fd();
command.env("PCID_CLIENT_CHANNEL", channel_fd.to_string());
#[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 {
+ #[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
+ );
+ }
+ }
}
Ok(())
diff --git a/drivers/pcid/src/main.rs b/drivers/pcid/src/main.rs
--- a/drivers/pcid/src/main.rs
+++ b/drivers/pcid/src/main.rs
@@ -12,6 +12,7 @@
};
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};
@@ -262,14 +263,13 @@
let access_fd = 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");
+ sendfd(
+ register_pci.raw(),
+ access_fd as usize,
+ SendFdFlags::empty().bits(),
+ 0,
+ )
+ .expect("failed to send pci_fd to acpid");
}
Err(err) => {
if err.errno() == libredox::errno::ENODEV {
@@ -1,144 +0,0 @@
# P2-boot-runtime-noise-and-net-race.patch
#
# Reduce expected boot-time warning noise and harden netstack startup ordering:
# - procmgr: unknown cancellation is trace-level (benign race)
# - acpid: warn once for unsupported power surface
# - ahcid: SATAPI probe failures are informational on empty media
# - netstack: retry network adapter discovery during early boot races
diff --git a/bootstrap/src/procmgr.rs b/bootstrap/src/procmgr.rs
--- a/bootstrap/src/procmgr.rs
+++ b/bootstrap/src/procmgr.rs
@@ -296,8 +296,8 @@ fn handle_scheme<'a>(
}
}
} else {
- log::warn!("Cancellation for unknown id {:?}", req.id);
+ log::trace!("Cancellation for unknown id {:?}", req.id);
Pending
}
}
diff --git a/drivers/acpid/src/scheme.rs b/drivers/acpid/src/scheme.rs
--- a/drivers/acpid/src/scheme.rs
+++ b/drivers/acpid/src/scheme.rs
@@ -8,6 +8,7 @@ use ron::de::SpannedError;
use scheme_utils::HandleMap;
use std::convert::{TryFrom, TryInto};
use std::str::FromStr;
+use std::sync::atomic::{AtomicBool, Ordering};
use syscall::dirent::{DirEntry, DirentBuf, DirentKind};
use syscall::schemev2::NewFdFlags;
use syscall::FobtainFdFlags;
@@ -29,6 +30,8 @@ use crate::acpi::{
};
use crate::resources::{decode_resource_template, ResourceDescriptor};
+static POWER_SURFACE_UNAVAILABLE_WARNED: AtomicBool = AtomicBool::new(false);
+
pub struct AcpiScheme<'acpi, 'sock> {
ctx: &'acpi AcpiContext,
handles: HandleMap<Handle<'acpi>>,
@@ -307,8 +310,10 @@ impl<'acpi, 'sock> AcpiScheme<'acpi, 'sock> {
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}");
+ if !POWER_SURFACE_UNAVAILABLE_WARNED.swap(true, Ordering::Relaxed) {
+ log::warn!("ACPI power surface unavailable: {message}");
+ }
Error::new(EOPNOTSUPP)
}
other => {
diff --git a/drivers/storage/ahcid/src/ahci/mod.rs b/drivers/storage/ahcid/src/ahci/mod.rs
--- a/drivers/storage/ahcid/src/ahci/mod.rs
+++ b/drivers/storage/ahcid/src/ahci/mod.rs
@@ -64,8 +64,8 @@ pub fn disks(base: usize, name: &str) -> (&'static mut HbaMem, Vec<AnyDisk>) {
HbaPortType::SATAPI => match DiskATAPI::new(i, port) {
Ok(disk) => Some(AnyDisk::Atapi(disk)),
Err(err) => {
- error!("{}: {}", i, err);
+ info!("{}: {}", i, err);
None
}
},
diff --git a/netstack/src/main.rs b/netstack/src/main.rs
--- a/netstack/src/main.rs
+++ b/netstack/src/main.rs
@@ -6,6 +6,8 @@ use anyhow::{anyhow, bail, Context, Result};
use event::{EventFlags, EventQueue};
use libredox::flag::{O_NONBLOCK, O_RDWR};
use libredox::Fd;
+use std::thread;
+use std::time::Duration;
use redox_scheme::Socket;
use scheme::Smolnetd;
@@ -22,32 +24,45 @@ mod scheme;
fn get_network_adapter() -> Result<String> {
use std::fs;
- let mut adapters = vec![];
+ const MAX_ATTEMPTS: u32 = 50;
+ const RETRY_DELAY: Duration = Duration::from_millis(100);
- for entry_res in fs::read_dir("/scheme")? {
- let Ok(entry) = entry_res else {
- continue;
- };
+ for attempt in 1..=MAX_ATTEMPTS {
+ let mut adapters = vec![];
- let Ok(scheme) = entry.file_name().into_string() else {
- continue;
- };
+ for entry_res in fs::read_dir("/scheme")? {
+ let Ok(entry) = entry_res else {
+ continue;
+ };
- if !scheme.starts_with("network") {
- continue;
- }
+ let Ok(scheme) = entry.file_name().into_string() else {
+ continue;
+ };
- adapters.push(scheme);
- }
+ if !scheme.starts_with("network") {
+ continue;
+ }
- if adapters.is_empty() {
- bail!("no network adapter found");
- } else {
- let adapter = adapters.remove(0);
+ adapters.push(scheme);
+ }
+
if !adapters.is_empty() {
- // FIXME allow using multiple network adapters at the same time
- warn!("Multiple network adapters found. Only {adapter} will be used");
+ let adapter = adapters.remove(0);
+ if !adapters.is_empty() {
+ // FIXME allow using multiple network adapters at the same time
+ warn!("Multiple network adapters found. Only {adapter} will be used");
+ }
+ return Ok(adapter);
+ }
+
+ if attempt < MAX_ATTEMPTS {
+ warn!(
+ "no network adapter found yet (attempt {attempt}/{MAX_ATTEMPTS}), waiting {} ms",
+ RETRY_DELAY.as_millis()
+ );
+ thread::sleep(RETRY_DELAY);
}
- Ok(adapter)
}
+
+ bail!("no network adapter found")
}
@@ -1,23 +0,0 @@
# P2-daemon-ready-graceful.patch
#
# Replace unwrap() in Daemon::ready() with graceful error handling.
# When hwd or pcid-spawner spawns a daemon fire-and-forget (dropping the
# pipe's read end before the child signals readiness), the unwrap() causes
# a BrokenPipe panic that kills the child and cascades into boot failures.
#
# The write may fail because init already closed the read end (service
# timeout, duplicate start, or oneshot completion). In all cases the
# daemon should continue running rather than panic.
diff --git a/daemon/src/lib.rs b/daemon/src/lib.rs
--- a/daemon/src/lib.rs
+++ b/daemon/src/lib.rs
@@ -51,7 +51,7 @@ 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();
+ let _ = self.write_pipe.write_all(&[0]);
}
/// Executes `Command` as a child process.
+5 -9
View File
@@ -1,18 +1,14 @@
# P2-hwd-misc.patch
# Keep hwd focused on hardware probing. Init owns boot-time pcid startup.
diff --git a/drivers/hwd/src/main.rs b/drivers/hwd/src/main.rs
index 79360e34..4de3d9f3 100644
--- a/drivers/hwd/src/main.rs
+++ b/drivers/hwd/src/main.rs
@@ -37,10 +37,7 @@ fn daemon(daemon: daemon::Daemon) -> ! {
@@ -35,12 +35,6 @@ fn daemon(daemon: daemon::Daemon) -> ! {
}
};
//TODO: launch pcid based on backend information?
// Must launch after acpid but before probe calls /scheme/acpi/symbols
- //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"));
-
daemon.ready();
//TODO: HWD is meant to locate PCI/XHCI/etc devices in ACPI and DeviceTree definitions and start their drivers
@@ -0,0 +1,21 @@
--- drivers/hwd/src/backend/acpi.rs
+++ b/drivers/hwd/src/backend/acpi.rs
@@ -1,5 +1,5 @@
use amlserde::{AmlSerde, AmlSerdeValue};
-use std::{error::Error, fs, process::Command};
+use std::{error::Error, fs};
use super::Backend;
@@ -11,11 +11,6 @@
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 })
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
View File
View File
@@ -1,607 +0,0 @@
# P2-network-driver-mains.patch
# Extract network driver main.rs hardening: replace panic/unwrap/expect with
# proper error handling and graceful exits.
#
# Files: drivers/net/e1000d/src/main.rs, drivers/net/ixgbed/src/main.rs,
# drivers/net/rtl8139d/src/main.rs, drivers/net/rtl8168d/src/main.rs,
# drivers/net/virtio-netd/src/main.rs
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/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/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/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,11 +134,22 @@ 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}");
+ }
}
@@ -1,118 +0,0 @@
# P2-network-error-handling.patch
#
# Network driver error handling: replace unwrap()/expect()/panic!() with proper
# error propagation and graceful exits across e1000, ixgbe, rtl8139, rtl8168d,
# and virtio-net drivers.
#
# Covers:
# - e1000d/device.rs: replace unreachable!() in DMA array conversion
# - ixgbed/Cargo.toml: add log dependency
# - rtl8139d/device.rs: replace unreachable!() with EIO error
# - rtl8168d/device.rs: replace unreachable!() with EIO error
# - virtio-netd/scheme.rs: DMA allocation error handling for rx buffers
#
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,12 +207,11 @@ 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/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,7 +7,8 @@ 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/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,8 +215,8 @@ 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/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,8 +185,8 @@ 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/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))
@@ -0,0 +1,35 @@
--- a/drivers/pcid/src/main.rs
+++ b/drivers/pcid/src/main.rs
@@ -263,13 +263,13 @@
.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(
+ if let Err(err) = register_pci.call_wo(
&access_bytes,
syscall::CallFlags::WRITE | syscall::CallFlags::FD,
&[],
- )
- .expect("failed to send pci_fd to acpid");
+ ) {
+ warn!("pcid: failed to send pci_fd to acpid (error: {}). Running without ACPI integration.", err);
+ }
}
Err(err) => {
if err.errno() == libredox::errno::ENODEV {
@@ -304,8 +304,11 @@
}
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) {
+ warn!("pcid: failed to register pci scheme (error: {}). pcid-spawner may already own it.", err);
+ let _ = daemon.ready();
+ std::process::exit(0);
+ }
let _ = daemon.ready();
+8 -134
View File
@@ -1,19 +1,9 @@
# P2-pcid-cfg-access.patch
#
# PCI config access error handling: replace unwrap()/expect()/assert!() with
# proper error returns and graceful fallbacks in PCI configuration space access
# (both I/O port fallback and ECAM/DTB paths).
#
# Covers:
# - pcid/cfg_access/fallback.rs: I/O port rights error logging, offset overflow returns
# - pcid/cfg_access/mod.rs: DTB property access with proper error propagation,
# bus-range validation, interrupt-map phandle/property fallbacks
#
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 {
@@ -22,14 +22,17 @@ impl Pci {
// make sure that pcid is not granted io port permission unless pcie memory-mapped
// configuration space is not available.
info!(
"PCI: couldn't find or access PCIe extended configuration, \
and thus falling back to PCI 3.0 io ports"
);
@@ -27,143 +17,27 @@ index 671d17f7..ea8f69f8 100644
}
});
}
@@ -61,8 +66,9 @@ impl ConfigRegionAccess for Pci {
@@ -55,7 +58,10 @@ 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 {
+ // PCI config space is only 256 bytes (offset 0-255)
+ return 0xFFFFFFFF;
+ };
let address = Self::address(address, offset);
Pio::<u32>::new(0xCF8).write(address);
@@ -74,8 +80,9 @@ impl ConfigRegionAccess for Pci {
@@ -67,7 +73,10 @@ 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 {
+ // PCI config space is only 256 bytes (offset 0-255)
+ 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) {
File diff suppressed because it is too large Load Diff
@@ -1,384 +0,0 @@
# P2-ps2d-improvements.patch
#
# PS/2 controller improvements: flush/retry logic, mouse state machine fixes.
#
# Covers:
# - ps2d/controller.rs: flush stale bytes, self-test with retry, AUX port test
# - ps2d/mouse.rs: ACK/RESEND/BAT constant names, resend handling, state machine fixes
# - ps2d/state.rs: non-fatal init error handling
#
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/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
@@ -61,9 +61,11 @@ impl Ps2d {
pub fn new(input: ProducerHandle, 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;
let vmmouse = vm::enable(vmmouse_relative);
// TODO: QEMU hack, maybe do this when Init timed out?
@@ -1,625 +0,0 @@
# P2-storage-driver-mains.patch
# Extract storage driver main.rs hardening: replace panic/unwrap/expect with
# proper error handling, debug_assert for invariant checks, and graceful exits.
#
# Files: drivers/storage/ahcid/src/main.rs, drivers/storage/ided/src/main.rs,
# drivers/storage/nvmed/src/main.rs, drivers/storage/virtio-blkd/src/main.rs
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/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/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}");
+ }
+ }
}
}
@@ -1,601 +0,0 @@
# P2-storage-error-handling.patch
#
# Storage driver error handling: replace unwrap()/expect()/panic!() with proper
# error propagation and graceful exits across AHCI, IDE, NVMe, and VirtIO block drivers.
#
# Covers:
# - ahcid/disk_ata.rs: replace unreachable!() with EIO error
# - ahcid/disk_atapi.rs: replace unreachable!() with EBADF error
# - ahcid/hba.rs: DMA allocation error handling
# - ided/ide.rs: assert→debug_assert, try_into error handling
# - nvmed/executor.rs: executor initialization error handling
# - nvmed/identify.rs: DMA allocation, unreachable!() fallback
# - nvmed/mod.rs: assert→debug_assert, unwrap→proper error/exit
# - nvmed/queues.rs: unreachable!()→safe fallback
# - virtio-blkd/scheme.rs: DMA allocation error handling, assert→if check
#
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,8 +178,11 @@ 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/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,8 +357,8 @@ 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/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,8 +145,8 @@ 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/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()
}
+27 -74
View File
@@ -1,20 +1,6 @@
# P2-usb-pm-and-drivers.patch
#
# USB power management and driver interface improvements:
# suspend/resume commands, SCSI driver enablement, PortPmState type,
# IRQ reactor staged port state fallback.
#
# Covers:
# - usbctl/main.rs: pm-state, suspend, resume subcommands
# - xhcid/drivers.toml: enable SCSI over USB driver (was commented out)
# - xhcid/driver_interface.rs: PortPmState enum, suspend/resume/port_pm_state methods
# - xhcid/irq_reactor.rs: staged_port_states fallback in with_ring/with_ring_mut
#
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() {
@@ -15,6 +15,9 @@
Command::new("port")
.arg(Arg::new("PORT").num_args(1).required(true))
.subcommand(Command::new("status"))
@@ -24,7 +10,7 @@ index 9b5773d9..232f7cfc 100644
.subcommand(
Command::new("endpoint")
.arg(Arg::new("ENDPOINT_NUM").num_args(1).required(true))
@@ -38,7 +41,16 @@ fn main() {
@@ -38,6 +41,15 @@
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());
@@ -39,13 +25,11 @@ index 9b5773d9..232f7cfc 100644
+ 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,10 +1,9 @@
@@ -1,9 +1,8 @@
-#TODO: causes XHCI errors
-#[[drivers]]
-#name = "SCSI over USB"
@@ -61,16 +45,12 @@ index 83c90e23..470ec063 100644
[[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 {
}
}
@@ -446,6 +446,33 @@
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
+pub enum PortPmState {
+ Active,
+ Suspended,
@@ -96,13 +76,18 @@ index 727f8d7e..82f839ae 100644
+ }
+}
+
#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
+#[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(())
Disabled,
Enabled,
@@ -557,6 +584,16 @@
}
pub fn detach(&self) -> result::Result<(), XhciClientHandleError> {
let file = self.fd.openat("detach", libredox::flag::O_WRONLY, 0)?;
+ 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(&[])?;
@@ -110,49 +95,17 @@ index 727f8d7e..82f839ae 100644
+ }
+ 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,7 +619,11 @@ impl XhciClientHandle {
let string = self.read_to_string("state")?;
Ok(string.parse()?)
let _bytes_written = file.write(&[])?;
Ok(())
}
+ pub fn port_pm_state(&self) -> result::Result<PortPmState, XhciClientHandleError> {
+ let string = self.read_to_string("pm_state")?;
@@ -580,6 +617,10 @@
}
pub fn port_state(&self) -> result::Result<PortState, XhciClientHandleError> {
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/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 {
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,844 +0,0 @@
diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs
index 94a1eb17..c8919290 100644
--- a/drivers/acpid/src/acpi.rs
+++ b/drivers/acpid/src/acpi.rs
@@ -52,9 +52,7 @@ impl SdtHeader {
}
}
pub fn length(&self) -> usize {
- self.length
- .try_into()
- .expect("expected usize to be at least 32 bits")
+ self.length as usize
}
}
@@ -132,6 +130,9 @@ impl Drop for PhysmapGuard {
pub struct Sdt(Arc<[u8]>);
impl Sdt {
+ // SDT validation is split between parser and caller policy:
+ // - this parser only decides whether a given byte slice is structurally valid,
+ // - callers decide whether rejection is fatal (root [R|X]SDT) or degradable (child tables).
pub fn new(slice: Arc<[u8]>) -> Result<Self, InvalidSdtError> {
let header = match plain::from_bytes::<SdtHeader>(&slice) {
Ok(header) => header,
@@ -233,6 +234,177 @@ impl fmt::Debug for Sdt {
pub struct Dsdt(Sdt);
pub struct Ssdt(Sdt);
+#[derive(Clone, Copy, Debug)]
+pub enum AmlBootstrapMethod {
+ HwdEnv,
+ X86BiosFallback,
+}
+impl AmlBootstrapMethod {
+ fn as_str(self) -> &'static str {
+ match self {
+ Self::HwdEnv => "hwd RSDP_ADDR/RSDP_SIZE handoff",
+ Self::X86BiosFallback => "x86 BIOS fallback",
+ }
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct AmlBootstrap {
+ rsdp_addr: usize,
+ rsdp_size: Option<usize>,
+ method: AmlBootstrapMethod,
+}
+impl AmlBootstrap {
+ pub fn from_env() -> Result<Self, Box<dyn Error>> {
+ let rsdp_addr = usize::from_str_radix(&std::env::var("RSDP_ADDR")?, 16)?;
+ let rsdp_size = match std::env::var("RSDP_SIZE") {
+ Ok(size) => Some(usize::from_str_radix(&size, 16)?),
+ Err(std::env::VarError::NotPresent) => None,
+ Err(err) => return Err(Box::new(err)),
+ };
+
+ Ok(Self {
+ rsdp_addr,
+ rsdp_size,
+ method: AmlBootstrapMethod::HwdEnv,
+ })
+ }
+
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ pub fn x86_bios_fallback() -> Result<Option<Self>, Box<dyn Error>> {
+ if let Some(rsdp_addr) = search_x86_bios_rsdp()? {
+ return Ok(Some(Self {
+ rsdp_addr,
+ rsdp_size: None,
+ method: AmlBootstrapMethod::X86BiosFallback,
+ }));
+ }
+
+ Ok(None)
+ }
+
+ #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
+ pub fn x86_bios_fallback() -> Result<Option<Self>, Box<dyn Error>> {
+ Ok(None)
+ }
+
+ pub fn log_bootstrap(&self) {
+ log::info!(
+ "acpid: AML bootstrap via {} (RSDP at {:#X})",
+ self.method.as_str(),
+ self.rsdp_addr
+ );
+
+ if let Some(rsdp_size) = self.rsdp_size {
+ log::debug!("acpid: AML bootstrap RSDP_SIZE={:#X}", rsdp_size);
+ }
+ }
+}
+
+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+const RSDP_SIGNATURE: &[u8; 8] = b"RSD PTR ";
+
+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+fn search_x86_bios_rsdp() -> Result<Option<usize>, Box<dyn Error>> {
+ let ebda_segment = read_u16_physical(0x40E)?;
+ let ebda_addr = usize::from(ebda_segment) << 4;
+
+ if ebda_addr != 0 {
+ if let Some(rsdp_addr) = search_rsdp_region(ebda_addr, 1024)? {
+ return Ok(Some(rsdp_addr));
+ }
+ }
+
+ search_rsdp_region(0xE0000, 0x20000).map_err(Into::into)
+}
+
+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+fn read_u16_physical(physaddr: usize) -> std::io::Result<u16> {
+ let start_page = physaddr / PAGE_SIZE * PAGE_SIZE;
+ let page_offset = physaddr % PAGE_SIZE;
+ let map = PhysmapGuard::map(start_page, 1)?;
+ let bytes = map
+ .get(page_offset..page_offset + mem::size_of::<u16>())
+ .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "short BIOS map"))?;
+
+ Ok(u16::from_le_bytes([bytes[0], bytes[1]]))
+}
+
+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+fn search_rsdp_region(physaddr: usize, length: usize) -> std::io::Result<Option<usize>> {
+ let start_page = physaddr / PAGE_SIZE * PAGE_SIZE;
+ let page_offset = physaddr % PAGE_SIZE;
+ let mapped_len = page_offset + length;
+ let page_count = mapped_len.div_ceil(PAGE_SIZE);
+ let map = PhysmapGuard::map(start_page, page_count)?;
+ let region = map.get(page_offset..page_offset + length).ok_or_else(|| {
+ std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "short BIOS RSDP search window")
+ })?;
+
+ for candidate_offset in (0..=length.saturating_sub(20)).step_by(16) {
+ if region
+ .get(candidate_offset..candidate_offset + RSDP_SIGNATURE.len())
+ != Some(&RSDP_SIGNATURE[..])
+ {
+ continue;
+ }
+
+ if rsdp_candidate_valid(&region[candidate_offset..]) {
+ return Ok(Some(physaddr + candidate_offset));
+ }
+ }
+
+ Ok(None)
+}
+
+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+fn rsdp_candidate_valid(candidate: &[u8]) -> bool {
+ if candidate.len() < 20 || &candidate[..RSDP_SIGNATURE.len()] != RSDP_SIGNATURE {
+ return false;
+ }
+
+ if checksum_is_zero(&candidate[..20]).is_err() {
+ return false;
+ }
+
+ let revision = candidate[15];
+ if revision < 2 {
+ return true;
+ }
+
+ if candidate.len() < 36 {
+ return false;
+ }
+
+ let declared_length = u32::from_le_bytes([candidate[20], candidate[21], candidate[22], candidate[23]])
+ as usize;
+ if declared_length < 36 || candidate.len() < declared_length {
+ return false;
+ }
+
+ checksum_is_zero(&candidate[..declared_length]).is_ok()
+}
+
+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+fn checksum_is_zero(bytes: &[u8]) -> Result<(), ()> {
+ let checksum = bytes
+ .iter()
+ .copied()
+ .fold(0_u8, |current_sum, item| current_sum.wrapping_add(item));
+
+ if checksum == 0 {
+ Ok(())
+ } else {
+ Err(())
+ }
+}
+
+#[derive(Clone, Copy, Debug)]
+struct SleepTypeData {
+ slp_typa: u16,
+ slp_typb: u16,
+}
+
// Current AML implementation builds the aml_context.namespace at startup,
// but the cache for symbols is lazy-loaded when someone
// reads from the acpi:/symbols scheme.
@@ -245,15 +417,20 @@ pub struct AmlSymbols {
symbol_cache: FxHashMap<String, String>,
page_cache: Arc<Mutex<AmlPageCache>>,
aml_region_handlers: Vec<(RegionSpace, Box<dyn RegionHandler>)>,
+ aml_bootstrap: Option<AmlBootstrap>,
}
impl AmlSymbols {
- pub fn new(aml_region_handlers: Vec<(RegionSpace, Box<dyn RegionHandler>)>) -> Self {
+ pub fn new(
+ aml_bootstrap: Option<AmlBootstrap>,
+ aml_region_handlers: Vec<(RegionSpace, Box<dyn RegionHandler>)>,
+ ) -> Self {
Self {
aml_context: None,
symbol_cache: FxHashMap::default(),
page_cache: Arc::new(Mutex::new(AmlPageCache::default())),
aml_region_handlers,
+ aml_bootstrap,
}
}
@@ -264,9 +441,12 @@ impl AmlSymbols {
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
- let rsdp_address = usize::from_str_radix(&std::env::var("RSDP_ADDR")?, 16)?;
+ let bootstrap = self
+ .aml_bootstrap
+ .as_ref()
+ .ok_or_else(|| std::io::Error::other("AML bootstrap unavailable"))?;
let tables =
- unsafe { AcpiTables::from_rsdp(handler.clone(), rsdp_address).map_err(format_err)? };
+ unsafe { AcpiTables::from_rsdp(handler.clone(), bootstrap.rsdp_addr).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(..) {
@@ -316,7 +496,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)
{
@@ -379,6 +559,7 @@ pub struct AcpiContext {
tables: Vec<Sdt>,
dsdt: Option<Dsdt>,
fadt: Option<Fadt>,
+ shutdown_s5: RwLock<Option<SleepTypeData>>,
aml_symbols: RwLock<AmlSymbols>,
@@ -426,27 +607,56 @@ impl AcpiContext {
pub fn init(
rxsdt_physaddrs: impl Iterator<Item = u64>,
+ aml_bootstrap: Option<AmlBootstrap>,
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");
-
- log::trace!("TABLE AT {:#>08X}", physaddr);
-
- Sdt::load_from_physical(physaddr).expect("failed to load physical SDT")
- })
- .collect::<Vec<Sdt>>();
+ // Child-table validation policy:
+ // - checksum/length failures are degradable: warn, skip the table, continue boot,
+ // - malformed FADT is handled separately as "raw-table-only" mode for ACPI control paths,
+ // - MADT subtable interpretation is delegated to consumers, which must skip unknown entry
+ // types instead of treating them as daemon-fatal.
+ let mut tables = Vec::new();
+ for physaddr in rxsdt_physaddrs {
+ let physaddr: usize = match physaddr.try_into() {
+ Ok(physaddr) => physaddr,
+ Err(_) => {
+ log::warn!(
+ "acpid: skipping ACPI table at {:#X}: physical address out of range",
+ physaddr
+ );
+ continue;
+ }
+ };
+
+ match Sdt::load_from_physical(physaddr) {
+ Ok(table) => {
+ log::debug!(
+ "acpid: accepted ACPI table {} at {:#X}",
+ String::from_utf8_lossy(&table.signature),
+ physaddr
+ );
+ tables.push(table);
+ }
+ Err(TablePhysLoadError::Validity(InvalidSdtError::BadChecksum)) => {
+ log::warn!(
+ "acpid: skipping ACPI table at {:#X}: checksum validation failed",
+ physaddr
+ );
+ }
+ Err(err) => {
+ log::warn!("acpid: skipping ACPI table at {:#X}: {}", physaddr, err);
+ }
+ }
+ }
let mut this = Self {
tables,
dsdt: None,
fadt: None,
+ shutdown_s5: RwLock::new(None),
// Temporary values
- aml_symbols: RwLock::new(AmlSymbols::new(ec)),
+ aml_symbols: RwLock::new(AmlSymbols::new(aml_bootstrap, ec)),
next_ctx: RwLock::new(0),
@@ -581,55 +791,26 @@ impl AcpiContext {
let port = fadt.pm1a_control_block as u16;
let mut val = 1 << 13;
- let aml_symbols = self.aml_symbols.read();
-
- 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;
- }
- };
-
- 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;
+ if self.shutdown_s5.read().is_none() {
+ match self.cache_shutdown_s5_from_ready_aml("existing AML context") {
+ Ok(true) | Ok(false) => {}
+ Err(err) => {
+ log::warn!("acpid: _S5 was not ready at shutdown: {}", err);
}
- },
- 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;
- }
- };
+ }
- let slp_typa = match package[0].deref() {
- acpi::aml::object::Object::Integer(i) => i.to_owned(),
- _ => {
- log::error!("typa is not an Integer");
- return;
- }
- };
- let slp_typb = match package[1].deref() {
- acpi::aml::object::Object::Integer(i) => i.to_owned(),
- _ => {
- log::error!("typb is not an Integer");
- return;
- }
+ let Some(sleep_types) = *self.shutdown_s5.read() else {
+ log::error!("Cannot set S-state, missing derived \\_S5 sleep types");
+ return;
};
- log::trace!("Shutdown SLP_TYPa {:X}, SLP_TYPb {:X}", slp_typa, slp_typb);
- val |= slp_typa as u16;
+ log::trace!(
+ "Shutdown SLP_TYPa {:X}, SLP_TYPb {:X}",
+ sleep_types.slp_typa,
+ sleep_types.slp_typb
+ );
+ val |= sleep_types.slp_typa;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
@@ -652,6 +833,86 @@ impl AcpiContext {
core::hint::spin_loop();
}
}
+
+ pub fn prime_shutdown_s5(&self, pci_fd: Option<&libredox::Fd>, source: &'static str) {
+ match self.cache_shutdown_s5(pci_fd, source) {
+ Ok(()) => {}
+ Err(err) => {
+ log::warn!("acpid: unable to derive _S5 from {}: {}", source, err);
+ }
+ }
+ }
+
+ fn cache_shutdown_s5(
+ &self,
+ pci_fd: Option<&libredox::Fd>,
+ source: &'static str,
+ ) -> Result<(), String> {
+ if self.shutdown_s5.read().is_some() {
+ return Ok(());
+ }
+
+ let mut aml_symbols = self.aml_symbols.write();
+ let aml_context = aml_symbols
+ .aml_context_mut(pci_fd)
+ .map_err(|err| format!("AML not ready: {err}"))?;
+ let sleep_types = extract_s5_sleep_types(aml_context)?;
+
+ *self.shutdown_s5.write() = Some(sleep_types);
+ log::info!("acpid: _S5 derived from {}", source);
+ Ok(())
+ }
+
+ fn cache_shutdown_s5_from_ready_aml(&self, source: &'static str) -> Result<bool, String> {
+ if self.shutdown_s5.read().is_some() {
+ return Ok(true);
+ }
+
+ let aml_symbols = self.aml_symbols.read();
+ let Some(aml_context) = aml_symbols.aml_context.as_ref() else {
+ return Ok(false);
+ };
+
+ let sleep_types = extract_s5_sleep_types(aml_context)?;
+ drop(aml_symbols);
+
+ *self.shutdown_s5.write() = Some(sleep_types);
+ log::info!("acpid: _S5 derived from {}", source);
+ Ok(true)
+ }
+}
+
+fn extract_s5_sleep_types(
+ aml_context: &Interpreter<AmlPhysMemHandler>,
+) -> Result<SleepTypeData, String> {
+ let s5_aml_name = acpi::aml::namespace::AmlName::from_str("\\_S5")
+ .map_err(|error| format!("failed to build \\_S5 name: {error:?}"))?;
+ let s5 = aml_context
+ .namespace
+ .lock()
+ .get(s5_aml_name)
+ .map_err(|error| format!("missing \\_S5: {error:?}"))?;
+ let package = match s5.deref() {
+ acpi::aml::object::Object::Package(package) => package,
+ _ => return Err("\\_S5 is not a package".into()),
+ };
+
+ let slp_typa = extract_sleep_type(package.get(0), "SLP_TYPa")?;
+ let slp_typb = extract_sleep_type(package.get(1), "SLP_TYPb")?;
+
+ Ok(SleepTypeData { slp_typa, slp_typb })
+}
+
+fn extract_sleep_type(value: Option<&WrappedObject>, label: &'static str) -> Result<u16, String> {
+ let Some(value) = value else {
+ return Err(format!("missing {label} in \\_S5 package"));
+ };
+
+ match value.deref() {
+ acpi::aml::object::Object::Integer(i) => u16::try_from(*i)
+ .map_err(|_| format!("{label} out of range for PM1 control register")),
+ _ => Err(format!("{label} is not an Integer")),
+ }
}
#[repr(C, packed)]
@@ -760,45 +1021,66 @@ impl Deref for Fadt {
type Target = FadtStruct;
fn deref(&self) -> &Self::Target {
- plain::from_bytes::<FadtStruct>(&self.0 .0)
- .expect("expected FADT struct to already be validated in Deref impl")
+ match plain::from_bytes::<FadtStruct>(&self.0 .0) {
+ Ok(fadt) => fadt,
+ Err(plain::Error::TooShort) => unreachable!(
+ "Fadt::new validates the minimum FADT size before constructing Fadt"
+ ),
+ Err(plain::Error::BadAlignment) => unreachable!(
+ "plain::from_bytes reported bad alignment, but FadtStruct is #[repr(packed)]"
+ ),
+ }
}
}
impl Fadt {
pub fn new(sdt: Sdt) -> Option<Fadt> {
- if sdt.signature != *b"FACP" || sdt.length() < mem::size_of::<Fadt>() {
+ if sdt.signature != *b"FACP" || sdt.length() < mem::size_of::<FadtStruct>() {
return None;
}
Some(Fadt(sdt))
}
pub fn init(context: &mut AcpiContext) {
- let fadt_sdt = context
- .take_single_sdt(*b"FACP")
- .expect("expected ACPI to always have a FADT");
+ // FADT policy: this table is mandatory for ACPI control services such as shutdown/reboot.
+ // If it is missing or malformed, acpid stays alive for diagnostics/raw tables but degrades
+ // into raw-table-only mode instead of crashing the boot.
+ let Some(fadt_sdt) = context.take_single_sdt(*b"FACP") else {
+ log::error!("acpid: missing FADT; booting without ACPI control services");
+ return;
+ };
let fadt = match Fadt::new(fadt_sdt) {
Some(fadt) => fadt,
None => {
- log::error!("Failed to find FADT");
+ log::error!("acpid: corrupt FADT; booting without ACPI control services");
return;
}
};
let dsdt_ptr = match fadt.acpi_2_struct() {
- Some(fadt2) => usize::try_from(fadt2.x_dsdt).unwrap_or_else(|_| {
- usize::try_from(fadt.dsdt).expect("expected any given u32 to fit within usize")
- }),
- None => usize::try_from(fadt.dsdt).expect("expected any given u32 to fit within usize"),
+ Some(fadt2) if fadt2.x_dsdt != 0 => match usize::try_from(fadt2.x_dsdt) {
+ Ok(dsdt_ptr) => dsdt_ptr,
+ Err(_) => {
+ log::warn!(
+ "acpid: x_dsdt address out of range; falling back to 32-bit DSDT pointer"
+ );
+ fadt.dsdt as usize
+ }
+ },
+ _ => fadt.dsdt as usize,
};
log::debug!("FACP at {:X}", { dsdt_ptr });
- let dsdt_sdt = match Sdt::load_from_physical(fadt.dsdt as usize) {
+ let dsdt_sdt = match Sdt::load_from_physical(dsdt_ptr) {
Ok(dsdt) => dsdt,
Err(error) => {
- log::error!("Failed to load DSDT: {}", error);
+ log::error!(
+ "acpid: corrupt FADT/DSDT linkage (DSDT at {:#X}): booting without ACPI control services: {}",
+ dsdt_ptr,
+ error
+ );
return;
}
};
diff --git a/drivers/acpid/src/main.rs b/drivers/acpid/src/main.rs
index 059254b3..25566553 100644
--- a/drivers/acpid/src/main.rs
+++ b/drivers/acpid/src/main.rs
@@ -3,6 +3,7 @@ use std::fs::File;
use std::mem;
use std::ops::ControlFlow;
use std::os::unix::io::AsRawFd;
+use std::process;
use std::sync::Arc;
use ::acpi::aml::op_region::{RegionHandler, RegionSpace};
@@ -28,94 +29,206 @@ fn daemon(daemon: daemon::Daemon) -> ! {
log::info!("acpid start");
- let rxsdt_raw_data: Arc<[u8]> = std::fs::read("/scheme/kernel.acpi/rxsdt")
- .expect("acpid: failed to read `/scheme/kernel.acpi/rxsdt`")
- .into();
+ let rxsdt_raw_data: Arc<[u8]> = match std::fs::read("/scheme/kernel.acpi/rxsdt") {
+ Ok(data) => data.into(),
+ Err(err) => {
+ log::error!("acpid: failed to read `/scheme/kernel.acpi/rxsdt`: {}", err);
+ process::exit(1);
+ }
+ };
if rxsdt_raw_data.is_empty() {
log::info!("System doesn't use ACPI");
daemon.ready();
- std::process::exit(0);
+ process::exit(0);
}
- let sdt = self::acpi::Sdt::new(rxsdt_raw_data).expect("acpid: failed to parse [RX]SDT");
+ // Root-table policy: if the kernel-provided [R|X]SDT is malformed, acpid cannot enumerate any
+ // firmware tables at all. That is fatal to this daemon, but it must fail with a logged exit
+ // rather than a panic on malformed firmware input.
+ let sdt = match self::acpi::Sdt::new(rxsdt_raw_data) {
+ Ok(sdt) => sdt,
+ Err(err) => {
+ log::error!("acpid: failed to parse kernel [R|X]SDT: {}", err);
+ process::exit(1);
+ }
+ };
+
+ // AML bootstrap contract:
+ // - preferred path: RSDP_ADDR[/RSDP_SIZE] inherited into acpid by the boot path,
+ // - x86 fallback: bounded BIOS RSDP search when that explicit handoff is absent or unusable.
+ let aml_bootstrap = match self::acpi::AmlBootstrap::from_env() {
+ Ok(bootstrap) => {
+ bootstrap.log_bootstrap();
+ Some(bootstrap)
+ }
+ Err(err) => {
+ log::warn!(
+ "acpid: explicit AML bootstrap handoff unavailable ({}); trying x86 BIOS fallback",
+ err
+ );
- let mut thirty_two_bit;
- let mut sixty_four_bit;
+ match self::acpi::AmlBootstrap::x86_bios_fallback() {
+ Ok(Some(bootstrap)) => {
+ bootstrap.log_bootstrap();
+ Some(bootstrap)
+ }
+ Ok(None) => {
+ log::warn!(
+ "acpid: AML bootstrap unavailable; continuing without AML-backed ACPI services"
+ );
+ None
+ }
+ Err(err) => {
+ log::warn!(
+ "acpid: x86 BIOS AML bootstrap fallback failed ({}); continuing without AML-backed ACPI services",
+ err
+ );
+ None
+ }
+ }
+ }
+ };
- let physaddrs_iter = match &sdt.signature {
+ let physaddrs = 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);
-
- &mut thirty_two_bit as &mut dyn Iterator<Item = u64>
+ let chunks = sdt.data().chunks_exact(mem::size_of::<u32>());
+ if !chunks.remainder().is_empty() {
+ log::error!("acpid: malformed RSDT payload length {}", sdt.data().len());
+ process::exit(1);
+ }
+
+ chunks
+ .map(|chunk| {
+ let chunk = <[u8; mem::size_of::<u32>()]>::try_from(chunk)
+ .map_err(|_| "invalid 32-bit RSDT entry width")?;
+ Ok(u64::from(u32::from_le_bytes(chunk)))
+ })
+ .collect::<Result<Vec<u64>, &str>>()
}
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 chunks = sdt.data().chunks_exact(mem::size_of::<u64>());
+ if !chunks.remainder().is_empty() {
+ log::error!("acpid: malformed XSDT payload length {}", sdt.data().len());
+ process::exit(1);
+ }
- &mut sixty_four_bit as &mut dyn Iterator<Item = u64>
+ chunks
+ .map(|chunk| {
+ let chunk = <[u8; mem::size_of::<u64>()]>::try_from(chunk)
+ .map_err(|_| "invalid 64-bit XSDT entry width")?;
+ Ok(u64::from_le_bytes(chunk))
+ })
+ .collect::<Result<Vec<u64>, &str>>()
+ }
+ _ => {
+ log::error!(
+ "acpid: expected kernel root table to be RSDT or XSDT, got {}",
+ String::from_utf8_lossy(&sdt.signature)
+ );
+ process::exit(1);
+ }
+ };
+ let physaddrs = match physaddrs {
+ Ok(physaddrs) => physaddrs,
+ Err(err) => {
+ log::error!("acpid: failed to decode root table pointers: {}", err);
+ process::exit(1);
}
- _ => panic!("acpid: expected [RX]SDT from kernel to be either of those"),
};
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 acpi_context = self::acpi::AcpiContext::init(physaddrs.into_iter(), aml_bootstrap, region_handlers);
// 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");
+ if let Err(err) = common::acquire_port_io_rights() {
+ log::error!(
+ "acpid: failed to set I/O privilege level to Ring 3: {}",
+ err
+ );
+ process::exit(1);
+ }
- let shutdown_pipe = File::open("/scheme/kernel.acpi/kstop")
- .expect("acpid: failed to open `/scheme/kernel.acpi/kstop`");
+ let shutdown_pipe = match File::open("/scheme/kernel.acpi/kstop") {
+ Ok(file) => file,
+ Err(err) => {
+ log::error!("acpid: failed to open `/scheme/kernel.acpi/kstop`: {}", err);
+ process::exit(1);
+ }
+ };
- 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 = match RawEventQueue::new() {
+ Ok(event_queue) => event_queue,
+ Err(err) => {
+ log::error!("acpid: failed to create event queue: {}", err);
+ process::exit(1);
+ }
+ };
+ let socket = match Socket::nonblock() {
+ Ok(socket) => socket,
+ Err(err) => {
+ log::error!("acpid: failed to create acpi scheme socket: {}", err);
+ process::exit(1);
+ }
+ };
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");
- event_queue
- .subscribe(socket.inner().raw(), 1, EventFlags::READ)
- .expect("acpid: failed to register scheme socket for event queue");
+ if let Err(err) = event_queue.subscribe(shutdown_pipe.as_raw_fd() as usize, 0, EventFlags::READ)
+ {
+ log::error!(
+ "acpid: failed to register shutdown pipe for event queue: {}",
+ err
+ );
+ process::exit(1);
+ }
+ if let Err(err) = event_queue.subscribe(socket.inner().raw(), 1, EventFlags::READ) {
+ log::error!(
+ "acpid: failed to register scheme socket for event queue: {}",
+ err
+ );
+ process::exit(1);
+ }
- register_sync_scheme(&socket, "acpi", &mut scheme)
- .expect("acpid: failed to register acpi scheme to namespace");
+ if let Err(err) = register_sync_scheme(&socket, "acpi", &mut scheme) {
+ log::error!("acpid: failed to register acpi scheme to namespace: {}", err);
+ process::exit(1);
+ }
daemon.ready();
- libredox::call::setrens(0, 0).expect("acpid: failed to enter null namespace");
+ if let Err(err) = libredox::call::setrens(0, 0) {
+ log::error!("acpid: failed to enter null namespace: {}", err);
+ process::exit(1);
+ }
let mut mounted = true;
while mounted {
- let Some(event) = event_queue
- .next()
- .transpose()
- .expect("acpid: failed to read event file")
- else {
+ let event = match event_queue.next().transpose() {
+ Ok(event) => event,
+ Err(err) => {
+ log::error!("acpid: failed to read event file: {}", err);
+ process::exit(1);
+ }
+ };
+ let Some(event) = event else {
break;
};
if event.fd == socket.inner().raw() {
loop {
- match handler
- .process_requests_nonblocking(&mut scheme)
- .expect("acpid: failed to process requests")
- {
+ match match handler.process_requests_nonblocking(&mut scheme) {
+ Ok(flow) => flow,
+ Err(err) => {
+ log::error!("acpid: failed to process requests: {}", err);
+ process::exit(1);
+ }
+ } {
ControlFlow::Continue(()) => {}
ControlFlow::Break(()) => break,
}
diff --git a/drivers/acpid/src/scheme.rs b/drivers/acpid/src/scheme.rs
index 5a5040c3..6e57624a 100644
--- a/drivers/acpid/src/scheme.rs
+++ b/drivers/acpid/src/scheme.rs
@@ -474,6 +474,8 @@ impl SchemeSync for AcpiScheme<'_, '_> {
return Err(Error::new(EINVAL));
} else {
self.pci_fd = Some(new_fd);
+ self.ctx
+ .prime_shutdown_s5(self.pci_fd.as_ref(), "PCI-backed AML handoff");
}
Ok(num_fds)
-398
View File
@@ -1,398 +0,0 @@
--- a/drivers/pcid/src/cfg_access/mod.rs
+++ b/drivers/pcid/src/cfg_access/mod.rs
@@ -349,7 +349,11 @@
let bus_addr = self.bus_addr(address.segment(), address.bus())?;
Some(unsafe { bus_addr.add(Self::bus_addr_offset_in_dwords(address, offset)) })
}
+
+ pub fn has_extended_config(&self, address: PciAddress) -> bool {
+ self.mmio_addr(address, 0x100).is_some()
+ }
}
impl ConfigRegionAccess for Pcie {
--- a/drivers/pcid/src/scheme.rs
+++ b/drivers/pcid/src/scheme.rs
@@ -5,12 +5,61 @@
use redox_scheme::{CallerCtx, OpenResult};
use scheme_utils::HandleMap;
use syscall::dirent::{DirEntry, DirentBuf, DirentKind};
-use syscall::error::{Error, Result, EACCES, EBADF, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR, EALREADY};
+use syscall::error::{
+ Error, Result, EACCES, EALREADY, EBADF, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR, EROFS,
+};
use syscall::flag::{MODE_CHR, MODE_DIR, O_DIRECTORY, O_STAT};
use syscall::schemev2::NewFdFlags;
use syscall::ENOLCK;
use crate::cfg_access::Pcie;
+
+const PCIE_EXTENDED_CAPABILITY_AER: u16 = 0x0001;
+
+#[derive(Clone, Copy)]
+enum AerRegisterName {
+ UncorStatus,
+ UncorMask,
+ UncorSeverity,
+ CorStatus,
+ CorMask,
+ Cap,
+ HeaderLog,
+}
+
+impl AerRegisterName {
+ fn from_path(path: &str) -> Option<Self> {
+ Some(match path {
+ "uncor_status" => Self::UncorStatus,
+ "uncor_mask" => Self::UncorMask,
+ "uncor_severity" => Self::UncorSeverity,
+ "cor_status" => Self::CorStatus,
+ "cor_mask" => Self::CorMask,
+ "cap" => Self::Cap,
+ "header_log" => Self::HeaderLog,
+ _ => return None,
+ })
+ }
+
+ const fn offset(self) -> u16 {
+ match self {
+ Self::UncorStatus => 0x00,
+ Self::UncorMask => 0x04,
+ Self::UncorSeverity => 0x08,
+ Self::CorStatus => 0x0C,
+ Self::CorMask => 0x10,
+ Self::Cap => 0x14,
+ Self::HeaderLog => 0x18,
+ }
+ }
+
+ const fn len(self) -> usize {
+ match self {
+ Self::HeaderLog => 16,
+ _ => 4,
+ }
+ }
+}
pub struct PciScheme {
handles: HandleMap<HandleWrapper>,
@@ -20,13 +69,27 @@
binds: HashMap<String, u32>,
}
enum Handle {
- TopLevel { entries: Vec<String> },
+ TopLevel {
+ entries: Vec<String>,
+ },
Access,
- Device,
- Channel { addr: PciAddress, st: ChannelState },
+ Device {
+ addr: PciAddress,
+ },
+ Channel {
+ addr: PciAddress,
+ st: ChannelState,
+ },
SchemeRoot,
/// Represents an open handle to a device's bind endpoint
- Bind { addr: PciAddress },
+ Bind {
+ addr: PciAddress,
+ },
+ AerDir,
+ Aer {
+ addr: PciAddress,
+ register: AerRegisterName,
+ },
/// Uevent surface for hotplug consumers. Opening uevent returns an object
/// from which device add/remove events can be read. Since pcid currently
/// only scans at startup, this surface is ready for hotplug polling consumers.
@@ -38,13 +101,23 @@
}
impl Handle {
fn is_file(&self) -> bool {
- matches!(self, Self::Access | Self::Channel { .. } | Self::Bind { .. } | Self::Uevent)
+ matches!(
+ self,
+ Self::Access
+ | Self::Channel { .. }
+ | Self::Bind { .. }
+ | Self::Aer { .. }
+ | Self::Uevent
+ )
}
fn is_dir(&self) -> bool {
!self.is_file()
}
fn requires_root(&self) -> bool {
- matches!(self, Self::Access | Self::Channel { .. } | Self::Bind { .. })
+ matches!(
+ self,
+ Self::Access | Self::Channel { .. } | Self::Bind { .. }
+ )
}
fn is_scheme_root(&self) -> bool {
matches!(self, Self::SchemeRoot)
@@ -57,6 +130,16 @@
}
const DEVICE_CONTENTS: &[&str] = &["channel", "bind"];
+const DEVICE_AER_CONTENTS: &[&str] = &["channel", "bind", "aer"];
+const AER_CONTENTS: &[&str] = &[
+ "uncor_status",
+ "uncor_mask",
+ "uncor_severity",
+ "cor_status",
+ "cor_mask",
+ "cap",
+ "header_log",
+];
impl PciScheme {
pub fn access(&mut self) -> usize {
@@ -141,7 +224,12 @@
let (len, mode) = match handle.inner {
Handle::TopLevel { ref entries } => (entries.len(), MODE_DIR | 0o755),
- Handle::Device => (DEVICE_CONTENTS.len(), MODE_DIR | 0o755),
+ Handle::Device { addr } => (
+ Self::device_entries(&self.pcie, addr).len(),
+ MODE_DIR | 0o755,
+ ),
+ Handle::AerDir => (AER_CONTENTS.len(), MODE_DIR | 0o755),
+ Handle::Aer { register, .. } => (register.len(), MODE_CHR | 0o444),
Handle::Access | Handle::Channel { .. } | Handle::Bind { .. } => (0, MODE_CHR | 0o600),
Handle::Uevent => (0, MODE_CHR | 0o644),
Handle::SchemeRoot => return Err(Error::new(EBADF)),
@@ -154,7 +242,7 @@
&mut self,
id: usize,
buf: &mut [u8],
- _offset: u64,
+ offset: u64,
_fcntl_flags: u32,
_ctx: &CallerCtx,
) -> Result<usize> {
@@ -166,11 +254,14 @@
match handle.inner {
Handle::TopLevel { .. } => Err(Error::new(EISDIR)),
- Handle::Device => Err(Error::new(EISDIR)),
+ Handle::Device { .. } | Handle::AerDir => Err(Error::new(EISDIR)),
Handle::Channel {
addr: _,
ref mut st,
} => Self::read_channel(st, buf),
+ Handle::Aer { addr, register } => {
+ Self::read_aer_register(&self.pcie, addr, register, buf, offset)
+ }
Handle::Uevent => {
// Uevent surface is ready for hotplug polling consumers.
// pcid currently only scans at startup, so return empty (EAGAIN would indicate no data available).
@@ -209,8 +300,15 @@
}
return Ok(buf);
}
- Handle::Device => DEVICE_CONTENTS,
- Handle::Access | Handle::Channel { .. } | Handle::Bind { .. } | Handle::Uevent => return Err(Error::new(ENOTDIR)),
+ Handle::Device { addr } => Self::device_entries(&self.pcie, addr),
+ Handle::AerDir => AER_CONTENTS,
+ Handle::Access
+ | Handle::Channel { .. }
+ | Handle::Bind { .. }
+ | Handle::Aer { .. }
+ | Handle::Uevent => {
+ return Err(Error::new(ENOTDIR));
+ }
Handle::SchemeRoot => return Err(Error::new(EBADF)),
};
@@ -243,6 +341,7 @@
Handle::Channel { addr, ref mut st } => {
Self::write_channel(&self.pcie, &mut self.tree, addr, st, buf)
}
+ Handle::Aer { .. } => Err(Error::new(EROFS)),
_ => Err(Error::new(EBADF)),
}
@@ -357,45 +456,151 @@
binds: HashMap::new(),
}
}
- fn parse_after_pci_addr(&mut self, addr: PciAddress, after: &str, ctx: &CallerCtx) -> Result<Handle> {
+ fn device_entries(pcie: &Pcie, addr: PciAddress) -> &'static [&'static str] {
+ if Self::find_pcie_extended_capability(pcie, addr, PCIE_EXTENDED_CAPABILITY_AER).is_some() {
+ DEVICE_AER_CONTENTS
+ } else {
+ DEVICE_CONTENTS
+ }
+ }
+ fn find_pcie_extended_capability(
+ pcie: &Pcie,
+ addr: PciAddress,
+ capability_id: u16,
+ ) -> Option<u16> {
+ if !pcie.has_extended_config(addr) {
+ return None;
+ }
+
+ let mut offset = 0x100_u16;
+
+ while offset <= 0xFFC {
+ let header = unsafe { pcie.read(addr, offset) };
+ if header == 0 || header == u32::MAX {
+ return None;
+ }
+
+ if (header & 0xFFFF) as u16 == capability_id {
+ return Some(offset);
+ }
+
+ let next = ((header >> 20) & 0xFFF) as u16;
+ if next < 0x100 || next <= offset || next > 0xFFC || next % 4 != 0 {
+ return None;
+ }
+ offset = next;
+ }
+
+ None
+ }
+ fn read_file_bytes(data: &[u8], buf: &mut [u8], offset: u64) -> Result<usize> {
+ let Ok(offset) = usize::try_from(offset) else {
+ return Ok(0);
+ };
+ if offset >= data.len() {
+ return Ok(0);
+ }
+
+ let count = std::cmp::min(buf.len(), data.len() - offset);
+ buf[..count].copy_from_slice(&data[offset..offset + count]);
+ Ok(count)
+ }
+ fn read_aer_register(
+ pcie: &Pcie,
+ addr: PciAddress,
+ register: AerRegisterName,
+ buf: &mut [u8],
+ offset: u64,
+ ) -> Result<usize> {
+ let Some(aer_base) =
+ Self::find_pcie_extended_capability(pcie, addr, PCIE_EXTENDED_CAPABILITY_AER)
+ else {
+ return Err(Error::new(ENOENT));
+ };
+
+ let mut data = [0_u8; 16];
+ for (index, chunk) in data[..register.len()].chunks_exact_mut(4).enumerate() {
+ let index = u16::try_from(index).map_err(|_| Error::new(EIO))?;
+ let value = unsafe { pcie.read(addr, aer_base + register.offset() + index * 4) };
+ chunk.copy_from_slice(&value.to_le_bytes());
+ }
+
+ Self::read_file_bytes(&data[..register.len()], buf, offset)
+ }
+ fn parse_after_pci_addr(
+ &mut self,
+ addr: PciAddress,
+ after: &str,
+ ctx: &CallerCtx,
+ ) -> Result<Handle> {
if after.chars().next().map_or(false, |c| c != '/') {
return Err(Error::new(ENOENT));
}
let func = self.tree.get_mut(&addr).ok_or(Error::new(ENOENT))?;
Ok(if after.is_empty() {
- Handle::Device
+ Handle::Device { addr }
} else {
let path = &after[1..];
- match path {
- "channel" => {
- if func.enabled {
- return Err(Error::new(ENOLCK));
+ if path == "aer" {
+ if Self::find_pcie_extended_capability(
+ &self.pcie,
+ addr,
+ PCIE_EXTENDED_CAPABILITY_AER,
+ )
+ .is_none()
+ {
+ return Err(Error::new(ENOENT));
+ }
+ Handle::AerDir
+ } else if let Some(register_name) = path.strip_prefix("aer/") {
+ let register =
+ AerRegisterName::from_path(register_name).ok_or(Error::new(ENOENT))?;
+ if Self::find_pcie_extended_capability(
+ &self.pcie,
+ addr,
+ PCIE_EXTENDED_CAPABILITY_AER,
+ )
+ .is_none()
+ {
+ return Err(Error::new(ENOENT));
+ }
+ Handle::Aer { addr, register }
+ } else {
+ match path {
+ "channel" => {
+ if func.enabled {
+ return Err(Error::new(ENOLCK));
+ }
+ func.inner.legacy_interrupt_line = crate::enable_function(
+ &self.pcie,
+ &mut func.endpoint_header,
+ &mut func.capabilities,
+ );
+ func.enabled = true;
+ Handle::Channel {
+ addr,
+ st: ChannelState::AwaitingData,
+ }
}
- func.inner.legacy_interrupt_line = crate::enable_function(
- &self.pcie,
- &mut func.endpoint_header,
- &mut func.capabilities,
- );
- func.enabled = true;
- Handle::Channel {
- addr,
- st: ChannelState::AwaitingData,
+ "bind" => {
+ let addr_str = format!("{}", addr);
+ if let Some(&owner_pid) = self.binds.get(&addr_str) {
+ log::info!(
+ "pcid: device {} already bound by pid {}",
+ addr_str,
+ owner_pid
+ );
+ return Err(Error::new(EALREADY));
+ }
+ let caller_pid = u32::try_from(ctx.pid).map_err(|_| Error::new(EINVAL))?;
+ self.binds.insert(addr_str.clone(), caller_pid);
+ log::info!("pcid: device {} bound by pid {}", addr_str, caller_pid);
+ Handle::Bind { addr }
}
- }
- "bind" => {
- let addr_str = format!("{}", addr);
- if let Some(&owner_pid) = self.binds.get(&addr_str) {
- log::info!("pcid: device {} already bound by pid {}", addr_str, owner_pid);
- return Err(Error::new(EALREADY));
- }
- let caller_pid = ctx.pid;
- self.binds.insert(addr_str.clone(), caller_pid);
- log::info!("pcid: device {} bound by pid {}", addr_str, caller_pid);
- Handle::Bind { addr }
- }
- _ => return Err(Error::new(ENOENT)),
+ _ => return Err(Error::new(ENOENT)),
+ }
}
})
}
@@ -1,182 +0,0 @@
diff --git a/drivers/pcid/src/scheme.rs b/drivers/pcid/src/scheme.rs
index bb9f39a3..06be6267 100644
--- a/drivers/pcid/src/scheme.rs
+++ b/drivers/pcid/src/scheme.rs
@@ -1,11 +1,11 @@
-use std::collections::{BTreeMap, VecDeque};
+use std::collections::{BTreeMap, HashMap, VecDeque};
use pci_types::{ConfigRegionAccess, PciAddress};
use redox_scheme::scheme::SchemeSync;
use redox_scheme::{CallerCtx, OpenResult};
use scheme_utils::HandleMap;
use syscall::dirent::{DirEntry, DirentBuf, DirentKind};
-use syscall::error::{Error, Result, EACCES, EBADF, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR};
+use syscall::error::{Error, Result, EACCES, EBADF, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR, EALREADY};
use syscall::flag::{MODE_CHR, MODE_DIR, O_DIRECTORY, O_STAT};
use syscall::schemev2::NewFdFlags;
use syscall::ENOLCK;
@@ -16,6 +16,8 @@ pub struct PciScheme {
handles: HandleMap<HandleWrapper>,
pub pcie: Pcie,
pub tree: BTreeMap<PciAddress, crate::Func>,
+ /// Maps device address string (e.g. "0000:00:14.0") to owning PID
+ binds: HashMap<String, u32>,
}
enum Handle {
TopLevel { entries: Vec<String> },
@@ -23,6 +25,12 @@ enum Handle {
Device,
Channel { addr: PciAddress, st: ChannelState },
SchemeRoot,
+ /// Represents an open handle to a device's bind endpoint
+ Bind { addr: PciAddress },
+ /// Uevent surface for hotplug consumers. Opening uevent returns an object
+ /// from which device add/remove events can be read. Since pcid currently
+ /// only scans at startup, this surface is ready for hotplug polling consumers.
+ Uevent,
}
struct HandleWrapper {
inner: Handle,
@@ -30,14 +38,13 @@ struct HandleWrapper {
}
impl Handle {
fn is_file(&self) -> bool {
- matches!(self, Self::Access | Self::Channel { .. })
+ matches!(self, Self::Access | Self::Channel { .. } | Self::Bind { .. } | Self::Uevent)
}
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::Channel { .. } | Self::Bind { .. })
}
fn is_scheme_root(&self) -> bool {
matches!(self, Self::SchemeRoot)
@@ -49,7 +56,7 @@ enum ChannelState {
AwaitingResponseRead(VecDeque<u8>),
}
-const DEVICE_CONTENTS: &[&str] = &["channel"];
+const DEVICE_CONTENTS: &[&str] = &["channel", "bind"];
impl PciScheme {
pub fn access(&mut self) -> usize {
@@ -88,22 +95,25 @@ impl SchemeSync for PciScheme {
let path = path.trim_matches('/');
let handle = if path.is_empty() {
- Handle::TopLevel {
- entries: self
- .tree
- .iter()
- // FIXME remove replacement of : once the old scheme format is no longer supported.
- .map(|(addr, _)| format!("{}", addr).replace(':', "--"))
- .collect::<Vec<_>>(),
- }
+ let mut entries: Vec<String> = self
+ .tree
+ .iter()
+ // FIXME remove replacement of : once the old scheme format is no longer supported.
+ .map(|(addr, _)| format!("{}", addr).replace(':', "--"))
+ .collect();
+ entries.push(String::from("uevent"));
+ entries.push(String::from("access"));
+ Handle::TopLevel { entries }
} else if path == "access" {
Handle::Access
+ } else if path == "uevent" {
+ Handle::Uevent
} else {
let idx = path.find('/').unwrap_or(path.len());
let (addr_str, after) = path.split_at(idx);
let addr = parse_pci_addr(addr_str).ok_or(Error::new(ENOENT))?;
- self.parse_after_pci_addr(addr, after)?
+ self.parse_after_pci_addr(addr, after, ctx)?
};
let stat = flags & O_STAT != 0;
@@ -132,7 +142,8 @@ 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::Access | Handle::Channel { .. } => (0, MODE_CHR | 0o600),
+ Handle::Access | Handle::Channel { .. } | Handle::Bind { .. } => (0, MODE_CHR | 0o600),
+ Handle::Uevent => (0, MODE_CHR | 0o644),
Handle::SchemeRoot => return Err(Error::new(EBADF)),
};
stat.st_size = len as u64;
@@ -160,7 +171,13 @@ impl SchemeSync for PciScheme {
addr: _,
ref mut st,
} => Self::read_channel(st, buf),
- Handle::SchemeRoot => Err(Error::new(EBADF)),
+ Handle::Uevent => {
+ // Uevent surface is ready for hotplug polling consumers.
+ // pcid currently only scans at startup, so return empty (EAGAIN would indicate no data available).
+ // Consumers can poll and re-read to check for new events.
+ Ok(0)
+ }
+ Handle::SchemeRoot | Handle::Bind { .. } => Err(Error::new(EBADF)),
_ => Err(Error::new(EBADF)),
}
}
@@ -193,7 +210,7 @@ impl SchemeSync for PciScheme {
return Ok(buf);
}
Handle::Device => DEVICE_CONTENTS,
- Handle::Access | Handle::Channel { .. } => return Err(Error::new(ENOTDIR)),
+ Handle::Access | Handle::Channel { .. } | Handle::Bind { .. } | Handle::Uevent => return Err(Error::new(ENOTDIR)),
Handle::SchemeRoot => return Err(Error::new(EBADF)),
};
@@ -316,6 +333,16 @@ impl SchemeSync for PciScheme {
func.enabled = false;
}
}
+ Some(HandleWrapper {
+ inner: Handle::Bind { addr },
+ ..
+ }) => {
+ let addr_str = format!("{}", addr);
+ if let Some(&owner_pid) = self.binds.get(&addr_str) {
+ log::info!("pcid: device {} unbound by pid {}", addr_str, owner_pid);
+ }
+ self.binds.remove(&addr_str);
+ }
_ => {}
}
}
@@ -327,9 +354,10 @@ impl PciScheme {
handles: HandleMap::new(),
pcie,
tree: BTreeMap::new(),
+ binds: HashMap::new(),
}
}
- fn parse_after_pci_addr(&mut self, addr: PciAddress, after: &str) -> Result<Handle> {
+ fn parse_after_pci_addr(&mut self, addr: PciAddress, after: &str, ctx: &CallerCtx) -> Result<Handle> {
if after.chars().next().map_or(false, |c| c != '/') {
return Err(Error::new(ENOENT));
}
@@ -356,6 +384,17 @@ impl PciScheme {
st: ChannelState::AwaitingData,
}
}
+ "bind" => {
+ let addr_str = format!("{}", addr);
+ if let Some(&owner_pid) = self.binds.get(&addr_str) {
+ log::info!("pcid: device {} already bound by pid {}", addr_str, owner_pid);
+ return Err(Error::new(EALREADY));
+ }
+ let caller_pid = ctx.pid;
+ self.binds.insert(addr_str.clone(), caller_pid);
+ log::info!("pcid: device {} bound by pid {}", addr_str, caller_pid);
+ Handle::Bind { addr }
+ }
_ => return Err(Error::new(ENOENT)),
}
})
@@ -1,479 +0,0 @@
diff --git a/drivers/pcid/src/scheme.rs b/drivers/pcid/src/scheme.rs
index bb9f39a3..b6f8711e 100644
--- a/drivers/pcid/src/scheme.rs
+++ b/drivers/pcid/src/scheme.rs
@@ -1,28 +1,100 @@
-use std::collections::{BTreeMap, VecDeque};
+use std::collections::{BTreeMap, HashMap, VecDeque};
+use std::fmt::Write;
use pci_types::{ConfigRegionAccess, PciAddress};
use redox_scheme::scheme::SchemeSync;
use redox_scheme::{CallerCtx, OpenResult};
use scheme_utils::HandleMap;
use syscall::dirent::{DirEntry, DirentBuf, DirentKind};
-use syscall::error::{Error, Result, EACCES, EBADF, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR};
+use syscall::error::{
+ Error, Result, EACCES, EALREADY, EBADF, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR, EROFS,
+};
use syscall::flag::{MODE_CHR, MODE_DIR, O_DIRECTORY, O_STAT};
use syscall::schemev2::NewFdFlags;
use syscall::ENOLCK;
use crate::cfg_access::Pcie;
+const PCIE_EXTENDED_CAPABILITY_AER: u16 = 0x0001;
+
+#[derive(Clone, Copy)]
+enum AerRegisterName {
+ UncorStatus,
+ UncorMask,
+ UncorSeverity,
+ CorStatus,
+ CorMask,
+ Cap,
+ HeaderLog,
+}
+
+impl AerRegisterName {
+ fn from_path(path: &str) -> Option<Self> {
+ Some(match path {
+ "uncor_status" => Self::UncorStatus,
+ "uncor_mask" => Self::UncorMask,
+ "uncor_severity" => Self::UncorSeverity,
+ "cor_status" => Self::CorStatus,
+ "cor_mask" => Self::CorMask,
+ "cap" => Self::Cap,
+ "header_log" => Self::HeaderLog,
+ _ => return None,
+ })
+ }
+
+ const fn offset(self) -> u16 {
+ match self {
+ Self::UncorStatus => 0x00,
+ Self::UncorMask => 0x04,
+ Self::UncorSeverity => 0x08,
+ Self::CorStatus => 0x0C,
+ Self::CorMask => 0x10,
+ Self::Cap => 0x14,
+ Self::HeaderLog => 0x18,
+ }
+ }
+
+ const fn len(self) -> usize {
+ match self {
+ Self::HeaderLog => 16,
+ _ => 4,
+ }
+ }
+}
+
pub struct PciScheme {
handles: HandleMap<HandleWrapper>,
pub pcie: Pcie,
pub tree: BTreeMap<PciAddress, crate::Func>,
+ /// Maps device address string (e.g. "0000:00:14.0") to owning PID
+ binds: HashMap<String, u32>,
}
enum Handle {
- TopLevel { entries: Vec<String> },
+ TopLevel {
+ entries: Vec<String>,
+ },
Access,
- Device,
- Channel { addr: PciAddress, st: ChannelState },
+ Device {
+ addr: PciAddress,
+ },
+ Channel {
+ addr: PciAddress,
+ st: ChannelState,
+ },
SchemeRoot,
+ /// Represents an open handle to a device's bind endpoint
+ Bind {
+ addr: PciAddress,
+ },
+ AerDir,
+ Aer {
+ addr: PciAddress,
+ register: AerRegisterName,
+ },
+ /// Uevent surface for hotplug consumers. Opening uevent returns an object
+ /// from which device add/remove events can be read. Since pcid currently
+ /// only scans at startup, this surface is ready for hotplug polling consumers.
+ Uevent,
}
struct HandleWrapper {
inner: Handle,
@@ -30,14 +102,23 @@ struct HandleWrapper {
}
impl Handle {
fn is_file(&self) -> bool {
- matches!(self, Self::Access | Self::Channel { .. })
+ matches!(
+ self,
+ Self::Access
+ | Self::Channel { .. }
+ | Self::Bind { .. }
+ | Self::Aer { .. }
+ | Self::Uevent
+ )
}
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::Channel { .. } | Self::Bind { .. }
+ )
}
fn is_scheme_root(&self) -> bool {
matches!(self, Self::SchemeRoot)
@@ -49,7 +130,17 @@ enum ChannelState {
AwaitingResponseRead(VecDeque<u8>),
}
-const DEVICE_CONTENTS: &[&str] = &["channel"];
+const DEVICE_CONTENTS: &[&str] = &["channel", "bind"];
+const DEVICE_AER_CONTENTS: &[&str] = &["channel", "bind", "aer"];
+const AER_CONTENTS: &[&str] = &[
+ "uncor_status",
+ "uncor_mask",
+ "uncor_severity",
+ "cor_status",
+ "cor_mask",
+ "cap",
+ "header_log",
+];
impl PciScheme {
pub fn access(&mut self) -> usize {
@@ -88,22 +179,25 @@ impl SchemeSync for PciScheme {
let path = path.trim_matches('/');
let handle = if path.is_empty() {
- Handle::TopLevel {
- entries: self
- .tree
- .iter()
- // FIXME remove replacement of : once the old scheme format is no longer supported.
- .map(|(addr, _)| format!("{}", addr).replace(':', "--"))
- .collect::<Vec<_>>(),
- }
+ let mut entries: Vec<String> = self
+ .tree
+ .iter()
+ // FIXME remove replacement of : once the old scheme format is no longer supported.
+ .map(|(addr, _)| format!("{}", addr).replace(':', "--"))
+ .collect();
+ entries.push(String::from("uevent"));
+ entries.push(String::from("access"));
+ Handle::TopLevel { entries }
} else if path == "access" {
Handle::Access
+ } else if path == "uevent" {
+ Handle::Uevent
} else {
let idx = path.find('/').unwrap_or(path.len());
let (addr_str, after) = path.split_at(idx);
let addr = parse_pci_addr(addr_str).ok_or(Error::new(ENOENT))?;
- self.parse_after_pci_addr(addr, after)?
+ self.parse_after_pci_addr(addr, after, ctx)?
};
let stat = flags & O_STAT != 0;
@@ -131,8 +225,14 @@ 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::Access | Handle::Channel { .. } => (0, MODE_CHR | 0o600),
+ Handle::Device { addr } => (
+ Self::device_entries(&self.pcie, addr).len(),
+ MODE_DIR | 0o755,
+ ),
+ Handle::AerDir => (AER_CONTENTS.len(), MODE_DIR | 0o755),
+ Handle::Aer { register, .. } => (register.len(), MODE_CHR | 0o444),
+ Handle::Access | Handle::Channel { .. } | Handle::Bind { .. } => (0, MODE_CHR | 0o600),
+ Handle::Uevent => (0, MODE_CHR | 0o644),
Handle::SchemeRoot => return Err(Error::new(EBADF)),
};
stat.st_size = len as u64;
@@ -143,7 +243,7 @@ impl SchemeSync for PciScheme {
&mut self,
id: usize,
buf: &mut [u8],
- _offset: u64,
+ offset: u64,
_fcntl_flags: u32,
_ctx: &CallerCtx,
) -> Result<usize> {
@@ -155,12 +255,45 @@ impl SchemeSync for PciScheme {
match handle.inner {
Handle::TopLevel { .. } => Err(Error::new(EISDIR)),
- Handle::Device => Err(Error::new(EISDIR)),
+ Handle::Device { .. } | Handle::AerDir => Err(Error::new(EISDIR)),
Handle::Channel {
addr: _,
ref mut st,
} => Self::read_channel(st, buf),
- Handle::SchemeRoot => Err(Error::new(EBADF)),
+ Handle::Aer { addr, register } => {
+ Self::read_aer_register(&self.pcie, addr, register, buf, offset)
+ }
+ Handle::Uevent => {
+ // Uevent surface for hotplug polling consumers.
+ // pcid currently only scans at startup, so return the current
+ // device tree as "add" events. Consumers can poll and re-read
+ // to check for new events.
+ let mut o = String::new();
+ for (a, f) in &self.tree {
+ let _ = write!(
+ o,
+ "add device {:02x}:{:02x}.{:x}.{:x} vendor=0x{:04x} device=0x{:04x} class=0x{:02x}.{:02x}\n",
+ a.segment(),
+ a.bus(),
+ a.device(),
+ a.function(),
+ f.inner.full_device_id.vendor_id,
+ f.inner.full_device_id.device_id,
+ f.inner.full_device_id.class,
+ f.inner.full_device_id.subclass
+ );
+ }
+ let b = o.as_bytes();
+ let s = offset as usize;
+ if s < b.len() {
+ let n = (b.len() - s).min(buf.len());
+ buf[..n].copy_from_slice(&b[s..s + n]);
+ Ok(n)
+ } else {
+ Ok(0)
+ }
+ }
+ Handle::SchemeRoot | Handle::Bind { .. } => Err(Error::new(EBADF)),
_ => Err(Error::new(EBADF)),
}
}
@@ -192,8 +325,15 @@ impl SchemeSync for PciScheme {
}
return Ok(buf);
}
- Handle::Device => DEVICE_CONTENTS,
- Handle::Access | Handle::Channel { .. } => return Err(Error::new(ENOTDIR)),
+ Handle::Device { addr } => Self::device_entries(&self.pcie, addr),
+ Handle::AerDir => AER_CONTENTS,
+ Handle::Access
+ | Handle::Channel { .. }
+ | Handle::Bind { .. }
+ | Handle::Aer { .. }
+ | Handle::Uevent => {
+ return Err(Error::new(ENOTDIR));
+ }
Handle::SchemeRoot => return Err(Error::new(EBADF)),
};
@@ -226,6 +366,7 @@ impl SchemeSync for PciScheme {
Handle::Channel { addr, ref mut st } => {
Self::write_channel(&self.pcie, &mut self.tree, addr, st, buf)
}
+ Handle::Aer { .. } => Err(Error::new(EROFS)),
_ => Err(Error::new(EBADF)),
}
@@ -316,6 +457,16 @@ impl SchemeSync for PciScheme {
func.enabled = false;
}
}
+ Some(HandleWrapper {
+ inner: Handle::Bind { addr },
+ ..
+ }) => {
+ let addr_str = format!("{}", addr);
+ if let Some(&owner_pid) = self.binds.get(&addr_str) {
+ log::info!("pcid: device {} unbound by pid {}", addr_str, owner_pid);
+ }
+ self.binds.remove(&addr_str);
+ }
_ => {}
}
}
@@ -327,36 +478,154 @@ impl PciScheme {
handles: HandleMap::new(),
pcie,
tree: BTreeMap::new(),
+ binds: HashMap::new(),
+ }
+ }
+ fn device_entries(pcie: &Pcie, addr: PciAddress) -> &'static [&'static str] {
+ if Self::find_pcie_extended_capability(pcie, addr, PCIE_EXTENDED_CAPABILITY_AER).is_some() {
+ DEVICE_AER_CONTENTS
+ } else {
+ DEVICE_CONTENTS
}
}
- fn parse_after_pci_addr(&mut self, addr: PciAddress, after: &str) -> Result<Handle> {
+ fn find_pcie_extended_capability(
+ pcie: &Pcie,
+ addr: PciAddress,
+ capability_id: u16,
+ ) -> Option<u16> {
+ if !pcie.has_extended_config(addr) {
+ return None;
+ }
+
+ let mut offset = 0x100_u16;
+
+ while offset <= 0xFFC {
+ let header = unsafe { pcie.read(addr, offset) };
+ if header == 0 || header == u32::MAX {
+ return None;
+ }
+
+ if (header & 0xFFFF) as u16 == capability_id {
+ return Some(offset);
+ }
+
+ let next = ((header >> 20) & 0xFFF) as u16;
+ if next < 0x100 || next <= offset || next > 0xFFC || next % 4 != 0 {
+ return None;
+ }
+ offset = next;
+ }
+
+ None
+ }
+ fn read_file_bytes(data: &[u8], buf: &mut [u8], offset: u64) -> Result<usize> {
+ let Ok(offset) = usize::try_from(offset) else {
+ return Ok(0);
+ };
+ if offset >= data.len() {
+ return Ok(0);
+ }
+
+ let count = std::cmp::min(buf.len(), data.len() - offset);
+ buf[..count].copy_from_slice(&data[offset..offset + count]);
+ Ok(count)
+ }
+ fn read_aer_register(
+ pcie: &Pcie,
+ addr: PciAddress,
+ register: AerRegisterName,
+ buf: &mut [u8],
+ offset: u64,
+ ) -> Result<usize> {
+ let Some(aer_base) =
+ Self::find_pcie_extended_capability(pcie, addr, PCIE_EXTENDED_CAPABILITY_AER)
+ else {
+ return Err(Error::new(ENOENT));
+ };
+
+ let mut data = [0_u8; 16];
+ for (index, chunk) in data[..register.len()].chunks_exact_mut(4).enumerate() {
+ let index = u16::try_from(index).map_err(|_| Error::new(EIO))?;
+ let value = unsafe { pcie.read(addr, aer_base + register.offset() + index * 4) };
+ chunk.copy_from_slice(&value.to_le_bytes());
+ }
+
+ Self::read_file_bytes(&data[..register.len()], buf, offset)
+ }
+ fn parse_after_pci_addr(
+ &mut self,
+ addr: PciAddress,
+ after: &str,
+ ctx: &CallerCtx,
+ ) -> Result<Handle> {
if after.chars().next().map_or(false, |c| c != '/') {
return Err(Error::new(ENOENT));
}
let func = self.tree.get_mut(&addr).ok_or(Error::new(ENOENT))?;
Ok(if after.is_empty() {
- Handle::Device
+ Handle::Device { addr }
} else {
let path = &after[1..];
- match path {
- "channel" => {
- if func.enabled {
- return Err(Error::new(ENOLCK));
+ if path == "aer" {
+ if Self::find_pcie_extended_capability(
+ &self.pcie,
+ addr,
+ PCIE_EXTENDED_CAPABILITY_AER,
+ )
+ .is_none()
+ {
+ return Err(Error::new(ENOENT));
+ }
+ Handle::AerDir
+ } else if let Some(register_name) = path.strip_prefix("aer/") {
+ let register =
+ AerRegisterName::from_path(register_name).ok_or(Error::new(ENOENT))?;
+ if Self::find_pcie_extended_capability(
+ &self.pcie,
+ addr,
+ PCIE_EXTENDED_CAPABILITY_AER,
+ )
+ .is_none()
+ {
+ return Err(Error::new(ENOENT));
+ }
+ Handle::Aer { addr, register }
+ } else {
+ match path {
+ "channel" => {
+ if func.enabled {
+ return Err(Error::new(ENOLCK));
+ }
+ func.inner.legacy_interrupt_line = crate::enable_function(
+ &self.pcie,
+ &mut func.endpoint_header,
+ &mut func.capabilities,
+ );
+ func.enabled = true;
+ Handle::Channel {
+ addr,
+ st: ChannelState::AwaitingData,
+ }
}
- func.inner.legacy_interrupt_line = crate::enable_function(
- &self.pcie,
- &mut func.endpoint_header,
- &mut func.capabilities,
- );
- func.enabled = true;
- Handle::Channel {
- addr,
- st: ChannelState::AwaitingData,
+ "bind" => {
+ let addr_str = format!("{}", addr);
+ if let Some(&owner_pid) = self.binds.get(&addr_str) {
+ log::info!(
+ "pcid: device {} already bound by pid {}",
+ addr_str,
+ owner_pid
+ );
+ return Err(Error::new(EALREADY));
+ }
+ let caller_pid = u32::try_from(ctx.pid).map_err(|_| Error::new(EINVAL))?;
+ self.binds.insert(addr_str.clone(), caller_pid);
+ log::info!("pcid: device {} bound by pid {}", addr_str, caller_pid);
+ Handle::Bind { addr }
}
+ _ => return Err(Error::new(ENOENT)),
}
- _ => return Err(Error::new(ENOENT)),
}
})
}
@@ -1,43 +0,0 @@
--- a/src/bin/login.rs 2026-05-03 09:46:36.866954421 +0100
+++ /mnt/data/homes/kellito/Builds/rbos/b/src/bin/login.rs 2026-05-03 09:46:36.867061170 +0100
@@ -120,6 +120,7 @@
pub fn main() {
let mut stdout = io::stdout();
let mut stderr = io::stderr();
+ let mut consecutive_failures: u32 = 0;
let _args = clap_app!(login =>
(author: "Jeremy Soller, Jose Narvaez")
@@ -133,6 +134,10 @@
}
loop {
+ if consecutive_failures >= 3 {
+ let delay_secs = std::cmp::min(consecutive_failures as u64, 30);
+ std::thread::sleep(std::time::Duration::from_secs(delay_secs));
+ }
let user = liner::Context::new()
.read_line(
liner::Prompt::from("\x1B[1mredox login:\x1B[0m "),
@@ -150,11 +155,13 @@
None => {
stdout.write(b"\nLogin incorrect\n").r#try(&mut stderr);
stdout.write(b"\n").r#try(&mut stderr);
+ consecutive_failures += 1;
stdout.flush().r#try(&mut stderr);
continue;
}
Some(user) => {
if user.is_passwd_blank() {
+ consecutive_failures = 0;
if let Ok(mut motd) = File::open(MOTD_FILE) {
io::copy(&mut motd, &mut stdout).r#try(&mut stderr);
stdout.flush().r#try(&mut stderr);
@@ -185,6 +192,7 @@
stdout.flush().r#try(&mut stderr);
if user.verify_passwd(&password) {
+ consecutive_failures = 0;
if let Ok(mut motd) = File::open(MOTD_FILE) {
io::copy(&mut motd, &mut stdout).r#try(&mut stderr);
stdout.flush().r#try(&mut stderr);
@@ -1,685 +0,0 @@
diff --git a/drivers/acpid/src/main.rs b/drivers/acpid/src/main.rs
index 059254b3..a3f5f996 100644
--- a/drivers/acpid/src/main.rs
+++ b/drivers/acpid/src/main.rs
@@ -3,6 +3,7 @@ use std::fs::File;
use std::mem;
use std::ops::ControlFlow;
use std::os::unix::io::AsRawFd;
+use std::process;
use std::sync::Arc;
use ::acpi::aml::op_region::{RegionHandler, RegionSpace};
@@ -17,6 +18,58 @@ mod ec;
mod scheme;
+fn parse_physaddrs(sdt: &self::acpi::Sdt) -> Vec<u64> {
+ match &sdt.signature {
+ b"RSDT" => {
+ let chunks = sdt.data().chunks_exact(mem::size_of::<u32>());
+ if !chunks.remainder().is_empty() {
+ eprintln!(
+ "acpid: malformed RSDT length {}: expected 4-byte entries",
+ sdt.data().len()
+ );
+ process::exit(1);
+ }
+
+ chunks
+ .map(|chunk| match <[u8; mem::size_of::<u32>()]>::try_from(chunk) {
+ Ok(bytes) => u32::from_le_bytes(bytes) as u64,
+ Err(_) => {
+ eprintln!("acpid: failed to decode RSDT physical address entry");
+ process::exit(1);
+ }
+ })
+ .collect()
+ }
+ b"XSDT" => {
+ let chunks = sdt.data().chunks_exact(mem::size_of::<u64>());
+ if !chunks.remainder().is_empty() {
+ eprintln!(
+ "acpid: malformed XSDT length {}: expected 8-byte entries",
+ sdt.data().len()
+ );
+ process::exit(1);
+ }
+
+ chunks
+ .map(|chunk| match <[u8; mem::size_of::<u64>()]>::try_from(chunk) {
+ Ok(bytes) => u64::from_le_bytes(bytes),
+ Err(_) => {
+ eprintln!("acpid: failed to decode XSDT physical address entry");
+ process::exit(1);
+ }
+ })
+ .collect()
+ }
+ signature => {
+ eprintln!(
+ "acpid: expected [RX]SDT from kernel, got {:?}",
+ String::from_utf8_lossy(signature)
+ );
+ process::exit(1);
+ }
+ }
+}
+
fn daemon(daemon: daemon::Daemon) -> ! {
common::setup_logging(
"misc",
@@ -29,7 +82,10 @@ fn daemon(daemon: daemon::Daemon) -> ! {
log::info!("acpid start");
let rxsdt_raw_data: Arc<[u8]> = std::fs::read("/scheme/kernel.acpi/rxsdt")
- .expect("acpid: failed to read `/scheme/kernel.acpi/rxsdt`")
+ .unwrap_or_else(|err| {
+ eprintln!("acpid: failed to read `/scheme/kernel.acpi/rxsdt`: {err}");
+ process::exit(1);
+ })
.into();
if rxsdt_raw_data.is_empty() {
@@ -38,84 +94,84 @@ fn daemon(daemon: daemon::Daemon) -> ! {
std::process::exit(0);
}
- let sdt = self::acpi::Sdt::new(rxsdt_raw_data).expect("acpid: failed to parse [RX]SDT");
-
- let mut thirty_two_bit;
- let mut sixty_four_bit;
-
- let physaddrs_iter = 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);
-
- &mut thirty_two_bit as &mut dyn Iterator<Item = u64>
- }
- 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));
-
- &mut sixty_four_bit as &mut dyn Iterator<Item = u64>
- }
- _ => panic!("acpid: expected [RX]SDT from kernel to be either of those"),
- };
+ let sdt = self::acpi::Sdt::new(rxsdt_raw_data).unwrap_or_else(|err| {
+ eprintln!("acpid: failed to parse [RX]SDT: {err}");
+ process::exit(1);
+ });
+ let physaddrs = parse_physaddrs(&sdt);
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 acpi_context = self::acpi::AcpiContext::init(physaddrs.into_iter(), region_handlers);
// 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().unwrap_or_else(|err| {
+ eprintln!("acpid: failed to set I/O privilege level to Ring 3: {err}");
+ process::exit(1);
+ });
let shutdown_pipe = File::open("/scheme/kernel.acpi/kstop")
- .expect("acpid: failed to open `/scheme/kernel.acpi/kstop`");
-
- let mut event_queue = RawEventQueue::new().expect("acpid: failed to create event queue");
- let socket = Socket::nonblock().expect("acpid: failed to create disk scheme");
+ .unwrap_or_else(|err| {
+ eprintln!("acpid: failed to open `/scheme/kernel.acpi/kstop`: {err}");
+ process::exit(1);
+ });
+
+ let mut event_queue = RawEventQueue::new().unwrap_or_else(|err| {
+ eprintln!("acpid: failed to create event queue: {err}");
+ process::exit(1);
+ });
+ let socket = Socket::nonblock().unwrap_or_else(|err| {
+ eprintln!("acpid: failed to create disk scheme: {err}");
+ process::exit(1);
+ });
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");
+ .unwrap_or_else(|err| {
+ eprintln!("acpid: failed to register shutdown pipe for event queue: {err}");
+ process::exit(1);
+ });
event_queue
.subscribe(socket.inner().raw(), 1, EventFlags::READ)
- .expect("acpid: failed to register scheme socket for event queue");
+ .unwrap_or_else(|err| {
+ eprintln!("acpid: failed to register scheme socket for event queue: {err}");
+ process::exit(1);
+ });
register_sync_scheme(&socket, "acpi", &mut scheme)
- .expect("acpid: failed to register acpi scheme to namespace");
+ .unwrap_or_else(|err| {
+ eprintln!("acpid: failed to register acpi scheme to namespace: {err}");
+ process::exit(1);
+ });
daemon.ready();
- libredox::call::setrens(0, 0).expect("acpid: failed to enter null namespace");
+ libredox::call::setrens(0, 0).unwrap_or_else(|err| {
+ eprintln!("acpid: failed to enter null namespace: {err}");
+ process::exit(1);
+ });
let mut mounted = true;
while mounted {
- let Some(event) = event_queue
- .next()
- .transpose()
- .expect("acpid: failed to read event file")
- else {
+ let Some(event) = event_queue.next().transpose().unwrap_or_else(|err| {
+ eprintln!("acpid: failed to read event file: {err}");
+ process::exit(1);
+ }) else {
break;
};
if event.fd == socket.inner().raw() {
loop {
- match handler
- .process_requests_nonblocking(&mut scheme)
- .expect("acpid: failed to process requests")
- {
+ match handler.process_requests_nonblocking(&mut scheme).unwrap_or_else(|err| {
+ eprintln!("acpid: failed to process requests: {err}");
+ process::exit(1);
+ }) {
ControlFlow::Continue(()) => {}
ControlFlow::Break(()) => break,
}
@@ -134,7 +190,8 @@ fn daemon(daemon: daemon::Daemon) -> ! {
acpi_context.set_global_s_state(5);
- unreachable!("System should have shut down before this is entered");
+ eprintln!("acpid: system did not shut down after requesting S5");
+ process::exit(1);
}
fn main() {
diff --git a/drivers/pcid/src/main.rs b/drivers/pcid/src/main.rs
index 61cd9a78..18ee18ab 100644
--- a/drivers/pcid/src/main.rs
+++ b/drivers/pcid/src/main.rs
@@ -3,6 +3,7 @@
#![feature(non_exhaustive_omitted_patterns_lint)]
use std::collections::BTreeMap;
+use std::process;
use log::{debug, info, trace, warn};
use pci_types::capability::PciCapability;
@@ -42,7 +43,16 @@ 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 port.try_into() {
+ Ok(port) => bars[i as usize] = PciBar::Port(port),
+ Err(_) => {
+ warn!(
+ "pcid: skipping invalid I/O BAR port {port:#x} on {}",
+ endpoint_header.header().address()
+ );
+ bars[i as usize] = PciBar::None;
+ }
+ },
Some(TyBar::Memory32 {
address,
size,
@@ -251,7 +261,10 @@ 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 = redox_scheme::Socket::create().unwrap_or_else(|err| {
+ eprintln!("pcid: failed to open pci scheme socket: {err}");
+ process::exit(1);
+ });
let handler = Blocking::new(&socket, 16);
{
@@ -259,17 +272,27 @@ fn daemon(daemon: daemon::Daemon) -> ! {
Ok(register_pci) => {
let access_id = scheme.access();
- let access_fd = 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");
+ match socket.create_this_scheme_fd(0, access_id, syscall::O_RDWR, 0) {
+ Ok(access_fd) => {
+ let access_bytes = access_fd.to_ne_bytes();
+ if let Err(err) = register_pci.call_wo(
+ &access_bytes,
+ syscall::CallFlags::WRITE | syscall::CallFlags::FD,
+ &[],
+ ) {
+ warn!(
+ "pcid: failed to send pci_fd to acpid (error: {}). Running without ACPI integration.",
+ err
+ );
+ }
+ }
+ Err(err) => {
+ warn!(
+ "pcid: failed to issue acpid registration resource (error: {}). Running without ACPI integration.",
+ err
+ );
+ }
+ }
}
Err(err) => {
if err.errno() == libredox::errno::ENODEV {
@@ -305,13 +328,20 @@ 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");
+ .unwrap_or_else(|err| {
+ eprintln!("pcid: failed to register pci scheme to namespace: {err}");
+ process::exit(1);
+ });
let _ = daemon.ready();
- handler
- .process_requests_blocking(scheme)
- .expect("pcid: failed to process requests");
+ match handler.process_requests_blocking(scheme) {
+ Ok(never) => match never {},
+ Err(err) => {
+ eprintln!("pcid: failed to process requests: {err}");
+ process::exit(1);
+ }
+ }
}
fn scan_device(
@@ -323,6 +353,7 @@ fn scan_device(
) {
for func_num in 0..8 {
let header = TyPciHeader::new(PciAddress::new(0, bus_num, dev_num, func_num));
+ let header_address = header.address();
let (vendor_id, device_id) = header.id(pcie);
if vendor_id == 0xffff && device_id == 0xffff {
@@ -344,21 +375,40 @@ fn scan_device(
revision,
};
- info!("PCI {} {}", header.address(), full_device_id.display());
+ info!("PCI {} {}", header_address, full_device_id.display());
let has_multiple_functions = header.has_multiple_functions(pcie);
match header.header_type(pcie) {
HeaderType::Endpoint => {
+ let endpoint_header = match EndpointHeader::from_header(header, pcie) {
+ Some(endpoint_header) => endpoint_header,
+ None => {
+ warn!(
+ "pcid: failed to parse endpoint header for {}",
+ header_address,
+ );
+ continue;
+ }
+ };
handle_parsed_header(
pcie,
tree,
- EndpointHeader::from_header(header, pcie).unwrap(),
+ endpoint_header,
full_device_id,
);
}
HeaderType::PciPciBridge => {
- let bridge_header = PciPciBridgeHeader::from_header(header, pcie).unwrap();
+ let bridge_header = match PciPciBridgeHeader::from_header(header, pcie) {
+ Some(bridge_header) => bridge_header,
+ None => {
+ warn!(
+ "pcid: failed to parse bridge header for {}",
+ header_address,
+ );
+ continue;
+ }
+ };
bus_nums.push(bridge_header.secondary_bus_number(pcie));
}
ty => {
diff --git a/init/src/main.rs b/init/src/main.rs
index 5682cf44..cd270a6e 100644
--- a/init/src/main.rs
+++ b/init/src/main.rs
@@ -1,6 +1,7 @@
use std::collections::BTreeMap;
use std::ffi::OsString;
use std::path::Path;
+use std::time::Duration;
use std::{env, fs, io};
use libredox::flag::{O_RDONLY, O_WRONLY};
@@ -166,19 +167,36 @@ fn main() {
}
};
for entry in entries {
+ let Some(unit_name) = entry.file_name().and_then(|name| name.to_str()) else {
+ eprintln!(
+ "init: skipping config entry with invalid filename: {}",
+ entry.display()
+ );
+ continue;
+ };
scheduler.schedule_start_and_report_errors(
&mut unit_store,
- UnitId(entry.file_name().unwrap().to_str().unwrap().to_owned()),
+ UnitId(unit_name.to_owned()),
);
}
};
scheduler.step(&mut unit_store, &mut init_config);
- libredox::call::setrens(0, 0).expect("init: failed to enter null namespace");
+ if let Err(err) = libredox::call::setrens(0, 0) {
+ eprintln!("init: failed to enter null namespace: {err}");
+ std::process::exit(1);
+ }
loop {
let mut status = 0;
- libredox::call::waitpid(0, &mut status, 0).unwrap();
+ match libredox::call::waitpid(0, &mut status, 0) {
+ Ok(_) => {}
+ Err(err) if err.errno() == libredox::errno::EINTR => continue,
+ Err(err) => {
+ eprintln!("init: waitpid failed: {err}");
+ std::thread::sleep(Duration::from_millis(100));
+ }
+ }
}
}
diff --git a/init/src/scheduler.rs b/init/src/scheduler.rs
index d42a4e57..3b8d10b0 100644
--- a/init/src/scheduler.rs
+++ b/init/src/scheduler.rs
@@ -43,7 +43,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() {
+ if unit_store
+ .try_unit(&unit_id)
+ .is_ok_and(|unit| !unit.conditions_met())
+ {
continue;
}
@@ -62,7 +65,10 @@ impl Scheduler {
match job.kind {
JobKind::Start => {
- let unit = unit_store.unit_mut(&job.unit);
+ let Ok(unit) = unit_store.try_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 {
diff --git a/init/src/service.rs b/init/src/service.rs
index ed0023e9..827ae275 100644
--- a/init/src/service.rs
+++ b/init/src/service.rs
@@ -3,13 +3,24 @@ use std::ffi::OsString;
use std::io::Read;
use std::os::fd::{AsRawFd, OwnedFd};
use std::os::unix::process::CommandExt;
-use std::process::Command;
+use std::process::{Child, Command};
use std::{env, io};
use serde::Deserialize;
use crate::script::subst_env;
+fn terminate_child(child: &mut Child, command: &str) {
+ if let Err(err) = child.kill() {
+ if err.kind() != io::ErrorKind::InvalidInput {
+ eprintln!("init: failed to terminate {command}: {err}");
+ }
+ }
+ if let Err(err) = child.wait() {
+ eprintln!("init: failed to reap {command}: {err}");
+ }
+}
+
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Service {
@@ -37,7 +48,8 @@ pub enum ServiceType {
impl Service {
pub fn spawn(&self, base_envs: &BTreeMap<String, OsString>) {
let mut command = Command::new(&self.cmd);
- command.args(self.args.iter().map(|arg| subst_env(arg)));
+ let resolved_args: Vec<String> = self.args.iter().map(|arg| subst_env(arg)).collect();
+ command.args(&resolved_args);
command.env_clear();
for env in &self.inherit_envs {
if let Some(value) = env::var_os(env) {
@@ -45,14 +57,25 @@ impl Service {
}
}
command.envs(base_envs).envs(&self.envs);
+ let command_display = if resolved_args.is_empty() {
+ self.cmd.clone()
+ } else {
+ format!("{} {}", self.cmd, resolved_args.join(" "))
+ };
- let (mut read_pipe, write_pipe) = io::pipe().unwrap();
+ let (mut read_pipe, write_pipe) = match io::pipe().map_err(|err| {
+ eprintln!("init: failed to create readiness pipe for {command_display}: {err}");
+ err
+ }) {
+ Ok(pair) => pair,
+ Err(_) => return,
+ };
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);
+ eprintln!("init: failed to execute {command_display}: {err}");
return;
}
};
@@ -61,10 +84,10 @@ impl Service {
ServiceType::Notify => match read_pipe.read_exact(&mut [0]) {
Ok(()) => {}
Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => {
- eprintln!("init: {command:?} exited without notifying readiness");
+ eprintln!("init: {command_display} exited without notifying readiness");
}
Err(err) => {
- eprintln!("init: failed to wait for {command:?}: {err}");
+ eprintln!("init: failed to wait for {command_display}: {err}");
}
},
ServiceType::Scheme(scheme) => {
@@ -80,7 +103,7 @@ impl Service {
errno: syscall::EINTR,
}) => continue,
Ok(0) => {
- eprintln!("init: {command:?} exited without notifying readiness");
+ eprintln!("init: {command_display} exited without notifying readiness");
return;
}
Ok(1) => break,
@@ -89,26 +112,40 @@ impl Service {
return;
}
Err(err) => {
- eprintln!("init: failed to wait for {command:?}: {err}");
+ eprintln!("init: failed to wait for {command_display}: {err}");
return;
}
}
}
- 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_display}: {err}");
+ terminate_child(&mut child, &command_display);
+ return;
+ }
+ };
+ 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_display}: {err}"
+ );
+ terminate_child(&mut child, &command_display);
+ return;
+ }
}
ServiceType::Oneshot => {
drop(read_pipe);
match child.wait() {
Ok(exit_status) => {
if !exit_status.success() {
- eprintln!("init: {command:?} failed with {exit_status}");
+ eprintln!("init: {command_display} failed with {exit_status}");
}
}
Err(err) => {
- eprintln!("init: failed to wait for {:?}: {}", command, err)
+ eprintln!("init: failed to wait for {command_display}: {err}")
}
}
}
diff --git a/init/src/unit.rs b/init/src/unit.rs
index 98053cb2..bd998394 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,15 @@ 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());
+ match self.try_unit(&unit) {
+ Ok(unit) => {
+ for dep in &unit.info.requires_weak {
+ pending_units.push(dep.clone());
+ }
+ }
+ Err(err) => {
+ errors.push(err);
+ }
}
}
}
@@ -94,12 +107,34 @@ impl UnitStore {
loaded_units
}
+ pub fn try_unit(&self, unit: &UnitId) -> Result<&Unit, String> {
+ self.units
+ .get(unit)
+ .ok_or_else(|| format!("unit {} not found in store", unit.0))
+ }
+
+ // Keep the legacy infallible accessors for compatibility while scheduler/load paths
+ // use the fallible helpers to avoid panicking on missing units.
+ #[allow(dead_code)]
pub fn unit(&self, unit: &UnitId) -> &Unit {
- self.units.get(unit).unwrap()
+ self.try_unit(unit).unwrap_or_else(|err| {
+ eprintln!("init: {err}");
+ std::process::exit(1);
+ })
+ }
+
+ pub fn try_unit_mut(&mut self, unit: &UnitId) -> Result<&mut Unit, String> {
+ self.units
+ .get_mut(unit)
+ .ok_or_else(|| format!("unit {} not found in store", unit.0))
}
+ #[allow(dead_code)]
pub fn unit_mut(&mut self, unit: &UnitId) -> &mut Unit {
- self.units.get_mut(unit).unwrap()
+ self.try_unit_mut(unit).unwrap_or_else(|err| {
+ eprintln!("init: {err}");
+ std::process::exit(1);
+ })
}
}
@@ -180,7 +215,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,
@@ -1,633 +0,0 @@
diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs
index 94a1eb17e..3521bfc7b 100644
--- a/drivers/acpid/src/acpi.rs
+++ b/drivers/acpid/src/acpi.rs
@@ -136,9 +136,10 @@ impl Sdt {
let header = match plain::from_bytes::<SdtHeader>(&slice) {
Ok(header) => header,
Err(plain::Error::TooShort) => return Err(InvalidSdtError::InvalidSize),
- Err(plain::Error::BadAlignment) => panic!(
- "plain::from_bytes failed due to alignment, but SdtHeader is #[repr(packed)]!"
- ),
+ Err(plain::Error::BadAlignment) => {
+ log::error!("acpid: plain::from_bytes failed due to alignment, but SdtHeader is #[repr(packed)] - internal inconsistency");
+ return Err(InvalidSdtError::InvalidSize);
+ }
};
if header.length() != slice.len() {
diff --git a/drivers/acpid/src/main.rs b/drivers/acpid/src/main.rs
index 059254b3e..8f99f2ea9 100644
--- a/drivers/acpid/src/main.rs
+++ b/drivers/acpid/src/main.rs
@@ -28,9 +28,13 @@ fn daemon(daemon: daemon::Daemon) -> ! {
log::info!("acpid start");
- let rxsdt_raw_data: Arc<[u8]> = std::fs::read("/scheme/kernel.acpi/rxsdt")
- .expect("acpid: failed to read `/scheme/kernel.acpi/rxsdt`")
- .into();
+ let rxsdt_raw_data: Arc<[u8]> = match std::fs::read("/scheme/kernel.acpi/rxsdt") {
+ Ok(data) => data.into(),
+ Err(err) => {
+ log::error!("acpid: failed to read `/scheme/kernel.acpi/rxsdt`: {}", err);
+ std::process::exit(1);
+ }
+ };
if rxsdt_raw_data.is_empty() {
log::info!("System doesn't use ACPI");
@@ -38,7 +42,13 @@ fn daemon(daemon: daemon::Daemon) -> ! {
std::process::exit(0);
}
- let sdt = self::acpi::Sdt::new(rxsdt_raw_data).expect("acpid: failed to parse [RX]SDT");
+ let sdt = match self::acpi::Sdt::new(rxsdt_raw_data) {
+ Ok(sdt) => sdt,
+ Err(err) => {
+ log::error!("acpid: failed to parse [RX]SDT: {:?}", err);
+ std::process::exit(1);
+ }
+ };
let mut thirty_two_bit;
let mut sixty_four_bit;
@@ -64,7 +74,10 @@ fn daemon(daemon: daemon::Daemon) -> ! {
&mut sixty_four_bit as &mut dyn Iterator<Item = u64>
}
- _ => panic!("acpid: expected [RX]SDT from kernel to be either of those"),
+ _ => {
+ log::error!("acpid: expected [RX]SDT from kernel to be RSDT or XSDT");
+ std::process::exit(1);
+ }
};
let region_handlers: Vec<(RegionSpace, Box<dyn RegionHandler + 'static>)> = vec![
@@ -75,49 +88,84 @@ fn daemon(daemon: daemon::Daemon) -> ! {
// 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");
+ if let Err(err) = common::acquire_port_io_rights() {
+ log::error!("acpid: failed to set I/O privilege level to Ring 3: {:?}", err);
+ std::process::exit(1);
+ }
- let shutdown_pipe = File::open("/scheme/kernel.acpi/kstop")
- .expect("acpid: failed to open `/scheme/kernel.acpi/kstop`");
+ let shutdown_pipe = match File::open("/scheme/kernel.acpi/kstop") {
+ Ok(f) => f,
+ Err(err) => {
+ log::error!("acpid: failed to open `/scheme/kernel.acpi/kstop`: {}", err);
+ std::process::exit(1);
+ }
+ };
- 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 = match RawEventQueue::new() {
+ Ok(q) => q,
+ Err(err) => {
+ log::error!("acpid: failed to create event queue: {:?}", err);
+ std::process::exit(1);
+ }
+ };
+ let socket = match Socket::nonblock() {
+ Ok(s) => s,
+ Err(err) => {
+ log::error!("acpid: failed to create scheme socket: {:?}", err);
+ std::process::exit(1);
+ }
+ };
let mut scheme = self::scheme::AcpiScheme::new(&acpi_context, &socket);
let mut handler = Blocking::new(&socket, 16);
- event_queue
+ if let Err(err) = event_queue
.subscribe(shutdown_pipe.as_raw_fd() as usize, 0, EventFlags::READ)
- .expect("acpid: failed to register shutdown pipe for event queue");
- event_queue
+ {
+ log::error!("acpid: failed to register shutdown pipe for event queue: {:?}", err);
+ std::process::exit(1);
+ }
+ if let Err(err) = event_queue
.subscribe(socket.inner().raw(), 1, EventFlags::READ)
- .expect("acpid: failed to register scheme socket for event queue");
+ {
+ log::error!("acpid: failed to register scheme socket for event queue: {:?}", err);
+ std::process::exit(1);
+ }
- register_sync_scheme(&socket, "acpi", &mut scheme)
- .expect("acpid: failed to register acpi scheme to namespace");
+ if let Err(err) = register_sync_scheme(&socket, "acpi", &mut scheme) {
+ log::error!("acpid: failed to register acpi scheme to namespace: {:?}", err);
+ std::process::exit(1);
+ }
daemon.ready();
- libredox::call::setrens(0, 0).expect("acpid: failed to enter null namespace");
+ if let Err(err) = libredox::call::setrens(0, 0) {
+ log::error!("acpid: failed to enter null namespace: {}", err);
+ std::process::exit(1);
+ }
let mut mounted = true;
while mounted {
- let Some(event) = event_queue
- .next()
- .transpose()
- .expect("acpid: failed to read event file")
- else {
- break;
+ let event = match event_queue.next().transpose() {
+ Ok(Some(ev)) => ev,
+ Ok(None) => break,
+ Err(err) => {
+ log::error!("acpid: failed to read event file: {:?}", err);
+ break;
+ }
};
if event.fd == socket.inner().raw() {
loop {
- match handler
- .process_requests_nonblocking(&mut scheme)
- .expect("acpid: failed to process requests")
- {
- ControlFlow::Continue(()) => {}
- ControlFlow::Break(()) => break,
+ match handler.process_requests_nonblocking(&mut scheme) {
+ Ok(flow) => match flow {
+ ControlFlow::Continue(()) => {}
+ ControlFlow::Break(()) => break,
+ },
+ Err(err) => {
+ log::error!("acpid: failed to process requests: {:?}", err);
+ break;
+ }
}
}
} else if event.fd == shutdown_pipe.as_raw_fd() as usize {
diff --git a/drivers/pcid/src/main.rs b/drivers/pcid/src/main.rs
index 61cd9a787..cad33114b 100644
--- a/drivers/pcid/src/main.rs
+++ b/drivers/pcid/src/main.rs
@@ -4,7 +4,7 @@
use std::collections::BTreeMap;
-use log::{debug, info, trace, warn};
+use log::{debug, error, info, trace, warn};
use pci_types::capability::PciCapability;
use pci_types::{
Bar as TyBar, CommandRegister, EndpointHeader, HeaderType, PciAddress,
@@ -259,17 +259,25 @@ 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(
+ {
+ Ok(fd) => Some(fd),
+ Err(err) => {
+ warn!("pcid: failed to issue acpi resource fd: {:?}", err);
+ None
+ }
+ };
+ if let Some(access_fd) = access_fd {
+ let access_bytes = access_fd.to_ne_bytes();
+ if let Err(err) = register_pci.call_wo(
&access_bytes,
syscall::CallFlags::WRITE | syscall::CallFlags::FD,
&[],
- )
- .expect("failed to send pci_fd to acpid");
+ ) {
+ warn!("pcid: failed to send pci_fd to acpid: {:?}", err);
+ }
+ }
}
Err(err) => {
if err.errno() == libredox::errno::ENODEV {
@@ -304,14 +312,17 @@ 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) {
+ 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");
+ handler.process_requests_blocking(scheme).unwrap_or_else(|err| {
+ error!("pcid: failed to process requests: {:?}", err);
+ std::process::exit(1);
+ });
}
fn scan_device(
diff --git a/init/src/main.rs b/init/src/main.rs
index 5682cf445..72c97f53c 100644
--- a/init/src/main.rs
+++ b/init/src/main.rs
@@ -166,19 +166,29 @@ fn main() {
}
};
for entry in entries {
+ let Some(file_name) = entry.file_name().and_then(|n| n.to_str()) else {
+ eprintln!("init: skipping entry with invalid filename: {}", entry.display());
+ continue;
+ };
scheduler.schedule_start_and_report_errors(
&mut unit_store,
- UnitId(entry.file_name().unwrap().to_str().unwrap().to_owned()),
+ UnitId(file_name.to_owned()),
);
}
};
scheduler.step(&mut unit_store, &mut init_config);
- libredox::call::setrens(0, 0).expect("init: failed to enter null namespace");
+ if let Err(err) = libredox::call::setrens(0, 0) {
+ eprintln!("init: failed to enter null namespace: {}", err);
+ return;
+ }
loop {
let mut status = 0;
- libredox::call::waitpid(0, &mut status, 0).unwrap();
+ match libredox::call::waitpid(0, &mut status, 0) {
+ Ok(()) => {}
+ Err(err) => eprintln!("init: waitpid error: {}", err),
+ }
}
}
diff --git a/init/src/scheduler.rs b/init/src/scheduler.rs
index d42a4e570..333e0e20e 100644
--- a/init/src/scheduler.rs
+++ b/init/src/scheduler.rs
@@ -1,7 +1,16 @@
use std::collections::VecDeque;
+use std::io::Read;
+use std::os::fd::AsRawFd;
+use std::os::unix::process::CommandExt;
+use std::process::Command;
+use std::time::Duration;
+use std::{env, io};
use crate::InitConfig;
-use crate::unit::{Unit, UnitId, UnitKind, UnitStore};
+use crate::service::ServiceType;
+use crate::unit::{RestartPolicy, UnitId, UnitKind, UnitStore};
+
+const MAX_DEPENDENCY_WAIT_RETRIES: u32 = 1000;
pub struct Scheduler {
pending: VecDeque<Job>,
@@ -10,10 +19,12 @@ pub struct Scheduler {
struct Job {
unit: UnitId,
kind: JobKind,
+ dep_retries: u32,
}
enum JobKind {
Start,
+ Restart { backoff: Duration },
}
impl Scheduler {
@@ -50,37 +61,97 @@ impl Scheduler {
self.pending.push_back(Job {
unit: unit_id,
kind: JobKind::Start,
+ dep_retries: 0,
});
}
}
pub fn step(&mut self, unit_store: &mut UnitStore, init_config: &mut InitConfig) {
'a: loop {
- let Some(job) = self.pending.pop_front() else {
+ let Some(mut job) = self.pending.pop_front() else {
return;
};
match job.kind {
JobKind::Start => {
- let unit = unit_store.unit_mut(&job.unit);
+ let unit = unit_store.unit(&job.unit);
+ let timeout_secs = unit.info.dependency_timeout_secs;
+ let mut deps_pending = false;
for dep in &unit.info.requires_weak {
for pending_job in &self.pending {
if &pending_job.unit == dep {
- self.pending.push_back(job);
- continue 'a;
+ deps_pending = true;
+ break;
}
}
+ if deps_pending {
+ break;
+ }
}
- run(unit, init_config);
+ if deps_pending {
+ if timeout_secs > 0 {
+ job.dep_retries += 1;
+ let max_retries = timeout_secs * 100; // ~10ms per retry
+ if job.dep_retries > max_retries as u32 {
+ eprintln!(
+ "init: {}: dependency timeout after {}s, failing",
+ job.unit.0, timeout_secs
+ );
+ continue;
+ }
+ } else if job.dep_retries >= MAX_DEPENDENCY_WAIT_RETRIES {
+ eprintln!(
+ "init: {}: dependency wait exceeded {} retries, failing",
+ job.unit.0, MAX_DEPENDENCY_WAIT_RETRIES
+ );
+ continue;
+ }
+ job.dep_retries += 1;
+ self.pending.push_back(job);
+ continue 'a;
+ }
+
+ if let Err(restart) = run(unit_store, &job.unit, init_config) {
+ if let Some(backoff) = restart {
+ self.pending.push_back(Job {
+ unit: job.unit.clone(),
+ kind: JobKind::Restart { backoff },
+ dep_retries: 0,
+ });
+ }
+ }
+ }
+ JobKind::Restart { backoff } => {
+ std::thread::sleep(backoff);
+ let next_backoff = (backoff * 2).min(Duration::from_secs(60));
+ if let Err(restart) = run(unit_store, &job.unit, init_config) {
+ if let Some(_next) = restart {
+ self.pending.push_back(Job {
+ unit: job.unit,
+ kind: JobKind::Restart {
+ backoff: next_backoff,
+ },
+ dep_retries: 0,
+ });
+ }
+ }
}
}
}
}
}
-fn run(unit: &mut Unit, config: &mut InitConfig) {
+fn run(
+ unit_store: &UnitStore,
+ unit_id: &UnitId,
+ config: &mut InitConfig,
+) -> Result<(), Option<Duration>> {
+ let unit = unit_store.unit(unit_id);
+
+ let restart_policy = unit.info.restart;
+
match &unit.kind {
UnitKind::LegacyScript { script } => {
for cmd in script.clone() {
@@ -89,11 +160,12 @@ fn run(unit: &mut Unit, config: &mut InitConfig) {
}
cmd.run(config);
}
+ Ok(())
}
UnitKind::Service { service } => {
if config.skip_cmd.contains(&service.cmd) {
eprintln!("Skipping '{} {}'", service.cmd, service.args.join(" "));
- return;
+ return Ok(());
}
if config.log_debug {
eprintln!(
@@ -102,7 +174,44 @@ fn run(unit: &mut Unit, config: &mut InitConfig) {
service.cmd,
);
}
- service.spawn(&config.envs);
+
+ let mut command = Command::new(&service.cmd);
+ command.args(&service.args);
+ command.env_clear();
+ for env in &service.inherit_envs {
+ if let Some(value) = env::var_os(env) {
+ command.env(env, value);
+ }
+ }
+ command.envs(config.envs.iter().map(|(k, v)| (k.as_str(), v.as_os_str())));
+
+ let (read_pipe, write_pipe) = match io::pipe() {
+ Ok(p) => p,
+ Err(err) => {
+ eprintln!("init: pipe failed for {}: {}", service.cmd, err);
+ return Err(restart_signal(restart_policy));
+ }
+ };
+
+ let write_fd: std::os::fd::OwnedFd = write_pipe.into();
+ unsafe {
+ command.env("INIT_NOTIFY", format!("{}", write_fd.as_raw_fd()));
+ command.pre_exec(move || {
+ if unsafe { libc::fcntl(write_fd.as_raw_fd(), libc::F_SETFD, 0) } == -1 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(())
+ }
+ });
+ }
+
+ let status = service_spawn_status(read_pipe, command, &service.type_, &service.cmd);
+
+ match status {
+ SpawnStatus::Success => Ok(()),
+ SpawnStatus::Failed => Err(restart_signal(restart_policy)),
+ SpawnStatus::Async => Ok(()),
+ }
}
UnitKind::Target {} => {
if config.log_debug {
@@ -111,6 +220,113 @@ fn run(unit: &mut Unit, config: &mut InitConfig) {
unit.info.description.as_ref().unwrap_or(&unit.id.0),
);
}
+ Ok(())
+ }
+ }
+}
+
+enum SpawnStatus {
+ Success,
+ Failed,
+ Async,
+}
+
+fn restart_signal(policy: RestartPolicy) -> Option<Duration> {
+ match policy {
+ RestartPolicy::No => None,
+ RestartPolicy::OnFailure | RestartPolicy::Always => Some(Duration::from_secs(1)),
+ }
+}
+
+fn service_spawn_status(
+ mut read_pipe: impl Read + AsRawFd,
+ mut command: Command,
+ service_type: &ServiceType,
+ cmd: &str,
+) -> SpawnStatus {
+ let mut child = match command.spawn() {
+ Ok(child) => child,
+ Err(err) => {
+ eprintln!("init: failed to execute {}: {}", cmd, err);
+ return SpawnStatus::Failed;
+ }
+ };
+
+ match service_type {
+ ServiceType::Notify => match read_pipe.read_exact(&mut [0]) {
+ Ok(()) => SpawnStatus::Success,
+ Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => {
+ eprintln!("init: {cmd} exited without notifying readiness");
+ SpawnStatus::Failed
+ }
+ Err(err) => {
+ eprintln!("init: failed to wait for {cmd}: {err}");
+ SpawnStatus::Failed
+ }
+ },
+ ServiceType::Scheme(scheme) => {
+ let scheme = scheme.clone();
+ let mut new_fd = usize::MAX;
+ let res = loop {
+ match syscall::call_ro(
+ read_pipe.as_raw_fd() as usize,
+ unsafe { plain::as_mut_bytes(&mut new_fd) },
+ syscall::CallFlags::FD | syscall::CallFlags::FD_UPPER,
+ &[],
+ ) {
+ Err(syscall::Error {
+ errno: syscall::EINTR,
+ }) => continue,
+ Ok(0) => break SpawnStatus::Failed,
+ Ok(1) => break SpawnStatus::Success,
+ Ok(n) => {
+ eprintln!("init: incorrect amount of fds {n} returned from {cmd}");
+ break SpawnStatus::Failed;
+ }
+ Err(err) => {
+ eprintln!("init: failed to wait for {cmd}: {err}");
+ break SpawnStatus::Failed;
+ }
+ }
+ };
+
+ if matches!(res, SpawnStatus::Success) {
+ match libredox::call::getns() {
+ Ok(current_namespace_fd) => {
+ if let Err(err) = libredox::call::register_scheme_to_ns(
+ current_namespace_fd,
+ &scheme,
+ new_fd,
+ ) {
+ eprintln!("init: scheme registration failed for {cmd}: {err}");
+ return SpawnStatus::Failed;
+ }
+ }
+ Err(err) => {
+ eprintln!("init: getns failed for {cmd}: {err}");
+ return SpawnStatus::Failed;
+ }
+ }
+ }
+ res
+ }
+ ServiceType::Oneshot => {
+ drop(read_pipe);
+ match child.wait() {
+ Ok(exit_status) => {
+ if !exit_status.success() {
+ eprintln!("init: {cmd} failed with {exit_status}");
+ SpawnStatus::Failed
+ } else {
+ SpawnStatus::Success
+ }
+ }
+ Err(err) => {
+ eprintln!("init: failed to wait for {cmd}: {err}");
+ SpawnStatus::Failed
+ }
+ }
}
+ ServiceType::OneshotAsync => SpawnStatus::Async,
}
}
diff --git a/init/src/unit.rs b/init/src/unit.rs
index 98053cb2d..414b92d17 100644
--- a/init/src/unit.rs
+++ b/init/src/unit.rs
@@ -125,6 +125,25 @@ pub struct UnitInfo {
pub condition_architecture: Option<Vec<String>>,
// FIXME replace this with hwd reading from the devicetree
pub condition_board: Option<Vec<String>>,
+ /// Restart policy for the service (only applies to Service units)
+ #[serde(default)]
+ pub restart: RestartPolicy,
+ /// Maximum time in seconds to wait for dependencies before failing (0 = no timeout)
+ #[serde(default)]
+ pub dependency_timeout_secs: u64,
+}
+
+/// Restart policy for managed services
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Default)]
+#[serde(rename_all = "kebab-case")]
+pub enum RestartPolicy {
+ /// Never restart the service (default)
+ #[default]
+ No,
+ /// Restart on failure (non-zero exit or crash)
+ OnFailure,
+ /// Always restart (on any exit)
+ Always,
}
fn true_bool() -> bool {
@@ -190,6 +209,8 @@ impl Unit {
requires_weak: script.1,
condition_architecture: None,
condition_board: None,
+ restart: RestartPolicy::No,
+ dependency_timeout_secs: 0,
},
kind: UnitKind::LegacyScript { script: script.0 },
});
+170 -177
View File
@@ -1,177 +1,170 @@
0a1,176
> use std::env;
> use std::fs;
> use std::io::{Read, Write};
> use std::thread;
> use std::time::{Duration, Instant};
> use log::{info, warn, error, LevelFilter};
>
> const IA32_PERF_CTL: u32 = 0x199;
> const IA32_PERF_STATUS: u32 = 0x198;
> const POLL_INTERVAL_MS: u64 = 100;
>
> struct StderrLogger;
> impl log::Log for StderrLogger {
> fn enabled(&self, m: &log::Metadata) -> bool { m.level() <= LevelFilter::Info }
> fn log(&self, r: &log::Record) { eprintln!("[{}] cpufreqd: {}", r.level(), r.args()); }
> fn flush(&self) {}
> }
>
> #[derive(Clone, Copy, PartialEq)]
> enum Governor { Performance, Powersave, Ondemand }
>
> struct PState { freq_mhz: u32, power_mw: u32, latency_us: u32, ctl_value: u64 }
>
> struct CpuState { id: u32, current_pstate: usize, load: f64 }
>
> fn read_msr(cpu: u32, msr: u32) -> Option<u64> {
> let path = format!("/dev/cpu/{}/msr", cpu);
> let mut f = fs::OpenOptions::new().read(true).open(&path).ok()?;
> let mut buf = [0u8; 8];
> f.read_exact(&mut buf).ok()?;
> Some(u64::from_ne_bytes(buf))
> }
>
> fn write_msr(cpu: u32, msr: u32, value: u64) -> bool {
> let path = format!("/dev/cpu/{}/msr", cpu);
> fs::OpenOptions::new().write(true).open(&path)
> .and_then(|mut f| f.write_all(&value.to_ne_bytes()))
> .is_ok()
> }
>
> fn read_acpi_pss(cpu: u32) -> Vec<PState> {
> let path = format!("/scheme/acpi/processor/CPU{}/pss", cpu);
> let data = fs::read_to_string(&path).unwrap_or_default();
> let mut states = Vec::new();
> for line in data.lines() {
> let parts: Vec<&str> = line.split_whitespace().collect();
> if parts.len() >= 6 {
> if let (Ok(freq), Ok(power), Ok(latency), Ok(ctl)) = (
> parts[0].parse::<u32>(), parts[2].parse::<u32>(),
> parts[4].parse::<u32>(), u64::from_str_radix(parts[5], 16)
> ) {
> states.push(PState { freq_mhz: freq, power_mw: power, latency_us: latency, ctl_value: ctl });
> }
> }
> }
> if states.is_empty() {
> states.push(PState { freq_mhz: 2400, power_mw: 35000, latency_us: 10, ctl_value: 0x1a00 });
> states.push(PState { freq_mhz: 1200, power_mw: 15000, latency_us: 10, ctl_value: 0x0d00 });
> }
> states
> }
>
> fn detect_cpus() -> Vec<u32> {
> let mut cpus = Vec::new();
> if let Ok(entries) = fs::read_dir("/dev/cpu") {
> for entry in entries.flatten() {
> if let Ok(name) = entry.file_name().into_string() {
> if let Ok(id) = name.parse::<u32>() {
> cpus.push(id);
> }
> }
> }
> }
> if cpus.is_empty() { cpus.push(0); }
> cpus
> }
>
> fn measure_cpu_load(cpu: u32, prev: &mut (u64, u64)) -> f64 {
> let path = format!("/scheme/sys/cpu/{}/stat", cpu);
> if let Ok(data) = fs::read_to_string(&path) {
> let parts: Vec<u64> = data.split_whitespace().filter_map(|s| s.parse().ok()).collect();
> if parts.len() >= 4 {
> let total: u64 = parts.iter().sum();
> let idle = parts.get(3).copied().unwrap_or(0);
> let prev_total = prev.0;
> let prev_idle = prev.1;
> *prev = (total, idle);
> if total > prev_total {
> let total_delta = total - prev_total;
> let idle_delta = idle.saturating_sub(prev_idle);
> return 1.0 - (idle_delta as f64 / total_delta as f64);
> }
> }
> }
> 0.0
> }
>
> fn choose_pstate(governor: Governor, pstates: &[PState], current: usize, load: f64) -> usize {
> match governor {
> Governor::Performance => 0,
> Governor::Powersave => pstates.len() - 1,
> Governor::Ondemand => {
> if load > 0.8 && current > 0 { current - 1 }
> else if load < 0.3 && current + 1 < pstates.len() { current + 1 }
> else { current }
> }
> }
> }
>
> fn main() {
> log::set_logger(&StderrLogger).ok();
> log::set_max_level(LevelFilter::Info);
>
> let governor = match env::var("CPUFREQ_GOVERNOR").unwrap_or_else(|_| "ondemand".to_string()).as_str() {
> "performance" => Governor::Performance,
> "powersave" => Governor::Powersave,
> _ => Governor::Ondemand,
> };
>
> let cpus = detect_cpus();
> info!("detected {} CPU(s)", cpus.len());
>
> let all_pstates: Vec<Vec<PState>> = cpus.iter().map(|cpu| read_acpi_pss(*cpu)).collect();
> if all_pstates.iter().all(|p| p.is_empty()) {
> error!("no P-states found, cannot scale frequency");
> return;
> }
>
> let mut cpu_states: Vec<CpuState> = cpus.iter().enumerate().map(|(i, &id)| {
> CpuState { id, current_pstate: 0, load: 0.0 }
> }).collect();
> let mut prev_stats: Vec<(u64, u64)> = vec![(0, 0); cpus.len()];
>
> info!("governor={:?}, {} P-states available", governor, all_pstates[0].len());
> for (i, p) in all_pstates[0].iter().enumerate() {
> info!(" P{}: {} MHz, {} mW, latency {} us", i, p.freq_mhz, p.power_mw, p.latency_us);
> }
>
> for (i, cs) in cpu_states.iter_mut().enumerate() {
> let pstates = &all_pstates[i];
> if !pstates.is_empty() {
> let ctl = pstates[0].ctl_value;
> if write_msr(cs.id, IA32_PERF_CTL, ctl) {
> info!("CPU{}: set P0 ({} MHz)", cs.id, pstates[0].freq_mhz);
> } else {
> warn!("CPU{}: MSR write failed, trying ACPI path", cs.id);
> }
> }
> }
>
> let poll = Duration::from_millis(POLL_INTERVAL_MS);
>
> loop {
> thread::sleep(poll);
>
> for (i, cs) in cpu_states.iter_mut().enumerate() {
> let load = measure_cpu_load(cs.id, &mut prev_stats[i]);
> cs.load = load;
>
> let pstates = &all_pstates[i];
> if pstates.is_empty() { continue; }
>
> let new_idx = choose_pstate(governor, pstates, cs.current_pstate, load);
> if new_idx != cs.current_pstate {
> let ctl = pstates[new_idx].ctl_value;
> if write_msr(cs.id, IA32_PERF_CTL, ctl) {
> info!("CPU{}: P{}→P{} ({}→{} MHz, load={:.1}%)",
> cs.id, cs.current_pstate, new_idx,
> pstates[cs.current_pstate].freq_mhz, pstates[new_idx].freq_mhz,
> load * 100.0);
> cs.current_pstate = new_idx;
> }
> }
> }
> }
> }
--- a/drivers/cpufreqd/src/main.rs
+++ b/drivers/cpufreqd/src/main.rs
@@ -0,0 +1,167 @@
+use std::env;
+use std::fs;
+use std::io::{Read, Write};
+use std::thread;
+use std::time::{Duration, Instant};
+
+const IA32_PERF_CTL: u32 = 0x199;
+const IA32_PERF_STATUS: u32 = 0x198;
+const POLL_INTERVAL_MS: u64 = 100;
+
+#[derive(Clone, Copy, PartialEq)]
+enum Governor { Performance, Powersave, Ondemand }
+
+struct PState { freq_mhz: u32, power_mw: u32, latency_us: u32, ctl_value: u64 }
+
+struct CpuState { id: u32, current_pstate: usize, load: f64 }
+
+fn read_msr(cpu: u32, msr: u32) -> Option<u64> {
+ let path = format!("/dev/cpu/{}/msr", cpu);
+ let mut f = fs::OpenOptions::new().read(true).open(&path).ok()?;
+ let mut buf = [0u8; 8];
+ f.read_exact(&mut buf).ok()?;
+ Some(u64::from_ne_bytes(buf))
+}
+
+fn write_msr(cpu: u32, msr: u32, value: u64) -> bool {
+ let path = format!("/dev/cpu/{}/msr", cpu);
+ fs::OpenOptions::new().write(true).open(&path)
+ .and_then(|mut f| f.write_all(&value.to_ne_bytes()))
+ .is_ok()
+}
+
+fn read_acpi_pss(cpu: u32) -> Vec<PState> {
+ let path = format!("/scheme/acpi/processor/CPU{}/pss", cpu);
+ let data = fs::read_to_string(&path).unwrap_or_default();
+ let mut states = Vec::new();
+ for line in data.lines() {
+ let parts: Vec<&str> = line.split_whitespace().collect();
+ if parts.len() >= 6 {
+ if let (Ok(freq), Ok(power), Ok(latency), Ok(ctl)) = (
+ parts[0].parse::<u32>(), parts[2].parse::<u32>(),
+ parts[4].parse::<u32>(), u64::from_str_radix(parts[5], 16)
+ ) {
+ states.push(PState { freq_mhz: freq, power_mw: power, latency_us: latency, ctl_value: ctl });
+ }
+ }
+ }
+ if states.is_empty() {
+ states.push(PState { freq_mhz: 2400, power_mw: 35000, latency_us: 10, ctl_value: 0x1a00 });
+ states.push(PState { freq_mhz: 1200, power_mw: 15000, latency_us: 10, ctl_value: 0x0d00 });
+ }
+ states
+}
+
+fn detect_cpus() -> Vec<u32> {
+ let mut cpus = Vec::new();
+ if let Ok(entries) = fs::read_dir("/dev/cpu") {
+ for entry in entries.flatten() {
+ if let Ok(name) = entry.file_name().into_string() {
+ if let Ok(id) = name.parse::<u32>() {
+ cpus.push(id);
+ }
+ }
+ }
+ }
+ if cpus.is_empty() { cpus.push(0); }
+ cpus
+}
+
+fn measure_cpu_load(cpu: u32, prev: &mut (u64, u64)) -> f64 {
+ let path = format!("/scheme/sys/cpu/{}/stat", cpu);
+ if let Ok(data) = fs::read_to_string(&path) {
+ let parts: Vec<u64> = data.split_whitespace().filter_map(|s| s.parse().ok()).collect();
+ if parts.len() >= 4 {
+ let total: u64 = parts.iter().sum();
+ let idle = parts.get(3).copied().unwrap_or(0);
+ let prev_total = prev.0;
+ let prev_idle = prev.1;
+ *prev = (total, idle);
+ if total > prev_total {
+ let total_delta = total - prev_total;
+ let idle_delta = idle.saturating_sub(prev_idle);
+ return 1.0 - (idle_delta as f64 / total_delta as f64);
+ }
+ }
+ }
+ 0.0
+}
+
+fn choose_pstate(governor: Governor, pstates: &[PState], current: usize, load: f64) -> usize {
+ match governor {
+ Governor::Performance => 0,
+ Governor::Powersave => pstates.len() - 1,
+ Governor::Ondemand => {
+ if load > 0.8 && current > 0 { current - 1 }
+ else if load < 0.3 && current + 1 < pstates.len() { current + 1 }
+ else { current }
+ }
+ }
+}
+
+fn main() {
+ eprintln!("cpufreqd: started");
+
+ let governor = match env::var("CPUFREQ_GOVERNOR").unwrap_or_else(|_| "ondemand".to_string()).as_str() {
+ "performance" => Governor::Performance,
+ "powersave" => Governor::Powersave,
+ _ => Governor::Ondemand,
+ };
+
+ let cpus = detect_cpus();
+ eprintln!("cpufreqd: detected {} CPU(s)", cpus.len());
+
+ let all_pstates: Vec<Vec<PState>> = cpus.iter().map(|cpu| read_acpi_pss(*cpu)).collect();
+ if all_pstates.iter().all(|p| p.is_empty()) {
+ eprintln!("cpufreqd: no P-states found, cannot scale frequency");
+ return;
+ }
+
+ let mut cpu_states: Vec<CpuState> = cpus.iter().enumerate().map(|(i, &id)| {
+ CpuState { id, current_pstate: 0, load: 0.0 }
+ }).collect();
+ let mut prev_stats: Vec<(u64, u64)> = vec![(0, 0); cpus.len()];
+
+ eprintln!("cpufreqd: governor={:?}, {} P-states available", governor, all_pstates[0].len());
+ for (i, p) in all_pstates[0].iter().enumerate() {
+ eprintln!("cpufreqd: P{}: {} MHz, {} mW, latency {} us", i, p.freq_mhz, p.power_mw, p.latency_us);
+ }
+
+ for (i, cs) in cpu_states.iter_mut().enumerate() {
+ let pstates = &all_pstates[i];
+ if !pstates.is_empty() {
+ let ctl = pstates[0].ctl_value;
+ if write_msr(cs.id, IA32_PERF_CTL, ctl) {
+ eprintln!("cpufreqd: CPU{}: set P0 ({} MHz)", cs.id, pstates[0].freq_mhz);
+ } else {
+ eprintln!("cpufreqd: CPU{}: MSR write failed, trying ACPI path", cs.id);
+ }
+ }
+ }
+
+ let poll = Duration::from_millis(POLL_INTERVAL_MS);
+
+ loop {
+ thread::sleep(poll);
+
+ for (i, cs) in cpu_states.iter_mut().enumerate() {
+ let load = measure_cpu_load(cs.id, &mut prev_stats[i]);
+ cs.load = load;
+
+ let pstates = &all_pstates[i];
+ if pstates.is_empty() { continue; }
+
+ let new_idx = choose_pstate(governor, pstates, cs.current_pstate, load);
+ if new_idx != cs.current_pstate {
+ let ctl = pstates[new_idx].ctl_value;
+ if write_msr(cs.id, IA32_PERF_CTL, ctl) {
+ eprintln!("cpufreqd: CPU{}: P{}→P{} ({}→{} MHz, load={:.1}%)",
+ cs.id, cs.current_pstate, new_idx,
+ pstates[cs.current_pstate].freq_mhz, pstates[new_idx].freq_mhz,
+ load * 100.0);
+ cs.current_pstate = new_idx;
+ }
+ }
+ }
+ }
+}
@@ -1,89 +0,0 @@
diff --git a/drivers/audio/ihdad/src/main.rs b/drivers/audio/ihdad/src/main.rs
index 31a2add7..a75a0a35 100755
--- a/drivers/audio/ihdad/src/main.rs
+++ b/drivers/audio/ihdad/src/main.rs
@@ -57,7 +57,15 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
EventQueue::<Source>::new().expect("ihdad: Could not create event queue.");
let socket = Socket::nonblock().expect("ihdad: failed to create socket");
let mut device = unsafe {
- hda::IntelHDA::new(address, vend_prod).expect("ihdad: failed to allocate device")
+ match hda::IntelHDA::new(address, vend_prod) {
+ Ok(dev) => dev,
+ Err(e) => {
+ log::error!("ihdad: failed to initialize HDA device (err {}), exiting gracefully", e);
+ log::info!("ihdad: this is expected in virtual environments without functional HDA hardware");
+ daemon.ready();
+ return loop {};
+ }
+ }
};
let mut readiness_based = ReadinessBased::new(&socket, 16);
diff --git a/drivers/net/e1000d/src/main.rs b/drivers/net/e1000d/src/main.rs
index 373ea9b3..2e4cf579 100644
--- a/drivers/net/e1000d/src/main.rs
+++ b/drivers/net/e1000d/src/main.rs
@@ -70,15 +70,15 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
libredox::call::setrens(0, 0).expect("e1000d: failed to enter null namespace");
- scheme.tick().unwrap();
+ scheme.tick().expect("e1000d: initial scheme tick failed");
for event in event_queue.map(|e| e.expect("e1000d: failed to get event")) {
match event.user_data {
Source::Irq => {
let mut irq = [0; 8];
- irq_file.read(&mut irq).unwrap();
+ irq_file.read(&mut irq).expect("e1000d: failed to read IRQ from kernel");
if unsafe { scheme.adapter().irq() } {
- irq_file.write(&mut irq).unwrap();
+ irq_file.write(&mut irq).expect("e1000d: failed to acknowledge IRQ");
scheme.tick().expect("e1000d: failed to handle IRQ")
}
diff --git a/drivers/net/rtl8168d/src/main.rs b/drivers/net/rtl8168d/src/main.rs
index 1d9963a3..08efda6c 100644
--- a/drivers/net/rtl8168d/src/main.rs
+++ b/drivers/net/rtl8168d/src/main.rs
@@ -81,33 +81,33 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
Source::Irq,
event::EventFlags::READ,
)
- .unwrap();
+ .expect("rtl8168d: failed to subscribe to scheme event");
event_queue
.subscribe(
scheme.event_handle().raw(),
Source::Scheme,
event::EventFlags::READ,
)
- .unwrap();
+ .expect("rtl8168d: failed to subscribe to scheme event");
libredox::call::setrens(0, 0).expect("rtl8168d: failed to enter null namespace");
- scheme.tick().unwrap();
+ scheme.tick().expect("rtl8168d: scheme tick failed");
for event in event_queue.map(|e| e.expect("rtl8168d: failed to get next event")) {
match event.user_data {
Source::Irq => {
let mut irq = [0; 8];
- irq_file.irq_handle().read(&mut irq).unwrap();
+ irq_file.irq_handle().read(&mut irq).expect("rtl8168d: failed to read IRQ");
//TODO: This may be causing spurious interrupts
if unsafe { scheme.adapter_mut().irq() } {
- irq_file.irq_handle().write(&mut irq).unwrap();
+ irq_file.irq_handle().write(&mut irq).expect("rtl8168d: failed to acknowledge IRQ");
- scheme.tick().unwrap();
+ scheme.tick().expect("rtl8168d: scheme tick failed");
}
}
Source::Scheme => {
- scheme.tick().unwrap();
+ scheme.tick().expect("rtl8168d: scheme tick failed");
}
}
}
@@ -1,63 +0,0 @@
diff --git a/drivers/net/e1000d/src/main.rs b/drivers/net/e1000d/src/main.rs
index 373ea9b3..f62eaad7 100644
--- a/drivers/net/e1000d/src/main.rs
+++ b/drivers/net/e1000d/src/main.rs
@@ -3,6 +3,7 @@ use std::os::unix::io::AsRawFd;
use driver_network::NetworkScheme;
use event::{user_data, EventQueue};
+use pcid_interface::irq_helpers::pci_allocate_interrupt_vector;
use pcid_interface::PciFunctionHandle;
pub mod device;
@@ -25,14 +26,11 @@ 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_vector = pci_allocate_interrupt_vector(&mut pcid_handle, "e1000d");
log::info!("E1000 {}", pci_config.func.display());
- let mut irq_file = irq.irq_handle("e1000d");
+ let irq_file = irq_vector.irq_handle();
let address = unsafe { pcid_handle.map_bar(0) }.ptr.as_ptr() as usize;
@@ -53,9 +51,11 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
let event_queue = EventQueue::<Source>::new().expect("e1000d: failed to create event queue");
+ let irq_fd = irq_vector.irq_handle().as_raw_fd();
+
event_queue
.subscribe(
- irq_file.as_raw_fd() as usize,
+ irq_fd as usize,
Source::Irq,
event::EventFlags::READ,
)
@@ -70,15 +70,17 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
libredox::call::setrens(0, 0).expect("e1000d: failed to enter null namespace");
- scheme.tick().unwrap();
+ scheme.tick().expect("e1000d: tick failed");
+
+ let mut irq_file = irq_vector.irq_handle();
for event in event_queue.map(|e| e.expect("e1000d: failed to get event")) {
match event.user_data {
Source::Irq => {
let mut irq = [0; 8];
- irq_file.read(&mut irq).unwrap();
+ irq_file.read(&mut irq).expect("e1000d: IRQ read failed");
if unsafe { scheme.adapter().irq() } {
- irq_file.write(&mut irq).unwrap();
+ irq_file.write(&mut irq).expect("e1000d: IRQ ack failed");
scheme.tick().expect("e1000d: failed to handle IRQ")
}
@@ -1,32 +0,0 @@
--- a/init/src/main.rs
+++ b/init/src/main.rs
@@ -157,13 +157,17 @@
for entry in entries {
+ status_ok(&format!("RB_LOAD {}", entry.display()));
scheduler.schedule_start_and_report_errors(
&mut unit_store,
UnitId(entry.file_name().unwrap().to_str().unwrap().to_owned()),
);
}
+ status_ok(&format!("RB_STORE {} units", unit_store.units.len()));
};
scheduler.step(&mut unit_store, &mut init_config);
+ status_ok("RB_STEP_DONE");
libredox::call::setrens(0, 0).expect("init: failed to enter null namespace");
--- a/init/src/scheduler.rs
+++ b/init/src/scheduler.rs
@@ -64,9 +64,11 @@
JobKind::Start => {
let unit = unit_store.unit_mut(&job.unit);
+ status_ok(&format!("RB_RUN {}", unit.id.0));
for dep in &unit.info.requires_weak {
for pending_job in &self.pending {
if &pending_job.unit == dep {
+ status_ok(&format!("RB_DEFER {} (dep {} pending)", unit.id.0, dep.0));
self.pending.push_back(job);
continue 'a';
}
@@ -1,11 +0,0 @@
diff --git a/init/src/scheduler.rs b/init/src/scheduler.rs
--- a/init/src/scheduler.rs
+++ b/init/src/scheduler.rs
@@ -63,6 +63,7 @@
match job.kind {
JobKind::Start => {
let unit = unit_store.unit_mut(&job.unit);
+ eprintln!("init: DBG job={}", job.unit.0);
for dep in &unit.info.requires_weak {
for pending_job in &self.pending {
@@ -1,36 +0,0 @@
--- a/init/src/scheduler.rs 2026-05-04 22:01:55.238384585 +0100
+++ b/init/src/scheduler.rs 2026-05-04 22:37:31.323354810 +0100
@@ -6,6 +6,7 @@
pub struct Scheduler {
pending: VecDeque<Job>,
+ completed: VecDeque<UnitId>,
}
struct Job {
@@ -21,6 +22,7 @@
pub fn new() -> Scheduler {
Scheduler {
pending: VecDeque::new(),
+ completed: VecDeque::new(),
}
}
@@ -66,6 +68,9 @@
let unit = unit_store.unit_mut(&job.unit);
for dep in &unit.info.requires_weak {
+ if self.completed.contains(dep) {
+ continue;
+ }
for pending_job in &self.pending {
if &pending_job.unit == dep {
self.pending.push_back(job);
@@ -75,6 +80,7 @@
}
run(unit, init_config);
+ self.completed.push_back(job.unit);
}
}
}
@@ -0,0 +1,13 @@
[package]
name = "acpi-resource"
description = "Shared ACPI resource template decoder"
version = "0.0.1"
authors = ["Red Bear OS"]
repository = "https://gitlab.redox-os.org/redox-os/drivers"
categories = ["hardware-support"]
license = "MIT/Apache-2.0"
edition = "2021"
[dependencies]
serde.workspace = true
thiserror.workspace = true
@@ -0,0 +1,688 @@
use serde::{Deserialize, Serialize};
use thiserror::Error;
const SMALL_IRQ: u8 = 0x20;
const SMALL_END_TAG: u8 = 0x78;
const LARGE_MEMORY32: u8 = 0x85;
const LARGE_FIXED_MEMORY32: u8 = 0x86;
const LARGE_ADDRESS32: u8 = 0x87;
const LARGE_EXTENDED_IRQ: u8 = 0x89;
const LARGE_ADDRESS64: u8 = 0x8A;
const LARGE_GPIO: u8 = 0x8C;
const LARGE_SERIAL_BUS: u8 = 0x8E;
const SERIAL_BUS_I2C: u8 = 1;
const I2C_TYPE_DATA_LEN: usize = 6;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum InterruptTrigger {
Edge,
Level,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum InterruptPolarity {
ActiveHigh,
ActiveLow,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum AddressResourceType {
MemoryRange,
IoRange,
BusNumberRange,
Unknown(u8),
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ResourceSource {
pub index: u8,
pub source: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct IrqDescriptor {
pub interrupts: Vec<u8>,
pub triggering: InterruptTrigger,
pub polarity: InterruptPolarity,
pub shareable: bool,
pub wake_capable: bool,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ExtendedIrqDescriptor {
pub producer_consumer: bool,
pub interrupts: Vec<u32>,
pub triggering: InterruptTrigger,
pub polarity: InterruptPolarity,
pub shareable: bool,
pub wake_capable: bool,
pub resource_source: Option<ResourceSource>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct GpioDescriptor {
pub revision_id: u8,
pub producer_consumer: bool,
pub pin_config: u8,
pub shareable: bool,
pub wake_capable: bool,
pub io_restriction: u8,
pub triggering: InterruptTrigger,
pub polarity: InterruptPolarity,
pub drive_strength: u16,
pub debounce_timeout: u16,
pub pins: Vec<u16>,
pub resource_source: Option<ResourceSource>,
pub vendor_data: Vec<u8>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct I2cSerialBusDescriptor {
pub revision_id: u8,
pub producer_consumer: bool,
pub slave_mode: bool,
pub connection_sharing: bool,
pub type_revision_id: u8,
pub access_mode_10bit: bool,
pub slave_address: u16,
pub connection_speed: u32,
pub resource_source: Option<ResourceSource>,
pub vendor_data: Vec<u8>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Memory32RangeDescriptor {
pub write_protect: bool,
pub minimum: u32,
pub maximum: u32,
pub alignment: u32,
pub address_length: u32,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct FixedMemory32Descriptor {
pub write_protect: bool,
pub address: u32,
pub address_length: u32,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Address32Descriptor {
pub resource_type: AddressResourceType,
pub producer_consumer: bool,
pub decode: bool,
pub min_address_fixed: bool,
pub max_address_fixed: bool,
pub specific_flags: u8,
pub granularity: u32,
pub minimum: u32,
pub maximum: u32,
pub translation_offset: u32,
pub address_length: u32,
pub resource_source: Option<ResourceSource>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Address64Descriptor {
pub resource_type: AddressResourceType,
pub producer_consumer: bool,
pub decode: bool,
pub min_address_fixed: bool,
pub max_address_fixed: bool,
pub specific_flags: u8,
pub granularity: u64,
pub minimum: u64,
pub maximum: u64,
pub translation_offset: u64,
pub address_length: u64,
pub resource_source: Option<ResourceSource>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum ResourceDescriptor {
Irq(IrqDescriptor),
ExtendedIrq(ExtendedIrqDescriptor),
GpioInt(GpioDescriptor),
GpioIo(GpioDescriptor),
I2cSerialBus(I2cSerialBusDescriptor),
Memory32Range(Memory32RangeDescriptor),
FixedMemory32(FixedMemory32Descriptor),
Address32(Address32Descriptor),
Address64(Address64Descriptor),
}
#[derive(Debug, Error, PartialEq, Eq)]
pub enum ResourceDecodeError {
#[error("descriptor at offset {offset} overruns the resource template")]
TruncatedDescriptor { offset: usize },
#[error("unsupported small descriptor length {length} for tag {tag:#04x} at offset {offset}")]
InvalidSmallLength {
offset: usize,
tag: u8,
length: usize,
},
#[error("descriptor {descriptor} at offset {offset} is shorter than {minimum} bytes")]
InvalidLargeLength {
offset: usize,
descriptor: &'static str,
minimum: usize,
},
#[error("descriptor {descriptor} at offset {offset} has an invalid internal offset")]
InvalidInternalOffset {
offset: usize,
descriptor: &'static str,
},
}
pub fn decode_resource_template(
bytes: &[u8],
) -> Result<Vec<ResourceDescriptor>, ResourceDecodeError> {
let mut resources = Vec::new();
let mut offset = 0usize;
while offset < bytes.len() {
let descriptor = *bytes
.get(offset)
.ok_or(ResourceDecodeError::TruncatedDescriptor { offset })?;
if descriptor & 0x80 == 0 {
let length = usize::from(descriptor & 0x07);
let end = offset + 1 + length;
let desc = bytes
.get(offset..end)
.ok_or(ResourceDecodeError::TruncatedDescriptor { offset })?;
let body = &desc[1..];
match descriptor & 0x78 {
SMALL_IRQ => resources.push(ResourceDescriptor::Irq(parse_irq(body, offset)?)),
SMALL_END_TAG => break,
_ => {}
}
offset = end;
continue;
}
let length = usize::from(read_u16(bytes, offset + 1)?);
let end = offset + 3 + length;
let desc = bytes
.get(offset..end)
.ok_or(ResourceDecodeError::TruncatedDescriptor { offset })?;
let body = &desc[3..];
match descriptor {
LARGE_MEMORY32 => resources.push(ResourceDescriptor::Memory32Range(parse_memory32(
body, offset,
)?)),
LARGE_FIXED_MEMORY32 => resources.push(ResourceDescriptor::FixedMemory32(
parse_fixed_memory32(body, offset)?,
)),
LARGE_ADDRESS32 => {
resources.push(ResourceDescriptor::Address32(parse_address32(
desc, body, offset,
)?));
}
LARGE_ADDRESS64 => {
resources.push(ResourceDescriptor::Address64(parse_address64(
desc, body, offset,
)?));
}
LARGE_EXTENDED_IRQ => resources.push(ResourceDescriptor::ExtendedIrq(
parse_extended_irq(desc, body, offset)?,
)),
LARGE_GPIO => {
let (is_interrupt, descriptor) = parse_gpio(desc, body, offset)?;
resources.push(if is_interrupt {
ResourceDescriptor::GpioInt(descriptor)
} else {
ResourceDescriptor::GpioIo(descriptor)
});
}
LARGE_SERIAL_BUS => {
if let Some(descriptor) = parse_i2c_serial_bus(desc, body, offset)? {
resources.push(ResourceDescriptor::I2cSerialBus(descriptor));
}
}
_ => {}
}
offset = end;
}
Ok(resources)
}
fn parse_irq(body: &[u8], offset: usize) -> Result<IrqDescriptor, ResourceDecodeError> {
if body.len() != 2 && body.len() != 3 {
return Err(ResourceDecodeError::InvalidSmallLength {
offset,
tag: SMALL_IRQ,
length: body.len(),
});
}
let mask = u16::from_le_bytes([body[0], body[1]]);
let flags = body.get(2).copied().unwrap_or(0);
let interrupts = (0..16)
.filter(|irq| mask & (1 << irq) != 0)
.map(|irq| irq as u8)
.collect();
Ok(IrqDescriptor {
interrupts,
triggering: if flags & 0x01 != 0 {
InterruptTrigger::Level
} else {
InterruptTrigger::Edge
},
polarity: if flags & 0x08 != 0 {
InterruptPolarity::ActiveLow
} else {
InterruptPolarity::ActiveHigh
},
shareable: flags & 0x10 != 0,
wake_capable: flags & 0x20 != 0,
})
}
fn parse_extended_irq(
desc: &[u8],
body: &[u8],
offset: usize,
) -> Result<ExtendedIrqDescriptor, ResourceDecodeError> {
ensure_length(body, 2, offset, "ExtendedIrq")?;
let flags = body[0];
let count = usize::from(body[1]);
let ints_len = count * 4;
ensure_length(body, 2 + ints_len, offset, "ExtendedIrq")?;
let interrupts = (0..count)
.map(|index| read_u32(body, 2 + index * 4))
.collect::<Result<Vec<_>, _>>()?;
let resource_source = if body.len() > 2 + ints_len {
Some(parse_source_inline(&body[2 + ints_len..]))
} else {
None
};
let _ = desc;
Ok(ExtendedIrqDescriptor {
producer_consumer: flags & 0x01 != 0,
triggering: if flags & 0x02 != 0 {
InterruptTrigger::Level
} else {
InterruptTrigger::Edge
},
polarity: if flags & 0x04 != 0 {
InterruptPolarity::ActiveLow
} else {
InterruptPolarity::ActiveHigh
},
shareable: flags & 0x08 != 0,
wake_capable: flags & 0x10 != 0,
interrupts,
resource_source,
})
}
fn parse_gpio(
desc: &[u8],
body: &[u8],
offset: usize,
) -> Result<(bool, GpioDescriptor), ResourceDecodeError> {
ensure_length(body, 20, offset, "Gpio")?;
let connection_type = body[1];
let flags = read_u16(body, 2)?;
let int_flags = read_u16(body, 4)?;
let pin_table_offset = usize::from(read_u16(body, 11)?);
let resource_source_index = body[13];
let resource_source_offset = usize::from(read_u16(body, 14)?);
let vendor_offset = usize::from(read_u16(body, 16)?);
let vendor_length = usize::from(read_u16(body, 18)?);
let pins_end = min_nonzero([resource_source_offset, vendor_offset, desc.len()]);
let pins = parse_u16_list(desc, pin_table_offset, pins_end, offset, "Gpio")?;
let resource_source = parse_source_absolute(
desc,
resource_source_offset,
min_nonzero([vendor_offset, desc.len()]),
resource_source_index,
offset,
"Gpio",
)?;
let vendor_data = parse_blob_absolute(desc, vendor_offset, vendor_length, offset, "Gpio")?;
Ok((
connection_type == 0,
GpioDescriptor {
revision_id: body[0],
producer_consumer: flags & 0x0001 != 0,
pin_config: body[6],
shareable: int_flags & 0x0008 != 0,
wake_capable: int_flags & 0x0010 != 0,
io_restriction: (int_flags & 0x0003) as u8,
triggering: if int_flags & 0x0001 != 0 {
InterruptTrigger::Level
} else {
InterruptTrigger::Edge
},
polarity: if int_flags & 0x0002 != 0 {
InterruptPolarity::ActiveLow
} else {
InterruptPolarity::ActiveHigh
},
drive_strength: read_u16(body, 7)?,
debounce_timeout: read_u16(body, 9)?,
pins,
resource_source,
vendor_data,
},
))
}
fn parse_i2c_serial_bus(
desc: &[u8],
body: &[u8],
offset: usize,
) -> Result<Option<I2cSerialBusDescriptor>, ResourceDecodeError> {
ensure_length(body, 15, offset, "SerialBus")?;
if body[2] != SERIAL_BUS_I2C {
return Ok(None);
}
let type_data_length = usize::from(read_u16(body, 7)?);
if type_data_length < I2C_TYPE_DATA_LEN {
return Err(ResourceDecodeError::InvalidLargeLength {
offset,
descriptor: "I2cSerialBus",
minimum: 15,
});
}
let vendor_length = type_data_length - I2C_TYPE_DATA_LEN;
let vendor_data = parse_blob_absolute(desc, 18, vendor_length, offset, "I2cSerialBus")?;
let resource_source = parse_source_absolute(
desc,
12 + type_data_length,
desc.len(),
body[1],
offset,
"I2cSerialBus",
)?;
Ok(Some(I2cSerialBusDescriptor {
revision_id: body[0],
producer_consumer: body[3] & 0x02 != 0,
slave_mode: body[3] & 0x01 != 0,
connection_sharing: body[3] & 0x04 != 0,
type_revision_id: body[6],
access_mode_10bit: read_u16(body, 4)? & 0x0001 != 0,
connection_speed: read_u32(body, 9)?,
slave_address: read_u16(body, 13)?,
resource_source,
vendor_data,
}))
}
fn parse_memory32(
body: &[u8],
offset: usize,
) -> Result<Memory32RangeDescriptor, ResourceDecodeError> {
ensure_length(body, 17, offset, "Memory32Range")?;
Ok(Memory32RangeDescriptor {
write_protect: body[0] & 0x01 != 0,
minimum: read_u32(body, 1)?,
maximum: read_u32(body, 5)?,
alignment: read_u32(body, 9)?,
address_length: read_u32(body, 13)?,
})
}
fn parse_fixed_memory32(
body: &[u8],
offset: usize,
) -> Result<FixedMemory32Descriptor, ResourceDecodeError> {
ensure_length(body, 9, offset, "FixedMemory32")?;
Ok(FixedMemory32Descriptor {
write_protect: body[0] & 0x01 != 0,
address: read_u32(body, 1)?,
address_length: read_u32(body, 5)?,
})
}
fn parse_address32(
desc: &[u8],
body: &[u8],
offset: usize,
) -> Result<Address32Descriptor, ResourceDecodeError> {
ensure_length(body, 23, offset, "Address32")?;
Ok(Address32Descriptor {
resource_type: parse_address_type(body[0]),
producer_consumer: body[1] & 0x01 != 0,
decode: body[1] & 0x02 != 0,
min_address_fixed: body[1] & 0x04 != 0,
max_address_fixed: body[1] & 0x08 != 0,
specific_flags: body[2],
granularity: read_u32(body, 3)?,
minimum: read_u32(body, 7)?,
maximum: read_u32(body, 11)?,
translation_offset: read_u32(body, 15)?,
address_length: read_u32(body, 19)?,
resource_source: if desc.len() > 26 {
parse_source_absolute(desc, 26, desc.len(), desc[26], offset, "Address32")?
} else {
None
},
})
}
fn parse_address64(
desc: &[u8],
body: &[u8],
offset: usize,
) -> Result<Address64Descriptor, ResourceDecodeError> {
ensure_length(body, 43, offset, "Address64")?;
Ok(Address64Descriptor {
resource_type: parse_address_type(body[0]),
producer_consumer: body[1] & 0x01 != 0,
decode: body[1] & 0x02 != 0,
min_address_fixed: body[1] & 0x04 != 0,
max_address_fixed: body[1] & 0x08 != 0,
specific_flags: body[2],
granularity: read_u64(body, 3)?,
minimum: read_u64(body, 11)?,
maximum: read_u64(body, 19)?,
translation_offset: read_u64(body, 27)?,
address_length: read_u64(body, 35)?,
resource_source: if desc.len() > 46 {
parse_source_absolute(desc, 46, desc.len(), desc[46], offset, "Address64")?
} else {
None
},
})
}
fn ensure_length(
body: &[u8],
minimum: usize,
offset: usize,
descriptor: &'static str,
) -> Result<(), ResourceDecodeError> {
if body.len() < minimum {
return Err(ResourceDecodeError::InvalidLargeLength {
offset,
descriptor,
minimum,
});
}
Ok(())
}
fn parse_source_inline(bytes: &[u8]) -> ResourceSource {
let index = bytes.first().copied().unwrap_or(0);
let source = bytes.get(1..).map(parse_nul_string).unwrap_or_default();
ResourceSource { index, source }
}
fn parse_source_absolute(
desc: &[u8],
start: usize,
end: usize,
index: u8,
offset: usize,
descriptor: &'static str,
) -> Result<Option<ResourceSource>, ResourceDecodeError> {
if start == 0 || start >= end || start > desc.len() {
return Ok(None);
}
let slice = desc
.get(start..end)
.ok_or(ResourceDecodeError::InvalidInternalOffset { offset, descriptor })?;
Ok(Some(ResourceSource {
index,
source: parse_nul_string(slice),
}))
}
fn parse_blob_absolute(
desc: &[u8],
start: usize,
length: usize,
offset: usize,
descriptor: &'static str,
) -> Result<Vec<u8>, ResourceDecodeError> {
if start == 0 || length == 0 {
return Ok(Vec::new());
}
let end = start + length;
Ok(desc
.get(start..end)
.ok_or(ResourceDecodeError::InvalidInternalOffset { offset, descriptor })?
.to_vec())
}
fn parse_u16_list(
desc: &[u8],
start: usize,
end: usize,
offset: usize,
descriptor: &'static str,
) -> Result<Vec<u16>, ResourceDecodeError> {
if start == 0 || start >= end || start > desc.len() {
return Ok(Vec::new());
}
let slice = desc
.get(start..end)
.ok_or(ResourceDecodeError::InvalidInternalOffset { offset, descriptor })?;
if slice.len() % 2 != 0 {
return Err(ResourceDecodeError::InvalidInternalOffset { offset, descriptor });
}
slice
.chunks_exact(2)
.map(|chunk| Ok(u16::from_le_bytes([chunk[0], chunk[1]])))
.collect()
}
fn parse_nul_string(bytes: &[u8]) -> String {
let end = bytes
.iter()
.position(|byte| *byte == 0)
.unwrap_or(bytes.len());
String::from_utf8_lossy(&bytes[..end]).to_string()
}
fn parse_address_type(value: u8) -> AddressResourceType {
match value {
0 => AddressResourceType::MemoryRange,
1 => AddressResourceType::IoRange,
2 => AddressResourceType::BusNumberRange,
other => AddressResourceType::Unknown(other),
}
}
fn read_u16(bytes: &[u8], offset: usize) -> Result<u16, ResourceDecodeError> {
let slice = bytes
.get(offset..offset + 2)
.ok_or(ResourceDecodeError::TruncatedDescriptor { offset })?;
Ok(u16::from_le_bytes([slice[0], slice[1]]))
}
fn read_u32(bytes: &[u8], offset: usize) -> Result<u32, ResourceDecodeError> {
let slice = bytes
.get(offset..offset + 4)
.ok_or(ResourceDecodeError::TruncatedDescriptor { offset })?;
Ok(u32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]]))
}
fn read_u64(bytes: &[u8], offset: usize) -> Result<u64, ResourceDecodeError> {
let slice = bytes
.get(offset..offset + 8)
.ok_or(ResourceDecodeError::TruncatedDescriptor { offset })?;
Ok(u64::from_le_bytes([
slice[0], slice[1], slice[2], slice[3], slice[4], slice[5], slice[6], slice[7],
]))
}
fn min_nonzero<const N: usize>(values: [usize; N]) -> usize {
values
.into_iter()
.filter(|value| *value != 0)
.min()
.unwrap_or(0)
}
#[cfg(test)]
mod tests {
use super::{decode_resource_template, ResourceDescriptor};
#[test]
fn decodes_small_irq_descriptor() {
let resources = decode_resource_template(&[0x23, 0x0A, 0x00, 0x19, 0x79, 0x00]).unwrap();
assert!(matches!(
&resources[0],
ResourceDescriptor::Irq(descriptor)
if descriptor.interrupts == vec![1, 3]
&& descriptor.shareable
&& descriptor.wake_capable == false
));
}
#[test]
fn decodes_i2c_serial_bus_descriptor() {
let template = [
0x8E, 0x14, 0x00, 0x01, 0x02, 0x01, 0x02, 0x00, 0x00, 0x01, 0x06, 0x00, 0x80, 0x1A,
0x06, 0x00, 0x15, 0x00, b'I', b'2', b'C', b'0', 0x00, 0x79, 0x00,
];
let resources = decode_resource_template(&template).unwrap();
assert!(matches!(
&resources[0],
ResourceDescriptor::I2cSerialBus(descriptor)
if descriptor.connection_speed == 400_000
&& descriptor.slave_address == 0x15
&& descriptor.resource_source.as_ref().map(|source| source.source.as_str())
== Some("I2C0")
));
}
#[test]
fn decodes_gpio_interrupt_descriptor() {
let template = [
0x8C, 0x1B, 0x00, 0x01, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17,
0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, b'\\', b'_', b'S', b'B',
0x00, 0x79, 0x00,
];
let resources = decode_resource_template(&template).unwrap();
assert!(matches!(&resources[0], ResourceDescriptor::GpioInt(_)));
}
}
@@ -0,0 +1,21 @@
[package]
name = "gpiod"
description = "GPIO controller registry daemon"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow.workspace = true
log.workspace = true
redox_syscall = { workspace = true, features = ["std"] }
libredox.workspace = true
redox-scheme.workspace = true
ron.workspace = true
serde.workspace = true
common = { path = "../../common" }
daemon = { path = "../../../daemon" }
scheme-utils = { path = "../../../scheme-utils" }
[lints]
workspace = true
@@ -0,0 +1,496 @@
use std::collections::BTreeMap;
use std::process;
use anyhow::{Context, Result};
use redox_scheme::scheme::SchemeSync;
use redox_scheme::{CallerCtx, OpenResult, Socket};
use scheme_utils::{Blocking, HandleMap};
use serde::{Deserialize, Serialize};
use syscall::schemev2::NewFdFlags;
use syscall::{Error as SysError, EACCES, EBADF, EINVAL, ENOENT};
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct GpioControllerInfo {
pub id: u32,
pub name: String,
pub pin_count: usize,
pub supports_interrupt: bool,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum GpioControlRequest {
RegisterController { info: GpioControllerInfo },
ReadPin { controller_id: u32, pin: u32 },
WritePin { controller_id: u32, pin: u32, value: bool },
ConfigurePin { controller_id: u32, pin: u32, config: PinConfig },
ListControllers,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct PinConfig {
pub direction: PinDirection,
pub pull: PullMode,
pub interrupt_mode: Option<InterruptMode>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum PinDirection {
Input,
Output,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum PullMode {
None,
Up,
Down,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum InterruptMode {
EdgeRising,
EdgeFalling,
EdgeBoth,
LevelHigh,
LevelLow,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
enum GpioControlResponse {
ControllerRegistered { id: u32 },
Controllers(Vec<GpioControllerInfo>),
Controller(GpioControllerInfo),
PinValue(bool),
Ack,
Error(String),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum PinOpKind {
Read,
Write,
Configure,
}
enum Handle {
SchemeRoot,
Register { pending: Vec<u8> },
Provider { controller_id: u32, pending: Vec<u8> },
ControllersDir { pending: Vec<u8> },
ControllerDetail { id: u32, pending: Vec<u8> },
PinOp { kind: PinOpKind, pending: Vec<u8> },
}
struct ControllerEntry {
info: GpioControllerInfo,
provider_handle: usize,
}
struct GpioDaemon {
handles: HandleMap<Handle>,
controllers: BTreeMap<u32, ControllerEntry>,
next_id: u32,
}
impl GpioDaemon {
fn new() -> Self {
Self {
handles: HandleMap::new(),
controllers: BTreeMap::new(),
next_id: 0,
}
}
fn controller_list(&self) -> Vec<GpioControllerInfo> {
self.controllers
.values()
.map(|entry| entry.info.clone())
.collect()
}
fn serialize_response(response: &GpioControlResponse) -> syscall::Result<Vec<u8>> {
ron::ser::to_string(response)
.map(|text| text.into_bytes())
.map_err(|err| {
log::error!("gpiod: failed to serialize control response: {err}");
SysError::new(EINVAL)
})
}
fn deserialize_request(buf: &[u8]) -> syscall::Result<GpioControlRequest> {
let text = std::str::from_utf8(buf).map_err(|err| {
log::warn!("gpiod: invalid UTF-8 request payload: {err}");
SysError::new(EINVAL)
})?;
ron::from_str(text).map_err(|err| {
log::warn!("gpiod: failed to decode control request: {err}");
SysError::new(EINVAL)
})
}
fn set_pending_response(handle: &mut Handle, response: GpioControlResponse) -> syscall::Result<()> {
let pending = Self::serialize_response(&response)?;
Self::set_pending_bytes(handle, pending)
}
fn set_pending_bytes(handle: &mut Handle, pending: Vec<u8>) -> syscall::Result<()> {
match handle {
Handle::Register { pending: slot }
| Handle::Provider { pending: slot, .. }
| Handle::ControllersDir { pending: slot }
| Handle::ControllerDetail { pending: slot, .. }
| Handle::PinOp { pending: slot, .. } => {
*slot = pending;
Ok(())
}
Handle::SchemeRoot => Err(SysError::new(EBADF)),
}
}
fn copy_pending(handle: &mut Handle, buf: &mut [u8], offset: u64) -> syscall::Result<usize> {
let pending = match handle {
Handle::Register { pending }
| Handle::Provider { pending, .. }
| Handle::ControllersDir { pending }
| Handle::ControllerDetail { pending, .. }
| Handle::PinOp { pending, .. } => pending,
Handle::SchemeRoot => return Err(SysError::new(EBADF)),
};
let offset = usize::try_from(offset).map_err(|_| SysError::new(EINVAL))?;
if offset >= pending.len() {
return Ok(0);
}
let copy_len = buf.len().min(pending.len() - offset);
buf[..copy_len].copy_from_slice(&pending[offset..offset + copy_len]);
Ok(copy_len)
}
fn validate_pin_target(
&self,
controller_id: u32,
pin: u32,
) -> std::result::Result<GpioControllerInfo, String> {
let entry = self
.controllers
.get(&controller_id)
.ok_or_else(|| format!("unknown controller {controller_id}"))?;
if usize::try_from(pin)
.ok()
.filter(|pin| *pin < entry.info.pin_count)
.is_none()
{
return Err(format!(
"pin {pin} is out of range for controller {} (pin_count={})",
entry.info.name, entry.info.pin_count
));
}
Ok(entry.info.clone())
}
}
impl SchemeSync for GpioDaemon {
fn scheme_root(&mut self) -> syscall::Result<usize> {
Ok(self.handles.insert(Handle::SchemeRoot))
}
fn openat(
&mut self,
dirfd: usize,
path: &str,
_flags: usize,
_fcntl_flags: u32,
_ctx: &CallerCtx,
) -> syscall::Result<OpenResult> {
let handle = self.handles.get(dirfd)?;
let segments = path.trim_matches('/');
let new_handle = match handle {
Handle::SchemeRoot => {
if segments.is_empty() {
return Err(SysError::new(EINVAL));
}
let mut parts = segments.split('/');
match parts.next() {
Some("register") if parts.next().is_none() => Handle::Register {
pending: Vec::new(),
},
Some("controllers") => match parts.next() {
None => Handle::ControllersDir {
pending: Vec::new(),
},
Some(id) if parts.next().is_none() => Handle::ControllerDetail {
id: id.parse::<u32>().map_err(|_| SysError::new(EINVAL))?,
pending: Vec::new(),
},
_ => return Err(SysError::new(EINVAL)),
},
Some("read_pin") if parts.next().is_none() => Handle::PinOp {
kind: PinOpKind::Read,
pending: Vec::new(),
},
Some("write_pin") if parts.next().is_none() => Handle::PinOp {
kind: PinOpKind::Write,
pending: Vec::new(),
},
Some("configure_pin") if parts.next().is_none() => Handle::PinOp {
kind: PinOpKind::Configure,
pending: Vec::new(),
},
_ => return Err(SysError::new(ENOENT)),
}
}
Handle::ControllersDir { .. } => {
if segments.is_empty() {
return Err(SysError::new(EINVAL));
}
Handle::ControllerDetail {
id: segments.parse::<u32>().map_err(|_| SysError::new(EINVAL))?,
pending: Vec::new(),
}
}
_ => return Err(SysError::new(EACCES)),
};
let fd = self.handles.insert(new_handle);
Ok(OpenResult::ThisScheme {
number: fd,
flags: NewFdFlags::empty(),
})
}
fn read(
&mut self,
id: usize,
buf: &mut [u8],
offset: u64,
_fcntl_flags: u32,
_ctx: &CallerCtx,
) -> syscall::Result<usize> {
let controllers = self.controller_list();
let detail = match self.handles.get(id)? {
Handle::ControllerDetail { id, .. } => self.controllers.get(id).map(|entry| entry.info.clone()),
_ => None,
};
let handle = self.handles.get_mut(id)?;
match handle {
Handle::ControllersDir { pending } if pending.is_empty() => {
*pending = Self::serialize_response(&GpioControlResponse::Controllers(controllers))?;
}
Handle::ControllerDetail { id, pending } if pending.is_empty() => {
let info = detail.ok_or(SysError::new(ENOENT))?;
*pending = Self::serialize_response(&GpioControlResponse::Controller(info))?;
log::debug!("gpiod: served controller detail for id={id}");
}
_ => {}
}
Self::copy_pending(handle, buf, offset)
}
fn write(
&mut self,
id: usize,
buf: &[u8],
_offset: u64,
_fcntl_flags: u32,
_ctx: &CallerCtx,
) -> syscall::Result<usize> {
let request = Self::deserialize_request(buf)?;
match request {
GpioControlRequest::RegisterController { mut info } => {
if !matches!(self.handles.get(id)?, Handle::Register { .. }) {
return Err(SysError::new(EINVAL));
}
let controller_id = self.next_id;
self.next_id = self.next_id.checked_add(1).ok_or(SysError::new(EINVAL))?;
info.id = controller_id;
self.controllers.insert(
controller_id,
ControllerEntry {
info: info.clone(),
provider_handle: id,
},
);
let handle = self.handles.get_mut(id)?;
*handle = Handle::Provider {
controller_id,
pending: Self::serialize_response(&GpioControlResponse::ControllerRegistered {
id: controller_id,
})?,
};
log::info!(
"RB_GPIOD_CONTROLLER_REGISTERED id={} name={} pin_count={} supports_interrupt={}",
info.id,
info.name,
info.pin_count,
info.supports_interrupt,
);
Ok(buf.len())
}
GpioControlRequest::ListControllers => {
let controllers = self.controller_list();
let handle = self.handles.get_mut(id)?;
Self::set_pending_response(handle, GpioControlResponse::Controllers(controllers))?;
Ok(buf.len())
}
GpioControlRequest::ReadPin { controller_id, pin } => {
let validation = self.validate_pin_target(controller_id, pin);
let handle = self.handles.get_mut(id)?;
match handle {
Handle::PinOp {
kind: PinOpKind::Read,
..
} => {
match validation {
Ok(info) => {
log::info!(
"RB_GPIOD_PIN_READ controller_id={} name={} pin={} routed=stub",
controller_id,
info.name,
pin,
);
Self::set_pending_response(handle, GpioControlResponse::PinValue(false))?;
}
Err(message) => {
Self::set_pending_response(handle, GpioControlResponse::Error(message))?;
}
}
Ok(buf.len())
}
_ => Err(SysError::new(EINVAL)),
}
}
GpioControlRequest::WritePin {
controller_id,
pin,
value,
} => {
let validation = self.validate_pin_target(controller_id, pin);
let handle = self.handles.get_mut(id)?;
match handle {
Handle::PinOp {
kind: PinOpKind::Write,
..
} => {
match validation {
Ok(info) => {
log::info!(
"RB_GPIOD_PIN_WRITE controller_id={} name={} pin={} value={} routed=stub",
controller_id,
info.name,
pin,
value,
);
Self::set_pending_response(handle, GpioControlResponse::Ack)?;
}
Err(message) => {
Self::set_pending_response(handle, GpioControlResponse::Error(message))?;
}
}
Ok(buf.len())
}
_ => Err(SysError::new(EINVAL)),
}
}
GpioControlRequest::ConfigurePin {
controller_id,
pin,
config,
} => {
let validation = self.validate_pin_target(controller_id, pin);
let handle = self.handles.get_mut(id)?;
match handle {
Handle::PinOp {
kind: PinOpKind::Configure,
..
} => {
match validation {
Ok(info) => {
log::info!(
"RB_GPIOD_PIN_CONFIG controller_id={} name={} pin={} direction={:?} pull={:?} interrupt={:?} routed=stub",
controller_id,
info.name,
pin,
config.direction,
config.pull,
config.interrupt_mode,
);
Self::set_pending_response(handle, GpioControlResponse::Ack)?;
}
Err(message) => {
Self::set_pending_response(handle, GpioControlResponse::Error(message))?;
}
}
Ok(buf.len())
}
_ => Err(SysError::new(EINVAL)),
}
}
}
}
fn on_close(&mut self, id: usize) {
let Some(handle) = self.handles.remove(id) else {
return;
};
if let Handle::Provider { controller_id, .. } = handle {
if let Some(entry) = self.controllers.remove(&controller_id) {
log::info!(
"RB_GPIOD_CONTROLLER_REMOVED id={} name={} provider_handle={}",
controller_id,
entry.info.name,
entry.provider_handle,
);
}
}
}
}
fn run_daemon(daemon: daemon::SchemeDaemon) -> Result<()> {
let socket = Socket::create().context("failed to create gpio scheme socket")?;
let mut scheme = GpioDaemon::new();
let handler = Blocking::new(&socket, 16);
daemon
.ready_sync_scheme(&socket, &mut scheme)
.context("failed to publish gpio scheme root")?;
log::info!("RB_GPIOD_SCHEMA version=1");
libredox::call::setrens(0, 0).context("failed to enter null namespace")?;
handler
.process_requests_blocking(scheme)
.context("failed to process gpiod requests")?;
}
fn daemon_runner(daemon: daemon::SchemeDaemon) -> ! {
if let Err(err) = run_daemon(daemon) {
log::error!("gpiod: {err:#}");
process::exit(1);
}
process::exit(0);
}
fn main() {
common::setup_logging(
"gpio",
"gpio",
"gpiod",
common::output_level(),
common::file_level(),
);
daemon::SchemeDaemon::new(daemon_runner);
}
@@ -0,0 +1,21 @@
[package]
name = "i2c-gpio-expanderd"
description = "I2C GPIO expander bridge daemon"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow.workspace = true
log.workspace = true
redox_syscall = { workspace = true, features = ["std"] }
libredox.workspace = true
serde.workspace = true
ron.workspace = true
acpi-resource = { path = "../../acpi-resource" }
common = { path = "../../common" }
daemon = { path = "../../../daemon" }
i2c-interface = { path = "../../i2c/i2c-interface" }
[lints]
workspace = true
@@ -0,0 +1,454 @@
use std::collections::BTreeMap;
use std::fs::{self, File, OpenOptions};
use std::io::{Read, Write};
use std::path::Path;
use std::process;
use acpi_resource::{GpioDescriptor, I2cSerialBusDescriptor, ResourceDescriptor};
use anyhow::{Context, Result};
use i2c_interface::{
I2cAdapterInfo, I2cControlRequest, I2cControlResponse, I2cTransferRequest,
I2cTransferResponse, I2cTransferSegment,
};
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize)]
struct AmlSymbol {
name: String,
value: AmlValue,
}
#[derive(Debug, Deserialize)]
enum AmlValue {
Integer(u64),
String(String),
}
#[derive(Clone, Debug)]
struct ExpanderResources {
i2c: I2cSerialBusDescriptor,
pin_count: usize,
gpio_int_count: usize,
gpio_io_count: usize,
}
#[derive(Debug)]
struct ExpanderDescriptor {
device: String,
hid: String,
resources: ExpanderResources,
}
struct RegisteredExpander {
_registration: File,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
struct GpioControllerInfo {
id: u32,
name: String,
pin_count: usize,
supports_interrupt: bool,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
enum GpioControlRequest {
RegisterController { info: GpioControllerInfo },
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
enum GpioControlResponse {
ControllerRegistered { id: u32 },
Error(String),
}
fn main() {
common::setup_logging(
"gpio",
"i2c-gpio-expander",
"i2c-gpio-expanderd",
common::output_level(),
common::file_level(),
);
daemon::Daemon::new(daemon_runner);
}
fn daemon_runner(daemon: daemon::Daemon) -> ! {
if let Err(err) = daemon_main(daemon) {
log::error!("i2c-gpio-expanderd: {err:#}");
process::exit(1);
}
process::exit(0);
}
fn daemon_main(daemon: daemon::Daemon) -> Result<()> {
let expanders = discover_expanders().context("failed to discover ACPI I2C GPIO expanders")?;
if expanders.is_empty() {
log::info!("i2c-gpio-expanderd: no probable ACPI I2C GPIO expanders found");
}
let adapters = list_i2c_adapters().unwrap_or_else(|err| {
log::warn!("i2c-gpio-expanderd: unable to query i2cd adapters: {err:#}");
Vec::new()
});
let mut registered = Vec::new();
for expander in expanders {
match register_expander(expander, &adapters) {
Ok(expander) => registered.push(expander),
Err(err) => log::warn!("i2c-gpio-expanderd: expander registration skipped: {err:#}"),
}
}
daemon.ready();
libredox::call::setrens(0, 0).context("failed to enter null namespace")?;
log::info!("i2c-gpio-expanderd: registered {} expander(s)", registered.len());
loop {
std::thread::park();
}
}
fn discover_expanders() -> Result<Vec<ExpanderDescriptor>> {
let mut matched = BTreeMap::new();
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!("i2c-gpio-expanderd: ACPI symbols are not ready yet");
return Ok(Vec::new());
}
Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"),
};
for entry in entries {
let entry = entry.context("failed to read ACPI symbol directory entry")?;
let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else {
continue;
};
if !file_name.ends_with("_HID") && !file_name.ends_with("_CID") {
continue;
}
let Some(id) = read_symbol_id(&entry.path())? else {
continue;
};
if is_excluded_device_id(&id) {
continue;
}
let Some(device) = file_name
.strip_suffix("_HID")
.or_else(|| file_name.strip_suffix("_CID"))
.map(str::to_owned)
else {
continue;
};
let resources = match read_expander_resources(&device) {
Ok(resources) => resources,
Err(err) => {
log::debug!("i2c-gpio-expanderd: skipping {device}: {err:#}");
continue;
}
};
if resources.gpio_int_count == 0 && resources.gpio_io_count == 0 {
continue;
}
matched.entry(device).or_insert((id, resources));
}
let mut expanders = Vec::new();
for (device, (hid, resources)) in matched {
expanders.push(ExpanderDescriptor {
device,
hid,
resources,
});
}
Ok(expanders)
}
fn read_symbol_id(path: &Path) -> Result<Option<String>> {
let contents = fs::read_to_string(path)
.with_context(|| format!("failed to read ACPI symbol {}", path.display()))?;
let symbol = match ron::from_str::<AmlSymbol>(&contents) {
Ok(symbol) => symbol,
Err(err) => {
log::debug!(
"i2c-gpio-expanderd: skipping {} because the symbol payload was not a scalar ID: {err}",
path.display(),
);
return Ok(None);
}
};
let id = match symbol.value {
AmlValue::Integer(integer) => eisa_id_from_integer(integer),
AmlValue::String(string) => string,
};
log::debug!("i2c-gpio-expanderd: {} -> {id}", symbol.name);
Ok(Some(id))
}
fn read_expander_resources(device: &str) -> Result<ExpanderResources> {
let contents = fs::read_to_string(format!("/scheme/acpi/resources/{device}"))
.with_context(|| format!("failed to read /scheme/acpi/resources/{device}"))?;
let resources = ron::from_str::<Vec<ResourceDescriptor>>(&contents)
.with_context(|| format!("failed to decode RON resources for {device}"))?;
let mut i2c = None;
let mut pin_count = 0usize;
let mut gpio_int_count = 0usize;
let mut gpio_io_count = 0usize;
for resource in resources {
match resource {
ResourceDescriptor::I2cSerialBus(bus) if i2c.is_none() => i2c = Some(bus),
ResourceDescriptor::GpioInt(descriptor) => {
gpio_int_count += 1;
pin_count = pin_count.max(pin_count_from_descriptor(&descriptor));
}
ResourceDescriptor::GpioIo(descriptor) => {
gpio_io_count += 1;
pin_count = pin_count.max(pin_count_from_descriptor(&descriptor));
}
_ => {}
}
}
Ok(ExpanderResources {
i2c: i2c.context("no I2cSerialBus resource was found")?,
pin_count,
gpio_int_count,
gpio_io_count,
})
}
fn pin_count_from_descriptor(descriptor: &GpioDescriptor) -> usize {
descriptor
.pins
.iter()
.copied()
.max()
.map(|pin| usize::from(pin).saturating_add(1))
.unwrap_or(0)
}
fn is_excluded_device_id(id: &str) -> bool {
matches!(
id,
"PNP0C50"
| "ACPI0C50"
| "INT34C5"
| "INTC1055"
| "INT33C2"
| "INT33C3"
| "INT3432"
| "INT3433"
| "INTC10EF"
| "AMDI0010"
| "AMDI0019"
| "AMDI0510"
| "PNP0CA0"
| "AMDI0042"
) || id.starts_with("ELAN")
|| id.starts_with("CYAP")
|| id.starts_with("SYNA")
}
fn register_expander(expander: ExpanderDescriptor, adapters: &[I2cAdapterInfo]) -> Result<RegisteredExpander> {
let ExpanderDescriptor {
device,
hid,
resources,
} = expander;
let adapter_name = resources
.i2c
.resource_source
.as_ref()
.map(|source| source.source.clone())
.filter(|source| !source.is_empty())
.unwrap_or_else(|| String::from("ACPI-I2C"));
let adapter = match match_i2c_adapter(adapters, &adapter_name) {
Some(adapter) => Some(adapter.clone()),
None => {
log::warn!(
"i2c-gpio-expanderd: unable to resolve I2C adapter {} for {}",
adapter_name,
device,
);
None
}
};
if let Some(adapter) = adapter.as_ref() {
if let Err(err) = probe_expander(adapter, &adapter_name, resources.i2c.slave_address) {
log::warn!(
"i2c-gpio-expanderd: expander {} probe on {}@{:04x} failed: {err:#}",
device,
adapter_name,
resources.i2c.slave_address,
);
}
}
let info = GpioControllerInfo {
id: 0,
name: format!("i2c-gpio-expander:{device}"),
pin_count: resources.pin_count,
supports_interrupt: resources.gpio_int_count > 0,
};
let mut registration = register_with_gpiod(&info)
.with_context(|| format!("failed to register {device} with gpiod"))?;
let response = read_gpio_registration_response(&mut registration)
.with_context(|| format!("failed to read gpiod registration response for {device}"))?;
match response {
GpioControlResponse::ControllerRegistered { id } => {
log::info!(
"RB_I2C_GPIO_EXPANDERD_DEVICE device={} hid={} controller_id={} adapter={} addr={:04x} pin_count={} gpio_int={} gpio_io={}",
device,
hid,
id,
adapter_name,
resources.i2c.slave_address,
info.pin_count,
resources.gpio_int_count,
resources.gpio_io_count,
);
}
GpioControlResponse::Error(message) => {
anyhow::bail!("gpiod rejected expander {device}: {message}");
}
}
Ok(RegisteredExpander {
_registration: registration,
})
}
fn list_i2c_adapters() -> Result<Vec<I2cAdapterInfo>> {
let mut file = OpenOptions::new()
.read(true)
.write(true)
.open("/scheme/i2c/adapters")
.context("failed to open /scheme/i2c/adapters")?;
let payload = ron::ser::to_string(&I2cControlRequest::ListAdapters)
.context("failed to encode I2C list-adapters request")?;
file.write_all(payload.as_bytes())
.context("failed to request I2C adapter list")?;
let response = read_i2c_control_response(&mut file)?;
match response {
I2cControlResponse::AdapterList(adapters) => Ok(adapters),
I2cControlResponse::Error(message) => anyhow::bail!("i2cd returned an error: {message}"),
other => anyhow::bail!("unexpected i2cd list-adapters response: {other:?}"),
}
}
fn match_i2c_adapter<'a>(adapters: &'a [I2cAdapterInfo], wanted: &str) -> Option<&'a I2cAdapterInfo> {
adapters
.iter()
.find(|adapter| adapter.name == wanted)
.or_else(|| adapters.iter().find(|adapter| adapter.name.ends_with(wanted)))
.or_else(|| adapters.iter().find(|adapter| wanted.ends_with(&adapter.name)))
}
fn probe_expander(adapter: &I2cAdapterInfo, adapter_name: &str, address: u16) -> Result<I2cTransferResponse> {
let request = I2cTransferRequest {
adapter: adapter_name.to_string(),
segments: vec![I2cTransferSegment::read(address, 1)],
stop: true,
};
let mut file = OpenOptions::new()
.read(true)
.write(true)
.open("/scheme/i2c/transfer")
.context("failed to open /scheme/i2c/transfer")?;
let payload = ron::ser::to_string(&I2cControlRequest::Transfer {
adapter_id: adapter.id,
request,
})
.context("failed to encode I2C expander probe request")?;
file.write_all(payload.as_bytes())
.context("failed to send I2C expander probe request")?;
let response = read_i2c_control_response(&mut file)?;
match response {
I2cControlResponse::TransferResult(result) => {
if !result.ok {
let detail = result
.error
.clone()
.unwrap_or_else(|| String::from("unknown I2C transfer failure"));
anyhow::bail!("I2C probe failed: {detail}");
}
Ok(result)
}
I2cControlResponse::Error(message) => anyhow::bail!("i2cd returned an error: {message}"),
other => anyhow::bail!("unexpected I2C transfer response: {other:?}"),
}
}
fn register_with_gpiod(info: &GpioControllerInfo) -> Result<File> {
let mut file = OpenOptions::new()
.read(true)
.write(true)
.open("/scheme/gpio/register")
.context("failed to open /scheme/gpio/register")?;
let payload = ron::ser::to_string(&GpioControlRequest::RegisterController { info: info.clone() })
.context("failed to encode GPIO controller registration")?;
file.write_all(payload.as_bytes())
.context("failed to send GPIO controller registration")?;
Ok(file)
}
fn read_gpio_registration_response(file: &mut File) -> Result<GpioControlResponse> {
let mut buffer = vec![0_u8; 4096];
let count = file
.read(&mut buffer)
.context("failed to read GPIO registration response")?;
buffer.truncate(count);
let text = std::str::from_utf8(&buffer).context("GPIO registration response was not UTF-8")?;
ron::from_str(text).context("failed to decode GPIO registration response")
}
fn read_i2c_control_response(file: &mut File) -> Result<I2cControlResponse> {
let mut buffer = vec![0_u8; 4096];
let count = file
.read(&mut buffer)
.context("failed to read I2C control response")?;
buffer.truncate(count);
let text = std::str::from_utf8(&buffer).context("I2C control response was not UTF-8")?;
let trimmed = text.trim();
if trimmed.is_empty() {
return Ok(I2cControlResponse::AdapterList(Vec::new()));
}
ron::from_str(trimmed).context("failed to decode I2C control response")
}
fn eisa_id_from_integer(integer: u64) -> String {
let vendor = integer & 0xFFFF;
let device = (integer >> 16) & 0xFFFF;
let vendor_rev = ((vendor & 0xFF) << 8) | (vendor >> 8);
let vendor_1 = (((vendor_rev >> 10) & 0x1F) as u8 + 64) as char;
let vendor_2 = (((vendor_rev >> 5) & 0x1F) as u8 + 64) as char;
let vendor_3 = (((vendor_rev >> 0) & 0x1F) as u8 + 64) as char;
let device_1 = (device >> 4) & 0xF;
let device_2 = (device >> 0) & 0xF;
let device_3 = (device >> 12) & 0xF;
let device_4 = (device >> 8) & 0xF;
format!(
"{vendor_1}{vendor_2}{vendor_3}{device_1:01X}{device_2:01X}{device_3:01X}{device_4:01X}"
)
}
@@ -0,0 +1,20 @@
[package]
name = "intel-gpiod"
description = "Intel ACPI GPIO registrar daemon"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow.workspace = true
log.workspace = true
redox_syscall = { workspace = true, features = ["std"] }
libredox.workspace = true
serde.workspace = true
ron.workspace = true
acpi-resource = { path = "../../acpi-resource" }
common = { path = "../../common" }
daemon = { path = "../../../daemon" }
[lints]
workspace = true
@@ -0,0 +1,401 @@
use std::collections::BTreeMap;
use std::fs::{self, File, OpenOptions};
use std::io::{Read, Write};
use std::path::Path;
use std::process;
use acpi_resource::{
AddressResourceType, ExtendedIrqDescriptor, FixedMemory32Descriptor, GpioDescriptor,
IrqDescriptor, Memory32RangeDescriptor, ResourceDescriptor,
};
use anyhow::{Context, Result};
use common::{MemoryType, PhysBorrowed, Prot};
use serde::{Deserialize, Serialize};
const SUPPORTED_IDS: &[&str] = &["INT34C5", "INTC1055"];
const PADNFGPIO_OWN_BASE: usize = 0x20;
const PADNFGPIO_PADCFG_BASE: usize = 0x700;
const GPI_INT_STATUS: usize = 0x100;
const GPI_INT_EN: usize = 0x120;
const INTEL_GPIO_MMIO_WINDOW: usize = PADNFGPIO_PADCFG_BASE + core::mem::size_of::<u32>();
#[derive(Debug, Deserialize)]
struct AmlSymbol {
name: String,
value: AmlValue,
}
#[derive(Debug, Deserialize)]
enum AmlValue {
Integer(u64),
String(String),
}
#[derive(Clone, Debug)]
struct ControllerResources {
mmio_base: usize,
mmio_len: usize,
pin_count: usize,
supports_interrupt: bool,
gpio_int_count: usize,
gpio_io_count: usize,
}
#[derive(Debug)]
struct ControllerDescriptor {
device: String,
hid: String,
resources: ControllerResources,
}
struct RegisteredController {
_mmio: Option<PhysBorrowed>,
_registration: File,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
struct GpioControllerInfo {
id: u32,
name: String,
pin_count: usize,
supports_interrupt: bool,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
enum GpioControlRequest {
RegisterController { info: GpioControllerInfo },
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
enum GpioControlResponse {
ControllerRegistered { id: u32 },
Error(String),
}
fn main() {
common::setup_logging(
"gpio",
"intel-gpio",
"intel-gpiod",
common::output_level(),
common::file_level(),
);
daemon::Daemon::new(daemon_runner);
}
fn daemon_runner(daemon: daemon::Daemon) -> ! {
if let Err(err) = daemon_main(daemon) {
log::error!("intel-gpiod: {err:#}");
process::exit(1);
}
process::exit(0);
}
fn daemon_main(daemon: daemon::Daemon) -> Result<()> {
common::init();
let controllers =
discover_controllers(SUPPORTED_IDS).context("failed to discover Intel GPIO controllers")?;
if controllers.is_empty() {
log::info!("intel-gpiod: no supported Intel GPIO ACPI controllers found");
}
let mut registered = Vec::new();
for controller in controllers {
match register_controller(controller) {
Ok(controller) => registered.push(controller),
Err(err) => log::warn!("intel-gpiod: controller registration skipped: {err:#}"),
}
}
daemon.ready();
libredox::call::setrens(0, 0).context("failed to enter null namespace")?;
log::info!("intel-gpiod: registered {} controller(s)", registered.len());
loop {
std::thread::park();
}
}
fn discover_controllers(supported_ids: &[&str]) -> Result<Vec<ControllerDescriptor>> {
let mut matched = BTreeMap::new();
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!("intel-gpiod: ACPI symbols are not ready yet");
return Ok(Vec::new());
}
Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"),
};
for entry in entries {
let entry = entry.context("failed to read ACPI symbol directory entry")?;
let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else {
continue;
};
if !file_name.ends_with("_HID") && !file_name.ends_with("_CID") {
continue;
}
let Some(id) = read_symbol_id(&entry.path())? else {
continue;
};
if !supported_ids.iter().any(|candidate| *candidate == id) {
continue;
}
let device = file_name
.strip_suffix("_HID")
.or_else(|| file_name.strip_suffix("_CID"))
.map(str::to_owned);
if let Some(device) = device {
matched.entry(device).or_insert(id);
}
}
let mut controllers = Vec::new();
for (device, hid) in matched {
let resources = read_controller_resources(&device)
.with_context(|| format!("failed to read resources for {device}"))?;
controllers.push(ControllerDescriptor {
device,
hid,
resources,
});
}
Ok(controllers)
}
fn read_symbol_id(path: &Path) -> Result<Option<String>> {
let contents = fs::read_to_string(path)
.with_context(|| format!("failed to read ACPI symbol {}", path.display()))?;
let symbol = match ron::from_str::<AmlSymbol>(&contents) {
Ok(symbol) => symbol,
Err(err) => {
log::debug!(
"intel-gpiod: skipping {} because the symbol payload was not a scalar ID: {err}",
path.display(),
);
return Ok(None);
}
};
let id = match symbol.value {
AmlValue::Integer(integer) => eisa_id_from_integer(integer),
AmlValue::String(string) => string,
};
log::debug!("intel-gpiod: {} -> {id}", symbol.name);
Ok(Some(id))
}
fn read_controller_resources(device: &str) -> Result<ControllerResources> {
let contents = fs::read_to_string(format!("/scheme/acpi/resources/{device}"))
.with_context(|| format!("failed to read /scheme/acpi/resources/{device}"))?;
let resources = ron::from_str::<Vec<ResourceDescriptor>>(&contents)
.with_context(|| format!("failed to decode RON resources for {device}"))?;
let mut mmio = None;
let mut supports_interrupt = false;
let mut gpio_int_count = 0usize;
let mut gpio_io_count = 0usize;
let mut pin_count = 0usize;
for resource in &resources {
match resource {
ResourceDescriptor::FixedMemory32(FixedMemory32Descriptor {
address,
address_length,
..
}) if mmio.is_none() => {
mmio = Some((
*address as usize,
(*address_length as usize).max(INTEL_GPIO_MMIO_WINDOW),
));
}
ResourceDescriptor::Memory32Range(Memory32RangeDescriptor {
minimum,
maximum,
address_length,
..
}) if mmio.is_none() && maximum >= minimum => {
let span = maximum.saturating_sub(*minimum).saturating_add(1) as usize;
mmio = Some((
*minimum as usize,
span.max((*address_length as usize).max(INTEL_GPIO_MMIO_WINDOW)),
));
}
ResourceDescriptor::Address32(descriptor)
if mmio.is_none()
&& matches!(descriptor.resource_type, AddressResourceType::MemoryRange) =>
{
mmio = Some((
descriptor.minimum as usize,
(descriptor.address_length as usize).max(INTEL_GPIO_MMIO_WINDOW),
));
}
ResourceDescriptor::Address64(descriptor)
if mmio.is_none()
&& matches!(descriptor.resource_type, AddressResourceType::MemoryRange) =>
{
let base = usize::try_from(descriptor.minimum)
.context("64-bit MMIO base does not fit in usize")?;
let len = usize::try_from(descriptor.address_length)
.context("64-bit MMIO length does not fit in usize")?;
mmio = Some((base, len.max(INTEL_GPIO_MMIO_WINDOW)));
}
ResourceDescriptor::Irq(IrqDescriptor { interrupts, .. }) => {
supports_interrupt |= !interrupts.is_empty();
}
ResourceDescriptor::ExtendedIrq(ExtendedIrqDescriptor { interrupts, .. }) => {
supports_interrupt |= !interrupts.is_empty();
}
ResourceDescriptor::GpioInt(descriptor) => {
gpio_int_count += 1;
supports_interrupt = true;
pin_count = pin_count.max(pin_count_from_descriptor(descriptor));
}
ResourceDescriptor::GpioIo(descriptor) => {
gpio_io_count += 1;
pin_count = pin_count.max(pin_count_from_descriptor(descriptor));
}
_ => {}
}
}
let (mmio_base, mmio_len) = mmio.context("no MMIO resource was found")?;
Ok(ControllerResources {
mmio_base,
mmio_len,
pin_count,
supports_interrupt,
gpio_int_count,
gpio_io_count,
})
}
fn pin_count_from_descriptor(descriptor: &GpioDescriptor) -> usize {
descriptor
.pins
.iter()
.copied()
.max()
.map(|pin| usize::from(pin).saturating_add(1))
.unwrap_or(0)
}
fn register_controller(controller: ControllerDescriptor) -> Result<RegisteredController> {
let ControllerDescriptor {
device,
hid,
resources,
} = controller;
let mmio = match PhysBorrowed::map(
resources.mmio_base,
resources.mmio_len,
Prot::RW,
MemoryType::Uncacheable,
) {
Ok(mapping) => Some(mapping),
Err(err) => {
log::warn!(
"intel-gpiod: failed to map MMIO for {device} ({:#x}, len {:#x}): {err}",
resources.mmio_base,
resources.mmio_len,
);
None
}
};
log::info!(
"intel-gpiod: discovered {device} hid={hid} mmio={:#x}+{:#x} pin_count={} gpio_int={} gpio_io={} supports_interrupt={}",
resources.mmio_base,
resources.mmio_len,
resources.pin_count,
resources.gpio_int_count,
resources.gpio_io_count,
resources.supports_interrupt,
);
log::debug!(
"intel-gpiod: register model own={PADNFGPIO_OWN_BASE:#x} padcfg={PADNFGPIO_PADCFG_BASE:#x} gpi_int_status={GPI_INT_STATUS:#x} gpi_int_en={GPI_INT_EN:#x}",
);
let info = GpioControllerInfo {
id: 0,
name: format!("intel-gpio:{device}"),
pin_count: resources.pin_count,
supports_interrupt: resources.supports_interrupt,
};
let mut registration = register_with_gpiod(&info)
.with_context(|| format!("failed to register {device} with gpiod"))?;
let response = read_registration_response(&mut registration)
.with_context(|| format!("failed to read gpiod registration response for {device}"))?;
match response {
GpioControlResponse::ControllerRegistered { id } => {
log::info!(
"RB_INTEL_GPIOD_DEVICE device={} hid={} controller_id={} pin_count={} supports_interrupt={}",
device,
hid,
id,
info.pin_count,
info.supports_interrupt,
);
}
GpioControlResponse::Error(message) => {
anyhow::bail!("gpiod rejected Intel GPIO controller {device}: {message}");
}
}
Ok(RegisteredController {
_mmio: mmio,
_registration: registration,
})
}
fn register_with_gpiod(info: &GpioControllerInfo) -> Result<File> {
let mut file = OpenOptions::new()
.read(true)
.write(true)
.open("/scheme/gpio/register")
.context("failed to open /scheme/gpio/register")?;
let payload = ron::ser::to_string(&GpioControlRequest::RegisterController { info: info.clone() })
.context("failed to encode GPIO controller registration")?;
file.write_all(payload.as_bytes())
.context("failed to send GPIO controller registration")?;
Ok(file)
}
fn read_registration_response(file: &mut File) -> Result<GpioControlResponse> {
let mut buffer = vec![0_u8; 4096];
let count = file
.read(&mut buffer)
.context("failed to read GPIO registration response")?;
buffer.truncate(count);
let text = std::str::from_utf8(&buffer).context("GPIO registration response was not UTF-8")?;
ron::from_str(text).context("failed to decode GPIO registration response")
}
fn eisa_id_from_integer(integer: u64) -> String {
let vendor = integer & 0xFFFF;
let device = (integer >> 16) & 0xFFFF;
let vendor_rev = ((vendor & 0xFF) << 8) | (vendor >> 8);
let vendor_1 = (((vendor_rev >> 10) & 0x1F) as u8 + 64) as char;
let vendor_2 = (((vendor_rev >> 5) & 0x1F) as u8 + 64) as char;
let vendor_3 = (((vendor_rev >> 0) & 0x1F) as u8 + 64) as char;
let device_1 = (device >> 4) & 0xF;
let device_2 = (device >> 0) & 0xF;
let device_3 = (device >> 12) & 0xF;
let device_4 = (device >> 8) & 0xF;
format!(
"{vendor_1}{vendor_2}{vendor_3}{device_1:01X}{device_2:01X}{device_3:01X}{device_4:01X}"
)
}
@@ -0,0 +1,22 @@
[package]
name = "amd-mp2-i2cd"
description = "AMD MP2 PCI I2C controller driver"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow.workspace = true
log.workspace = true
redox_syscall = { workspace = true, features = ["std"] }
libredox.workspace = true
serde.workspace = true
ron.workspace = true
acpi-resource = { path = "../../acpi-resource" }
common = { path = "../../common" }
daemon = { path = "../../../daemon" }
i2c-interface = { path = "../i2c-interface" }
pcid = { path = "../../pcid" }
[lints]
workspace = true
@@ -0,0 +1,106 @@
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
use std::process;
use anyhow::{Context, Result};
use i2c_interface::{I2cAdapterInfo, I2cControlRequest, I2cControlResponse};
use pcid_interface::PciFunctionHandle;
const MP2_MAILBOX_STATUS: usize = 0x00;
const MP2_MAILBOX_COMMAND: usize = 0x04;
const MP2_MAILBOX_ARGUMENT0: usize = 0x08;
const MP2_MAILBOX_ARGUMENT1: usize = 0x0C;
fn main() {
pcid_interface::pci_daemon(daemon_runner);
}
fn daemon_runner(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
if let Err(err) = daemon_main(daemon, &mut pcid_handle) {
log::error!("amd-mp2-i2cd: {err:#}");
process::exit(1);
}
process::exit(0);
}
fn daemon_main(daemon: daemon::Daemon, pcid_handle: &mut PciFunctionHandle) -> Result<()> {
let pci_config = pcid_handle.config();
let log_name = format!("{}_amd-mp2-i2c", pci_config.func.name());
common::setup_logging(
"bus",
"i2c",
&log_name,
common::output_level(),
common::file_level(),
);
let mapped_bar = unsafe { pcid_handle.map_bar(0) };
let bar_addr = mapped_bar.ptr.as_ptr() as usize;
let bar_size = mapped_bar.bar_size;
log::info!(
"amd-mp2-i2cd: {} BAR0={:#x}+{:#x} mapped={:p}+{:#x}",
pci_config.func.display(),
bar_addr,
bar_size,
mapped_bar.ptr.as_ptr(),
mapped_bar.bar_size,
);
log::debug!(
"amd-mp2-i2cd: MP2 mailbox regs status={MP2_MAILBOX_STATUS:#x} cmd={MP2_MAILBOX_COMMAND:#x} arg0={MP2_MAILBOX_ARGUMENT0:#x} arg1={MP2_MAILBOX_ARGUMENT1:#x}",
);
let info = I2cAdapterInfo {
id: 0,
name: format!("amd-mp2:{}", pci_config.func.name()),
max_transaction_size: 0,
supports_10bit_addr: false,
};
let mut registration = register_adapter(&info)
.context("failed to register AMD MP2 controller with i2cd")?;
let response = read_registration_response(&mut registration)
.context("failed to read AMD MP2 i2cd registration response")?;
match response {
I2cControlResponse::AdapterRegistered { id } => {
log::info!("amd-mp2-i2cd: controller registered with i2cd as adapter {id}");
}
other => anyhow::bail!("unexpected i2cd registration response: {other:?}"),
}
daemon.ready();
libredox::call::setrens(0, 0).context("failed to enter null namespace")?;
let _keep_registration = registration;
let _keep_pcid = pcid_handle;
loop {
std::thread::park();
}
}
fn register_adapter(info: &I2cAdapterInfo) -> Result<File> {
let mut file = OpenOptions::new()
.read(true)
.write(true)
.open("/scheme/i2c/register")
.context("failed to open /scheme/i2c/register")?;
let payload = ron::ser::to_string(&I2cControlRequest::RegisterAdapter { info: info.clone() })
.context("failed to encode AMD MP2 I2C registration payload")?;
file.write_all(payload.as_bytes())
.context("failed to send AMD MP2 I2C registration payload")?;
Ok(file)
}
fn read_registration_response(file: &mut File) -> Result<I2cControlResponse> {
let mut buffer = vec![0_u8; 4096];
let count = file
.read(&mut buffer)
.context("failed to read AMD MP2 I2C registration response")?;
buffer.truncate(count);
let text = std::str::from_utf8(&buffer)
.context("AMD MP2 I2C registration response was not UTF-8")?;
ron::from_str(text).context("failed to decode AMD MP2 I2C registration response")
}
@@ -0,0 +1,21 @@
[package]
name = "dw-acpi-i2cd"
description = "Generic DesignWare ACPI I2C controller driver"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow.workspace = true
log.workspace = true
redox_syscall = { workspace = true, features = ["std"] }
libredox.workspace = true
serde.workspace = true
ron.workspace = true
acpi-resource = { path = "../../acpi-resource" }
common = { path = "../../common" }
daemon = { path = "../../../daemon" }
i2c-interface = { path = "../i2c-interface" }
[lints]
workspace = true
@@ -0,0 +1,361 @@
use std::collections::BTreeMap;
use std::fs::{self, File, OpenOptions};
use std::io::{Read, Write};
use std::path::Path;
use std::process;
use acpi_resource::{
AddressResourceType, ExtendedIrqDescriptor, FixedMemory32Descriptor, I2cSerialBusDescriptor,
IrqDescriptor, Memory32RangeDescriptor, ResourceDescriptor,
};
use anyhow::{Context, Result};
use common::{MemoryType, PhysBorrowed, Prot};
use i2c_interface::{I2cAdapterInfo, I2cControlRequest, I2cControlResponse};
use serde::Deserialize;
const SUPPORTED_IDS: &[&str] = &["80860F41", "808622C1", "AMDI0010", "AMDI0019", "AMDI0510"];
const DW_IC_CON: usize = 0x00;
const DW_IC_TAR: usize = 0x04;
const DW_IC_SS_SCL_HCNT: usize = 0x14;
const DW_IC_SS_SCL_LCNT: usize = 0x18;
const DW_IC_DATA_CMD: usize = 0x10;
const DW_IC_INTR_MASK: usize = 0x30;
const DW_IC_CLR_INTR: usize = 0x40;
const DW_IC_ENABLE: usize = 0x6C;
const DW_IC_STATUS: usize = 0x70;
const DW_MMIO_WINDOW: usize = DW_IC_STATUS + core::mem::size_of::<u32>();
#[derive(Debug, Deserialize)]
struct AmlSymbol {
name: String,
value: AmlValue,
}
#[derive(Debug, Deserialize)]
enum AmlValue {
Integer(u64),
String(String),
}
#[derive(Clone, Debug)]
struct ControllerResources {
mmio_base: usize,
mmio_len: usize,
irq: Option<u32>,
serial_bus: Option<I2cSerialBusDescriptor>,
}
#[derive(Debug)]
struct ControllerDescriptor {
device: String,
hid: String,
resources: ControllerResources,
}
struct RegisteredController {
_mmio: Option<PhysBorrowed>,
_registration: File,
}
fn main() {
common::setup_logging(
"bus",
"i2c",
"dw-acpi-i2cd",
common::output_level(),
common::file_level(),
);
daemon::Daemon::new(daemon_runner);
}
fn daemon_runner(daemon: daemon::Daemon) -> ! {
if let Err(err) = daemon_main(daemon) {
log::error!("dw-acpi-i2cd: {err:#}");
process::exit(1);
}
process::exit(0);
}
fn daemon_main(daemon: daemon::Daemon) -> Result<()> {
common::init();
let controllers = discover_controllers(SUPPORTED_IDS)
.context("failed to discover DesignWare ACPI I2C controllers")?;
if controllers.is_empty() {
log::info!("dw-acpi-i2cd: no supported ACPI controllers found");
}
let mut registered = Vec::new();
for controller in controllers {
match register_controller("dw-acpi", controller) {
Ok(controller) => registered.push(controller),
Err(err) => log::warn!("dw-acpi-i2cd: controller registration skipped: {err:#}"),
}
}
daemon.ready();
libredox::call::setrens(0, 0).context("failed to enter null namespace")?;
log::info!("dw-acpi-i2cd: registered {} controller(s)", registered.len());
loop {
std::thread::park();
}
}
fn discover_controllers(supported_ids: &[&str]) -> Result<Vec<ControllerDescriptor>> {
let mut matched = BTreeMap::new();
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!("dw-acpi-i2cd: ACPI symbols are not ready yet");
return Ok(Vec::new());
}
Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"),
};
for entry in entries {
let entry = entry.context("failed to read ACPI symbol directory entry")?;
let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else {
continue;
};
if !file_name.ends_with("_HID") && !file_name.ends_with("_CID") {
continue;
}
let Some(id) = read_symbol_id(&entry.path())? else {
continue;
};
if !supported_ids.iter().any(|candidate| *candidate == id) {
continue;
}
let device = file_name
.strip_suffix("_HID")
.or_else(|| file_name.strip_suffix("_CID"))
.map(str::to_owned);
if let Some(device) = device {
matched.entry(device).or_insert(id);
}
}
let mut controllers = Vec::new();
for (device, hid) in matched {
let resources = read_controller_resources(&device)
.with_context(|| format!("failed to read resources for {device}"))?;
controllers.push(ControllerDescriptor {
device,
hid,
resources,
});
}
Ok(controllers)
}
fn read_symbol_id(path: &Path) -> Result<Option<String>> {
let contents = fs::read_to_string(path)
.with_context(|| format!("failed to read ACPI symbol {}", path.display()))?;
let symbol = match ron::from_str::<AmlSymbol>(&contents) {
Ok(symbol) => symbol,
Err(err) => {
log::debug!(
"dw-acpi-i2cd: skipping {} because the symbol payload was not a scalar ID: {err}",
path.display(),
);
return Ok(None);
}
};
let id = match symbol.value {
AmlValue::Integer(integer) => eisa_id_from_integer(integer),
AmlValue::String(string) => string,
};
log::debug!("dw-acpi-i2cd: {} -> {id}", symbol.name);
Ok(Some(id))
}
fn read_controller_resources(device: &str) -> Result<ControllerResources> {
let contents = fs::read_to_string(format!("/scheme/acpi/resources/{device}"))
.with_context(|| format!("failed to read /scheme/acpi/resources/{device}"))?;
let resources = ron::from_str::<Vec<ResourceDescriptor>>(&contents)
.with_context(|| format!("failed to decode RON resources for {device}"))?;
let mut mmio = None;
let mut irq = None;
let mut serial_bus = None;
for resource in &resources {
match resource {
ResourceDescriptor::FixedMemory32(FixedMemory32Descriptor {
address,
address_length,
..
}) if mmio.is_none() => {
mmio = Some((*address as usize, (*address_length as usize).max(DW_MMIO_WINDOW)));
}
ResourceDescriptor::Memory32Range(Memory32RangeDescriptor {
minimum,
maximum,
address_length,
..
}) if mmio.is_none() && maximum >= minimum => {
let span = maximum.saturating_sub(*minimum).saturating_add(1) as usize;
mmio = Some((
*minimum as usize,
span.max((*address_length as usize).max(DW_MMIO_WINDOW)),
));
}
ResourceDescriptor::Address32(descriptor)
if mmio.is_none()
&& matches!(descriptor.resource_type, AddressResourceType::MemoryRange) =>
{
mmio = Some((
descriptor.minimum as usize,
(descriptor.address_length as usize).max(DW_MMIO_WINDOW),
));
}
ResourceDescriptor::Address64(descriptor)
if mmio.is_none()
&& matches!(descriptor.resource_type, AddressResourceType::MemoryRange) =>
{
let base = usize::try_from(descriptor.minimum)
.context("64-bit MMIO base does not fit in usize")?;
let len = usize::try_from(descriptor.address_length)
.context("64-bit MMIO length does not fit in usize")?;
mmio = Some((base, len.max(DW_MMIO_WINDOW)));
}
ResourceDescriptor::Irq(IrqDescriptor { interrupts, .. }) if irq.is_none() => {
irq = interrupts.first().copied().map(u32::from);
}
ResourceDescriptor::ExtendedIrq(ExtendedIrqDescriptor { interrupts, .. })
if irq.is_none() =>
{
irq = interrupts.first().copied();
}
ResourceDescriptor::I2cSerialBus(descriptor) if serial_bus.is_none() => {
serial_bus = Some(descriptor.clone());
}
_ => {}
}
}
let (mmio_base, mmio_len) = mmio.context("no MMIO resource was found")?;
Ok(ControllerResources {
mmio_base,
mmio_len,
irq,
serial_bus,
})
}
fn register_controller(prefix: &str, controller: ControllerDescriptor) -> Result<RegisteredController> {
let ControllerDescriptor {
device,
hid,
resources,
} = controller;
let mmio = match PhysBorrowed::map(
resources.mmio_base,
resources.mmio_len,
Prot::RW,
MemoryType::Uncacheable,
) {
Ok(mapping) => Some(mapping),
Err(err) => {
log::warn!(
"dw-acpi-i2cd: failed to map MMIO for {device} ({:#x}, len {:#x}): {err}",
resources.mmio_base,
resources.mmio_len,
);
None
}
};
log::info!(
"dw-acpi-i2cd: discovered {device} hid={hid} mmio={:#x}+{:#x} irq={:?}",
resources.mmio_base,
resources.mmio_len,
resources.irq,
);
log::debug!(
"dw-acpi-i2cd: DesignWare regs con={DW_IC_CON:#x} tar={DW_IC_TAR:#x} data_cmd={DW_IC_DATA_CMD:#x} intr_mask={DW_IC_INTR_MASK:#x} clr_intr={DW_IC_CLR_INTR:#x} enable={DW_IC_ENABLE:#x} ss_hcnt={DW_IC_SS_SCL_HCNT:#x} ss_lcnt={DW_IC_SS_SCL_LCNT:#x}",
);
let info = I2cAdapterInfo {
id: 0,
name: format!("{prefix}:{device}"),
max_transaction_size: 0,
supports_10bit_addr: resources
.serial_bus
.as_ref()
.map(|bus| bus.access_mode_10bit)
.unwrap_or(false),
};
let mut registration = register_adapter(&info)
.with_context(|| format!("failed to register {device} with i2cd"))?;
let response = read_registration_response(&mut registration)
.with_context(|| format!("failed to read i2cd registration response for {device}"))?;
match response {
I2cControlResponse::AdapterRegistered { id } => {
log::info!("dw-acpi-i2cd: adapter {device} registered with i2cd as {id}");
}
other => {
anyhow::bail!("unexpected i2cd registration response for {device}: {other:?}");
}
}
Ok(RegisteredController {
_mmio: mmio,
_registration: registration,
})
}
fn register_adapter(info: &I2cAdapterInfo) -> Result<File> {
let mut file = OpenOptions::new()
.read(true)
.write(true)
.open("/scheme/i2c/register")
.context("failed to open /scheme/i2c/register")?;
let payload = ron::ser::to_string(&I2cControlRequest::RegisterAdapter { info: info.clone() })
.context("failed to encode I2C adapter registration")?;
file.write_all(payload.as_bytes())
.context("failed to send I2C adapter registration")?;
Ok(file)
}
fn read_registration_response(file: &mut File) -> Result<I2cControlResponse> {
let mut buffer = vec![0_u8; 4096];
let count = file
.read(&mut buffer)
.context("failed to read I2C registration response")?;
buffer.truncate(count);
let text = std::str::from_utf8(&buffer).context("I2C registration response was not UTF-8")?;
ron::from_str(text).context("failed to decode I2C registration response")
}
fn eisa_id_from_integer(integer: u64) -> String {
let vendor = integer & 0xFFFF;
let device = (integer >> 16) & 0xFFFF;
let vendor_rev = ((vendor & 0xFF) << 8) | (vendor >> 8);
let vendor_1 = (((vendor_rev >> 10) & 0x1F) as u8 + 64) as char;
let vendor_2 = (((vendor_rev >> 5) & 0x1F) as u8 + 64) as char;
let vendor_3 = (((vendor_rev >> 0) & 0x1F) as u8 + 64) as char;
let device_1 = (device >> 4) & 0xF;
let device_2 = (device >> 0) & 0xF;
let device_3 = (device >> 12) & 0xF;
let device_4 = (device >> 8) & 0xF;
format!(
"{vendor_1}{vendor_2}{vendor_3}{device_1:01X}{device_2:01X}{device_3:01X}{device_4:01X}"
)
}
@@ -0,0 +1,12 @@
[package]
name = "i2c-interface"
description = "Shared I2C transfer and registry types"
version = "0.1.0"
edition = "2021"
[dependencies]
serde.workspace = true
redox_syscall = { workspace = true, features = ["std"] }
[lints]
workspace = true
@@ -0,0 +1,92 @@
use serde::{Deserialize, Serialize};
pub use syscall;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum I2cTransferOp {
Write(Vec<u8>),
Read(usize),
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct I2cTransferSegment {
pub address: u16,
pub ten_bit_address: bool,
pub op: I2cTransferOp,
}
impl I2cTransferSegment {
pub fn write(address: u16, payload: impl Into<Vec<u8>>) -> Self {
Self {
address,
ten_bit_address: false,
op: I2cTransferOp::Write(payload.into()),
}
}
pub fn read(address: u16, len: usize) -> Self {
Self {
address,
ten_bit_address: false,
op: I2cTransferOp::Read(len),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct I2cTransferRequest {
pub adapter: String,
pub segments: Vec<I2cTransferSegment>,
pub stop: bool,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct I2cTransferResponse {
pub ok: bool,
pub read_data: Vec<Vec<u8>>,
pub error: Option<String>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct I2cAdapterRegistration {
pub name: String,
pub description: String,
pub acpi_companion: Option<String>,
pub slave_address_override: Option<u16>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct I2cAdapterInfo {
pub id: u32,
pub name: String,
pub max_transaction_size: usize,
pub supports_10bit_addr: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum I2cTransferStatus {
Ok,
Nack,
Timeout,
Error,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum I2cControlRequest {
RegisterAdapter { info: I2cAdapterInfo },
OpenAdapter { id: u32 },
Transfer {
adapter_id: u32,
request: I2cTransferRequest,
},
ListAdapters,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum I2cControlResponse {
AdapterRegistered { id: u32 },
AdapterOpened,
TransferResult(I2cTransferResponse),
AdapterList(Vec<I2cAdapterInfo>),
Error(String),
}
@@ -0,0 +1,22 @@
[package]
name = "i2cd"
description = "I2C adapter registry scheme daemon"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow.workspace = true
log.workspace = true
redox_syscall = { workspace = true, features = ["std"] }
libredox.workspace = true
redox-scheme.workspace = true
serde.workspace = true
ron.workspace = true
common = { path = "../../common" }
daemon = { path = "../../../daemon" }
scheme-utils = { path = "../../../scheme-utils" }
i2c-interface = { path = "../i2c-interface" }
[lints]
workspace = true
@@ -0,0 +1,377 @@
use std::collections::BTreeMap;
use std::process;
use anyhow::{Context, Result};
use i2c_interface::{
I2cAdapterInfo, I2cControlRequest, I2cControlResponse, I2cTransferRequest,
I2cTransferResponse,
};
use redox_scheme::scheme::SchemeSync;
use redox_scheme::{CallerCtx, OpenResult, Socket};
use scheme_utils::{Blocking, HandleMap};
use syscall::schemev2::NewFdFlags;
use syscall::{Error as SysError, EACCES, EBADF, EINVAL, ENOENT};
enum Handle {
SchemeRoot,
Register { pending: Vec<u8> },
Provider { adapter_id: u32, pending: Vec<u8> },
Adapters { pending: Vec<u8> },
AdapterDetail { id: u32, pending: Vec<u8> },
Transfer { pending: Vec<u8> },
}
struct AdapterEntry {
info: I2cAdapterInfo,
provider_handle: usize,
}
struct I2cDaemon {
handles: HandleMap<Handle>,
adapters: BTreeMap<u32, AdapterEntry>,
next_id: u32,
}
impl I2cDaemon {
fn new() -> Self {
Self {
handles: HandleMap::new(),
adapters: BTreeMap::new(),
next_id: 0,
}
}
fn adapter_list(&self) -> Vec<I2cAdapterInfo> {
self.adapters.values().map(|entry| entry.info.clone()).collect()
}
fn serialize_response(response: &I2cControlResponse) -> syscall::Result<Vec<u8>> {
ron::ser::to_string(response)
.map(|text| text.into_bytes())
.map_err(|err| {
log::error!("i2cd: failed to serialize control response: {err}");
SysError::new(EINVAL)
})
}
fn deserialize_request(buf: &[u8]) -> syscall::Result<I2cControlRequest> {
let text = std::str::from_utf8(buf).map_err(|err| {
log::warn!("i2cd: invalid UTF-8 control payload: {err}");
SysError::new(EINVAL)
})?;
ron::from_str(text).map_err(|err| {
log::warn!("i2cd: failed to decode control request: {err}");
SysError::new(EINVAL)
})
}
fn set_pending_response(handle: &mut Handle, response: I2cControlResponse) -> syscall::Result<()> {
let pending = Self::serialize_response(&response)?;
match handle {
Handle::Register { pending: slot }
| Handle::Provider { pending: slot, .. }
| Handle::Adapters { pending: slot }
| Handle::AdapterDetail { pending: slot, .. }
| Handle::Transfer { pending: slot } => {
*slot = pending;
Ok(())
}
Handle::SchemeRoot => Err(SysError::new(EBADF)),
}
}
fn queue_adapter_list(handle: &mut Handle, adapters: Vec<I2cAdapterInfo>) -> syscall::Result<()> {
Self::set_pending_response(handle, I2cControlResponse::AdapterList(adapters))
}
fn queue_transfer_stub(
handle: &mut Handle,
adapter: &I2cAdapterInfo,
request: &I2cTransferRequest,
) -> syscall::Result<()> {
let write_bytes = request
.segments
.iter()
.filter_map(|segment| match &segment.op {
i2c_interface::I2cTransferOp::Write(bytes) => Some(bytes.len()),
i2c_interface::I2cTransferOp::Read(_) => None,
})
.sum::<usize>();
let read_segments = request
.segments
.iter()
.filter(|segment| matches!(segment.op, i2c_interface::I2cTransferOp::Read(_)))
.count();
log::info!(
"i2cd: routing transfer to adapter {} ({}) name={} segments={} write_bytes={} read_segments={} stop={} (stubbed)",
adapter.id,
adapter.name,
request.adapter,
request.segments.len(),
write_bytes,
read_segments,
request.stop,
);
Self::set_pending_response(
handle,
I2cControlResponse::TransferResult(I2cTransferResponse {
ok: false,
read_data: Vec::new(),
error: Some(String::from("I2C controller transfer path is not implemented yet")),
}),
)
}
fn copy_pending(handle: &mut Handle, buf: &mut [u8], offset: u64) -> syscall::Result<usize> {
let pending = match handle {
Handle::Register { pending }
| Handle::Provider { pending, .. }
| Handle::Adapters { pending }
| Handle::AdapterDetail { pending, .. }
| Handle::Transfer { pending } => pending,
Handle::SchemeRoot => return Err(SysError::new(EBADF)),
};
let offset = usize::try_from(offset).map_err(|_| SysError::new(EINVAL))?;
if offset >= pending.len() {
return Ok(0);
}
let copy_len = buf.len().min(pending.len() - offset);
buf[..copy_len].copy_from_slice(&pending[offset..offset + copy_len]);
Ok(copy_len)
}
}
impl SchemeSync for I2cDaemon {
fn scheme_root(&mut self) -> syscall::Result<usize> {
Ok(self.handles.insert(Handle::SchemeRoot))
}
fn openat(
&mut self,
dirfd: usize,
path: &str,
_flags: usize,
_fcntl_flags: u32,
_ctx: &CallerCtx,
) -> syscall::Result<OpenResult> {
let handle = self.handles.get(dirfd)?;
let segments = path.trim_matches('/');
let new_handle = match handle {
Handle::SchemeRoot => {
if segments.is_empty() {
return Err(SysError::new(EINVAL));
}
let mut parts = segments.split('/');
match parts.next() {
Some("register") if parts.next().is_none() => Handle::Register {
pending: Vec::new(),
},
Some("adapters") => match parts.next() {
None => Handle::Adapters {
pending: Vec::new(),
},
Some(id) if parts.next().is_none() => {
let id = id.parse::<u32>().map_err(|_| SysError::new(EINVAL))?;
Handle::AdapterDetail {
id,
pending: Vec::new(),
}
}
_ => return Err(SysError::new(EINVAL)),
},
Some("transfer") if parts.next().is_none() => Handle::Transfer {
pending: Vec::new(),
},
_ => return Err(SysError::new(ENOENT)),
}
}
Handle::Adapters { .. } => {
if segments.is_empty() {
return Err(SysError::new(EINVAL));
}
let id = segments.parse::<u32>().map_err(|_| SysError::new(EINVAL))?;
Handle::AdapterDetail {
id,
pending: Vec::new(),
}
}
_ => return Err(SysError::new(EACCES)),
};
let fd = self.handles.insert(new_handle);
Ok(OpenResult::ThisScheme {
number: fd,
flags: NewFdFlags::empty(),
})
}
fn read(
&mut self,
id: usize,
buf: &mut [u8],
offset: u64,
_fcntl_flags: u32,
_ctx: &CallerCtx,
) -> syscall::Result<usize> {
let adapters = self.adapter_list();
let handle = self.handles.get_mut(id)?;
match handle {
Handle::Adapters { pending } if pending.is_empty() => {
*pending = Self::serialize_response(&I2cControlResponse::AdapterList(adapters))?;
}
Handle::AdapterDetail { id, pending } if pending.is_empty() => {
let info = self
.adapters
.get(id)
.map(|entry| entry.info.clone())
.ok_or(SysError::new(ENOENT))?;
*pending = Self::serialize_response(&I2cControlResponse::AdapterList(vec![info]))?;
}
_ => {}
}
Self::copy_pending(handle, buf, offset)
}
fn write(
&mut self,
id: usize,
buf: &[u8],
_offset: u64,
_fcntl_flags: u32,
_ctx: &CallerCtx,
) -> syscall::Result<usize> {
let request = Self::deserialize_request(buf)?;
match request {
I2cControlRequest::RegisterAdapter { mut info } => {
let adapter_id = self.next_id;
self.next_id = self.next_id.checked_add(1).ok_or(SysError::new(EINVAL))?;
info.id = adapter_id;
self.adapters.insert(
adapter_id,
AdapterEntry {
info: info.clone(),
provider_handle: id,
},
);
let handle = self.handles.get_mut(id)?;
*handle = Handle::Provider {
adapter_id,
pending: Self::serialize_response(&I2cControlResponse::AdapterRegistered {
id: adapter_id,
})?,
};
log::info!(
"RB_I2CD_ADAPTER_REGISTERED id={} name={} max_transaction_size={} supports_10bit_addr={}",
info.id,
info.name,
info.max_transaction_size,
info.supports_10bit_addr,
);
Ok(buf.len())
}
I2cControlRequest::ListAdapters => {
let adapters = self.adapter_list();
let handle = self.handles.get_mut(id)?;
Self::queue_adapter_list(handle, adapters)?;
Ok(buf.len())
}
I2cControlRequest::OpenAdapter { id: adapter_id } => {
if !self.adapters.contains_key(&adapter_id) {
return Err(SysError::new(ENOENT));
}
let handle = self.handles.get_mut(id)?;
match handle {
Handle::Adapters { .. } | Handle::AdapterDetail { .. } => {
Self::set_pending_response(handle, I2cControlResponse::AdapterOpened)?;
Ok(buf.len())
}
_ => Err(SysError::new(EINVAL)),
}
}
I2cControlRequest::Transfer {
adapter_id,
request,
} => {
let entry = self.adapters.get(&adapter_id).ok_or(SysError::new(ENOENT))?;
log::debug!(
"i2cd: transfer requested for adapter {} via provider fd {}",
adapter_id,
entry.provider_handle,
);
let adapter_info = entry.info.clone();
let handle = self.handles.get_mut(id)?;
match handle {
Handle::Transfer { .. } => {
Self::queue_transfer_stub(handle, &adapter_info, &request)?;
Ok(buf.len())
}
_ => Err(SysError::new(EINVAL)),
}
}
}
}
fn on_close(&mut self, id: usize) {
let Some(handle) = self.handles.remove(id) else {
return;
};
if let Handle::Provider { adapter_id, .. } = handle {
self.adapters.remove(&adapter_id);
}
}
}
fn run_daemon(daemon: daemon::SchemeDaemon) -> Result<()> {
let socket = Socket::create().context("failed to create i2c scheme socket")?;
let mut scheme = I2cDaemon::new();
let handler = Blocking::new(&socket, 16);
daemon
.ready_sync_scheme(&socket, &mut scheme)
.context("failed to publish i2c scheme root")?;
log::info!("RB_I2CD_SCHEMA");
libredox::call::setrens(0, 0).context("failed to enter null namespace")?;
handler
.process_requests_blocking(scheme)
.context("failed to process i2cd requests")?;
}
fn daemon_runner(daemon: daemon::SchemeDaemon) -> ! {
if let Err(err) = run_daemon(daemon) {
log::error!("i2cd: {err:#}");
process::exit(1);
}
process::exit(0);
}
fn main() {
common::setup_logging(
"bus",
"i2c",
"i2cd",
common::output_level(),
common::file_level(),
);
daemon::SchemeDaemon::new(daemon_runner);
}
@@ -0,0 +1,21 @@
[package]
name = "intel-lpss-i2cd"
description = "Intel LPSS ACPI I2C controller driver"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow.workspace = true
log.workspace = true
redox_syscall = { workspace = true, features = ["std"] }
libredox.workspace = true
serde.workspace = true
ron.workspace = true
acpi-resource = { path = "../../acpi-resource" }
common = { path = "../../common" }
daemon = { path = "../../../daemon" }
i2c-interface = { path = "../i2c-interface" }
[lints]
workspace = true
@@ -0,0 +1,361 @@
use std::collections::BTreeMap;
use std::fs::{self, File, OpenOptions};
use std::io::{Read, Write};
use std::path::Path;
use std::process;
use acpi_resource::{
AddressResourceType, ExtendedIrqDescriptor, FixedMemory32Descriptor, I2cSerialBusDescriptor,
IrqDescriptor, Memory32RangeDescriptor, ResourceDescriptor,
};
use anyhow::{Context, Result};
use common::{MemoryType, PhysBorrowed, Prot};
use i2c_interface::{I2cAdapterInfo, I2cControlRequest, I2cControlResponse};
use serde::Deserialize;
const SUPPORTED_IDS: &[&str] = &["INT33C2", "INT33C3", "INT3432", "INT3433", "INTC10EF"];
const DW_IC_CON: usize = 0x00;
const DW_IC_TAR: usize = 0x04;
const DW_IC_SS_SCL_HCNT: usize = 0x14;
const DW_IC_SS_SCL_LCNT: usize = 0x18;
const DW_IC_DATA_CMD: usize = 0x10;
const DW_IC_INTR_MASK: usize = 0x30;
const DW_IC_CLR_INTR: usize = 0x40;
const DW_IC_ENABLE: usize = 0x6C;
const DW_IC_STATUS: usize = 0x70;
const DW_MMIO_WINDOW: usize = DW_IC_STATUS + core::mem::size_of::<u32>();
#[derive(Debug, Deserialize)]
struct AmlSymbol {
name: String,
value: AmlValue,
}
#[derive(Debug, Deserialize)]
enum AmlValue {
Integer(u64),
String(String),
}
#[derive(Clone, Debug)]
struct ControllerResources {
mmio_base: usize,
mmio_len: usize,
irq: Option<u32>,
serial_bus: Option<I2cSerialBusDescriptor>,
}
#[derive(Debug)]
struct ControllerDescriptor {
device: String,
hid: String,
resources: ControllerResources,
}
struct RegisteredController {
_mmio: Option<PhysBorrowed>,
_registration: File,
}
fn main() {
common::setup_logging(
"bus",
"i2c",
"intel-lpss-i2cd",
common::output_level(),
common::file_level(),
);
daemon::Daemon::new(daemon_runner);
}
fn daemon_runner(daemon: daemon::Daemon) -> ! {
if let Err(err) = daemon_main(daemon) {
log::error!("intel-lpss-i2cd: {err:#}");
process::exit(1);
}
process::exit(0);
}
fn daemon_main(daemon: daemon::Daemon) -> Result<()> {
common::init();
let controllers = discover_controllers(SUPPORTED_IDS)
.context("failed to discover Intel LPSS ACPI I2C controllers")?;
if controllers.is_empty() {
log::info!("intel-lpss-i2cd: no supported ACPI controllers found");
}
let mut registered = Vec::new();
for controller in controllers {
match register_controller("intel-lpss", controller) {
Ok(controller) => registered.push(controller),
Err(err) => log::warn!("intel-lpss-i2cd: controller registration skipped: {err:#}"),
}
}
daemon.ready();
libredox::call::setrens(0, 0).context("failed to enter null namespace")?;
log::info!("intel-lpss-i2cd: registered {} controller(s)", registered.len());
loop {
std::thread::park();
}
}
fn discover_controllers(supported_ids: &[&str]) -> Result<Vec<ControllerDescriptor>> {
let mut matched = BTreeMap::new();
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!("intel-lpss-i2cd: ACPI symbols are not ready yet");
return Ok(Vec::new());
}
Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"),
};
for entry in entries {
let entry = entry.context("failed to read ACPI symbol directory entry")?;
let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else {
continue;
};
if !file_name.ends_with("_HID") && !file_name.ends_with("_CID") {
continue;
}
let Some(id) = read_symbol_id(&entry.path())? else {
continue;
};
if !supported_ids.iter().any(|candidate| *candidate == id) {
continue;
}
let device = file_name
.strip_suffix("_HID")
.or_else(|| file_name.strip_suffix("_CID"))
.map(str::to_owned);
if let Some(device) = device {
matched.entry(device).or_insert(id);
}
}
let mut controllers = Vec::new();
for (device, hid) in matched {
let resources = read_controller_resources(&device)
.with_context(|| format!("failed to read resources for {device}"))?;
controllers.push(ControllerDescriptor {
device,
hid,
resources,
});
}
Ok(controllers)
}
fn read_symbol_id(path: &Path) -> Result<Option<String>> {
let contents = fs::read_to_string(path)
.with_context(|| format!("failed to read ACPI symbol {}", path.display()))?;
let symbol = match ron::from_str::<AmlSymbol>(&contents) {
Ok(symbol) => symbol,
Err(err) => {
log::debug!(
"intel-lpss-i2cd: skipping {} because the symbol payload was not a scalar ID: {err}",
path.display(),
);
return Ok(None);
}
};
let id = match symbol.value {
AmlValue::Integer(integer) => eisa_id_from_integer(integer),
AmlValue::String(string) => string,
};
log::debug!("intel-lpss-i2cd: {} -> {id}", symbol.name);
Ok(Some(id))
}
fn read_controller_resources(device: &str) -> Result<ControllerResources> {
let contents = fs::read_to_string(format!("/scheme/acpi/resources/{device}"))
.with_context(|| format!("failed to read /scheme/acpi/resources/{device}"))?;
let resources = ron::from_str::<Vec<ResourceDescriptor>>(&contents)
.with_context(|| format!("failed to decode RON resources for {device}"))?;
let mut mmio = None;
let mut irq = None;
let mut serial_bus = None;
for resource in &resources {
match resource {
ResourceDescriptor::FixedMemory32(FixedMemory32Descriptor {
address,
address_length,
..
}) if mmio.is_none() => {
mmio = Some((*address as usize, (*address_length as usize).max(DW_MMIO_WINDOW)));
}
ResourceDescriptor::Memory32Range(Memory32RangeDescriptor {
minimum,
maximum,
address_length,
..
}) if mmio.is_none() && maximum >= minimum => {
let span = maximum.saturating_sub(*minimum).saturating_add(1) as usize;
mmio = Some((
*minimum as usize,
span.max((*address_length as usize).max(DW_MMIO_WINDOW)),
));
}
ResourceDescriptor::Address32(descriptor)
if mmio.is_none()
&& matches!(descriptor.resource_type, AddressResourceType::MemoryRange) =>
{
mmio = Some((
descriptor.minimum as usize,
(descriptor.address_length as usize).max(DW_MMIO_WINDOW),
));
}
ResourceDescriptor::Address64(descriptor)
if mmio.is_none()
&& matches!(descriptor.resource_type, AddressResourceType::MemoryRange) =>
{
let base = usize::try_from(descriptor.minimum)
.context("64-bit MMIO base does not fit in usize")?;
let len = usize::try_from(descriptor.address_length)
.context("64-bit MMIO length does not fit in usize")?;
mmio = Some((base, len.max(DW_MMIO_WINDOW)));
}
ResourceDescriptor::Irq(IrqDescriptor { interrupts, .. }) if irq.is_none() => {
irq = interrupts.first().copied().map(u32::from);
}
ResourceDescriptor::ExtendedIrq(ExtendedIrqDescriptor { interrupts, .. })
if irq.is_none() =>
{
irq = interrupts.first().copied();
}
ResourceDescriptor::I2cSerialBus(descriptor) if serial_bus.is_none() => {
serial_bus = Some(descriptor.clone());
}
_ => {}
}
}
let (mmio_base, mmio_len) = mmio.context("no MMIO resource was found")?;
Ok(ControllerResources {
mmio_base,
mmio_len,
irq,
serial_bus,
})
}
fn register_controller(prefix: &str, controller: ControllerDescriptor) -> Result<RegisteredController> {
let ControllerDescriptor {
device,
hid,
resources,
} = controller;
let mmio = match PhysBorrowed::map(
resources.mmio_base,
resources.mmio_len,
Prot::RW,
MemoryType::Uncacheable,
) {
Ok(mapping) => Some(mapping),
Err(err) => {
log::warn!(
"intel-lpss-i2cd: failed to map MMIO for {device} ({:#x}, len {:#x}): {err}",
resources.mmio_base,
resources.mmio_len,
);
None
}
};
log::info!(
"intel-lpss-i2cd: discovered {device} hid={hid} mmio={:#x}+{:#x} irq={:?}",
resources.mmio_base,
resources.mmio_len,
resources.irq,
);
log::debug!(
"intel-lpss-i2cd: DesignWare regs con={DW_IC_CON:#x} tar={DW_IC_TAR:#x} data_cmd={DW_IC_DATA_CMD:#x} intr_mask={DW_IC_INTR_MASK:#x} clr_intr={DW_IC_CLR_INTR:#x} enable={DW_IC_ENABLE:#x} ss_hcnt={DW_IC_SS_SCL_HCNT:#x} ss_lcnt={DW_IC_SS_SCL_LCNT:#x}",
);
let info = I2cAdapterInfo {
id: 0,
name: format!("{prefix}:{device}"),
max_transaction_size: 0,
supports_10bit_addr: resources
.serial_bus
.as_ref()
.map(|bus| bus.access_mode_10bit)
.unwrap_or(false),
};
let mut registration = register_adapter(&info)
.with_context(|| format!("failed to register {device} with i2cd"))?;
let response = read_registration_response(&mut registration)
.with_context(|| format!("failed to read i2cd registration response for {device}"))?;
match response {
I2cControlResponse::AdapterRegistered { id } => {
log::info!("intel-lpss-i2cd: adapter {device} registered with i2cd as {id}");
}
other => {
anyhow::bail!("unexpected i2cd registration response for {device}: {other:?}");
}
}
Ok(RegisteredController {
_mmio: mmio,
_registration: registration,
})
}
fn register_adapter(info: &I2cAdapterInfo) -> Result<File> {
let mut file = OpenOptions::new()
.read(true)
.write(true)
.open("/scheme/i2c/register")
.context("failed to open /scheme/i2c/register")?;
let payload = ron::ser::to_string(&I2cControlRequest::RegisterAdapter { info: info.clone() })
.context("failed to encode I2C adapter registration")?;
file.write_all(payload.as_bytes())
.context("failed to send I2C adapter registration")?;
Ok(file)
}
fn read_registration_response(file: &mut File) -> Result<I2cControlResponse> {
let mut buffer = vec![0_u8; 4096];
let count = file
.read(&mut buffer)
.context("failed to read I2C registration response")?;
buffer.truncate(count);
let text = std::str::from_utf8(&buffer).context("I2C registration response was not UTF-8")?;
ron::from_str(text).context("failed to decode I2C registration response")
}
fn eisa_id_from_integer(integer: u64) -> String {
let vendor = integer & 0xFFFF;
let device = (integer >> 16) & 0xFFFF;
let vendor_rev = ((vendor & 0xFF) << 8) | (vendor >> 8);
let vendor_1 = (((vendor_rev >> 10) & 0x1F) as u8 + 64) as char;
let vendor_2 = (((vendor_rev >> 5) & 0x1F) as u8 + 64) as char;
let vendor_3 = (((vendor_rev >> 0) & 0x1F) as u8 + 64) as char;
let device_1 = (device >> 4) & 0xF;
let device_2 = (device >> 0) & 0xF;
let device_3 = (device >> 12) & 0xF;
let device_4 = (device >> 8) & 0xF;
format!(
"{vendor_1}{vendor_2}{vendor_3}{device_1:01X}{device_2:01X}{device_3:01X}{device_4:01X}"
)
}
@@ -0,0 +1,26 @@
[package]
name = "i2c-hidd"
description = "I2C HID client daemon"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow.workspace = true
log.workspace = true
orbclient.workspace = true
redox_syscall = { workspace = true, features = ["std"] }
libredox.workspace = true
redox-scheme.workspace = true
ron.workspace = true
serde.workspace = true
acpi-resource = { path = "../../acpi-resource" }
amlserde = { path = "../../amlserde" }
common = { path = "../../common" }
daemon = { path = "../../../daemon" }
i2c-interface = { path = "../../i2c/i2c-interface" }
inputd = { path = "../../inputd" }
scheme-utils = { path = "../../../scheme-utils" }
[lints]
workspace = true
@@ -0,0 +1,307 @@
use acpi_resource::{GpioDescriptor, ResourceDescriptor};
use amlserde::{AmlSerde, AmlSerdeValue};
use anyhow::{anyhow, bail, Context, Result};
use libredox::flag::{O_CLOEXEC, O_RDWR};
use std::collections::BTreeSet;
use std::fs::{self, OpenOptions};
use std::io::{ErrorKind, Read};
use crate::quirks::ProbeFailureQuirk;
const I2C_HID_DSM_GUID: [u8; 16] = [
0xF7, 0xF6, 0xDF, 0x3C, 0x67, 0x42, 0x55, 0x45, 0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x41, 0x76,
];
#[derive(Clone, Debug)]
pub struct I2cBinding {
pub adapter: String,
pub address: u16,
}
#[derive(Clone, Debug)]
pub struct AcpiDeviceResources {
pub i2c: I2cBinding,
pub irq: Option<u32>,
pub gpio_int: Vec<GpioDescriptor>,
pub gpio_io: Vec<GpioDescriptor>,
}
pub fn scan_acpi_i2c_hid_devices() -> Result<Vec<String>> {
let entries = match fs::read_dir("/scheme/acpi/symbols") {
Ok(entries) => entries,
Err(err) if err.kind() == ErrorKind::WouldBlock || err.raw_os_error() == Some(11) => {
return Ok(Vec::new());
}
Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"),
};
let mut devices = BTreeSet::new();
for entry in entries {
let entry = entry.context("failed to enumerate ACPI symbol entry")?;
let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else {
continue;
};
if !file_name.ends_with("._HID") && !file_name.ends_with("._CID") {
continue;
}
let symbol = read_aml_symbol(&file_name)
.with_context(|| format!("failed to read ACPI symbol {file_name}"))?;
let Some(id) = decode_hardware_id(&symbol.value) else {
continue;
};
if matches!(id.as_str(), "PNP0C50" | "ACPI0C50") {
let device = symbol
.name
.strip_suffix("._HID")
.or_else(|| symbol.name.strip_suffix("._CID"))
.unwrap_or(&symbol.name)
.trim_start_matches('\\')
.trim_matches('/')
.replace('/', ".");
if !device.is_empty() {
devices.insert(device);
}
}
}
Ok(devices.into_iter().collect())
}
pub fn read_decoded_resources(path: &str) -> Result<AcpiDeviceResources> {
let resource_path = format!("/scheme/acpi/resources/{}", normalize_device_path(path));
let serialized = fs::read_to_string(&resource_path)
.with_context(|| format!("failed to read {resource_path}"))?;
let descriptors: Vec<ResourceDescriptor> = ron::from_str(&serialized)
.with_context(|| format!("invalid ACPI resources in {resource_path}"))?;
let mut i2c = None;
let mut irq = None;
let mut gpio_int = Vec::new();
let mut gpio_io = Vec::new();
for descriptor in descriptors {
match descriptor {
ResourceDescriptor::I2cSerialBus(bus) => {
if i2c.is_none() {
let adapter = bus
.resource_source
.as_ref()
.map(|source| source.source.clone())
.filter(|source| !source.is_empty())
.unwrap_or_else(|| "ACPI-I2C".to_string());
i2c = Some(I2cBinding {
adapter,
address: bus.slave_address,
});
}
}
ResourceDescriptor::Irq(descriptor) => {
if irq.is_none() {
irq = descriptor.interrupts.first().copied().map(u32::from);
}
}
ResourceDescriptor::ExtendedIrq(descriptor) => {
if irq.is_none() {
irq = descriptor.interrupts.first().copied();
}
}
ResourceDescriptor::GpioInt(descriptor) => gpio_int.push(descriptor),
ResourceDescriptor::GpioIo(descriptor) => gpio_io.push(descriptor),
_ => {}
}
}
let mut resources = AcpiDeviceResources {
i2c: i2c.ok_or_else(|| anyhow!("no I2cSerialBus resource in _CRS"))?,
irq,
gpio_int,
gpio_io,
};
if let Some(override_address) = companion_icrs_override(path)? {
log::info!(
"{}: applying THC companion ICRS override {:04x} -> {:04x}",
path,
resources.i2c.address,
override_address
);
resources.i2c.address = override_address;
}
Ok(resources)
}
pub fn prepare_acpi_device(path: &str) -> Result<()> {
let sta = evaluate_integer_method(path, "_STA").ok();
if let Some(sta) = sta {
if sta & 0x01 == 0 {
bail!("ACPI device is not present according to _STA={sta:#x}");
}
}
let _ = evaluate_method(path, "_PS0", &[]);
let _ = evaluate_method(path, "_INI", &[]);
Ok(())
}
pub fn recover_acpi_device(
path: &str,
resources: &AcpiDeviceResources,
quirk: Option<&ProbeFailureQuirk>,
) -> Result<()> {
let _ = evaluate_method(path, "_PS3", &[]);
if let Some(quirk) = quirk {
if !resources.gpio_io.is_empty() {
log::warn!(
"{}: applying GPIO probe-failure recovery quirk {} vendor={:?} product={:?} board={:?} across {} GPIO IO resources",
path,
quirk.name,
quirk.system_vendor,
quirk.product_name,
quirk.board_name,
resources.gpio_io.len()
);
} else {
log::warn!(
"{}: quirk {} vendor={:?} product={:?} board={:?} matched but no GPIO IO resource was exposed",
path,
quirk.name
,
quirk.system_vendor,
quirk.product_name,
quirk.board_name
);
}
}
let _ = evaluate_method(path, "_PS0", &[]);
let _ = evaluate_method(path, "_INI", &[]);
Ok(())
}
pub fn hid_descriptor_address(path: &str) -> Result<u16> {
let args = [
AmlSerdeValue::Buffer(I2C_HID_DSM_GUID.to_vec()),
AmlSerdeValue::Integer(1),
AmlSerdeValue::Integer(1),
AmlSerdeValue::Package {
contents: Vec::new(),
},
];
match evaluate_method(path, "_DSM", &args) {
Ok(AmlSerdeValue::Integer(value)) => {
return u16::try_from(value).context("_DSM descriptor address out of range")
}
Ok(other) => log::warn!(
"{}._DSM returned unexpected value {:?}; retrying fallback index",
path,
other
),
Err(err) => log::warn!(
"{}._DSM function 1 failed: {err}; retrying function 0",
path
),
}
let fallback = [
AmlSerdeValue::Buffer(I2C_HID_DSM_GUID.to_vec()),
AmlSerdeValue::Integer(1),
AmlSerdeValue::Integer(0),
AmlSerdeValue::Package {
contents: Vec::new(),
},
];
match evaluate_method(path, "_DSM", &fallback)? {
AmlSerdeValue::Integer(value) => {
u16::try_from(value).context("fallback _DSM descriptor address out of range")
}
other => bail!("fallback _DSM returned unexpected value {other:?}"),
}
}
fn companion_icrs_override(path: &str) -> Result<Option<u16>> {
let value = match evaluate_integer_method(path, "ICRS") {
Ok(value) => value,
Err(_) => return Ok(None),
};
Ok(Some(
u16::try_from(value).context("ICRS override out of range")?,
))
}
pub fn evaluate_integer_method(path: &str, method: &str) -> Result<u64> {
match evaluate_method(path, method, &[])? {
AmlSerdeValue::Integer(value) => Ok(value),
other => bail!(
"{}.{} returned non-integer AML value {other:?}",
path,
method
),
}
}
pub fn evaluate_method(path: &str, method: &str, args: &[AmlSerdeValue]) -> Result<AmlSerdeValue> {
let symbol_name = format!("{}.{}", normalize_device_path(path), method);
let symbol_path = format!("/scheme/acpi/symbols/{symbol_name}");
let fd = libredox::Fd::open(&symbol_path, O_RDWR | O_CLOEXEC, 0)
.with_context(|| format!("failed to open {symbol_path} for ACPI evaluation"))?;
let serialized = ron::to_string(args)
.with_context(|| format!("failed to serialize ACPI arguments for {symbol_name}"))?;
let mut payload = serialized.into_bytes();
payload.resize(payload.len() + 4096, 0);
let used = libredox::call::call_ro(fd.raw(), &mut payload, syscall::CallFlags::empty(), &[])
.with_context(|| format!("ACPI evaluation failed for {symbol_name}"))?;
let response = std::str::from_utf8(&payload[..used])
.with_context(|| format!("invalid UTF-8 ACPI response for {symbol_name}"))?;
ron::from_str(response)
.with_context(|| format!("failed to decode ACPI response for {symbol_name}"))
}
fn read_aml_symbol(file_name: &str) -> Result<AmlSerde> {
let path = format!("/scheme/acpi/symbols/{file_name}");
let mut file = OpenOptions::new()
.read(true)
.open(&path)
.with_context(|| format!("failed to open {path}"))?;
let mut ron_text = String::new();
file.read_to_string(&mut ron_text)
.with_context(|| format!("failed to read {path}"))?;
ron::from_str(&ron_text).with_context(|| format!("failed to decode {path}"))
}
fn decode_hardware_id(value: &AmlSerdeValue) -> Option<String> {
match value {
AmlSerdeValue::String(value) => Some(value.clone()),
AmlSerdeValue::Integer(integer) => {
let vendor = integer & 0xFFFF;
let device = (integer >> 16) & 0xFFFF;
let vendor_rev = ((vendor & 0xFF) << 8) | (vendor >> 8);
let vendor_1 = (((vendor_rev >> 10) & 0x1f) as u8 + 64) as char;
let vendor_2 = (((vendor_rev >> 5) & 0x1f) as u8 + 64) as char;
let vendor_3 = (((vendor_rev >> 0) & 0x1f) as u8 + 64) as char;
let device_1 = (device >> 4) & 0xF;
let device_2 = (device >> 0) & 0xF;
let device_3 = (device >> 12) & 0xF;
let device_4 = (device >> 8) & 0xF;
Some(format!(
"{}{}{}{:01X}{:01X}{:01X}{:01X}",
vendor_1, vendor_2, vendor_3, device_1, device_2, device_3, device_4
))
}
_ => None,
}
}
pub fn normalize_device_path(path: &str) -> String {
path.trim_start_matches('\\')
.trim_matches('/')
.replace('/', ".")
}
@@ -0,0 +1,195 @@
use std::fs::OpenOptions;
use std::io::{Read, Write};
use anyhow::{bail, Context, Result};
use i2c_interface::{I2cTransferRequest, I2cTransferResponse, I2cTransferSegment};
use serde::{Deserialize, Serialize};
use crate::acpi::I2cBinding;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct HidDescriptor {
pub hid_desc_length: u16,
pub bcd_version: u16,
pub report_desc_length: u16,
pub report_desc_register: u16,
pub input_register: u16,
pub max_input_length: u16,
pub output_register: u16,
pub max_output_length: u16,
pub command_register: u16,
pub data_register: u16,
}
#[derive(Clone, Debug, Default)]
pub struct ReportDescriptorSummary {
pub has_keyboard_page: bool,
pub has_pointer_page: bool,
pub report_ids: bool,
}
#[derive(Clone, Debug)]
pub struct I2cAdapterClient {
binding: I2cBinding,
}
impl I2cAdapterClient {
pub fn new(binding: I2cBinding) -> Self {
Self { binding }
}
pub fn transfer(&self, segments: Vec<I2cTransferSegment>) -> Result<I2cTransferResponse> {
let request = I2cTransferRequest {
adapter: self.binding.adapter.clone(),
segments,
stop: true,
};
let serialized = ron::to_string(&request).context("failed to serialize I2C request")?;
let mut handle = OpenOptions::new()
.read(true)
.write(true)
.open("/scheme/i2c/transfer")
.context("failed to open /scheme/i2c/transfer")?;
handle
.write_all(serialized.as_bytes())
.context("failed to write I2C transfer request")?;
let mut response = String::new();
handle
.read_to_string(&mut response)
.context("failed to read I2C transfer response")?;
let transfer: I2cTransferResponse =
ron::from_str(&response).context("failed to decode I2C transfer response")?;
if !transfer.ok {
bail!(
"I2C transfer failed: {}",
transfer
.error
.unwrap_or_else(|| "unspecified transfer error".to_string())
);
}
Ok(transfer)
}
pub fn write_read(&self, address: u16, write_data: &[u8], read_len: usize) -> Result<Vec<u8>> {
let response = self.transfer(vec![
I2cTransferSegment::write(address, write_data.to_vec()),
I2cTransferSegment::read(address, read_len),
])?;
response
.read_data
.last()
.cloned()
.ok_or_else(|| anyhow::anyhow!("I2C transfer returned no readable segment payload"))
}
}
pub fn fetch_hid_descriptor(
adapter: &I2cAdapterClient,
address: u16,
hid_desc_addr: u16,
) -> Result<HidDescriptor> {
let prefix = adapter
.write_read(address, &hid_desc_addr.to_le_bytes(), 2)
.context("failed to read HID descriptor length prefix")?;
if prefix.len() < 2 {
bail!("short HID descriptor prefix: {} bytes", prefix.len());
}
let hid_desc_length = u16::from_le_bytes([prefix[0], prefix[1]]);
if hid_desc_length < 18 {
bail!("invalid HID descriptor length {hid_desc_length}");
}
let raw = adapter
.write_read(
address,
&hid_desc_addr.to_le_bytes(),
usize::from(hid_desc_length),
)
.context("failed to read full HID descriptor")?;
parse_hid_descriptor(&raw)
}
pub fn fetch_report_descriptor(
adapter: &I2cAdapterClient,
address: u16,
desc: &HidDescriptor,
) -> Result<Vec<u8>> {
adapter
.write_read(
address,
&desc.report_desc_register.to_le_bytes(),
usize::from(desc.report_desc_length),
)
.context("failed to read HID report descriptor")
}
pub fn stream_input_reports(
adapter: &I2cAdapterClient,
address: u16,
desc: &HidDescriptor,
report_desc: &[u8],
sink: &mut crate::input::InputForwarder,
) -> Result<()> {
let summary = summarize_report_descriptor(report_desc);
let input_len = usize::from(desc.max_input_length.max(4));
loop {
let report = adapter
.write_read(address, &desc.input_register.to_le_bytes(), input_len)
.context("failed to fetch I2C HID input report")?;
sink.forward_report(&summary, &report)?;
}
}
fn parse_hid_descriptor(bytes: &[u8]) -> Result<HidDescriptor> {
if bytes.len() < 18 {
bail!("short HID descriptor: {} bytes", bytes.len());
}
Ok(HidDescriptor {
hid_desc_length: le16(bytes, 0)?,
bcd_version: le16(bytes, 2)?,
report_desc_length: le16(bytes, 4)?,
report_desc_register: le16(bytes, 6)?,
input_register: le16(bytes, 8)?,
max_input_length: le16(bytes, 10)?,
output_register: le16(bytes, 12)?,
max_output_length: le16(bytes, 14)?,
command_register: le16(bytes, 16)?,
data_register: if bytes.len() >= 20 {
le16(bytes, 18)?
} else {
0
},
})
}
fn summarize_report_descriptor(report_desc: &[u8]) -> ReportDescriptorSummary {
let mut summary = ReportDescriptorSummary::default();
for window in report_desc.windows(2) {
match window {
[0x05, 0x01] => summary.has_pointer_page = true,
[0x05, 0x07] => summary.has_keyboard_page = true,
[0x85, _] => summary.report_ids = true,
_ => {}
}
}
if !summary.has_keyboard_page && !summary.has_pointer_page {
summary.has_pointer_page = true;
}
summary
}
fn le16(bytes: &[u8], offset: usize) -> Result<u16> {
let slice = bytes
.get(offset..offset + 2)
.ok_or_else(|| anyhow::anyhow!("short LE16 field at offset {offset}"))?;
Ok(u16::from_le_bytes([slice[0], slice[1]]))
}
@@ -0,0 +1,175 @@
use std::collections::BTreeSet;
use anyhow::Result;
use inputd::ProducerHandle;
use orbclient::{
ButtonEvent, KeyEvent, MouseRelativeEvent, ScrollEvent, K_ALT, K_ALT_GR, K_BKSP, K_BRACE_CLOSE,
K_BRACE_OPEN, K_CAPS, K_COMMA, K_ENTER, K_EQUALS, K_ESC, K_LEFT_CTRL, K_LEFT_SHIFT,
K_LEFT_SUPER, K_MINUS, K_PERIOD, K_QUOTE, K_RIGHT_CTRL, K_RIGHT_SHIFT, K_RIGHT_SUPER,
K_SEMICOLON, K_SLASH, K_SPACE, K_TAB, K_TICK,
};
use crate::hid::ReportDescriptorSummary;
pub struct InputForwarder {
producer: ProducerHandle,
keyboard_state: BTreeSet<u8>,
last_buttons: u8,
}
impl InputForwarder {
pub fn new() -> Result<Self> {
Ok(Self {
producer: ProducerHandle::new()?,
keyboard_state: BTreeSet::new(),
last_buttons: 0,
})
}
pub fn forward_report(
&mut self,
summary: &ReportDescriptorSummary,
report: &[u8],
) -> Result<()> {
if report.is_empty() {
return Ok(());
}
if summary.has_keyboard_page && report.len() >= 8 {
self.forward_boot_keyboard(report)?;
return Ok(());
}
if summary.has_pointer_page && report.len() >= 3 {
self.forward_boot_pointer(report)?;
return Ok(());
}
Ok(())
}
fn forward_boot_keyboard(&mut self, report: &[u8]) -> Result<()> {
let modifiers = report[0];
for (bit, scancode) in [
(0_u8, K_LEFT_CTRL),
(1, K_LEFT_SHIFT),
(2, K_ALT),
(3, K_LEFT_SUPER),
(4, K_RIGHT_CTRL),
(5, K_RIGHT_SHIFT),
(6, K_ALT_GR),
(7, K_RIGHT_SUPER),
] {
self.producer.write_event(
KeyEvent {
character: '\0',
scancode,
pressed: modifiers & (1 << bit) != 0,
}
.to_event(),
)?;
}
let current = report[2..8]
.iter()
.copied()
.filter(|code| *code != 0)
.collect::<BTreeSet<_>>();
for code in current.difference(&self.keyboard_state) {
if let Some(scancode) = map_boot_keyboard_usage(*code) {
self.producer.write_event(
KeyEvent {
character: '\0',
scancode,
pressed: true,
}
.to_event(),
)?;
}
}
for code in self.keyboard_state.difference(&current) {
if let Some(scancode) = map_boot_keyboard_usage(*code) {
self.producer.write_event(
KeyEvent {
character: '\0',
scancode,
pressed: false,
}
.to_event(),
)?;
}
}
self.keyboard_state = current;
Ok(())
}
fn forward_boot_pointer(&mut self, report: &[u8]) -> Result<()> {
let dx = i8::from_ne_bytes([report[1]]) as i32;
let dy = i8::from_ne_bytes([report[2]]) as i32;
if dx != 0 || dy != 0 {
self.producer
.write_event(MouseRelativeEvent { dx, dy }.to_event())?;
}
if let Some(scroll) = report.get(3).copied() {
let scroll = i8::from_ne_bytes([scroll]) as i32;
if scroll != 0 {
self.producer
.write_event(ScrollEvent { x: 0, y: scroll }.to_event())?;
}
}
let buttons = report[0] & 0x07;
for index in 0..3 {
let mask = 1 << index;
if (buttons & mask) != (self.last_buttons & mask) {
self.producer.write_event(
ButtonEvent {
left: buttons & 0x01 != 0,
middle: buttons & 0x04 != 0,
right: buttons & 0x02 != 0,
}
.to_event(),
)?;
break;
}
}
self.last_buttons = buttons;
Ok(())
}
}
fn map_boot_keyboard_usage(usage: u8) -> Option<u8> {
Some(match usage {
0x04..=0x1D => b'a' + (usage - 0x04),
0x1E => b'1',
0x1F => b'2',
0x20 => b'3',
0x21 => b'4',
0x22 => b'5',
0x23 => b'6',
0x24 => b'7',
0x25 => b'8',
0x26 => b'9',
0x27 => b'0',
0x28 => K_ENTER,
0x29 => K_ESC,
0x2A => K_BKSP,
0x2B => K_TAB,
0x2C => K_SPACE,
0x2D => K_MINUS,
0x2E => K_EQUALS,
0x2F => K_BRACE_OPEN,
0x30 => K_BRACE_CLOSE,
0x33 => K_SEMICOLON,
0x34 => K_QUOTE,
0x35 => K_TICK,
0x36 => K_COMMA,
0x37 => K_PERIOD,
0x38 => K_SLASH,
0x39 => K_CAPS,
_ => return None,
})
}
@@ -0,0 +1,114 @@
use std::process;
use std::thread;
use std::time::Duration;
use anyhow::{Context, Result};
mod acpi;
mod hid;
mod input;
mod quirks;
use acpi::{
hid_descriptor_address, prepare_acpi_device, read_decoded_resources, recover_acpi_device,
scan_acpi_i2c_hid_devices,
};
use hid::{fetch_hid_descriptor, fetch_report_descriptor, stream_input_reports, I2cAdapterClient};
use input::InputForwarder;
use quirks::match_probe_failure_quirk;
fn main() {
daemon::Daemon::new(daemon);
}
fn daemon(daemon: daemon::Daemon) -> ! {
common::setup_logging(
"input",
"i2c-hid",
"i2c-hidd",
common::output_level(),
common::file_level(),
);
if let Err(err) = run(daemon) {
log::error!("RB_I2C_HIDD_BLOCKER stage=startup error={err:#}");
process::exit(1);
}
process::exit(0);
}
fn run(daemon: daemon::Daemon) -> Result<()> {
log::info!("RB_I2C_HIDD_SCHEMA version=1");
let devices = scan_acpi_i2c_hid_devices().context("failed to scan ACPI I2C HID devices")?;
if devices.is_empty() {
log::warn!("RB_I2C_HIDD_BLOCKER stage=scan error=no PNP0C50/ACPI0C50 devices found");
}
let mut workers = Vec::new();
for device in devices {
log::info!("RB_I2C_HIDD_SNAPSHOT device={device}");
workers.push(thread::spawn(move || {
if let Err(err) = bind_device(&device) {
log::error!("RB_I2C_HIDD_BLOCKER device={} error={:#}", device, err);
}
}));
}
daemon.ready();
if workers.is_empty() {
loop {
thread::sleep(Duration::from_secs(5));
}
}
for worker in workers {
let _ = worker.join();
}
Ok(())
}
pub fn bind_device(device_path: &str) -> Result<()> {
prepare_acpi_device(device_path)
.with_context(|| format!("failed to prepare ACPI device {device_path}"))?;
let resources = read_decoded_resources(device_path)
.with_context(|| format!("failed to decode _CRS for {device_path}"))?;
log::info!(
"RB_I2C_HIDD_SNAPSHOT device={} adapter={} addr={:04x} irq={:?} gpio_int={} gpio_io={}",
device_path,
resources.i2c.adapter,
resources.i2c.address,
resources.irq,
resources.gpio_int.len(),
resources.gpio_io.len()
);
let hid_desc_addr = hid_descriptor_address(device_path)
.with_context(|| format!("failed to evaluate _DSM for {device_path}"))?;
let adapter = I2cAdapterClient::new(resources.i2c.clone());
let hid_desc = fetch_hid_descriptor(&adapter, resources.i2c.address, hid_desc_addr)
.with_context(|| format!("failed to fetch HID descriptor for {device_path}"))?;
let report_desc = fetch_report_descriptor(&adapter, resources.i2c.address, &hid_desc)
.with_context(|| format!("failed to fetch report descriptor for {device_path}"))?;
let mut forwarder = InputForwarder::new().context("failed to connect to inputd producer")?;
match stream_input_reports(
&adapter,
resources.i2c.address,
&hid_desc,
&report_desc,
&mut forwarder,
) {
Ok(()) => Ok(()),
Err(err) => {
let quirk =
match_probe_failure_quirk().context("failed to evaluate DMI recovery quirks")?;
recover_acpi_device(device_path, &resources, quirk.as_ref())
.with_context(|| format!("failed ACPI recovery for {device_path}"))?;
Err(err).with_context(|| format!("streaming input reports failed for {device_path}"))
}
}
}
@@ -0,0 +1,88 @@
use std::fs;
use anyhow::{Context, Result};
use serde::Deserialize;
#[derive(Clone, Debug)]
pub struct ProbeFailureQuirk {
pub name: String,
pub system_vendor: Option<String>,
pub product_name: Option<String>,
pub board_name: Option<String>,
}
#[derive(Clone, Debug, Default, Deserialize)]
struct ProbeFailureQuirkFile {
quirks: Vec<ProbeFailureQuirkEntry>,
}
#[derive(Clone, Debug, Deserialize)]
struct ProbeFailureQuirkEntry {
name: String,
system_vendor: Option<String>,
product_name: Option<String>,
board_name: Option<String>,
}
#[derive(Default)]
struct DmiSnapshot {
system_vendor: String,
product_name: String,
board_name: String,
}
pub fn match_probe_failure_quirk() -> Result<Option<ProbeFailureQuirk>> {
let snapshot = read_dmi_snapshot()?;
for entry in load_quirks()? {
if field_matches(&entry.system_vendor, &snapshot.system_vendor)
&& field_matches(&entry.product_name, &snapshot.product_name)
&& field_matches(&entry.board_name, &snapshot.board_name)
{
return Ok(Some(ProbeFailureQuirk {
name: entry.name,
system_vendor: entry.system_vendor,
product_name: entry.product_name,
board_name: entry.board_name,
}));
}
}
Ok(None)
}
fn load_quirks() -> Result<Vec<ProbeFailureQuirkEntry>> {
let path = "/etc/i2c-hidd-quirks.ron";
let text = match fs::read_to_string(path) {
Ok(text) => text,
Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(Vec::new()),
Err(err) => return Err(err).with_context(|| format!("failed to read {path}")),
};
let file: ProbeFailureQuirkFile =
ron::from_str(&text).with_context(|| format!("failed to decode {path}"))?;
Ok(file.quirks)
}
fn read_dmi_snapshot() -> Result<DmiSnapshot> {
Ok(DmiSnapshot {
system_vendor: read_dmi_field("system_vendor")?,
product_name: read_dmi_field("product_name")?,
board_name: read_dmi_field("board_name")?,
})
}
fn read_dmi_field(field: &str) -> Result<String> {
let path = format!("/scheme/acpi/dmi/{field}");
match fs::read_to_string(&path) {
Ok(value) => Ok(value.trim().to_string()),
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(String::new()),
Err(err) => Err(err).with_context(|| format!("failed to read {path}")),
}
}
fn field_matches(expected: &Option<String>, actual: &str) -> bool {
expected
.as_deref()
.map(|expected| actual.eq_ignore_ascii_case(expected))
.unwrap_or(true)
}
@@ -0,0 +1,26 @@
[package]
name = "intel-thc-hidd"
description = "Intel THC QuickI2C HID transport daemon"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow.workspace = true
log.workspace = true
pci_types = "0.10.1"
redox_syscall = { workspace = true, features = ["std"] }
libredox.workspace = true
redox-scheme.workspace = true
ron.workspace = true
serde.workspace = true
acpi-resource = { path = "../../acpi-resource" }
amlserde = { path = "../../amlserde" }
common = { path = "../../common" }
daemon = { path = "../../../daemon" }
i2c-interface = { path = "../../i2c/i2c-interface" }
pcid = { path = "../../pcid" }
scheme-utils = { path = "../../../scheme-utils" }
[lints]
workspace = true
@@ -0,0 +1,260 @@
use std::collections::BTreeSet;
use std::fs::{self, OpenOptions};
use std::io::Read;
use std::process;
use std::thread;
use std::time::Duration;
use acpi_resource::ResourceDescriptor;
use amlserde::{AmlSerde, AmlSerdeValue};
use anyhow::{bail, Context, Result};
use libredox::flag::{O_CLOEXEC, O_RDWR};
use pcid_interface::PciFunctionHandle;
mod quicki2c;
mod thc;
use quicki2c::QuickI2cTransport;
use thc::{ThcController, SUPPORTED_PCI_IDS};
fn main() {
pcid_interface::pci_daemon(daemon);
}
fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
common::setup_logging(
"input",
"intel-thc",
"intel-thc-hidd",
common::output_level(),
common::file_level(),
);
if let Err(err) = run(daemon, &mut pcid_handle) {
log::error!("RB_THC_HIDD_FATAL error={err:#}");
process::exit(1);
}
process::exit(0);
}
fn run(daemon: daemon::Daemon, pcid_handle: &mut PciFunctionHandle) -> Result<()> {
log::info!("RB_THC_HIDD_SCHEMA version=1");
let pci_config = pcid_handle.config();
let id = (
pci_config.func.full_device_id.vendor_id,
pci_config.func.full_device_id.device_id,
);
if !SUPPORTED_PCI_IDS.contains(&id) {
bail!("unsupported Intel THC PCI device {:04x}:{:04x}", id.0, id.1);
}
pcid_handle.enable_device();
let bar = unsafe { pcid_handle.map_bar(0) };
let controller = ThcController::new(bar.ptr.as_ptr(), bar.bar_size)
.context("failed to create THC controller")?;
let companion = resolve_acpi_companion(&pci_config.func.addr)
.context("failed to resolve ACPI companion for THC device")?;
let override_address = companion
.as_deref()
.map(companion_slave_address_override)
.transpose()
.context("failed to evaluate THC slave-address override")?
.flatten();
let hid_devices = scan_bound_i2c_hid_devices(companion.as_deref())
.context("failed to scan PNP0C50 devices for THC controller")?;
let effective_address = override_address.unwrap_or(0x0015);
let transport = QuickI2cTransport::new(controller, effective_address);
transport.prime_controller();
transport.emulate_transfer(&[]);
log::debug!("RB_THC_HIDD status={:#x}", transport.status());
match transport.register_with_i2cd(companion.as_deref(), override_address) {
Ok(()) => {}
Err(err) => {
log::warn!("RB_THC_HIDD registration error={err:#}");
}
}
log::info!(
"RB_THC_HIDD pci={} companion={:?} override={:?} hid_devices={}",
pci_config.func.name(),
companion,
override_address,
hid_devices.len()
);
daemon.ready();
loop {
thread::sleep(Duration::from_secs(5));
}
}
fn resolve_acpi_companion(addr: &pci_types::PciAddress) -> Result<Option<String>> {
let entries =
fs::read_dir("/scheme/acpi/symbols").context("failed to read /scheme/acpi/symbols")?;
let expected_adr = (u64::from(addr.device()) << 16) | u64::from(addr.function());
for entry in entries {
let entry = entry.context("failed to enumerate ACPI symbol entry")?;
let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else {
continue;
};
if !file_name.ends_with("._ADR") {
continue;
}
let symbol = read_aml_symbol(&file_name)?;
if !matches!(symbol.value, AmlSerdeValue::Integer(value) if value == expected_adr) {
continue;
}
let device = symbol
.name
.strip_suffix("._ADR")
.unwrap_or(&symbol.name)
.trim_start_matches('\\')
.replace('/', ".");
return Ok(Some(device));
}
Ok(None)
}
fn companion_slave_address_override(path: &str) -> Result<Option<u16>> {
let icrs = evaluate_integer_method(path, "ICRS").ok();
let isub = evaluate_integer_method(path, "ISUB").ok();
Ok(icrs
.or(isub)
.map(|value| u16::try_from(value))
.transpose()
.context("THC ACPI override out of range")?)
}
fn scan_bound_i2c_hid_devices(companion: Option<&str>) -> Result<Vec<String>> {
let entries =
fs::read_dir("/scheme/acpi/symbols").context("failed to read /scheme/acpi/symbols")?;
let mut devices = BTreeSet::new();
for entry in entries {
let entry = entry.context("failed to enumerate ACPI HID entry")?;
let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else {
continue;
};
if !file_name.ends_with("._HID") && !file_name.ends_with("._CID") {
continue;
}
let symbol = read_aml_symbol(&file_name)?;
let is_hid = matches!(
decode_hardware_id(&symbol.value).as_deref(),
Some("PNP0C50" | "ACPI0C50")
);
if !is_hid {
continue;
}
let device = symbol
.name
.strip_suffix("._HID")
.or_else(|| symbol.name.strip_suffix("._CID"))
.unwrap_or(&symbol.name)
.trim_start_matches('\\')
.replace('/', ".");
if let Some(companion) = companion {
if !is_bound_to_companion(&device, companion)? {
continue;
}
}
devices.insert(device);
}
Ok(devices.into_iter().collect())
}
fn is_bound_to_companion(device: &str, companion: &str) -> Result<bool> {
let resource_path = format!("/scheme/acpi/resources/{device}");
let serialized = match fs::read_to_string(&resource_path) {
Ok(serialized) => serialized,
Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(false),
Err(err) => return Err(err).with_context(|| format!("failed to read {resource_path}")),
};
let resources: Vec<ResourceDescriptor> =
ron::from_str(&serialized).with_context(|| format!("failed to decode {resource_path}"))?;
Ok(resources.into_iter().any(|resource| match resource {
ResourceDescriptor::I2cSerialBus(bus) => bus
.resource_source
.as_ref()
.map(|source| source.source == companion)
.unwrap_or(false),
_ => false,
}))
}
fn evaluate_integer_method(path: &str, method: &str) -> Result<u64> {
let symbol_name = format!("{}.{}", normalize_device_path(path), method);
let symbol_path = format!("/scheme/acpi/symbols/{symbol_name}");
let fd = libredox::Fd::open(&symbol_path, O_RDWR | O_CLOEXEC, 0)
.with_context(|| format!("failed to open {symbol_path}"))?;
let mut payload = ron::to_string(&Vec::<AmlSerdeValue>::new())
.context("failed to serialize ACPI call arguments")?
.into_bytes();
payload.resize(payload.len() + 2048, 0);
let used = libredox::call::call_ro(fd.raw(), &mut payload, syscall::CallFlags::empty(), &[])
.with_context(|| format!("ACPI evaluation failed for {symbol_name}"))?;
let response = std::str::from_utf8(&payload[..used])
.with_context(|| format!("invalid UTF-8 ACPI response for {symbol_name}"))?;
match ron::from_str::<AmlSerdeValue>(response)
.with_context(|| format!("failed to decode ACPI response for {symbol_name}"))?
{
AmlSerdeValue::Integer(value) => Ok(value),
other => bail!("{}.{} returned non-integer value {other:?}", path, method),
}
}
fn read_aml_symbol(file_name: &str) -> Result<AmlSerde> {
let path = format!("/scheme/acpi/symbols/{file_name}");
let mut file = OpenOptions::new()
.read(true)
.open(&path)
.with_context(|| format!("failed to open {path}"))?;
let mut ron_text = String::new();
file.read_to_string(&mut ron_text)
.with_context(|| format!("failed to read {path}"))?;
ron::from_str(&ron_text).with_context(|| format!("failed to decode {path}"))
}
fn decode_hardware_id(value: &AmlSerdeValue) -> Option<String> {
match value {
AmlSerdeValue::String(value) => Some(value.clone()),
AmlSerdeValue::Integer(integer) => {
let vendor = integer & 0xFFFF;
let device = (integer >> 16) & 0xFFFF;
let vendor_rev = ((vendor & 0xFF) << 8) | (vendor >> 8);
let vendor_1 = (((vendor_rev >> 10) & 0x1f) as u8 + 64) as char;
let vendor_2 = (((vendor_rev >> 5) & 0x1f) as u8 + 64) as char;
let vendor_3 = (((vendor_rev >> 0) & 0x1f) as u8 + 64) as char;
let device_1 = (device >> 4) & 0xF;
let device_2 = (device >> 0) & 0xF;
let device_3 = (device >> 12) & 0xF;
let device_4 = (device >> 8) & 0xF;
Some(format!(
"{}{}{}{:01X}{:01X}{:01X}{:01X}",
vendor_1, vendor_2, vendor_3, device_1, device_2, device_3, device_4
))
}
_ => None,
}
}
fn normalize_device_path(path: &str) -> String {
path.trim_start_matches('\\')
.trim_matches('/')
.replace('/', ".")
}
@@ -0,0 +1,86 @@
use std::fs::OpenOptions;
use std::io::Write;
use anyhow::{Context, Result};
use i2c_interface::{I2cAdapterRegistration, I2cTransferSegment};
use crate::thc::ThcController;
const QUICKI2C_OPCODE_WRITE: u32 = 0x1;
const QUICKI2C_OPCODE_READ: u32 = 0x2;
pub struct QuickI2cTransport {
controller: ThcController,
slave_address: u16,
}
impl QuickI2cTransport {
pub fn new(controller: ThcController, slave_address: u16) -> Self {
Self {
controller,
slave_address,
}
}
pub fn prime_controller(&self) {
self.controller.initialize_quicki2c_mode();
}
pub fn emulate_transfer(&self, segments: &[I2cTransferSegment]) {
for segment in segments {
match &segment.op {
i2c_interface::I2cTransferOp::Write(data) => {
self.controller.program_subip_transaction(
QUICKI2C_OPCODE_WRITE,
segment.address,
data.len(),
);
for (index, chunk) in data.chunks(4).enumerate() {
let mut word = [0_u8; 4];
word[..chunk.len()].copy_from_slice(chunk);
self.controller
.write_subip_data(index * 4, u32::from_le_bytes(word));
}
}
i2c_interface::I2cTransferOp::Read(len) => {
self.controller.program_subip_transaction(
QUICKI2C_OPCODE_READ,
segment.address,
*len,
);
let _ = self.controller.read_subip_data(0);
}
}
}
}
pub fn status(&self) -> u32 {
self.controller.status()
}
pub fn register_with_i2cd(
&self,
acpi_companion: Option<&str>,
override_address: Option<u16>,
) -> Result<()> {
let registration = I2cAdapterRegistration {
name: "intel-thc-quicki2c".to_string(),
description: format!(
"Intel THC QuickI2C adapter for slave {:04x}",
self.slave_address
),
acpi_companion: acpi_companion.map(str::to_owned),
slave_address_override: override_address,
};
let payload =
ron::to_string(&registration).context("failed to serialize i2cd registration")?;
let mut file = OpenOptions::new()
.write(true)
.open("/scheme/i2c/register")
.context("failed to open /scheme/i2c/register")?;
file.write_all(payload.as_bytes())
.context("failed to write i2cd adapter registration")?;
Ok(())
}
}
@@ -0,0 +1,78 @@
use std::ptr::NonNull;
use anyhow::{bail, Result};
pub const SUPPORTED_PCI_IDS: &[(u16, u16)] = &[
(0x8086, 0x7eb8),
(0x8086, 0x7eb9),
(0x8086, 0x7ebd),
(0x8086, 0x7ebe),
(0x8086, 0xa8b8),
(0x8086, 0xa8b9),
];
pub const REG_CONTROL: usize = 0x0000;
pub const REG_STATUS: usize = 0x0004;
pub const REG_MODE: usize = 0x0010;
pub const REG_SUBIP_OPCODE: usize = 0x0800;
pub const REG_SUBIP_ADDRESS: usize = 0x0804;
pub const REG_SUBIP_LENGTH: usize = 0x0808;
pub const REG_SUBIP_DOORBELL: usize = 0x080C;
pub const REG_SUBIP_DATA: usize = 0x0810;
#[derive(Clone, Copy)]
pub struct ThcController {
base: NonNull<u8>,
len: usize,
}
impl ThcController {
pub fn new(base: *mut u8, len: usize) -> Result<Self> {
let Some(base) = NonNull::new(base) else {
bail!("THC BAR mapping returned null base pointer");
};
Ok(Self { base, len })
}
pub fn initialize_quicki2c_mode(&self) {
self.write32(REG_MODE, 0x1);
self.write32(REG_CONTROL, 0x1);
}
pub fn status(&self) -> u32 {
self.read32(REG_STATUS)
}
pub fn program_subip_transaction(&self, opcode: u32, address: u16, len: usize) {
self.write32(REG_SUBIP_OPCODE, opcode);
self.write32(REG_SUBIP_ADDRESS, u32::from(address));
self.write32(REG_SUBIP_LENGTH, len as u32);
self.write32(REG_SUBIP_DOORBELL, 1);
}
pub fn write_subip_data(&self, offset: usize, value: u32) {
self.write32(REG_SUBIP_DATA + offset, value);
}
pub fn read_subip_data(&self, offset: usize) -> u32 {
self.read32(REG_SUBIP_DATA + offset)
}
fn read32(&self, offset: usize) -> u32 {
if offset + 4 > self.len {
return 0;
}
let ptr = unsafe { self.base.as_ptr().add(offset).cast::<u32>() };
unsafe { ptr.read_volatile() }
}
fn write32(&self, offset: usize, value: u32) {
if offset + 4 > self.len {
return;
}
let ptr = unsafe { self.base.as_ptr().add(offset).cast::<u32>() };
unsafe { ptr.write_volatile(value) };
}
}
@@ -0,0 +1,12 @@
[package]
name = "thermald"
version = "0.1.0"
edition = "2021"
[dependencies]
log.workspace = true
anyhow.workspace = true
common = { path = "../common" }
[lints]
workspace = true
@@ -0,0 +1,30 @@
use anyhow::{Context, Result};
use std::{thread, time};
fn read_temp() -> Option<f32> {
for zone in 0..4 {
let path = format!("/scheme/acpi/thermal_zone/{}/temperature", zone);
if let Ok(data) = std::fs::read_to_string(&path) {
if let Ok(mv) = data.trim().parse::<u32>() {
return Some(mv as f32 / 1000.0);
}
}
}
None
}
fn main() -> Result<()> {
common::setup_logging("system", "thermald", "thermald",
common::output_level(), common::file_level());
log::info!("thermald: started");
loop {
if let Some(temp) = read_temp() {
if temp > 85.0 {
log::error!("thermald: CRITICAL {:.1}C", temp);
} else if temp > 70.0 {
log::warn!("thermald: WARNING {:.1}C", temp);
}
}
thread::sleep(time::Duration::from_secs(5));
}
}
@@ -0,0 +1,23 @@
[package]
name = "ucsid"
description = "USB-C UCSI topology daemon"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow.workspace = true
log.workspace = true
redox_syscall = { workspace = true, features = ["std"] }
libredox.workspace = true
redox-scheme.workspace = true
serde.workspace = true
ron.workspace = true
acpi-resource = { path = "../../acpi-resource" }
common = { path = "../../common" }
daemon = { path = "../../../daemon" }
i2c-interface = { path = "../../i2c/i2c-interface" }
scheme-utils = { path = "../../../scheme-utils" }
[lints]
workspace = true
@@ -0,0 +1,839 @@
use std::collections::BTreeMap;
use std::fs::{self, File, OpenOptions};
use std::io::{Read, Write};
use std::path::Path;
use std::process;
use acpi_resource::{
AddressResourceType, FixedMemory32Descriptor, I2cSerialBusDescriptor, Memory32RangeDescriptor,
ResourceDescriptor,
};
use anyhow::{bail, Context, Result};
use i2c_interface::{
I2cAdapterInfo, I2cControlRequest, I2cControlResponse, I2cTransferRequest,
I2cTransferSegment,
};
use libredox::flag::{O_CLOEXEC, O_RDWR};
use redox_scheme::scheme::SchemeSync;
use redox_scheme::{CallerCtx, OpenResult, Socket};
use scheme_utils::{Blocking, HandleMap};
use serde::{Deserialize, Serialize};
use syscall::schemev2::NewFdFlags;
use syscall::{Error as SysError, EACCES, EBADF, EINVAL, ENOENT};
const SUPPORTED_IDS: &[&str] = &["PNP0CA0", "AMDI0042"];
const GET_CAPABILITY: u8 = 0x01;
const GET_CONNECTOR_STATUS: u8 = 0x10;
const UCSI_RESPONSE_HEADER_LEN: usize = 4;
const UCSI_CAPABILITY_READ_LEN: usize = 20;
const UCSI_CONNECTOR_STATUS_READ_LEN: usize = 20;
const MAX_CONNECTOR_PROBE: u8 = 8;
#[derive(Debug, Deserialize)]
struct AmlSymbol {
name: String,
value: AmlValue,
}
#[derive(Debug, Deserialize)]
enum AmlValue {
Integer(u64),
String(String),
}
#[derive(Clone, Copy, Debug)]
struct UcsiCommand {
command: u8,
data_length: u8,
specific_data: [u8; 6],
}
impl UcsiCommand {
fn new(command: u8, data_length: u8, specific_data: [u8; 6]) -> Self {
Self {
command,
data_length,
specific_data,
}
}
fn as_bytes(self) -> [u8; 8] {
let mut bytes = [0_u8; 8];
bytes[0] = self.command;
bytes[1] = self.data_length;
bytes[2..].copy_from_slice(&self.specific_data);
bytes
}
}
#[derive(Clone, Copy, Debug)]
struct UcsiResponseHeader {
_status: u16,
data_length: u16,
}
impl UcsiResponseHeader {
fn parse(bytes: &[u8]) -> Option<Self> {
let header = bytes.get(..UCSI_RESPONSE_HEADER_LEN)?;
Some(Self {
_status: u16::from_le_bytes([header[0], header[1]]),
data_length: u16::from_le_bytes([header[2], header[3]]),
})
}
}
#[derive(Clone, Debug)]
struct DiscoveredUcsiDevice {
name: String,
hid: String,
transport: UcsiTransport,
dsm_probe: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
enum UcsiTransport {
I2c {
adapter: String,
address: u16,
ten_bit_address: bool,
},
Mmio {
base: usize,
len: usize,
},
Unknown,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct UcsiCapability {
connector_count: u8,
supports_usb_pd: bool,
supports_alt_modes: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct UcsiConnectorSummary {
device: String,
connector_number: u8,
connected: bool,
data_role: String,
power_direction: String,
input_critical: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct UcsiDeviceSummary {
name: String,
hid: String,
transport: UcsiTransport,
capability: Option<UcsiCapability>,
connectors: Vec<UcsiConnectorSummary>,
dsm_probe: bool,
issues: Vec<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct UcsiSummary {
schema_version: u32,
device_count: usize,
connector_count: usize,
input_critical_ports: usize,
devices: Vec<UcsiDeviceSummary>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct UcsiHealth {
healthy: bool,
scanned_devices: usize,
responsive_devices: usize,
issues: Vec<String>,
}
struct UcsiState {
summary: UcsiSummary,
connectors: Vec<UcsiConnectorSummary>,
health: UcsiHealth,
}
enum Handle {
SchemeRoot,
Summary { pending: Vec<u8> },
Connectors { pending: Vec<u8> },
Health { pending: Vec<u8> },
}
struct UcsiScheme {
handles: HandleMap<Handle>,
state: UcsiState,
}
impl UcsiScheme {
fn new(state: UcsiState) -> Self {
Self {
handles: HandleMap::new(),
state,
}
}
fn serialize_payload<T: Serialize>(value: &T) -> syscall::Result<Vec<u8>> {
ron::ser::to_string(value)
.map(|text| text.into_bytes())
.map_err(|err| {
log::error!("ucsid: failed to serialize scheme payload: {err}");
SysError::new(EINVAL)
})
}
fn set_pending(handle: &mut Handle, pending: Vec<u8>) -> syscall::Result<()> {
match handle {
Handle::Summary { pending: slot }
| Handle::Connectors { pending: slot }
| Handle::Health { pending: slot } => {
*slot = pending;
Ok(())
}
Handle::SchemeRoot => Err(SysError::new(EBADF)),
}
}
fn copy_pending(handle: &mut Handle, buf: &mut [u8], offset: u64) -> syscall::Result<usize> {
let pending = match handle {
Handle::Summary { pending }
| Handle::Connectors { pending }
| Handle::Health { pending } => pending,
Handle::SchemeRoot => return Err(SysError::new(EBADF)),
};
let offset = usize::try_from(offset).map_err(|_| SysError::new(EINVAL))?;
if offset >= pending.len() {
return Ok(0);
}
let copy_len = buf.len().min(pending.len() - offset);
buf[..copy_len].copy_from_slice(&pending[offset..offset + copy_len]);
Ok(copy_len)
}
}
impl SchemeSync for UcsiScheme {
fn scheme_root(&mut self) -> syscall::Result<usize> {
Ok(self.handles.insert(Handle::SchemeRoot))
}
fn openat(
&mut self,
dirfd: usize,
path: &str,
_flags: usize,
_fcntl_flags: u32,
_ctx: &CallerCtx,
) -> syscall::Result<OpenResult> {
if !matches!(self.handles.get(dirfd)?, Handle::SchemeRoot) {
return Err(SysError::new(EACCES));
}
let handle = match path.trim_matches('/') {
"summary" => Handle::Summary {
pending: Vec::new(),
},
"connectors" => Handle::Connectors {
pending: Vec::new(),
},
"health" => Handle::Health {
pending: Vec::new(),
},
"" => return Err(SysError::new(EINVAL)),
_ => return Err(SysError::new(ENOENT)),
};
let fd = self.handles.insert(handle);
Ok(OpenResult::ThisScheme {
number: fd,
flags: NewFdFlags::empty(),
})
}
fn read(
&mut self,
id: usize,
buf: &mut [u8],
offset: u64,
_fcntl_flags: u32,
_ctx: &CallerCtx,
) -> syscall::Result<usize> {
let payload = match self.handles.get(id)? {
Handle::Summary { pending } if pending.is_empty() => {
Some(Self::serialize_payload(&self.state.summary)?)
}
Handle::Connectors { pending } if pending.is_empty() => {
Some(Self::serialize_payload(&self.state.connectors)?)
}
Handle::Health { pending } if pending.is_empty() => {
log::info!(
"RB_UCSID_HEALTH healthy={} scanned_devices={} responsive_devices={} issues={}",
self.state.health.healthy,
self.state.health.scanned_devices,
self.state.health.responsive_devices,
self.state.health.issues.len(),
);
Some(Self::serialize_payload(&self.state.health)?)
}
_ => None,
};
let handle = self.handles.get_mut(id)?;
if let Some(payload) = payload {
Self::set_pending(handle, payload)?;
}
Self::copy_pending(handle, buf, offset)
}
}
fn main() {
common::setup_logging(
"usb",
"ucsi",
"ucsid",
common::output_level(),
common::file_level(),
);
daemon::SchemeDaemon::new(daemon_runner);
}
fn daemon_runner(daemon: daemon::SchemeDaemon) -> ! {
if let Err(err) = run_daemon(daemon) {
log::error!("ucsid: {err:#}");
process::exit(1);
}
process::exit(0);
}
fn run_daemon(daemon: daemon::SchemeDaemon) -> Result<()> {
log::info!("RB_UCSID_SCHEMA version=1");
let state = build_state().context("failed to build UCSI device snapshot")?;
let socket = Socket::create().context("failed to create ucsi scheme socket")?;
let mut scheme = UcsiScheme::new(state);
let handler = Blocking::new(&socket, 16);
daemon
.ready_sync_scheme(&socket, &mut scheme)
.context("failed to publish ucsi scheme root")?;
libredox::call::setrens(0, 0).context("failed to enter null namespace")?;
handler
.process_requests_blocking(scheme)
.context("failed to process ucsid requests")?;
}
fn build_state() -> Result<UcsiState> {
let adapters = list_i2c_adapters().unwrap_or_else(|err| {
log::warn!("ucsid: failed to query i2cd adapters: {err:#}");
Vec::new()
});
let devices = discover_ucsi_devices().context("failed to discover ACPI UCSI devices")?;
let mut summaries = Vec::new();
let mut connectors = Vec::new();
let mut issues = Vec::new();
let mut responsive_devices = 0usize;
for device in devices {
log::info!(
"RB_UCSID_DEVICE name={} hid={} transport={:?} dsm_probe={}",
device.name,
device.hid,
device.transport,
device.dsm_probe,
);
let summary = summarize_device(device, &adapters)
.context("failed to summarize discovered UCSI device")?;
if summary.capability.is_some() {
responsive_devices += 1;
}
issues.extend(summary.issues.iter().cloned());
connectors.extend(summary.connectors.iter().cloned());
summaries.push(summary);
}
let summary = UcsiSummary {
schema_version: 1,
device_count: summaries.len(),
connector_count: connectors.len(),
input_critical_ports: connectors.iter().filter(|connector| connector.input_critical).count(),
devices: summaries,
};
let health = UcsiHealth {
healthy: issues.is_empty(),
scanned_devices: summary.device_count,
responsive_devices,
issues,
};
log::info!(
"RB_UCSID_SUMMARY devices={} connectors={} input_critical_ports={} healthy={}",
summary.device_count,
summary.connector_count,
summary.input_critical_ports,
health.healthy,
);
Ok(UcsiState {
summary,
connectors,
health,
})
}
fn discover_ucsi_devices() -> Result<Vec<DiscoveredUcsiDevice>> {
let mut matched = BTreeMap::new();
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!("ucsid: ACPI symbols are not ready yet");
return Ok(Vec::new());
}
Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"),
};
for entry in entries {
let entry = entry.context("failed to read ACPI symbol directory entry")?;
let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else {
continue;
};
if !file_name.ends_with("_HID") && !file_name.ends_with("_CID") {
continue;
}
let Some(id) = read_symbol_id(&entry.path())? else {
continue;
};
if !SUPPORTED_IDS.iter().any(|candidate| *candidate == id) {
continue;
}
let Some(device) = file_name
.strip_suffix("_HID")
.or_else(|| file_name.strip_suffix("_CID"))
.map(str::to_owned)
else {
continue;
};
matched.entry(device).or_insert(id);
}
let mut devices = Vec::new();
for (device, hid) in matched {
let transport = read_ucsi_transport(&device)
.with_context(|| format!("failed to decode transport resources for {device}"))?;
let dsm_probe = bounded_dsm_probe(&device).unwrap_or_else(|err| {
log::debug!("ucsid: bounded _DSM probe failed for {device}: {err:#}");
false
});
devices.push(DiscoveredUcsiDevice {
name: device,
hid,
transport,
dsm_probe,
});
}
Ok(devices)
}
fn summarize_device(device: DiscoveredUcsiDevice, adapters: &[I2cAdapterInfo]) -> Result<UcsiDeviceSummary> {
let mut issues = Vec::new();
let capability = match &device.transport {
UcsiTransport::I2c {
adapter,
address,
ten_bit_address,
} => match match_i2c_adapter(adapters, adapter) {
Some(adapter_info) => match execute_ucsi_i2c_command(
adapter_info,
adapter,
*address,
*ten_bit_address,
UcsiCommand::new(GET_CAPABILITY, 0, [0; 6]),
UCSI_CAPABILITY_READ_LEN,
) {
Ok(bytes) => parse_ucsi_payload(&bytes)
.and_then(|(_header, payload)| parse_capability(payload))
.or_else(|| {
issues.push(format!(
"{}: GET_CAPABILITY returned an unexpected payload",
device.name
));
None
}),
Err(err) => {
issues.push(format!("{}: GET_CAPABILITY failed: {err:#}", device.name));
None
}
},
None => {
issues.push(format!(
"{}: no i2cd adapter matched ACPI source {}",
device.name, adapter
));
None
}
},
UcsiTransport::Mmio { base, len } => {
issues.push(format!(
"{}: MMIO UCSI transport discovered at {base:#x}+{len:#x} but command execution is not implemented yet",
device.name,
));
None
}
UcsiTransport::Unknown => {
issues.push(format!(
"{}: no supported UCSI transport was decoded from ACPI resources",
device.name,
));
None
}
};
let connector_count = capability
.as_ref()
.map(|capability| capability.connector_count.min(MAX_CONNECTOR_PROBE))
.unwrap_or(0);
let mut connectors = Vec::new();
for connector in 1..=connector_count {
match query_connector_status(&device, adapters, connector) {
Ok(connector_summary) => connectors.push(connector_summary),
Err(err) => issues.push(format!(
"{}: GET_CONNECTOR_STATUS({connector}) failed: {err:#}",
device.name,
)),
}
}
Ok(UcsiDeviceSummary {
name: device.name,
hid: device.hid,
transport: device.transport,
capability,
connectors,
dsm_probe: device.dsm_probe,
issues,
})
}
fn read_ucsi_transport(device: &str) -> Result<UcsiTransport> {
let contents = fs::read_to_string(format!("/scheme/acpi/resources/{device}"))
.with_context(|| format!("failed to read /scheme/acpi/resources/{device}"))?;
let resources = ron::from_str::<Vec<ResourceDescriptor>>(&contents)
.with_context(|| format!("failed to decode RON resources for {device}"))?;
let mut i2c = None::<I2cSerialBusDescriptor>;
let mut mmio = None::<(usize, usize)>;
for resource in resources {
match resource {
ResourceDescriptor::I2cSerialBus(bus) if i2c.is_none() => i2c = Some(bus),
ResourceDescriptor::FixedMemory32(FixedMemory32Descriptor {
address,
address_length,
..
}) if mmio.is_none() => {
mmio = Some((address as usize, address_length as usize));
}
ResourceDescriptor::Memory32Range(Memory32RangeDescriptor {
minimum,
maximum,
address_length,
..
}) if mmio.is_none() && maximum >= minimum => {
let span = maximum.saturating_sub(minimum).saturating_add(1) as usize;
mmio = Some((minimum as usize, span.max(address_length as usize)));
}
ResourceDescriptor::Address32(descriptor)
if mmio.is_none()
&& matches!(descriptor.resource_type, AddressResourceType::MemoryRange) =>
{
mmio = Some((descriptor.minimum as usize, descriptor.address_length as usize));
}
ResourceDescriptor::Address64(descriptor)
if mmio.is_none()
&& matches!(descriptor.resource_type, AddressResourceType::MemoryRange) =>
{
let base = usize::try_from(descriptor.minimum)
.context("64-bit MMIO base does not fit in usize")?;
let len = usize::try_from(descriptor.address_length)
.context("64-bit MMIO length does not fit in usize")?;
mmio = Some((base, len));
}
_ => {}
}
}
if let Some(bus) = i2c {
let adapter = bus
.resource_source
.as_ref()
.map(|source| source.source.clone())
.filter(|source| !source.is_empty())
.unwrap_or_else(|| String::from("ACPI-I2C"));
return Ok(UcsiTransport::I2c {
adapter,
address: bus.slave_address,
ten_bit_address: bus.access_mode_10bit,
});
}
if let Some((base, len)) = mmio {
return Ok(UcsiTransport::Mmio { base, len });
}
Ok(UcsiTransport::Unknown)
}
fn bounded_dsm_probe(device: &str) -> Result<bool> {
let symbol_name = format!("{}.{}", normalize_device_path(device), "_DSM");
let symbol_path = format!("/scheme/acpi/symbols/{symbol_name}");
let fd = match libredox::Fd::open(&symbol_path, O_RDWR | O_CLOEXEC, 0) {
Ok(fd) => fd,
Err(err) => {
log::debug!("ucsid: {} has no callable _DSM: {err}", device);
return Ok(false);
}
};
let mut payload = ron::to_string(&Vec::<u8>::new())
.context("failed to serialize bounded _DSM probe arguments")?
.into_bytes();
payload.resize(payload.len() + 1024, 0);
match libredox::call::call_ro(fd.raw(), &mut payload, syscall::CallFlags::empty(), &[]) {
Ok(_) => Ok(true),
Err(err) => {
log::debug!("ucsid: bounded _DSM probe for {} failed: {err}", device);
Ok(false)
}
}
}
fn parse_capability(payload: &[u8]) -> Option<UcsiCapability> {
let connector_count = *payload.first()?;
let flags = payload.get(1).copied().unwrap_or(0);
Some(UcsiCapability {
connector_count,
supports_usb_pd: flags & 0x01 != 0,
supports_alt_modes: flags & 0x02 != 0,
})
}
fn query_connector_status(
device: &DiscoveredUcsiDevice,
adapters: &[I2cAdapterInfo],
connector: u8,
) -> Result<UcsiConnectorSummary> {
match &device.transport {
UcsiTransport::I2c {
adapter,
address,
ten_bit_address,
} => {
let adapter_info = match_i2c_adapter(adapters, adapter).with_context(|| {
format!("no i2cd adapter matched ACPI source {} for {}", adapter, device.name)
})?;
let bytes = execute_ucsi_i2c_command(
adapter_info,
adapter,
*address,
*ten_bit_address,
UcsiCommand::new(GET_CONNECTOR_STATUS, 1, [connector, 0, 0, 0, 0, 0]),
UCSI_CONNECTOR_STATUS_READ_LEN,
)?;
let (_header, payload) = parse_ucsi_payload(&bytes)
.with_context(|| format!("{}: malformed connector-status response", device.name))?;
Ok(parse_connector_summary(&device.name, connector, payload))
}
UcsiTransport::Mmio { base, len } => bail!(
"MMIO connector-status transport is not implemented yet for {:#x}+{:#x}",
base,
len,
),
UcsiTransport::Unknown => bail!("unknown UCSI transport"),
}
}
fn parse_connector_summary(device_name: &str, connector: u8, payload: &[u8]) -> UcsiConnectorSummary {
let state = payload.first().copied().unwrap_or(0);
let connected = state & 0x01 != 0;
let power_direction = if state & 0x02 != 0 { "source" } else { "sink" };
let data_role = if state & 0x04 != 0 { "dfp" } else { "ufp" };
UcsiConnectorSummary {
device: device_name.to_string(),
connector_number: connector,
connected,
data_role: data_role.to_string(),
power_direction: power_direction.to_string(),
input_critical: classify_input_critical(device_name),
}
}
fn classify_input_critical(device_name: &str) -> bool {
let normalized = device_name.to_ascii_lowercase();
normalized.contains("kbd")
|| normalized.contains("key")
|| normalized.contains("touch")
|| normalized.contains("thc")
}
fn parse_ucsi_payload(bytes: &[u8]) -> Option<(UcsiResponseHeader, &[u8])> {
let header = UcsiResponseHeader::parse(bytes)?;
let body = bytes.get(UCSI_RESPONSE_HEADER_LEN..)?;
let body_len = usize::from(header.data_length).min(body.len());
Some((header, &body[..body_len]))
}
fn execute_ucsi_i2c_command(
adapter: &I2cAdapterInfo,
adapter_name: &str,
address: u16,
ten_bit_address: bool,
command: UcsiCommand,
read_len: usize,
) -> Result<Vec<u8>> {
let request = I2cTransferRequest {
adapter: adapter_name.to_string(),
segments: vec![
I2cTransferSegment {
address,
ten_bit_address,
op: i2c_interface::I2cTransferOp::Write(command.as_bytes().to_vec()),
},
I2cTransferSegment {
address,
ten_bit_address,
op: i2c_interface::I2cTransferOp::Read(read_len),
},
],
stop: true,
};
let mut file = OpenOptions::new()
.read(true)
.write(true)
.open("/scheme/i2c/transfer")
.context("failed to open /scheme/i2c/transfer")?;
let payload = ron::ser::to_string(&I2cControlRequest::Transfer {
adapter_id: adapter.id,
request,
})
.context("failed to encode UCSI I2C transfer request")?;
file.write_all(payload.as_bytes())
.context("failed to send UCSI I2C transfer request")?;
let response = read_i2c_control_response(&mut file)?;
match response {
I2cControlResponse::TransferResult(result) => {
if !result.ok {
let detail = result
.error
.clone()
.unwrap_or_else(|| String::from("unknown I2C transfer failure"));
bail!("UCSI I2C transfer failed: {detail}");
}
result
.read_data
.into_iter()
.next()
.context("UCSI I2C transfer returned no response payload")
}
I2cControlResponse::Error(message) => bail!("i2cd returned an error: {message}"),
other => bail!("unexpected i2cd transfer response: {other:?}"),
}
}
fn list_i2c_adapters() -> Result<Vec<I2cAdapterInfo>> {
let mut file = OpenOptions::new()
.read(true)
.write(true)
.open("/scheme/i2c/adapters")
.context("failed to open /scheme/i2c/adapters")?;
let payload = ron::ser::to_string(&I2cControlRequest::ListAdapters)
.context("failed to encode I2C list-adapters request")?;
file.write_all(payload.as_bytes())
.context("failed to request I2C adapter list")?;
let response = read_i2c_control_response(&mut file)?;
match response {
I2cControlResponse::AdapterList(adapters) => Ok(adapters),
I2cControlResponse::Error(message) => bail!("i2cd returned an error: {message}"),
other => bail!("unexpected i2cd list-adapters response: {other:?}"),
}
}
fn match_i2c_adapter<'a>(adapters: &'a [I2cAdapterInfo], wanted: &str) -> Option<&'a I2cAdapterInfo> {
adapters
.iter()
.find(|adapter| adapter.name == wanted)
.or_else(|| adapters.iter().find(|adapter| adapter.name.ends_with(wanted)))
.or_else(|| adapters.iter().find(|adapter| wanted.ends_with(&adapter.name)))
}
fn read_i2c_control_response(file: &mut File) -> Result<I2cControlResponse> {
let mut buffer = vec![0_u8; 4096];
let count = file
.read(&mut buffer)
.context("failed to read I2C control response")?;
buffer.truncate(count);
let text = std::str::from_utf8(&buffer).context("I2C control response was not UTF-8")?;
let trimmed = text.trim();
if trimmed.is_empty() {
return Ok(I2cControlResponse::AdapterList(Vec::new()));
}
ron::from_str(trimmed).context("failed to decode I2C control response")
}
fn read_symbol_id(path: &Path) -> Result<Option<String>> {
let contents = fs::read_to_string(path)
.with_context(|| format!("failed to read ACPI symbol {}", path.display()))?;
let symbol = match ron::from_str::<AmlSymbol>(&contents) {
Ok(symbol) => symbol,
Err(err) => {
log::debug!(
"ucsid: skipping {} because the symbol payload was not a scalar ID: {err}",
path.display(),
);
return Ok(None);
}
};
let id = match symbol.value {
AmlValue::Integer(integer) => eisa_id_from_integer(integer),
AmlValue::String(string) => string,
};
log::debug!("ucsid: {} -> {id}", symbol.name);
Ok(Some(id))
}
fn normalize_device_path(path: &str) -> String {
path.trim_start_matches('\\')
.trim_matches('/')
.replace('/', ".")
}
fn eisa_id_from_integer(integer: u64) -> String {
let vendor = integer & 0xFFFF;
let device = (integer >> 16) & 0xFFFF;
let vendor_rev = ((vendor & 0xFF) << 8) | (vendor >> 8);
let vendor_1 = (((vendor_rev >> 10) & 0x1F) as u8 + 64) as char;
let vendor_2 = (((vendor_rev >> 5) & 0x1F) as u8 + 64) as char;
let vendor_3 = (((vendor_rev >> 0) & 0x1F) as u8 + 64) as char;
let device_1 = (device >> 4) & 0xF;
let device_2 = (device >> 0) & 0xF;
let device_3 = (device >> 12) & 0xF;
let device_4 = (device >> 8) & 0xF;
format!(
"{vendor_1}{vendor_2}{vendor_3}{device_1:01X}{device_2:01X}{device_3:01X}{device_4:01X}"
)
}
@@ -0,0 +1,3 @@
[unit]
description = "Boot essential services target"
requires_weak = ["00_base.target"]
@@ -0,0 +1,3 @@
[unit]
description = "Boot late stage target"
requires_weak = ["05_boot_essential.target"]
+8
View File
@@ -0,0 +1,8 @@
[unit]
description = "D-Bus system bus"
requires_weak = ["12_boot_late.target", "00_ipcd.service"]
[service]
cmd = "dbus-daemon"
args = ["--system", "--nopidfile"]
type = "oneshot_async"
@@ -0,0 +1,8 @@
[unit]
description = "seatd seat management"
requires_weak = ["12_dbus.service"]
[service]
cmd = "/usr/bin/seatd"
args = ["-l", "info"]
type = "oneshot_async"
@@ -0,0 +1,8 @@
[unit]
description = "Activate console VT"
requires_weak = ["00_base.target"]
[service]
cmd = "inputd"
args = ["-A", "2"]
type = "oneshot_async"
@@ -0,0 +1,8 @@
[unit]
description = "Console getty on VT2"
requires_weak = ["29_activate_console.service"]
[service]
cmd = "getty"
args = ["2"]
type = "oneshot_async"
@@ -0,0 +1,7 @@
[unit]
description = "CPU Thermal Monitor"
requires_weak = ["00_base.target"]
[service]
cmd = "thermald"
type = "oneshot_async"
@@ -0,0 +1,8 @@
[unit]
description = "Debug console on VT3"
requires_weak = ["29_activate_console.service"]
[service]
cmd = "getty"
args = ["3"]
type = "oneshot_async"
@@ -0,0 +1,8 @@
[unit]
description = "DRM/KMS Display Driver"
requires_weak = ["20_graphics.target", "40_hwd.service", "40_pcid-spawner-initfs.service"]
condition_architecture = ["x86", "x86_64"]
[service]
cmd = "redox-drm"
type = "notify"
@@ -0,0 +1,8 @@
[unit]
description = "USB Mass Storage Driver"
requires_weak = ["40_drivers.target"]
condition_architecture = ["x86", "x86_64"]
[service]
cmd = "usbscsid"
type = "notify"
@@ -0,0 +1,7 @@
[unit]
description = "Network Stack"
requires_weak = ["40_drivers.target"]
[service]
cmd = "smolnetd"
type = "notify"
@@ -0,0 +1,7 @@
[unit]
description = "DHCP Client"
requires_weak = ["60_smolnetd.service"]
[service]
cmd = "dhcpd"
type = "oneshot_async"

Some files were not shown because too many files have changed in this diff Show More