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:
@@ -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
@@ -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(®EX_PORT_DETACH, scheme, 0)?;
|
||||
|
||||
Ok(Self::DetachDevice(port_num))
|
||||
+ } else if REGEX_PORT_SUSPEND.is_match(scheme) {
|
||||
+ let port_num = get_port_id_from_regex(®EX_PORT_SUSPEND, scheme, 0)?;
|
||||
+
|
||||
+ Ok(Self::SuspendDevice(port_num))
|
||||
+ } else if REGEX_PORT_RESUME.is_match(scheme) {
|
||||
+ let port_num = get_port_id_from_regex(®EX_PORT_RESUME, scheme, 0)?;
|
||||
+
|
||||
+ Ok(Self::ResumeDevice(port_num))
|
||||
} else if REGEX_PORT_DESCRIPTORS.is_match(scheme) {
|
||||
let port_num = get_port_id_from_regex(®EX_PORT_DESCRIPTORS, scheme, 0)?;
|
||||
|
||||
@@ -391,6 +430,10 @@ impl SchemeParameters {
|
||||
let port_num = get_port_id_from_regex(®EX_PORT_STATE, scheme, 0)?;
|
||||
|
||||
Ok(Self::PortState(port_num))
|
||||
+ } else if REGEX_PORT_PM_STATE.is_match(scheme) {
|
||||
+ let port_num = get_port_id_from_regex(®EX_PORT_PM_STATE, scheme, 0)?;
|
||||
+
|
||||
+ Ok(Self::PortPmState(port_num))
|
||||
} else if REGEX_PORT_REQUEST.is_match(scheme) {
|
||||
let port_num = get_port_id_from_regex(®EX_PORT_REQUEST, scheme, 0)?;
|
||||
|
||||
@@ -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
@@ -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.
|
||||
@@ -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
@@ -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();
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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(®ion[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)
|
||||
@@ -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 },
|
||||
});
|
||||
@@ -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(¤t) {
|
||||
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(®istration).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"]
|
||||
@@ -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
Reference in New Issue
Block a user