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:
@@ -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))
|
||||
@@ -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 {
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
"""
|
||||
Reference in New Issue
Block a user