feat: build system transition to release fork + archive hardening

Release fork infrastructure:
- REDBEAR_RELEASE=0.1.1 with offline enforcement (fetch/distclean/unfetch blocked)
- 195 BLAKE3-verified source archives in standard format
- Atomic provisioning via provision-release.sh (staging + .complete sentry)
- 5-phase improvement plan: restore format auto-detection, source tree
  validation (validate-source-trees.py), archive-map.json, REPO_BINARY fallback

Archive normalization:
- Removed 87 duplicate/unversioned archives from shared pool
- Regenerated all archives in consistent format with source/ + recipe.toml
- BLAKE3SUMS and manifest.json generated from stable tarball set

Patch management:
- verify-patches.sh: pre-sync dry-run report (OK/REVERSED/CONFLICT)
- 121 upstream-absorbed patches moved to absorbed/ directories
- 43 active patches verified clean against rebased sources
- Stress test: base updated to upstream HEAD, relibc reset and patched

Compilation fixes:
- relibc: Vec imports in redox-rt (proc.rs, lib.rs, sys.rs)
- relibc: unsafe from_raw_parts in mod.rs (2024 edition)
- fetch.rs: rev comparison handles short/full hash prefixes
- kibi recipe: corrected rev mismatch

New scripts: restore-sources.sh, provision-release.sh, verify-sources-archived.sh,
check-upstream-releases.sh, validate-source-trees.py, verify-patches.sh,
repair-archive-format.sh, generate-manifest.py

Documentation: AGENTS.md, README.md, local/AGENTS.md updated for release fork model
This commit is contained in:
2026-05-02 01:41:17 +01:00
parent f55acba68c
commit 5851974b20
242 changed files with 29015 additions and 1818 deletions
@@ -0,0 +1,55 @@
From: Red Bear OS
Date: 2026-04-28
Subject: daemon: handle missing INIT_NOTIFY gracefully instead of panicking
The Daemon::new() and Daemon::ready() functions in the daemon library
called unwrap() on the INIT_NOTIFY environment variable and the ready
pipe write, causing a hard panic when a daemon is started outside the
init system's notification pipe mechanism.
Replace unwrap() with graceful error handling:
- get_fd() returns -1 if the env var is missing or invalid, logging
a warning via eprintln
- ready() logs a warning on write failure instead of panicking
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
}
@@ -50,7 +61,9 @@ 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]) {
+ eprintln!("daemon::ready write failed: {err}");
+ }
}
/// Executes `Command` as a child process.
@@ -0,0 +1,61 @@
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,
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,
SchemeRoot,
/// Represents an open handle to a device's bind endpoint
Bind { addr: PciAddress },
@@ -34,6 +38,6 @@ struct HandleWrapper {
}
fn is_file(&self) -> bool {
- matches!(self, Self::Access | Self::Channel { .. } | Self::Bind { .. })
+ matches!(self, Self::Access | Self::Channel { .. } | Self::Bind { .. } | Self::Uevent)
}
fn is_dir(&self) -> bool {
!self.is_file()
@@ -96,6 +100,8 @@ impl SchemeSync for PciScheme {
}
} 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);
@@ -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),
+ 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: _,
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.
+ Ok(0)
+ }
Handle::SchemeRoot | Handle::Bind { .. } => Err(Error::new(EBADF)),
_ => Err(Error::new(EBADF)),
}
@@ -199,6 +212,6 @@ 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::SchemeRoot => return Err(Error::new(EBADF)),
};
for (i, dent_name) in entries.iter().enumerate().skip(offset) {
@@ -0,0 +1,20 @@
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
}
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
@@ -0,0 +1,287 @@
# 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);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,313 @@
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 @@
use amlserde::{AmlSerde, AmlSerdeValue};
-use std::{error::Error, fs, process::Command};
+use std::{error::Error, fs};
use super::Backend;
pub struct AcpiBackend {
- rxsdt: Vec<u8>,
+ _rxsdt: Vec<u8>,
}
impl Backend for AcpiBackend {
fn new() -> Result<Self, Box<dyn Error>> {
let rxsdt = fs::read("/scheme/kernel.acpi/rxsdt")?;
- // Spawn acpid
- //TODO: pass rxsdt data to acpid?
- #[allow(deprecated, reason = "we can't yet move this to init")]
- daemon::Daemon::spawn(Command::new("acpid"));
-
- Ok(Self { rxsdt })
+ Ok(Self { _rxsdt: rxsdt })
}
fn probe(&mut self) -> Result<(), Box<dyn Error>> {
+ let mut boot_critical_input_candidates = 0usize;
+ let mut thc_candidates = 0usize;
+ let mut non_hid_i2c_candidates = 0usize;
+
// Read symbols from acpi scheme
- let entries = fs::read_dir("/scheme/acpi/symbols")?;
+ let entries = match fs::read_dir("/scheme/acpi/symbols") {
+ Ok(entries) => entries,
+ Err(err)
+ if err.kind() == std::io::ErrorKind::WouldBlock
+ || err.raw_os_error() == Some(11) =>
+ {
+ log::debug!("hwd: ACPI symbols are not ready yet");
+ return Ok(());
+ }
+ Err(err) => return Err(Box::new(err)),
+ };
// TODO: Reimplement with getdents?
let symbols_fd = libredox::Fd::open(
"/scheme/acpi/symbols",
@@ -100,13 +109,104 @@
"PNP0C0F" => "PCI interrupt link",
"PNP0C50" => "I2C HID",
"PNP0F13" => "PS/2 port for PS/2-style mouse",
+ "80860F41" | "808622C1" => "DesignWare I2C controller",
+ "AMDI0010" | "AMDI0019" | "AMDI0510" => "AMD laptop I2C controller",
+ "INT33C2" | "INT33C3" | "INT3432" | "INT3433" | "INTC10EF" => {
+ "Intel LPSS/SerialIO I2C controller"
+ }
+ "INT34C5" | "INTC1055" => "Intel GPIO controller",
+ "INTC1050" | "INTC1051" | "INTC1080" | "INTC1081" | "INTC1082" => {
+ "Intel THC companion (QuickI2C/QuickSPI path)"
+ }
+ _ if is_elan_touchpad_id(&id) => "ELAN touchpad (I2C/SMBus path)",
+ _ if is_cypress_touchpad_id(&id) => "Cypress/Trackpad (non-HID I2C path)",
+ _ if is_synaptics_rmi_id(&id) => "Synaptics RMI touchpad (I2C/SMBus path)",
_ => "?",
};
log::debug!("{}: {} ({})", name, id, what);
+ if is_boot_critical_i2c_surface(&id) {
+ boot_critical_input_candidates += 1;
+ log::info!("{}: {} is boot-critical for laptop input path", name, id);
+ }
+ if is_thc_companion(&id) {
+ thc_candidates += 1;
+ log::warn!(
+ "{}: {} indicates Intel THC path; DMA/report fast-path is not complete yet",
+ name,
+ id
+ );
+ }
+ if is_non_hid_i2c_input_id(&id) {
+ non_hid_i2c_candidates += 1;
+ }
}
}
}
+
+ if boot_critical_input_candidates == 0 {
+ log::warn!(
+ "hwd: no ACPI boot-critical I2C input candidates found; built-in laptop input may require additional controller/device support"
+ );
+ } else {
+ log::info!(
+ "hwd: ACPI input candidates: total={} thc={} non_hid_i2c={}",
+ boot_critical_input_candidates,
+ thc_candidates,
+ non_hid_i2c_candidates
+ );
+ }
+
Ok(())
}
}
+
+fn is_boot_critical_i2c_surface(id: &str) -> bool {
+ matches!(
+ id,
+ "PNP0C50"
+ | "ACPI0C50"
+ | "80860F41"
+ | "808622C1"
+ | "AMDI0010"
+ | "AMDI0019"
+ | "AMDI0510"
+ | "INT33C2"
+ | "INT33C3"
+ | "INT3432"
+ | "INT3433"
+ | "INTC10EF"
+ | "INT34C5"
+ | "INTC1055"
+ | "INTC1050"
+ | "INTC1051"
+ | "INTC1080"
+ | "INTC1081"
+ | "INTC1082"
+ ) || is_elan_touchpad_id(id)
+ || is_cypress_touchpad_id(id)
+ || is_synaptics_rmi_id(id)
+}
+
+fn is_thc_companion(id: &str) -> bool {
+ matches!(
+ id,
+ "INTC1050" | "INTC1051" | "INTC1080" | "INTC1081" | "INTC1082"
+ )
+}
+
+fn is_elan_touchpad_id(id: &str) -> bool {
+ id.starts_with("ELAN")
+}
+
+fn is_cypress_touchpad_id(id: &str) -> bool {
+ id.starts_with("CYAP")
+}
+
+fn is_synaptics_rmi_id(id: &str) -> bool {
+ id.starts_with("SYNA")
+}
+
+fn is_non_hid_i2c_input_id(id: &str) -> bool {
+ is_elan_touchpad_id(id) || is_cypress_touchpad_id(id) || is_synaptics_rmi_id(id)
+}
diff --git a/drivers/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 {
@@ -0,0 +1,144 @@
# 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")
}
@@ -0,0 +1,18 @@
# 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) -> ! {
//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,33 @@
diff --git a/init.initfs.d/41_acpid.service b/init.initfs.d/41_acpid.service
new file mode 100644
--- /dev/null
+++ b/init.initfs.d/41_acpid.service
@@ -0,0 +1,8 @@
+[unit]
+description = "ACPI daemon"
+default_dependencies = false
+
+[service]
+cmd = "acpid"
+inherit_envs = ["RSDP_ADDR", "RSDP_SIZE"]
+type = "notify"
diff --git a/init.initfs.d/40_drivers.target b/init.initfs.d/40_drivers.target
--- a/init.initfs.d/40_drivers.target
+++ b/init.initfs.d/40_drivers.target
@@ -7,4 +7,5 @@ requires_weak = [
"40_bcm2835-sdhcid.service",
"40_hwd.service",
"40_pcid-spawner-initfs.service",
+ "41_acpid.service",
]
diff --git a/init.initfs.d/40_hwd.service b/init.initfs.d/40_hwd.service
--- a/init.initfs.d/40_hwd.service
+++ b/init.initfs.d/40_hwd.service
@@ -1,6 +1,6 @@
[unit]
description = "Hardware manager"
-requires_weak = ["10_inputd.service", "10_lived.service", "20_graphics.target"]
+requires_weak = ["10_inputd.service", "10_lived.service", "20_graphics.target", "41_acpid.service"]
[service]
cmd = "hwd"
@@ -0,0 +1,607 @@
# 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}");
+ }
}
@@ -0,0 +1,118 @@
# 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,601 @@
# 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()
}
@@ -0,0 +1,158 @@
# 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() {
Command::new("port")
.arg(Arg::new("PORT").num_args(1).required(true))
.subcommand(Command::new("status"))
+ .subcommand(Command::new("pm-state"))
+ .subcommand(Command::new("suspend"))
+ .subcommand(Command::new("resume"))
.subcommand(
Command::new("endpoint")
.arg(Arg::new("ENDPOINT_NUM").num_args(1).required(true))
@@ -38,7 +41,16 @@ fn main() {
if let Some(_status_scmd_matches) = port_scmd_matches.subcommand_matches("status") {
let state = handle.port_state().expect("Failed to get port state");
println!("{}", state.as_str());
+ } else if let Some(_pm_state_scmd_matches) = port_scmd_matches.subcommand_matches("pm-state") {
+ let state = handle
+ .port_pm_state()
+ .expect("Failed to get port power-management state");
+ println!("{}", state.as_str());
+ } else if let Some(_suspend_scmd_matches) = port_scmd_matches.subcommand_matches("suspend") {
+ handle.suspend_device().expect("Failed to suspend device");
+ } else if let Some(_resume_scmd_matches) = port_scmd_matches.subcommand_matches("resume") {
+ handle.resume_device().expect("Failed to resume device");
} else if let Some(endp_scmd_matches) = port_scmd_matches.subcommand_matches("endpoint") {
let endp_num = endp_scmd_matches
.get_one::<String>("ENDPOINT_NUM")
diff --git a/drivers/usb/xhcid/drivers.toml b/drivers/usb/xhcid/drivers.toml
index 83c90e23..470ec063 100644
--- a/drivers/usb/xhcid/drivers.toml
+++ b/drivers/usb/xhcid/drivers.toml
@@ -1,10 +1,9 @@
-#TODO: causes XHCI errors
-#[[drivers]]
-#name = "SCSI over USB"
-#class = 8 # Mass Storage class
-#subclass = 6 # SCSI transparent command set
-#command = ["usbscsid", "$SCHEME", "$PORT", "$IF_PROTO"]
+[[drivers]]
+name = "SCSI over USB"
+class = 8 # Mass Storage class
+subclass = 6 # SCSI transparent command set
+command = ["usbscsid", "$SCHEME", "$PORT", "$IF_PROTO"]
[[drivers]]
name = "USB HUB"
diff --git a/drivers/usb/xhcid/src/driver_interface.rs b/drivers/usb/xhcid/src/driver_interface.rs
index 727f8d7e..82f839ae 100644
--- a/drivers/usb/xhcid/src/driver_interface.rs
+++ b/drivers/usb/xhcid/src/driver_interface.rs
@@ -444,6 +444,33 @@ impl str::FromStr for PortState {
}
}
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
+pub enum PortPmState {
+ Active,
+ Suspended,
+}
+impl PortPmState {
+ pub fn as_str(&self) -> &'static str {
+ match self {
+ Self::Active => "active",
+ Self::Suspended => "suspended",
+ }
+ }
+}
+
+impl str::FromStr for PortPmState {
+ type Err = Invalid;
+
+ fn from_str(s: &str) -> result::Result<Self, Self::Err> {
+ Ok(match s {
+ "active" => Self::Active,
+ "suspended" => Self::Suspended,
+ _ => return Err(Invalid("read reserved port PM state")),
+ })
+ }
+}
+
#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub enum EndpointStatus {
@@ -560,6 +587,16 @@ impl XhciClientHandle {
let _bytes_written = file.write(&[])?;
Ok(())
}
+ pub fn suspend_device(&self) -> result::Result<(), XhciClientHandleError> {
+ let file = self.fd.openat("suspend", libredox::flag::O_WRONLY, 0)?;
+ let _bytes_written = file.write(&[])?;
+ Ok(())
+ }
+ pub fn resume_device(&self) -> result::Result<(), XhciClientHandleError> {
+ let file = self.fd.openat("resume", libredox::flag::O_WRONLY, 0)?;
+ let _bytes_written = file.write(&[])?;
+ Ok(())
+ }
pub fn get_standard_descs(&self) -> result::Result<DevDesc, XhciClientHandleError> {
let json = self.read("descriptors")?;
Ok(serde_json::from_slice(&json)?)
@@ -582,7 +619,11 @@ impl XhciClientHandle {
let string = self.read_to_string("state")?;
Ok(string.parse()?)
}
+ pub fn port_pm_state(&self) -> result::Result<PortPmState, XhciClientHandleError> {
+ let string = self.read_to_string("pm_state")?;
+ Ok(string.parse()?)
+ }
pub fn open_endpoint_ctl(&self, num: u8) -> result::Result<File, XhciClientHandleError> {
let path = format!("endpoints/{}/ctl", num);
let fd = self.fd.openat(&path, libredox::flag::O_RDWR, 0)?;
diff --git a/drivers/usb/xhcid/src/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 {
@@ -0,0 +1,398 @@
--- 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)),
+ }
}
})
}
@@ -10625,7 +10625,7 @@ diff --git a/drivers/pcid-spawner/src/main.rs b/drivers/pcid-spawner/src/main.rs
index a968f4d4..bfff05c3 100644
--- a/drivers/pcid-spawner/src/main.rs
+++ b/drivers/pcid-spawner/src/main.rs
@@ -1,11 +1,40 @@
@@ -1,11 +1,41 @@
+use std::env;
use std::fs;
use std::process::Command;
@@ -10667,7 +10667,7 @@ index a968f4d4..bfff05c3 100644
fn main() -> Result<()> {
let mut args = pico_args::Arguments::from_env();
let initfs = args.contains("--initfs");
@@ -30,6 +59,12 @@ fn main() -> Result<()> {
@@ -30,12 +59,33 @@ fn main() -> Result<()> {
}
let config: Config = toml::from_str(&config_data)?;
File diff suppressed because it is too large Load Diff