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
|
||||||
|
"""
|
||||||
@@ -36,6 +36,7 @@ patches = [
|
|||||||
"../../../local/patches/relibc/P3-waitid-header.patch",
|
"../../../local/patches/relibc/P3-waitid-header.patch",
|
||||||
"../../../local/patches/relibc/P3-fenv.patch",
|
"../../../local/patches/relibc/P3-fenv.patch",
|
||||||
"../../../local/patches/relibc/P3-sched.patch",
|
"../../../local/patches/relibc/P3-sched.patch",
|
||||||
|
"../../../local/patches/relibc/P3-aio.patch",
|
||||||
]
|
]
|
||||||
|
|
||||||
[build]
|
[build]
|
||||||
|
|||||||
Reference in New Issue
Block a user