Split base cumulative patch and add relibc AIO stubs, KDE recipes

Base patch extraction (8 topic-grouped patches from the 17k-line monolith):
- P2-ps2d-improvements: PS/2 controller flush/retry, mouse state machine, named producers
- P2-storage-error-handling: AHCI/IDE/NVMe/VirtIO unwrap/expect removal
- P2-usb-pm-and-drivers: suspend/resume, SCSI enablement, staged port fallback
- P2-network-error-handling: e1000/ixgbe/rtl8139/rtl8168d/virtio-net error propagation
- P2-pcid-cfg-access: PCI config I/O port and ECAM graceful fallbacks
- P2-ihdad-hda-stream: InputStream support, public stream types, Debug derives
- P2-init-acpid-wiring: acpid weak dependency on drivers/hwd/pcid-spawner
- P2-misc-daemon-fixes: audiod/usbhidd/zerod graceful degradation

relibc P3-aio.patch: synchronous POSIX AIO fallback (aio_read, aio_write,
aio_error, aio_return, aio_cancel, aio_suspend, aio_fsync, lio_listio)
for Qt6 QIODevice compatibility. 36 patches total in relibc recipe.

KDE recipes: breeze (widget style, decorations disabled), kde-cli-tools
(kioclient, kreadconfig, etc., kdesu disabled).
This commit is contained in:
2026-04-25 19:10:00 +01:00
parent d6afe22f8c
commit 65acab85bb
12 changed files with 2335 additions and 0 deletions
@@ -0,0 +1,148 @@
# P2-ihdad-hda-stream.patch
#
# Intel HDA stream handling: add InputStream support, make stream types public,
# add digital/dispatch/fixup/parser modules, derive Debug for node types.
#
# Covers:
# - ihdad/hda/mod.rs: add digital, dispatch, fixup, parser modules
# - ihdad/hda/node.rs: derive Debug for HDANode
# - ihdad/hda/stream.rs: public SampleRate fields, Debug+Copy derives for BitsPerSample,
# new InputStream struct, StreamBuffer::read_block method
#
diff --git a/drivers/audio/ihdad/src/hda/mod.rs b/drivers/audio/ihdad/src/hda/mod.rs
index 7f01daf8..82ba89ae 100644
--- a/drivers/audio/ihdad/src/hda/mod.rs
+++ b/drivers/audio/ihdad/src/hda/mod.rs
@@ -2,7 +2,11 @@
pub mod cmdbuff;
pub mod common;
pub mod device;
+pub mod digital;
+pub mod dispatch;
+pub mod fixup;
pub mod node;
+pub mod parser;
pub mod stream;
pub use self::node::*;
@@ -10,6 +14,8 @@ pub use self::stream::*;
pub use self::cmdbuff::*;
pub use self::device::IntelHDA;
+pub use self::fixup::FixupEngine;
+pub use self::parser::AutoPinConfig;
pub use self::stream::BitsPerSample;
pub use self::stream::BufferDescriptorListEntry;
pub use self::stream::StreamBuffer;
diff --git a/drivers/audio/ihdad/src/hda/node.rs b/drivers/audio/ihdad/src/hda/node.rs
index 06c5121f..c1f9c31f 100644
--- a/drivers/audio/ihdad/src/hda/node.rs
+++ b/drivers/audio/ihdad/src/hda/node.rs
@@ -1,7 +1,7 @@
use super::common::*;
use std::{fmt, mem};
-#[derive(Clone)]
+#[derive(Clone, Debug)]
pub struct HDANode {
pub addr: WidgetAddr,
diff --git a/drivers/audio/ihdad/src/hda/stream.rs b/drivers/audio/ihdad/src/hda/stream.rs
index caa3c364..a3f5ed73 100644
--- a/drivers/audio/ihdad/src/hda/stream.rs
+++ b/drivers/audio/ihdad/src/hda/stream.rs
@@ -14,9 +14,9 @@ pub enum BaseRate {
}
pub struct SampleRate {
- base: BaseRate,
- mult: u16,
- div: u16,
+ pub base: BaseRate,
+ pub mult: u16,
+ pub div: u16,
}
use self::BaseRate::{BR44_1, BR48};
@@ -78,6 +78,7 @@ pub const SR_192: SampleRate = SampleRate {
div: 1,
};
+#[derive(Debug, Clone, Copy)]
#[repr(u8)]
pub enum BitsPerSample {
Bits8 = 0,
@@ -271,6 +272,52 @@ impl OutputStream {
}
}
+pub struct InputStream {
+ buff: StreamBuffer,
+ desc_regs: &'static mut StreamDescriptorRegs,
+}
+
+impl InputStream {
+ pub fn new(
+ block_count: usize,
+ block_length: usize,
+ regs: &'static mut StreamDescriptorRegs,
+ ) -> InputStream {
+ InputStream {
+ buff: StreamBuffer::new(block_length, block_count).unwrap(),
+ desc_regs: regs,
+ }
+ }
+
+ pub fn read_block(&mut self, buf: &mut [u8]) -> Result<usize> {
+ self.buff.read_block(buf)
+ }
+
+ pub fn block_size(&self) -> usize {
+ self.buff.block_size()
+ }
+
+ pub fn block_count(&self) -> usize {
+ self.buff.block_count()
+ }
+
+ pub fn current_block(&self) -> usize {
+ self.buff.current_block()
+ }
+
+ pub fn addr(&self) -> usize {
+ self.buff.addr()
+ }
+
+ pub fn phys(&self) -> usize {
+ self.buff.phys()
+ }
+
+ pub fn regs(&mut self) -> &mut StreamDescriptorRegs {
+ self.desc_regs
+ }
+}
+
#[repr(C, packed)]
pub struct BufferDescriptorListEntry {
addr_low: Mmio<u32>,
@@ -379,6 +426,20 @@ impl StreamBuffer {
Ok(len)
}
+
+ pub fn read_block(&mut self, buf: &mut [u8]) -> Result<usize> {
+ let len = min(self.block_size(), buf.len());
+ unsafe {
+ copy_nonoverlapping(
+ (self.addr() + self.current_block() * self.block_size()) as *const u8,
+ buf.as_mut_ptr(),
+ len,
+ );
+ }
+ self.cur_pos += 1;
+ self.cur_pos %= self.block_count();
+ Ok(len)
+ }
}
impl Drop for StreamBuffer {
fn drop(&mut self) {
@@ -0,0 +1,45 @@
# P2-init-acpid-wiring.patch
#
# Init service acpid dependency wiring: add 41_acpid.service as a weak
# dependency to the drivers target, hardware manager, and PCI spawner so
# acpid starts reliably during boot.
#
# Covers:
# - 40_drivers.target: add 41_acpid.service to requires_weak
# - 40_hwd.service: add 41_acpid.service to requires_weak
# - 40_pcid-spawner-initfs.service: add 41_acpid.service to requires_weak
#
diff --git a/init.initfs.d/40_drivers.target b/init.initfs.d/40_drivers.target
index 8ddb4795..029583a1 100644
--- a/init.initfs.d/40_drivers.target
+++ b/init.initfs.d/40_drivers.target
@@ -7,4 +7,5 @@ requires_weak = [
"40_bcm2835-sdhcid.service",
"40_hwd.service",
"40_pcid-spawner-initfs.service",
+ "41_acpid.service",
]
diff --git a/init.initfs.d/40_hwd.service b/init.initfs.d/40_hwd.service
index cba12dde..cf34a51b 100644
--- a/init.initfs.d/40_hwd.service
+++ b/init.initfs.d/40_hwd.service
@@ -1,6 +1,6 @@
[unit]
description = "Hardware manager"
-requires_weak = ["10_inputd.service", "10_lived.service", "20_graphics.target"]
+requires_weak = ["10_inputd.service", "10_lived.service", "20_graphics.target", "41_acpid.service"]
[service]
cmd = "hwd"
diff --git a/init.initfs.d/40_pcid-spawner-initfs.service b/init.initfs.d/40_pcid-spawner-initfs.service
index 6945b9ea..ba1ee0bb 100644
--- a/init.initfs.d/40_pcid-spawner-initfs.service
+++ b/init.initfs.d/40_pcid-spawner-initfs.service
@@ -1,6 +1,6 @@
[unit]
description = "PCI driver spawner"
-requires_weak = ["10_inputd.service", "20_graphics.target", "40_hwd.service"]
+requires_weak = ["10_inputd.service", "20_graphics.target", "40_hwd.service", "41_acpid.service"]
[service]
cmd = "pcid-spawner"
@@ -0,0 +1,126 @@
# P2-misc-daemon-fixes.patch
#
# Various daemon error handling and robustness fixes:
# graceful degradation when audio hardware is absent, InputProducer migration
# for USB HID, zerod argument handling and request loop resilience.
#
# Covers:
# - audiod/main.rs: handle ENODEV when no audio hardware present
# - usbhidd/main.rs: migrate ProducerHandle→InputProducer with named producer
# - zerod/main.rs: derive Copy for Ty, graceful argument parsing, resilient request loop
#
diff --git a/audiod/src/main.rs b/audiod/src/main.rs
index 51b103af..2354cf5f 100644
--- a/audiod/src/main.rs
+++ b/audiod/src/main.rs
@@ -48,7 +48,14 @@ fn daemon(daemon: SchemeDaemon) -> anyhow::Result<()> {
let pid = libredox::call::getpid()?;
- let hw_file = Fd::open("/scheme/audiohw", flag::O_WRONLY | flag::O_CLOEXEC, 0)?;
+ let hw_file = match Fd::open("/scheme/audiohw", flag::O_WRONLY | flag::O_CLOEXEC, 0) {
+ Ok(fd) => fd,
+ Err(err) if err.errno() == syscall::ENODEV => {
+ eprintln!("audiod: no audio hardware detected");
+ return Ok(());
+ }
+ Err(err) => return Err(err).context("failed to open /scheme/audiohw"),
+ };
let socket = Socket::create().context("failed to create scheme")?;
diff --git a/drivers/input/usbhidd/src/main.rs b/drivers/input/usbhidd/src/main.rs
index 15c5b778..706c4008 100644
--- a/drivers/input/usbhidd/src/main.rs
+++ b/drivers/input/usbhidd/src/main.rs
@@ -1,7 +1,7 @@
use anyhow::{Context, Result};
use std::{env, thread, time};
-use inputd::ProducerHandle;
+use inputd::InputProducer;
use orbclient::KeyEvent as OrbKeyEvent;
use rehid::{
report_desc::{ReportTy, REPORT_DESC_TY},
@@ -15,7 +15,7 @@ use xhcid_interface::{
mod reqs;
-fn send_key_event(display: &mut ProducerHandle, usage_page: u16, usage: u16, pressed: bool) {
+fn send_key_event(display: &mut InputProducer, usage_page: u16, usage: u16, pressed: bool) {
let scancode = match usage_page {
0x07 => match usage {
0x04 => orbclient::K_A,
@@ -272,7 +272,9 @@ fn main() -> Result<()> {
let report_ty = ReportTy::Input;
let report_id = 0;
- let mut display = ProducerHandle::new().context("Failed to open input socket")?;
+ let producer_name = format!("usb-{}-if{}", port, interface_num);
+ let mut display = InputProducer::new_named_or_fallback(&producer_name)
+ .context("Failed to open input socket")?;
let mut endpoint_opt = match endp_desc_opt {
Some((endp_num, _endp_desc)) => match handle.open_endpoint(endp_num as u8) {
Ok(ok) => Some(ok),
diff --git a/zerod/src/main.rs b/zerod/src/main.rs
index c9bd1465..59f6b97c 100644
--- a/zerod/src/main.rs
+++ b/zerod/src/main.rs
@@ -5,6 +5,7 @@ use scheme_utils::Blocking;
mod scheme;
+#[derive(Clone, Copy)]
enum Ty {
Null,
Zero,
@@ -15,21 +16,36 @@ fn main() {
}
fn daemon(daemon: daemon::SchemeDaemon) -> ! {
- let ty = match &*std::env::args().nth(1).unwrap() {
- "null" => Ty::Null,
- "zero" => Ty::Zero,
- _ => panic!("needs to be called with either null or zero as argument"),
+ let ty = match std::env::args().nth(1).as_deref() {
+ Some("null") => Ty::Null,
+ Some("zero") | None => Ty::Zero,
+ Some(other) => {
+ eprintln!("zerod: unknown argument '{other}', use 'null' or 'zero'");
+ std::process::exit(1);
+ }
};
- let socket = Socket::create().expect("zerod: failed to create zero scheme");
+ let socket = match Socket::create() {
+ Ok(s) => s,
+ Err(e) => {
+ eprintln!("zerod: failed to create zero scheme: {e}");
+ std::process::exit(1);
+ }
+ };
let mut zero_scheme = ZeroScheme(ty);
- let zero_handler = Blocking::new(&socket, 16);
let _ = daemon.ready_sync_scheme(&socket, &mut zero_scheme);
- libredox::call::setrens(0, 0).expect("zerod: failed to enter null namespace");
-
- zero_handler
- .process_requests_blocking(zero_scheme)
- .expect("zerod: failed to process events from zero scheme");
+ if let Err(e) = libredox::call::setrens(0, 0) {
+ eprintln!("zerod: failed to enter null namespace: {e}");
+ }
+
+ loop {
+ let zero_handler = Blocking::new(&socket, 16);
+ let scheme = ZeroScheme(ty);
+ match zero_handler.process_requests_blocking(scheme) {
+ Ok(never) => never,
+ Err(e) => eprintln!("zerod: error processing requests: {e}"),
+ }
+ }
}
@@ -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,11 +207,10 @@ impl NetworkAdapter for Intel8254x {
}
fn dma_array<T, const N: usize>() -> Result<[Dma<T>; N]> {
- Ok((0..N)
+ let vec: Vec<Dma<T>> = (0..N)
.map(|_| Ok(unsafe { Dma::zeroed()?.assume_init() }))
- .collect::<Result<Vec<_>>>()?
- .try_into()
- .unwrap_or_else(|_| unreachable!()))
+ .collect::<Result<Vec<_>>>()?;
+ vec.try_into().map_err(|_| Error::new(EIO))
}
impl Intel8254x {
pub unsafe fn new(base: usize) -> Result<Self> {
diff --git a/drivers/net/ixgbed/Cargo.toml b/drivers/net/ixgbed/Cargo.toml
index d97ff398..fcaf4b19 100644
--- a/drivers/net/ixgbed/Cargo.toml
+++ b/drivers/net/ixgbed/Cargo.toml
@@ -7,6 +7,7 @@ edition = "2021"
[dependencies]
bitflags.workspace = true
libredox.workspace = true
+log.workspace = true
redox_event.workspace = true
redox_syscall.workspace = true
diff --git a/drivers/net/rtl8139d/src/device.rs b/drivers/net/rtl8139d/src/device.rs
index 37167ee2..d7428132 100644
--- a/drivers/net/rtl8139d/src/device.rs
+++ b/drivers/net/rtl8139d/src/device.rs
@@ -215,7 +215,7 @@ impl Rtl8139 {
.map(|_| Ok(Dma::zeroed()?.assume_init()))
.collect::<Result<Vec<_>>>()?
.try_into()
- .unwrap_or_else(|_| unreachable!()),
+ .map_err(|_| Error::new(EIO))?,
transmit_i: 0,
mac_address: [0; 6],
};
diff --git a/drivers/net/rtl8168d/src/device.rs b/drivers/net/rtl8168d/src/device.rs
index ae545ec4..7229a52d 100644
--- a/drivers/net/rtl8168d/src/device.rs
+++ b/drivers/net/rtl8168d/src/device.rs
@@ -177,7 +177,7 @@ impl Rtl8168 {
.map(|_| Ok(Dma::zeroed()?.assume_init()))
.collect::<Result<Vec<_>>>()?
.try_into()
- .unwrap_or_else(|_| unreachable!()),
+ .map_err(|_| Error::new(EIO))?,
receive_ring: Dma::zeroed()?.assume_init(),
receive_i: 0,
@@ -185,7 +185,7 @@ impl Rtl8168 {
.map(|_| Ok(Dma::zeroed()?.assume_init()))
.collect::<Result<Vec<_>>>()?
.try_into()
- .unwrap_or_else(|_| unreachable!()),
+ .map_err(|_| Error::new(EIO))?,
transmit_ring: Dma::zeroed()?.assume_init(),
transmit_i: 0,
transmit_buffer_h: [Dma::zeroed()?.assume_init()],
diff --git a/drivers/net/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))
+169
View File
@@ -0,0 +1,169 @@
# 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 {
"PCI: couldn't find or access PCIe extended configuration, \
and thus falling back to PCI 3.0 io ports"
);
- common::acquire_port_io_rights().expect("pcid: failed to get IO port rights");
+ common::acquire_port_io_rights()
+ .map_err(|err| {
+ log::error!("pcid: failed to get IO port rights: {err}");
+ err
+ })
+ .expect("pcid: IO port rights required for PCI 3.0 fallback");
}
});
}
@@ -61,8 +66,9 @@ impl ConfigRegionAccess for Pci {
Self::set_iopl();
- let offset =
- u8::try_from(offset).expect("offset too large for PCI 3.0 configuration space");
+ let Ok(offset) = u8::try_from(offset) else {
+ return 0xFFFFFFFF;
+ };
let address = Self::address(address, offset);
Pio::<u32>::new(0xCF8).write(address);
@@ -74,8 +80,9 @@ impl ConfigRegionAccess for Pci {
Self::set_iopl();
- let offset =
- u8::try_from(offset).expect("offset too large for PCI 3.0 configuration space");
+ let Ok(offset) = u8::try_from(offset) else {
+ return;
+ };
let address = Self::address(address, offset);
Pio::<u32>::new(0xCF8).write(address);
diff --git a/drivers/pcid/src/cfg_access/mod.rs b/drivers/pcid/src/cfg_access/mod.rs
index c2552448..0fe215a6 100644
--- a/drivers/pcid/src/cfg_access/mod.rs
+++ b/drivers/pcid/src/cfg_access/mod.rs
@@ -38,42 +38,57 @@ fn locate_ecam_dtb<T>(
)
})?;
- let address = node.reg().unwrap().next().unwrap().starting_address as u64;
+ let mut reg = node.reg().ok_or_else(|| {
+ io::Error::new(io::ErrorKind::NotFound, "pci-host-ecam-generic missing 'reg' property")
+ })?;
+ let address = reg.next().ok_or_else(|| {
+ io::Error::new(io::ErrorKind::NotFound, "pci-host-ecam-generic 'reg' has no entries")
+ })?.starting_address as u64;
- let bus_range = node.property("bus-range").unwrap();
- assert_eq!(bus_range.value.len(), 8);
- let start_bus = u32::from_be_bytes(<[u8; 4]>::try_from(&bus_range.value[0..4]).unwrap());
- let end_bus = u32::from_be_bytes(<[u8; 4]>::try_from(&bus_range.value[4..8]).unwrap());
+ let bus_range = node.property("bus-range").ok_or_else(|| {
+ io::Error::new(io::ErrorKind::NotFound, "pci-host-ecam-generic missing 'bus-range' property")
+ })?;
+ if bus_range.value.len() != 8 {
+ return Err(io::Error::new(io::ErrorKind::InvalidData, "pci-host-ecam-generic 'bus-range' not 8 bytes"));
+ }
+ let start_bus = u32::from_be_bytes(<[u8; 4]>::try_from(&bus_range.value[0..4]).map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "bus-range start parse failed"))?);
+ let end_bus = u32::from_be_bytes(<[u8; 4]>::try_from(&bus_range.value[4..8]).map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "bus-range end parse failed"))?);
- // address-cells == 3, size-cells == 2, interrupt-cells == 1
- let mut interrupt_map_data = node
- .property("interrupt-map")
- .unwrap()
+ let interrupt_map_prop = node.property("interrupt-map").ok_or_else(|| {
+ io::Error::new(io::ErrorKind::NotFound, "pci-host-ecam-generic missing 'interrupt-map' property")
+ })?;
+ let mut interrupt_map_data = interrupt_map_prop
.value
.chunks_exact(4)
- .map(|x| u32::from_be_bytes(<[u8; 4]>::try_from(x).unwrap()));
+ .map(|x| u32::from_be_bytes(<[u8; 4]>::try_from(x).unwrap_or([0, 0, 0, 0])));
let mut interrupt_map = Vec::<InterruptMap>::new();
while let Ok([addr1, addr2, addr3, int1, phandle]) = interrupt_map_data.next_chunk::<5>() {
- let parent = dt.find_phandle(phandle).unwrap();
- let parent_address_cells = u32::from_be_bytes(
- parent.property("#address-cells").unwrap().value[..4]
- .try_into()
- .unwrap(),
- );
+ let Some(parent) = dt.find_phandle(phandle) else {
+ log::warn!("pcid: DTB interrupt-map references phandle {phandle} not found, skipping");
+ continue;
+ };
+ let parent_address_cells = match parent.property("#address-cells") {
+ Some(prop) => u32::from_be_bytes(
+ prop.value[..4]
+ .try_into()
+ .unwrap_or([0, 0, 0, 0]),
+ ),
+ None => 0,
+ };
match parent_address_cells {
0 => {}
1 => {
- assert_eq!(interrupt_map_data.next().unwrap(), 0);
+ let _ = interrupt_map_data.next();
}
2 => {
- assert_eq!(interrupt_map_data.next_chunk::<2>().unwrap(), [0, 0]);
+ let _ = interrupt_map_data.next_chunk::<2>();
}
3 => {
- assert_eq!(interrupt_map_data.next_chunk::<3>().unwrap(), [0, 0, 0]);
+ let _ = interrupt_map_data.next_chunk::<3>();
}
_ => break,
};
- let parent_interrupt_cells = parent.interrupt_cells().unwrap();
+ let parent_interrupt_cells = parent.interrupt_cells().unwrap_or(1);
let parent_interrupt = match parent_interrupt_cells {
1 if let Some(a) = interrupt_map_data.next() => [a, 0, 0],
2 if let Ok([a, b]) = interrupt_map_data.next_chunk::<2>() => [a, b, 0],
@@ -94,8 +109,8 @@ fn locate_ecam_dtb<T>(
let mut cells = interrupt_mask_node
.value
.chunks_exact(4)
- .map(|x| u32::from_be_bytes(<[u8; 4]>::try_from(x).unwrap()));
- cells.next_chunk::<4>().unwrap().to_owned()
+ .map(|x| u32::from_be_bytes(<[u8; 4]>::try_from(x).unwrap_or([0, 0, 0, 0])));
+ cells.next_chunk::<4>().unwrap_or([u32::MAX, u32::MAX, u32::MAX, u32::MAX]).to_owned()
} else {
[u32::MAX, u32::MAX, u32::MAX, u32::MAX]
};
@@ -104,8 +119,8 @@ fn locate_ecam_dtb<T>(
PcieAllocs(&[PcieAlloc {
base_addr: address,
seg_group_num: 0,
- start_bus: start_bus.try_into().unwrap(),
- end_bus: end_bus.try_into().unwrap(),
+ start_bus: start_bus.try_into().map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "start_bus overflow"))?,
+ end_bus: end_bus.try_into().map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "end_bus overflow"))?,
_rsvd: [0; 4],
}]),
interrupt_map,
@@ -165,7 +180,10 @@ impl Mcfg {
// crashing. `as_encoded_bytes()` returns some superset
// of ASCII, so directly comparing it with an ASCII name
// is fine.
- let table_filename = table_path.file_name().unwrap().as_encoded_bytes();
+ let table_filename = match table_path.file_name() {
+ Some(name) => name.as_encoded_bytes(),
+ None => continue,
+ };
if table_filename.get(0..4) == Some(&MCFG_NAME) {
let bytes = fs::read(table_path)?.into_boxed_slice();
match Mcfg::parse(&*bytes) {
@@ -0,0 +1,516 @@
# P2-ps2d-improvements.patch
#
# PS/2 controller improvements: flush/retry logic, mouse state machine,
# separate keyboard/mouse input producers.
#
# Covers:
# - ps2d/controller.rs: flush stale bytes, self-test with retry, AUX port test
# - ps2d/main.rs: separate InputProducer for keyboard and mouse
# - ps2d/mouse.rs: ACK/RESEND/BAT constant names, resend handling, state machine fixes
# - ps2d/state.rs: dual InputProducer fields, 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/main.rs b/drivers/input/ps2d/src/main.rs
index db17de2a..1ae055e4 100644
--- a/drivers/input/ps2d/src/main.rs
+++ b/drivers/input/ps2d/src/main.rs
@@ -11,7 +11,7 @@ use std::process;
use common::acquire_port_io_rights;
use event::{user_data, EventQueue};
-use inputd::ProducerHandle;
+use inputd::InputProducer;
use crate::state::Ps2d;
@@ -31,7 +31,10 @@ fn daemon(daemon: daemon::Daemon) -> ! {
acquire_port_io_rights().expect("ps2d: failed to get I/O permission");
- let input = ProducerHandle::new().expect("ps2d: failed to open input producer");
+ let keyboard_input = InputProducer::new_named_or_fallback("ps2-keyboard")
+ .expect("ps2d: failed to open input producer");
+ let mouse_input = InputProducer::new_named_or_fallback("ps2-mouse")
+ .expect("ps2d: failed to open input producer");
user_data! {
enum Source {
@@ -93,7 +96,7 @@ fn daemon(daemon: daemon::Daemon) -> ! {
daemon.ready();
- let mut ps2d = Ps2d::new(input, time_file);
+ let mut ps2d = Ps2d::new(keyboard_input, mouse_input, time_file);
let mut data = [0; 256];
for event in event_queue.map(|e| e.expect("ps2d: failed to get next event").user_data) {
diff --git a/drivers/input/ps2d/src/mouse.rs b/drivers/input/ps2d/src/mouse.rs
index 9e95ab88..8087c8c4 100644
--- a/drivers/input/ps2d/src/mouse.rs
+++ b/drivers/input/ps2d/src/mouse.rs
@@ -5,6 +5,11 @@ pub const RESET_RETRIES: usize = 10;
pub const RESET_TIMEOUT: Duration = Duration::from_millis(1000);
pub const COMMAND_TIMEOUT: Duration = Duration::from_millis(100);
+const CMD_ACK: u8 = 0xFA;
+const CMD_RESEND: u8 = 0xFE;
+const BAT_COMPLETE: u8 = 0xAA;
+const BAT_FAIL: u8 = 0xFC;
+
#[derive(Clone, Copy, Debug)]
#[repr(u8)]
#[allow(dead_code)]
@@ -58,9 +63,11 @@ impl MouseTx {
fn handle(&mut self, data: u8, ps2: &mut Ps2) -> Result<bool, ()> {
if self.write_i < self.write.len() {
- if data == 0xFA {
+ if data == CMD_ACK {
self.write_i += 1;
self.try_write(ps2)?;
+ } else if data == CMD_RESEND {
+ self.try_write(ps2)?;
} else {
log::error!("unknown mouse response {:02X}", data);
return Err(());
@@ -251,25 +258,43 @@ impl MouseState {
MouseResult::None
}
MouseState::Reset => {
- if data == 0xFA {
- log::debug!("mouse reset ok");
+ if data == CMD_ACK {
+ log::debug!("mouse reset ack");
MouseResult::Timeout(RESET_TIMEOUT)
- } else if data == 0xAA {
+ } else if data == BAT_COMPLETE {
log::debug!("BAT completed");
*self = MouseState::Bat;
MouseResult::Timeout(COMMAND_TIMEOUT)
+ } else if data == CMD_RESEND {
+ // Device asks us to resend the reset command (0xFF).
+ // Resend WITHOUT incrementing the retry counter — 0xFE is
+ // a normal protocol response, not a failure.
+ log::debug!("mouse requests resend during reset, resending 0xFF");
+ match ps2.mouse_command_async(MouseCommand::Reset as u8) {
+ Ok(()) => MouseResult::Timeout(RESET_TIMEOUT),
+ Err(err) => {
+ log::error!("failed to resend mouse reset: {:?}", err);
+ self.reset(ps2)
+ }
+ }
+ } else if data == BAT_FAIL {
+ log::warn!("mouse BAT failed (0xFC)");
+ self.reset(ps2)
} else {
log::warn!("unknown mouse response {:02X} after reset", data);
self.reset(ps2)
}
}
MouseState::Bat => {
- if data == MouseId::Base as u8 {
- // Enable intellimouse features
+ if data == CMD_RESEND {
+ // 0xFE after BAT is unusual — the device may be re-issuing
+ // BAT. Wait for the next byte (device ID or another BAT).
+ log::debug!("mouse resend (0xFE) during BAT, waiting");
+ MouseResult::Timeout(COMMAND_TIMEOUT)
+ } else if data == MouseId::Base as u8 {
log::debug!("BAT mouse id {:02X} (base)", data);
self.identify_touchpad(ps2)
} else if data == MouseId::Intellimouse1 as u8 {
- // Extra packet already enabled
log::debug!("BAT mouse id {:02X} (intellimouse)", data);
self.enable_reporting(data, ps2)
} else {
@@ -320,10 +345,17 @@ impl MouseState {
}
}
MouseState::DeviceId => {
- if data == 0xFA {
- // Command OK response
- //TODO: handle this separately?
+ if data == CMD_ACK {
MouseResult::Timeout(COMMAND_TIMEOUT)
+ } else if data == CMD_RESEND {
+ log::debug!("mouse resend during DeviceId, resending GetDeviceId");
+ match ps2.mouse_command_async(MouseCommand::GetDeviceId as u8) {
+ Ok(()) => MouseResult::Timeout(COMMAND_TIMEOUT),
+ Err(err) => {
+ log::error!("failed to resend GetDeviceId: {:?}", err);
+ self.reset(ps2)
+ }
+ }
} else if data == MouseId::Base as u8 || data == MouseId::Intellimouse1 as u8 {
log::debug!("mouse id {:02X}", data);
self.enable_reporting(data, ps2)
@@ -333,10 +365,28 @@ impl MouseState {
}
}
MouseState::EnableReporting { id } => {
- log::debug!("mouse id {:02X} enable reporting {:02X}", id, data);
- //TODO: handle response ok/error
- *self = MouseState::Streaming { id };
- MouseResult::None
+ if data == CMD_ACK {
+ log::debug!("mouse id {:02X} reporting enabled", id);
+ *self = MouseState::Streaming { id };
+ MouseResult::None
+ } else if data == CMD_RESEND {
+ log::debug!("mouse resend during EnableReporting, resending 0xF4");
+ match ps2.mouse_command_async(MouseCommand::EnableReporting as u8) {
+ Ok(()) => MouseResult::Timeout(COMMAND_TIMEOUT),
+ Err(err) => {
+ log::error!("failed to resend EnableReporting: {:?}", err);
+ *self = MouseState::Streaming { id };
+ MouseResult::None
+ }
+ }
+ } else {
+ log::warn!(
+ "unexpected mouse response {:02X} during enable reporting, streaming anyway",
+ data
+ );
+ *self = MouseState::Streaming { id };
+ MouseResult::None
+ }
}
MouseState::Streaming { id } => {
MouseResult::Packet(data, id == MouseId::Intellimouse1 as u8)
diff --git a/drivers/input/ps2d/src/state.rs b/drivers/input/ps2d/src/state.rs
index 9018dc6b..da304e05 100644
--- a/drivers/input/ps2d/src/state.rs
+++ b/drivers/input/ps2d/src/state.rs
@@ -1,4 +1,4 @@
-use inputd::ProducerHandle;
+use inputd::InputProducer;
use log::{error, warn};
use orbclient::{ButtonEvent, KeyEvent, MouseEvent, MouseRelativeEvent, ScrollEvent};
use std::{
@@ -44,7 +44,8 @@ pub struct Ps2d {
ps2: Ps2,
vmmouse: bool,
vmmouse_relative: bool,
- input: ProducerHandle,
+ keyboard_input: InputProducer,
+ mouse_input: InputProducer,
time_file: File,
extended: bool,
mouse_x: i32,
@@ -59,9 +60,11 @@ pub struct Ps2d {
}
impl Ps2d {
- pub fn new(input: ProducerHandle, time_file: File) -> Self {
+ pub fn new(keyboard_input: InputProducer, mouse_input: InputProducer, time_file: File) -> Self {
let mut ps2 = Ps2::new();
- ps2.init().expect("failed to initialize");
+ if let Err(err) = ps2.init() {
+ log::error!("ps2d: controller init failed: {:?}", err);
+ }
// FIXME add an option for orbital to disable this when an app captures the mouse.
let vmmouse_relative = false;
@@ -77,7 +80,8 @@ impl Ps2d {
ps2,
vmmouse,
vmmouse_relative,
- input,
+ keyboard_input,
+ mouse_input,
time_file,
extended: false,
mouse_x: 0,
@@ -273,7 +277,7 @@ impl Ps2d {
};
if scancode != 0 {
- self.input
+ self.keyboard_input
.write_event(
KeyEvent {
character: '\0',
@@ -304,7 +308,7 @@ impl Ps2d {
if self.vmmouse_relative {
if dx != 0 || dy != 0 {
- self.input
+ self.mouse_input
.write_event(
MouseRelativeEvent {
dx: dx as i32,
@@ -320,14 +324,14 @@ impl Ps2d {
if x != self.mouse_x || y != self.mouse_y {
self.mouse_x = x;
self.mouse_y = y;
- self.input
+ self.mouse_input
.write_event(MouseEvent { x, y }.to_event())
.expect("ps2d: failed to write mouse event");
}
};
if dz != 0 {
- self.input
+ self.mouse_input
.write_event(
ScrollEvent {
x: 0,
@@ -348,7 +352,7 @@ impl Ps2d {
self.mouse_left = left;
self.mouse_middle = middle;
self.mouse_right = right;
- self.input
+ self.mouse_input
.write_event(
ButtonEvent {
left,
@@ -441,13 +445,13 @@ impl Ps2d {
}
if dx != 0 || dy != 0 {
- self.input
+ self.mouse_input
.write_event(MouseRelativeEvent { dx, dy }.to_event())
.expect("ps2d: failed to write mouse event");
}
if dz != 0 {
- self.input
+ self.mouse_input
.write_event(ScrollEvent { x: 0, y: dz }.to_event())
.expect("ps2d: failed to write scroll event");
}
@@ -462,7 +466,7 @@ impl Ps2d {
self.mouse_left = left;
self.mouse_middle = middle;
self.mouse_right = right;
- self.input
+ self.mouse_input
.write_event(
ButtonEvent {
left,
@@ -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,7 +178,10 @@ impl HbaPort {
clb: &mut Dma<[HbaCmdHeader; 32]>,
ctbas: &mut [Dma<HbaCmdTable>; 32],
) -> Result<u64> {
- let dest: Dma<[u16; 256]> = Dma::new([0; 256]).unwrap();
+ let dest: Dma<[u16; 256]> = Dma::new([0; 256]).map_err(|err| {
+ error!("ahcid: failed to allocate DMA buffer: {err}");
+ Error::new(EIO)
+ })?;
let slot = self
.ata_start(clb, ctbas, |cmdheader, cmdfis, prdt_entries, _acmd| {
diff --git a/drivers/storage/ided/src/ide.rs b/drivers/storage/ided/src/ide.rs
index 5faf3250..094e5889 100644
--- a/drivers/storage/ided/src/ide.rs
+++ b/drivers/storage/ided/src/ide.rs
@@ -184,10 +184,10 @@ impl Disk for AtaDisk {
let block = start_block + (count as u64) / 512;
//TODO: support other LBA modes
- assert!(block < 0x1_0000_0000_0000);
+ debug_assert!(block < 0x1_0000_0000_0000);
let sectors = (chunk.len() + 511) / 512;
- assert!(sectors <= 128);
+ debug_assert!(sectors <= 128);
log::trace!(
"IDE read chan {} dev {} block {:#x} count {:#x}",
@@ -205,7 +205,7 @@ impl Disk for AtaDisk {
// Make PRDT EOT match chunk size
for i in 0..sectors {
chan.prdt[i] = PrdtEntry {
- phys: (chan.buf.physical() + i * 512).try_into().unwrap(),
+ phys: (chan.buf.physical() + i * 512).try_into().map_err(|_| Error::new(EIO))?,
size: 512,
flags: if i + 1 == sectors {
1 << 15 // End of table
@@ -216,7 +216,7 @@ impl Disk for AtaDisk {
}
// Set PRDT
let prdt = chan.prdt.physical();
- chan.busmaster_prdt.write(prdt.try_into().unwrap());
+ chan.busmaster_prdt.write(prdt.try_into().map_err(|_| Error::new(EIO))?);
// Set to read
chan.busmaster_command.writef(1 << 3, true);
// Clear interrupt and error bits
@@ -325,10 +325,10 @@ impl Disk for AtaDisk {
let block = start_block + (count as u64) / 512;
//TODO: support other LBA modes
- assert!(block < 0x1_0000_0000_0000);
+ debug_assert!(block < 0x1_0000_0000_0000);
let sectors = (chunk.len() + 511) / 512;
- assert!(sectors <= 128);
+ debug_assert!(sectors <= 128);
log::trace!(
"IDE write chan {} dev {} block {:#x} count {:#x}",
@@ -346,7 +346,7 @@ impl Disk for AtaDisk {
// Make PRDT EOT match chunk size
for i in 0..sectors {
chan.prdt[i] = PrdtEntry {
- phys: (chan.buf.physical() + i * 512).try_into().unwrap(),
+ phys: (chan.buf.physical() + i * 512).try_into().map_err(|_| Error::new(EIO))?,
size: 512,
flags: if i + 1 == sectors {
1 << 15 // End of table
@@ -357,7 +357,7 @@ impl Disk for AtaDisk {
}
// Set PRDT
let prdt = chan.prdt.physical();
- chan.busmaster_prdt.write(prdt.try_into().unwrap());
+ chan.busmaster_prdt.write(prdt.try_into().map_err(|_| Error::new(EIO))?);
// Set to write
chan.busmaster_command.writef(1 << 3, false);
// Clear interrupt and error bits
diff --git a/drivers/storage/nvmed/src/nvme/executor.rs b/drivers/storage/nvmed/src/nvme/executor.rs
index 6242fa98..c1435e88 100644
--- a/drivers/storage/nvmed/src/nvme/executor.rs
+++ b/drivers/storage/nvmed/src/nvme/executor.rs
@@ -34,7 +34,12 @@ impl Hardware for NvmeHw {
&VTABLE
}
fn current() -> std::rc::Rc<executor::LocalExecutor<Self>> {
- THE_EXECUTOR.with(|exec| Rc::clone(exec.borrow().as_ref().unwrap()))
+ THE_EXECUTOR.with(|exec| {
+ Rc::clone(exec.borrow().as_ref().unwrap_or_else(|| {
+ log::error!("nvmed: internal error: executor not initialized");
+ std::process::exit(1);
+ }))
+ })
}
fn try_submit(
nvme: &Arc<Nvme>,
diff --git a/drivers/storage/nvmed/src/nvme/identify.rs b/drivers/storage/nvmed/src/nvme/identify.rs
index 05e5b9b2..b1b6e959 100644
--- a/drivers/storage/nvmed/src/nvme/identify.rs
+++ b/drivers/storage/nvmed/src/nvme/identify.rs
@@ -126,7 +126,7 @@ impl LbaFormat {
0b01 => RelativePerformance::Better,
0b10 => RelativePerformance::Good,
0b11 => RelativePerformance::Degraded,
- _ => unreachable!(),
+ _ => RelativePerformance::Degraded,
}
}
pub fn is_available(&self) -> bool {
@@ -153,7 +153,14 @@ impl Nvme {
/// Returns the serial number, model, and firmware, in that order.
pub async fn identify_controller(&self) {
// TODO: Use same buffer
- let data: Dma<IdentifyControllerData> = unsafe { Dma::zeroed().unwrap().assume_init() };
+ let data: Dma<IdentifyControllerData> = unsafe {
+ Dma::zeroed()
+ .map(|dma| dma.assume_init())
+ .unwrap_or_else(|err| {
+ log::error!("nvmed: failed to allocate identify DMA: {err}");
+ std::process::exit(1);
+ })
+ };
// println!(" - Attempting to identify controller");
let comp = self
@@ -182,7 +189,14 @@ impl Nvme {
}
pub async fn identify_namespace_list(&self, base: u32) -> Vec<u32> {
// TODO: Use buffer
- let data: Dma<[u32; 1024]> = unsafe { Dma::zeroed().unwrap().assume_init() };
+ let data: Dma<[u32; 1024]> = unsafe {
+ Dma::zeroed()
+ .map(|dma| dma.assume_init())
+ .unwrap_or_else(|err| {
+ log::error!("nvmed: failed to allocate namespace list DMA: {err}");
+ std::process::exit(1);
+ })
+ };
// println!(" - Attempting to retrieve namespace ID list");
let comp = self
@@ -198,7 +212,14 @@ impl Nvme {
}
pub async fn identify_namespace(&self, nsid: u32) -> NvmeNamespace {
//TODO: Use buffer
- let data: Dma<IdentifyNamespaceData> = unsafe { Dma::zeroed().unwrap().assume_init() };
+ let data: Dma<IdentifyNamespaceData> = unsafe {
+ Dma::zeroed()
+ .map(|dma| dma.assume_init())
+ .unwrap_or_else(|err| {
+ log::error!("nvmed: failed to allocate namespace DMA: {err}");
+ std::process::exit(1);
+ })
+ };
log::debug!("Attempting to identify namespace {nsid}");
let comp = self
@@ -216,7 +237,10 @@ impl Nvme {
let block_size = data
.formatted_lba_size()
.lba_data_size()
- .expect("nvmed: error: size outside 512-2^64 range");
+ .unwrap_or_else(|| {
+ log::error!("nvmed: error: size outside 512-2^64 range");
+ std::process::exit(1);
+ });
log::debug!("NVME block size: {}", block_size);
NvmeNamespace {
diff --git a/drivers/storage/nvmed/src/nvme/mod.rs b/drivers/storage/nvmed/src/nvme/mod.rs
index 682ee933..90a25d5b 100644
--- a/drivers/storage/nvmed/src/nvme/mod.rs
+++ b/drivers/storage/nvmed/src/nvme/mod.rs
@@ -160,7 +160,15 @@ impl Nvme {
}
fn cur_thread_ctxt(&self) -> Arc<ReentrantMutex<ThreadCtxt>> {
// TODO: multi-threading
- Arc::clone(self.thread_ctxts.read().get(&0).unwrap())
+ Arc::clone(
+ self.thread_ctxts
+ .read()
+ .get(&0)
+ .unwrap_or_else(|| {
+ log::error!("nvmed: internal error: thread context 0 missing");
+ std::process::exit(1);
+ }),
+ )
}
pub unsafe fn submission_queue_tail(&self, qid: u16, tail: u16) {
@@ -208,10 +216,22 @@ impl Nvme {
}
for (qid, iv) in self.cq_ivs.get_mut().iter_mut() {
- let ctxt = thread_ctxts.get(&0).unwrap().lock();
+ let ctxt = match thread_ctxts.get(&0) {
+ Some(c) => c.lock(),
+ None => {
+ log::error!("nvmed: internal error: thread context 0 missing");
+ return Err(Error::new(EIO));
+ }
+ };
let queues = ctxt.queues.borrow();
- let &(ref cq, ref sq) = queues.get(qid).unwrap();
+ let (cq, sq) = match queues.get(qid) {
+ Some(pair) => pair,
+ None => {
+ log::error!("nvmed: internal error: queue {qid} missing");
+ return Err(Error::new(EIO));
+ }
+ };
log::debug!(
"iv {iv} [cq {qid}: {:X}, {}] [sq {qid}: {:X}, {}]",
cq.data.physical(),
@@ -222,7 +242,13 @@ impl Nvme {
}
{
- let main_ctxt = thread_ctxts.get(&0).unwrap().lock();
+ let main_ctxt = match thread_ctxts.get(&0) {
+ Some(c) => c.lock(),
+ None => {
+ log::error!("nvmed: internal error: thread context 0 missing");
+ return Err(Error::new(EIO));
+ }
+ };
for (i, prp) in main_ctxt.buffer_prp.borrow_mut().iter_mut().enumerate() {
*prp = (main_ctxt.buffer.borrow_mut().physical() + i * 4096) as u64;
@@ -231,7 +257,13 @@ impl Nvme {
let regs = self.regs.get_mut();
let mut queues = main_ctxt.queues.borrow_mut();
- let (asq, acq) = queues.get_mut(&0).unwrap();
+ let (asq, acq) = match queues.get_mut(&0) {
+ Some(pair) => pair,
+ None => {
+ log::error!("nvmed: internal error: admin queue pair missing");
+ return Err(Error::new(EIO));
+ }
+ };
regs.aqa
.write(((acq.data.len() as u32 - 1) << 16) | (asq.data.len() as u32 - 1));
regs.asq_low.write(asq.data.physical() as u32);
@@ -281,14 +313,14 @@ impl Nvme {
let vector = vector as u8;
if masked {
- assert_ne!(
+ debug_assert_ne!(
to_clear & (1 << vector),
(1 << vector),
"nvmed: internal error: cannot both mask and set"
);
to_mask |= 1 << vector;
} else {
- assert_ne!(
+ debug_assert_ne!(
to_mask & (1 << vector),
(1 << vector),
"nvmed: internal error: cannot both mask and set"
@@ -326,22 +358,27 @@ impl Nvme {
cmd_init: impl FnOnce(CmdId) -> NvmeCmd,
fail: impl FnOnce(),
) -> Option<(CqId, CmdId)> {
- match ctxt.queues.borrow_mut().get_mut(&sq_id).unwrap() {
- (sq, _cq) => {
- if sq.is_full() {
- fail();
- return None;
- }
- let cmd_id = sq.tail;
- let tail = sq.submit_unchecked(cmd_init(cmd_id));
-
- // TODO: Submit in bulk
- unsafe {
- self.submission_queue_tail(sq_id, tail);
- }
- Some((sq_id, cmd_id))
+ let mut queues_ref = ctxt.queues.borrow_mut();
+ let (sq, _cq) = match queues_ref.get_mut(&sq_id) {
+ Some(pair) => pair,
+ None => {
+ log::error!("nvmed: internal error: submission queue {sq_id} missing");
+ fail();
+ return None;
}
+ };
+ if sq.is_full() {
+ fail();
+ return None;
+ }
+ let cmd_id = sq.tail;
+ let tail = sq.submit_unchecked(cmd_init(cmd_id));
+
+ // TODO: Submit in bulk
+ unsafe {
+ self.submission_queue_tail(sq_id, tail);
}
+ Some((sq_id, cmd_id))
}
pub async fn create_io_completion_queue(
@@ -349,13 +386,19 @@ impl Nvme {
io_cq_id: CqId,
vector: Option<Iv>,
) -> NvmeCompQueue {
- let queue = NvmeCompQueue::new().expect("nvmed: failed to allocate I/O completion queue");
-
- let len = u16::try_from(queue.data.len())
- .expect("nvmed: internal error: I/O CQ longer than 2^16 entries");
- let raw_len = len
- .checked_sub(1)
- .expect("nvmed: internal error: CQID 0 for I/O CQ");
+ let queue = NvmeCompQueue::new().unwrap_or_else(|err| {
+ log::error!("nvmed: failed to allocate I/O completion queue: {err}");
+ std::process::exit(1);
+ });
+
+ let len = u16::try_from(queue.data.len()).unwrap_or_else(|_| {
+ log::error!("nvmed: internal error: I/O CQ longer than 2^16 entries");
+ std::process::exit(1);
+ });
+ let raw_len = len.checked_sub(1).unwrap_or_else(|| {
+ log::error!("nvmed: internal error: CQID 0 for I/O CQ");
+ std::process::exit(1);
+ });
let comp = self
.submit_and_complete_admin_command(|cid| {
@@ -370,22 +413,28 @@ impl Nvme {
.await;
/*match comp.status.specific {
- 1 => panic!("invalid queue identifier"),
- 2 => panic!("invalid queue size"),
- 8 => panic!("invalid interrupt vector"),
+ 1 => { log::error!("nvmed: invalid queue identifier"); std::process::exit(1); }
+ 2 => { log::error!("nvmed: invalid queue size"); std::process::exit(1); }
+ 8 => { log::error!("nvmed: invalid interrupt vector"); std::process::exit(1); }
_ => (),
}*/
queue
}
pub async fn create_io_submission_queue(&self, io_sq_id: SqId, io_cq_id: CqId) -> NvmeCmdQueue {
- let q = NvmeCmdQueue::new().expect("failed to create submission queue");
-
- let len = u16::try_from(q.data.len())
- .expect("nvmed: internal error: I/O SQ longer than 2^16 entries");
- let raw_len = len
- .checked_sub(1)
- .expect("nvmed: internal error: SQID 0 for I/O SQ");
+ let q = NvmeCmdQueue::new().unwrap_or_else(|err| {
+ log::error!("nvmed: failed to create submission queue: {err}");
+ std::process::exit(1);
+ });
+
+ let len = u16::try_from(q.data.len()).unwrap_or_else(|_| {
+ log::error!("nvmed: internal error: I/O SQ longer than 2^16 entries");
+ std::process::exit(1);
+ });
+ let raw_len = len.checked_sub(1).unwrap_or_else(|| {
+ log::error!("nvmed: internal error: SQID 0 for I/O SQ");
+ std::process::exit(1);
+ });
let comp = self
.submit_and_complete_admin_command(|cid| {
@@ -399,9 +448,9 @@ impl Nvme {
})
.await;
/*match comp.status.specific {
- 0 => panic!("completion queue invalid"),
- 1 => panic!("invalid queue identifier"),
- 2 => panic!("invalid queue size"),
+ 0 => { log::error!("nvmed: completion queue invalid"); std::process::exit(1); }
+ 1 => { log::error!("nvmed: invalid queue identifier"); std::process::exit(1); }
+ 2 => { log::error!("nvmed: invalid queue size"); std::process::exit(1); }
_ => (),
}*/
@@ -431,7 +480,10 @@ impl Nvme {
self.thread_ctxts
.read()
.get(&0)
- .unwrap()
+ .unwrap_or_else(|| {
+ log::error!("nvmed: internal error: thread context 0 missing");
+ std::process::exit(1);
+ })
.lock()
.queues
.borrow_mut()
@@ -497,8 +549,8 @@ impl Nvme {
for chunk in buf.chunks_mut(/* TODO: buf len */ 8192) {
let blocks = (chunk.len() + block_size - 1) / block_size;
- assert!(blocks > 0);
- assert!(blocks <= 0x1_0000);
+ debug_assert!(blocks > 0);
+ debug_assert!(blocks <= 0x1_0000);
self.namespace_rw(&*ctxt, namespace, lba, (blocks - 1) as u16, false)
.await?;
@@ -525,8 +577,8 @@ impl Nvme {
for chunk in buf.chunks(/* TODO: buf len */ 8192) {
let blocks = (chunk.len() + block_size - 1) / block_size;
- assert!(blocks > 0);
- assert!(blocks <= 0x1_0000);
+ debug_assert!(blocks > 0);
+ debug_assert!(blocks <= 0x1_0000);
ctxt.buffer.borrow_mut()[..chunk.len()].copy_from_slice(chunk);
diff --git a/drivers/storage/nvmed/src/nvme/queues.rs b/drivers/storage/nvmed/src/nvme/queues.rs
index a3712aeb..438c905c 100644
--- a/drivers/storage/nvmed/src/nvme/queues.rs
+++ b/drivers/storage/nvmed/src/nvme/queues.rs
@@ -145,7 +145,7 @@ impl Status {
3 => Self::PathRelatedStatus(code),
4..=6 => Self::Rsvd(code),
7 => Self::Vendor(code),
- _ => unreachable!(),
+ _ => Self::Vendor(code),
}
}
}
diff --git a/drivers/storage/virtio-blkd/src/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,6 +41,15 @@ fn main() {
if let Some(_status_scmd_matches) = port_scmd_matches.subcommand_matches("status") {
let state = handle.port_state().expect("Failed to get port state");
println!("{}", state.as_str());
+ } else if let Some(_pm_state_scmd_matches) = port_scmd_matches.subcommand_matches("pm-state") {
+ let state = handle
+ .port_pm_state()
+ .expect("Failed to get port power-management state");
+ println!("{}", state.as_str());
+ } else if let Some(_suspend_scmd_matches) = port_scmd_matches.subcommand_matches("suspend") {
+ handle.suspend_device().expect("Failed to suspend device");
+ } else if let Some(_resume_scmd_matches) = port_scmd_matches.subcommand_matches("resume") {
+ handle.resume_device().expect("Failed to resume device");
} else if let Some(endp_scmd_matches) = port_scmd_matches.subcommand_matches("endpoint") {
let endp_num = endp_scmd_matches
.get_one::<String>("ENDPOINT_NUM")
diff --git a/drivers/usb/xhcid/drivers.toml b/drivers/usb/xhcid/drivers.toml
index 83c90e23..470ec063 100644
--- a/drivers/usb/xhcid/drivers.toml
+++ b/drivers/usb/xhcid/drivers.toml
@@ -1,9 +1,8 @@
-#TODO: causes XHCI errors
-#[[drivers]]
-#name = "SCSI over USB"
-#class = 8 # Mass Storage class
-#subclass = 6 # SCSI transparent command set
-#command = ["usbscsid", "$SCHEME", "$PORT", "$IF_PROTO"]
+[[drivers]]
+name = "SCSI over USB"
+class = 8 # Mass Storage class
+subclass = 6 # SCSI transparent command set
+command = ["usbscsid", "$SCHEME", "$PORT", "$IF_PROTO"]
[[drivers]]
name = "USB HUB"
diff --git a/drivers/usb/xhcid/src/driver_interface.rs b/drivers/usb/xhcid/src/driver_interface.rs
index 727f8d7e..82f839ae 100644
--- a/drivers/usb/xhcid/src/driver_interface.rs
+++ b/drivers/usb/xhcid/src/driver_interface.rs
@@ -444,6 +444,33 @@ impl str::FromStr for PortState {
}
}
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
+pub enum PortPmState {
+ Active,
+ Suspended,
+}
+impl PortPmState {
+ pub fn as_str(&self) -> &'static str {
+ match self {
+ Self::Active => "active",
+ Self::Suspended => "suspended",
+ }
+ }
+}
+
+impl str::FromStr for PortPmState {
+ type Err = Invalid;
+
+ fn from_str(s: &str) -> result::Result<Self, Self::Err> {
+ Ok(match s {
+ "active" => Self::Active,
+ "suspended" => Self::Suspended,
+ _ => return Err(Invalid("read reserved port PM state")),
+ })
+ }
+}
+
#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub enum EndpointStatus {
@@ -560,6 +587,16 @@ impl XhciClientHandle {
let _bytes_written = file.write(&[])?;
Ok(())
}
+ pub fn suspend_device(&self) -> result::Result<(), XhciClientHandleError> {
+ let file = self.fd.openat("suspend", libredox::flag::O_WRONLY, 0)?;
+ let _bytes_written = file.write(&[])?;
+ Ok(())
+ }
+ pub fn resume_device(&self) -> result::Result<(), XhciClientHandleError> {
+ let file = self.fd.openat("resume", libredox::flag::O_WRONLY, 0)?;
+ let _bytes_written = file.write(&[])?;
+ Ok(())
+ }
pub fn get_standard_descs(&self) -> result::Result<DevDesc, XhciClientHandleError> {
let json = self.read("descriptors")?;
Ok(serde_json::from_slice(&json)?)
@@ -582,6 +619,10 @@ impl XhciClientHandle {
let string = self.read_to_string("state")?;
Ok(string.parse()?)
}
+ pub fn port_pm_state(&self) -> result::Result<PortPmState, XhciClientHandleError> {
+ let string = self.read_to_string("pm_state")?;
+ Ok(string.parse()?)
+ }
pub fn open_endpoint_ctl(&self, num: u8) -> result::Result<File, XhciClientHandleError> {
let path = format!("endpoints/{}/ctl", num);
let fd = self.fd.openat(&path, libredox::flag::O_RDWR, 0)?;
diff --git a/drivers/usb/xhcid/src/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 {
+336
View File
@@ -0,0 +1,336 @@
diff -ruN a/src/header/_aio/mod.rs b/src/header/_aio/mod.rs
--- a/src/header/_aio/mod.rs
+++ b/src/header/_aio/mod.rs
@@ -1,75 +1,283 @@
//! `aio.h` implementation.
//!
-//! See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/aio.h.html>.
+//! Synchronous emulation of POSIX AIO. All operations complete immediately
+//! in the calling thread. This provides sufficient compatibility for software
+//! (such as Qt6's QIODevice) that uses aio as an optional fallback path.
-use crate::{
- header::{bits_timespec::timespec, signal::sigevent},
- platform::types::{c_int, c_void},
-};
+use core::slice;
+
+use crate::{
+ error::Errno,
+ header::{
+ bits_timespec::timespec,
+ errno::{EFAULT, EINVAL, EINPROGRESS, EIO},
+ fcntl::O_SYNC,
+ signal::sigevent,
+ },
+ platform::{
+ Sys,
+ types::{c_int, c_void, off_t, size_t, ssize_t},
+ ERRNO,
+ },
+};
+
+// POSIX lio_listio operation codes
+pub const LIO_READ: c_int = 0;
+pub const LIO_WRITE: c_int = 1;
+pub const LIO_NOP: c_int = 2;
+
+// lio_listio modes
+pub const LIO_WAIT: c_int = 0;
+pub const LIO_NOWAIT: c_int = 1;
+
+// aio_cancel return values
+pub const AIO_CANCELED: c_int = 0;
+pub const AIO_NOTCANCELED: c_int = 1;
+pub const AIO_ALLDONE: c_int = 2;
+
+// O_DSYNC is not yet defined in relibc's fcntl module.
+// Accept it in aio_fsync by matching the Linux x86_64 value.
+// TODO: import from fcntl when O_DSYNC is added there.
+const _O_DSYNC: c_int = 0x0001_0000;
+
+// Internal operation states for synchronous emulation
+const _AIO_IDLE: c_int = 0;
+const _AIO_DONE: c_int = 2;
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/aio.h.html>.
+#[repr(C)]
pub struct aiocb {
pub aio_fildes: c_int,
+ pub aio_offset: off_t,
pub aio_lio_opcode: c_int,
pub aio_reqprio: c_int,
pub aio_buf: *mut c_void,
- pub aio_nbytes: usize,
+ pub aio_nbytes: size_t,
pub aio_sigevent: sigevent,
+ // Private emulation state
+ pub __state: c_int,
+ pub __error_code: c_int,
+ pub __return_value: ssize_t,
}
-/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/aio_read.html>.
-// #[unsafe(no_mangle)]
-pub extern "C" fn aio_read(aiocbp: *mut aiocb) -> c_int {
- unimplemented!();
+/// Perform a synchronous pread and store the result in the aiocb.
+///
+/// Returns 0 on success, -1 on error (with errno set).
+unsafe fn aio_do_read(cb: &mut aiocb) -> c_int {
+ let buf = unsafe { slice::from_raw_parts_mut(cb.aio_buf.cast::<u8>(), cb.aio_nbytes) };
+ match Sys::pread(cb.aio_fildes, buf, cb.aio_offset) {
+ Ok(n) => {
+ cb.__error_code = 0;
+ cb.__return_value = n as ssize_t;
+ cb.__state = _AIO_DONE;
+ 0
+ }
+ Err(Errno(e)) => {
+ cb.__error_code = e;
+ cb.__return_value = -1;
+ cb.__state = _AIO_DONE;
+ ERRNO.set(e);
+ -1
+ }
+ }
}
-/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/aio_write.html>.
-// #[unsafe(no_mangle)]
-pub extern "C" fn aio_write(aiocbp: *mut aiocb) -> c_int {
- unimplemented!();
+/// Perform a synchronous pwrite and store the result in the aiocb.
+///
+/// Returns 0 on success, -1 on error (with errno set).
+unsafe fn aio_do_write(cb: &mut aiocb) -> c_int {
+ let buf = unsafe { slice::from_raw_parts(cb.aio_buf.cast::<u8>(), cb.aio_nbytes) };
+ match Sys::pwrite(cb.aio_fildes, buf, cb.aio_offset) {
+ Ok(n) => {
+ cb.__error_code = 0;
+ cb.__return_value = n as ssize_t;
+ cb.__state = _AIO_DONE;
+ 0
+ }
+ Err(Errno(e)) => {
+ cb.__error_code = e;
+ cb.__return_value = -1;
+ cb.__state = _AIO_DONE;
+ ERRNO.set(e);
+ -1
+ }
+ }
}
-/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/lio_listio.html>.
-// #[unsafe(no_mangle)]
-pub extern "C" fn lio_listio(
- mode: c_int,
- list: *const *const aiocb,
- nent: c_int,
- sig: *mut sigevent,
-) -> c_int {
- unimplemented!();
-}
+/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/aio_read.html>.
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn aio_read(aiocbp: *mut aiocb) -> c_int {
+ if aiocbp.is_null() {
+ ERRNO.set(EINVAL);
+ return -1;
+ }
+ let cb = unsafe { &mut *aiocbp };
+ if cb.aio_buf.is_null() && cb.aio_nbytes > 0 {
+ ERRNO.set(EFAULT);
+ cb.__state = _AIO_DONE;
+ cb.__error_code = EFAULT;
+ cb.__return_value = -1;
+ return -1;
+ }
+ unsafe { aio_do_read(cb) }
+}
-/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/aio_error.html>.
-// #[unsafe(no_mangle)]
-pub extern "C" fn aio_error(aiocbp: *const aiocb) -> c_int {
- unimplemented!();
+/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/aio_write.html>.
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn aio_write(aiocbp: *mut aiocb) -> c_int {
+ if aiocbp.is_null() {
+ ERRNO.set(EINVAL);
+ return -1;
+ }
+ let cb = unsafe { &mut *aiocbp };
+ if cb.aio_buf.is_null() && cb.aio_nbytes > 0 {
+ ERRNO.set(EFAULT);
+ cb.__state = _AIO_DONE;
+ cb.__error_code = EFAULT;
+ cb.__return_value = -1;
+ return -1;
+ }
+ unsafe { aio_do_write(cb) }
}
-/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/aio_return.html>.
-// #[unsafe(no_mangle)]
-pub extern "C" fn aio_return(aiocbp: *mut aiocb) -> usize {
- unimplemented!();
-}
+/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/aio_fsync.html>.
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn aio_fsync(operation: c_int, aiocbp: *mut aiocb) -> c_int {
+ if aiocbp.is_null() {
+ ERRNO.set(EINVAL);
+ return -1;
+ }
+ // Validate operation: O_SYNC from fcntl, or _O_DSYNC (Linux compat value).
+ if operation != O_SYNC && operation != _O_DSYNC {
+ ERRNO.set(EINVAL);
+ return -1;
+ }
+ let cb = unsafe { &mut *aiocbp };
+ match Sys::fsync(cb.aio_fildes) {
+ Ok(()) => {
+ cb.__error_code = 0;
+ cb.__return_value = 0;
+ cb.__state = _AIO_DONE;
+ 0
+ }
+ Err(Errno(e)) => {
+ cb.__error_code = e;
+ cb.__return_value = -1;
+ cb.__state = _AIO_DONE;
+ ERRNO.set(e);
+ -1
+ }
+ }
+}
-/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/aio_cancel.html>.
-// #[unsafe(no_mangle)]
-pub extern "C" fn aio_cancel(fildes: c_int, aiocbp: *mut aiocb) -> c_int {
- unimplemented!();
+/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/aio_error.html>.
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn aio_error(aiocbp: *const aiocb) -> c_int {
+ if aiocbp.is_null() {
+ return EINVAL;
+ }
+ let cb = unsafe { &*aiocbp };
+ match cb.__state {
+ _AIO_IDLE => 0, // Never submitted -- no error
+ _AIO_DONE => cb.__error_code,
+ _ => EINPROGRESS, // Should not occur with sync emulation
+ }
}
-/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/aio_suspend.html>.
-// #[unsafe(no_mangle)]
-pub extern "C" fn aio_suspend(
- list: *const *const aiocb,
- nent: c_int,
- timeout: *const timespec,
-) -> c_int {
- unimplemented!();
+/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/aio_return.html>.
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn aio_return(aiocbp: *mut aiocb) -> ssize_t {
+ if aiocbp.is_null() {
+ ERRNO.set(EINVAL);
+ return -1;
+ }
+ let cb = unsafe { &*aiocbp };
+ if cb.__state != _AIO_DONE {
+ ERRNO.set(EINPROGRESS);
+ return -1;
+ }
+ cb.__return_value
}
-/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/aio_fsync.html>.
-// #[unsafe(no_mangle)]
-pub extern "C" fn aio_fsync(operation: c_int, aiocbp: *mut aiocb) -> c_int {
- unimplemented!();
+/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/aio_suspend.html>.
+///
+/// With synchronous emulation, all operations are already complete when
+/// aio_suspend is called, so this is effectively a no-op that returns 0.
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn aio_suspend(
+ list: *const *const aiocb,
+ nent: c_int,
+ timeout: *const timespec,
+) -> c_int {
+ let _ = timeout;
+ if list.is_null() || nent < 0 {
+ ERRNO.set(EINVAL);
+ return -1;
+ }
+ // All operations complete synchronously, so just return success.
+ 0
+}
+
+/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/aio_cancel.html>.
+///
+/// With synchronous emulation, operations complete before aio_cancel can be
+/// called, so this always returns AIO_ALLDONE.
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn aio_cancel(fildes: c_int, aiocbp: *mut aiocb) -> c_int {
+ if !aiocbp.is_null() {
+ let cb = unsafe { &*aiocbp };
+ if cb.aio_fildes != fildes {
+ ERRNO.set(EINVAL);
+ return -1;
+ }
+ }
+ AIO_ALLDONE
+}
+
+/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/lio_listio.html>.
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn lio_listio(
+ mode: c_int,
+ list: *const *const aiocb,
+ nent: c_int,
+ sig: *mut sigevent,
+) -> c_int {
+ let _ = sig;
+ if (mode != LIO_WAIT && mode != LIO_NOWAIT) || list.is_null() || nent < 0 {
+ ERRNO.set(EINVAL);
+ return -1;
+ }
+ let mut any_failed = false;
+ for i in 0..nent {
+ let entry = unsafe { *list.add(i as usize) };
+ if entry.is_null() {
+ continue;
+ }
+ let cb = unsafe { &mut *(entry as *mut aiocb) };
+ match cb.aio_lio_opcode {
+ LIO_READ => {
+ if unsafe { aio_read(cb) } != 0 {
+ any_failed = true;
+ }
+ }
+ LIO_WRITE => {
+ if unsafe { aio_write(cb) } != 0 {
+ any_failed = true;
+ }
+ }
+ LIO_NOP => {}
+ _ => {
+ cb.__state = _AIO_DONE;
+ cb.__error_code = EINVAL;
+ cb.__return_value = -1;
+ ERRNO.set(EINVAL);
+ any_failed = true;
+ }
+ }
+ }
+ if any_failed {
+ ERRNO.set(EIO);
+ return -1;
+ }
+ 0
}
+61
View File
@@ -0,0 +1,61 @@
#TODO: Breeze — KDE theme engine (widget style, colors, window decoration).
# Builds the Breeze Qt widget style plugin for consistent theming.
# The KWin decoration plugin is disabled (needs kwin at build time for the decoration API).
# Cursor and icon themes are not built here — those are separate upstream packages.
# Blockers: the window decoration subdirectory needs KDecoration3::Decoration which requires
# kwin headers at configure time; disabled via CMake option. The widget style plugin should
# build independently with just qtbase + KF6 deps.
[source]
tar = "https://invent.kde.org/plasma/breeze/-/archive/v6.3.4/breeze-v6.3.4.tar.gz"
[build]
template = "custom"
dependencies = [
"qtbase",
"qtdeclarative",
"kf6-extra-cmake-modules",
"kf6-kcoreaddons",
"kf6-kconfig",
"kf6-ki18n",
"kf6-kcolorscheme",
"kf6-kiconthemes",
"kf6-kwidgetsaddons",
"kf6-kwindowsystem",
"kdecoration",
]
script = """
DYNAMIC_INIT
HOST_BUILD="${COOKBOOK_ROOT}/build/qt-host-build"
for qtdir in plugins mkspecs metatypes modules; do
if [ -d "${COOKBOOK_SYSROOT}/usr/${qtdir}" ] && [ ! -e "${COOKBOOK_SYSROOT}/${qtdir}" ]; then
ln -s "usr/${qtdir}" "${COOKBOOK_SYSROOT}/${qtdir}"
fi
done
find "${COOKBOOK_SOURCE}" -name CMakeLists.txt -exec sed -i 's/^ecm_install_po_files_as_qm/#ecm_install_po_files_as_qm/' {} \\;
find "${COOKBOOK_SOURCE}" -name CMakeLists.txt -exec sed -i 's/^ki18n_install(po)/#ki18n_install(po)/' {} \\;
sed -i '/include(ECMQmlModule)/s/^/#/' "${COOKBOOK_SOURCE}/CMakeLists.txt" 2>/dev/null || true
rm -f CMakeCache.txt
rm -rf CMakeFiles
cmake "${COOKBOOK_SOURCE}" \
-DCMAKE_TOOLCHAIN_FILE="${COOKBOOK_ROOT}/local/recipes/qt/redox-toolchain.cmake" \
-DQT_HOST_PATH="${HOST_BUILD}" \
-DCMAKE_INSTALL_PREFIX=/usr \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH="${COOKBOOK_SYSROOT}" \
-DBUILD_TESTING=OFF \
-DBUILD_QCH=OFF \
-DBUILD_WITH_QML=OFF \
-DUSE_DBUS=OFF \
-DWITH_DECORATIONS=OFF \
-Wno-dev
cmake --build . -j"${COOKBOOK_MAKE_JOBS}"
cmake --install . --prefix "${COOKBOOK_STAGE}/usr"
find "${COOKBOOK_STAGE}" -name '*.so*' -exec patchelf --remove-rpath {} \\; 2>/dev/null || true
"""
@@ -0,0 +1,56 @@
#TODO: kde-cli-tools — KDE command-line utilities (kioclient, kreadconfig, kwriteconfig, kde-open, etc.).
# Some tools depend on KIO for file/URL handling; kf6-kio is available.
# Tools requiring X11 or DBus activation are disabled where possible.
# kdesu is not built (needs sudo or kdesu backend which doesn't exist on Redox).
[source]
tar = "https://invent.kde.org/plasma/kde-cli-tools/-/archive/v6.3.4/kde-cli-tools-v6.3.4.tar.gz"
[build]
template = "custom"
dependencies = [
"qtbase",
"kf6-extra-cmake-modules",
"kf6-kcoreaddons",
"kf6-ki18n",
"kf6-kconfig",
"kf6-kservice",
"kf6-kwindowsystem",
"kf6-kdbusaddons",
"kf6-kio",
]
script = """
DYNAMIC_INIT
HOST_BUILD="${COOKBOOK_ROOT}/build/qt-host-build"
for qtdir in plugins mkspecs metatypes modules; do
if [ -d "${COOKBOOK_SYSROOT}/usr/${qtdir}" ] && [ ! -e "${COOKBOOK_SYSROOT}/${qtdir}" ]; then
ln -s "usr/${qtdir}" "${COOKBOOK_SYSROOT}/${qtdir}"
fi
done
find "${COOKBOOK_SOURCE}" -name CMakeLists.txt -exec sed -i 's/^ecm_install_po_files_as_qm/#ecm_install_po_files_as_qm/' {} \\;
find "${COOKBOOK_SOURCE}" -name CMakeLists.txt -exec sed -i 's/^ki18n_install(po)/#ki18n_install(po)/' {} \\;
# Disable kdesu — no sudo/kdesu backend on Redox
sed -i 's/^add_subdirectory(kdesu/#add_subdirectory(kdesu/' "${COOKBOOK_SOURCE}/CMakeLists.txt" 2>/dev/null || true
rm -f CMakeCache.txt
rm -rf CMakeFiles
cmake "${COOKBOOK_SOURCE}" \
-DCMAKE_TOOLCHAIN_FILE="${COOKBOOK_ROOT}/local/recipes/qt/redox-toolchain.cmake" \
-DQT_HOST_PATH="${HOST_BUILD}" \
-DCMAKE_INSTALL_PREFIX=/usr \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH="${COOKBOOK_SYSROOT}" \
-DBUILD_TESTING=OFF \
-DBUILD_QCH=OFF \
-DUSE_DBUS=OFF \
-Wno-dev
cmake --build . -j"${COOKBOOK_MAKE_JOBS}"
cmake --install . --prefix "${COOKBOOK_STAGE}/usr"
find "${COOKBOOK_STAGE}" -name '*.so*' -exec patchelf --remove-rpath {} \\; 2>/dev/null || true
"""