Update Red Bear driver substrate

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-04-15 12:57:45 +01:00
parent 9dd372ad14
commit 8ad1242ab6
5 changed files with 395 additions and 20 deletions
+314
View File
@@ -61,6 +61,23 @@ index 94a1eb17..3fd91156 100644
+ Ok(values) => { + Ok(values) => {
+ log::debug!("{}._PS0 => {:?}", device_path, values); + log::debug!("{}._PS0 => {:?}", device_path, values);
+ } + }
@@
while let Some((descriptor, len)) = AnyDescriptor::parse(&data[i..]) {
descriptors.push(descriptor);
i += len;
}
+ let descriptor_count = descriptors.len();
+
+ if i < data.len() {
+ log::warn!(
+ "port {} slot {} config {} descriptor parsing stopped early at byte {} of {}",
+ port_id,
+ slot,
+ index,
+ i,
+ data.len()
+ );
+ }
+ Err(error) => { + Err(error) => {
+ log::warn!("Failed to power on {} with _PS0: {:?}", device_path, error); + log::warn!("Failed to power on {} with _PS0: {:?}", device_path, error);
+ } + }
@@ -1039,3 +1056,300 @@ index 670a5526..24ce3d68 100644
} }
UnitKind::Target {} => { UnitKind::Target {} => {
if config.log_debug { if config.log_debug {
diff --git a/drivers/storage/usbscsid/src/main.rs b/drivers/storage/usbscsid/src/main.rs
--- a/drivers/storage/usbscsid/src/main.rs
+++ b/drivers/storage/usbscsid/src/main.rs
@@
fn main() {
daemon::Daemon::new(daemon);
}
fn daemon(daemon: daemon::Daemon) -> ! {
- let mut args = env::args().skip(1);
+ if let Err(err) = run(daemon) {
+ eprintln!("usbscsid: startup failed: {err}");
+ std::process::exit(1);
+ }
+
+ std::process::exit(0);
+}
+
+fn run(daemon: daemon::Daemon) -> Result<(), String> {
+ let mut args = env::args().skip(1);
const USAGE: &'static str = "usbscsid <scheme> <port> <protocol>";
- let scheme = args.next().expect(USAGE);
- let port = args
- .next()
- .expect(USAGE)
- .parse::<PortId>()
- .expect("Expected port ID");
- let protocol = args
- .next()
- .expect(USAGE)
- .parse::<u8>()
- .expect("protocol has to be a number 0-255");
+ let scheme = args.next().ok_or_else(|| USAGE.to_string())?;
+ let port_arg = args.next().ok_or_else(|| USAGE.to_string())?;
+ let protocol_arg = args.next().ok_or_else(|| USAGE.to_string())?;
+ let port = port_arg
+ .parse::<PortId>()
+ .map_err(|err| format!("invalid port id `{port_arg}`: {err}"))?;
+ let protocol = protocol_arg
+ .parse::<u8>()
+ .map_err(|err| format!("invalid protocol `{protocol_arg}`: {err}"))?;
@@
- let handle =
- XhciClientHandle::new(scheme.to_owned(), port).expect("Failed to open XhciClientHandle");
+ let handle = XhciClientHandle::new(scheme.to_owned(), port)
+ .map_err(|err| format!("failed to open XHCI client handle: {err}"))?;
let desc = handle
.get_standard_descs()
- .expect("Failed to get standard descriptors");
+ .map_err(|err| format!("failed to get standard descriptors: {err}"))?;
@@
- .expect("Failed to find suitable configuration");
+ .ok_or_else(|| "failed to find suitable BOT configuration".to_string())?;
@@
- .expect("Failed to configure endpoints");
+ .map_err(|err| format!("failed to configure endpoints: {err}"))?;
- let mut protocol = protocol::setup(&handle, protocol, &desc, &conf_desc, &if_desc)
- .expect("Failed to setup protocol");
+ let mut protocol = protocol::setup(&handle, protocol, &desc, &conf_desc, &if_desc)
+ .map_err(|err| format!("failed to setup protocol: {err}"))?;
@@
- let mut scsi = Scsi::new(&mut *protocol).expect("usbscsid: failed to setup SCSI");
+ let mut scsi =
+ Scsi::new(&mut *protocol).map_err(|err| format!("failed to setup SCSI: {err}"))?;
println!("SCSI initialized");
- let mut buffer = [0u8; 512];
- scsi.read(&mut *protocol, 0, &mut buffer).unwrap();
- println!("DISK CONTENT: {}", base64::encode(&buffer[..]));
- let event_queue = event::EventQueue::new().unwrap();
+ let event_queue =
+ event::EventQueue::new().map_err(|err| format!("failed to create event queue: {err}"))?;
@@
- .unwrap();
+ .map_err(|err| format!("failed to subscribe scheme event handle: {err}"))?;
for event in event_queue {
- match event.unwrap().user_data {
- Event::Scheme => driver_block::FuturesExecutor
- .block_on(scheme.tick())
- .unwrap(),
+ let event = match event {
+ Ok(event) => event,
+ Err(err) => {
+ eprintln!("usbscsid: event queue error: {err}");
+ continue;
+ }
+ };
+
+ match event.user_data {
+ Event::Scheme => {
+ if let Err(err) = driver_block::FuturesExecutor.block_on(scheme.tick()) {
+ eprintln!("usbscsid: scheme tick failed: {err}");
+ }
+ }
}
}
- std::process::exit(0);
+ Ok(())
}
diff --git a/drivers/storage/usbscsid/src/protocol/mod.rs b/drivers/storage/usbscsid/src/protocol/mod.rs
--- a/drivers/storage/usbscsid/src/protocol/mod.rs
+++ b/drivers/storage/usbscsid/src/protocol/mod.rs
@@
#[error("attempted recovery failed")]
RecoveryFailed,
+ #[error("unsupported USB mass-storage protocol 0x{0:02X}")]
+ UnsupportedProtocol(u8),
+
#[error("protocol error")]
ProtocolError(&'static str),
}
@@
pub fn setup<'a>(
handle: &'a XhciClientHandle,
protocol: u8,
- dev_desc: &DevDesc,
+ _dev_desc: &DevDesc,
conf_desc: &ConfDesc,
if_desc: &IfDesc,
-) -> Option<Box<dyn Protocol + 'a>> {
+) -> Result<Box<dyn Protocol + 'a>, ProtocolError> {
match protocol {
- 0x50 => Some(Box::new(
- BulkOnlyTransport::init(handle, conf_desc, if_desc).unwrap(),
- )),
- _ => None,
+ 0x50 => BulkOnlyTransport::init(handle, conf_desc, if_desc)
+ .map(|transport| Box::new(transport) as Box<dyn Protocol + 'a>),
+ _ => Err(ProtocolError::UnsupportedProtocol(protocol)),
}
}
diff --git a/drivers/storage/usbscsid/src/scsi/mod.rs b/drivers/storage/usbscsid/src/scsi/mod.rs
--- a/drivers/storage/usbscsid/src/scsi/mod.rs
+++ b/drivers/storage/usbscsid/src/scsi/mod.rs
@@
#[error("overflow")]
Overflow(&'static str),
+
+ #[error("SCSI command failed: {0}")]
+ CommandFailed(&'static str),
}
@@
if let SendCommandStatus {
kind: SendCommandStatusKind::Failed,
..
} = protocol.send_command(
&self.command_buffer[..10],
DeviceReqData::In(&mut self.data_buffer[..initial_alloc_len as usize]),
)? {
self.get_ff_sense(protocol, 252)?;
- panic!("{:?}", self.res_ff_sense_data());
+ eprintln!(
+ "usbscsid: MODE SENSE(10) failed; sense data: {:?}",
+ self.res_ff_sense_data()
+ );
+ return Err(ScsiError::CommandFailed("MODE SENSE(10)"));
}
diff --git a/drivers/usb/xhcid/src/xhci/event.rs b/drivers/usb/xhcid/src/xhci/event.rs
--- a/drivers/usb/xhcid/src/xhci/event.rs
+++ b/drivers/usb/xhcid/src/xhci/event.rs
@@
#[test]
fn grow_preserves_existing_ring_state() {
- let mut ring = EventRing::new::<super::super::CONTEXT_64>(true).unwrap();
+ let mut ring = EventRing::new::<{ super::super::CONTEXT_64 }>(true).unwrap();
@@
- ring.grow::<super::super::CONTEXT_64>(true, old_len + 64)
+ ring.grow::<{ super::super::CONTEXT_64 }>(true, old_len + 64)
.unwrap();
diff --git a/drivers/usb/xhcid/src/xhci/mod.rs b/drivers/usb/xhcid/src/xhci/mod.rs
--- a/drivers/usb/xhcid/src/xhci/mod.rs
+++ b/drivers/usb/xhcid/src/xhci/mod.rs
@@
let dev_desc = self.get_desc(port_id, slot).await?;
debug!("Got the full device descriptor!");
+ info!(
+ "port {} slot {} device {:04X}:{:04X} class {}.{} proto {} has {} configuration(s)",
+ port_id,
+ slot,
+ dev_desc.vendor,
+ dev_desc.product,
+ dev_desc.class,
+ dev_desc.sub_class,
+ dev_desc.protocol,
+ dev_desc.config_descs.len()
+ );
self.port_states.get_mut(&port_id).unwrap().dev_desc = Some(dev_desc);
@@
debug!("Updated the default control pipe");
+ info!("port {} slot {} starting subdriver matching", port_id, slot);
match self.spawn_drivers(port_id) {
Ok(()) => (),
@@
trace!("Got config and device descriptors on port {}", port);
let drivers_usercfg: &DriversConfig = &DRIVERS_CONFIG;
+
+ if config_desc.interface_descs.is_empty() {
+ warn!(
+ "No interface descriptors found for port {} in configuration {}; no subdrivers can be matched",
+ port,
+ config_desc.configuration_value
+ );
+ }
for ifdesc in config_desc.interface_descs.iter() {
diff --git a/drivers/usb/xhcid/src/xhci/scheme.rs b/drivers/usb/xhcid/src/xhci/scheme.rs
--- a/drivers/usb/xhcid/src/xhci/scheme.rs
+++ b/drivers/usb/xhcid/src/xhci/scheme.rs
@@
pub enum AnyDescriptor {
// These are the ones that I have found, but there are more.
Device(usb::DeviceDescriptor),
Config(usb::ConfigDescriptor),
Interface(usb::InterfaceDescriptor),
Endpoint(usb::EndpointDescriptor),
Hid(usb::HidDescriptor),
SuperSpeedCompanion(usb::SuperSpeedCompanionDescriptor),
SuperSpeedPlusCompanion(usb::SuperSpeedPlusIsochCmpDescriptor),
+ Unknown { kind: u8 },
}
impl AnyDescriptor {
@@
- _ => {
- //panic!("Descriptor unknown {}: bytes {:#0x?}", kind, bytes);
- return None;
- }
+ _ => Self::Unknown { kind },
},
len.into(),
))
@@
- let mut interface_descs = SmallVec::new();
+ let mut interface_descs = SmallVec::<[IfDesc; 1]>::new();
let mut iter = descriptors.into_iter().peekable();
@@
Some(AnyDescriptor::Hid(h)) if idesc.class == 3 => {
hid_descs.push(h.into());
continue;
}
+ Some(AnyDescriptor::Unknown { kind }) => {
+ log::warn!(
+ "port {} slot {} iface {} skipping unknown descriptor kind {} while collecting endpoints",
+ port_id,
+ slot,
+ idesc.number,
+ kind
+ );
+ continue;
+ }
Some(unexpected) => {
log::warn!("expected endpoint, got {:X?}", unexpected);
break;
@@
} else {
log::warn!("expected interface, got {:?}", item);
// TODO
//break;
}
}
+
+ log::info!(
+ "port {} slot {} config {} parsed {} descriptor entries into {} interface(s)",
+ port_id,
+ slot,
+ index,
+ descriptor_count,
+ interface_descs.len()
+ );
+
+ for if_desc in interface_descs.iter() {
+ let number: u8 = if_desc.number;
+ let alternate_setting: u8 = if_desc.alternate_setting;
+ let class: u8 = if_desc.class;
+ let sub_class: u8 = if_desc.sub_class;
+ let protocol: u8 = if_desc.protocol;
+ let endpoint_count: usize = if_desc.endpoints.len();
+ log::info!(
+ "port {} slot {} config {} iface {} alt {} class {}.{} proto {} endpoints {}",
+ port_id,
+ slot,
+ index,
+ number,
+ alternate_setting,
+ class,
+ sub_class,
+ protocol,
+ endpoint_count
+ );
+ }
@@ -1,13 +1,34 @@
use core::ptr::NonNull; use core::ptr::NonNull;
use std::sync::atomic::{AtomicI32, Ordering}; use std::sync::atomic::{AtomicI32, Ordering};
use redox_syscall::flag::{MAP_SHARED, O_CLOEXEC, O_RDWR, PROT_READ, PROT_WRITE}; use redox_syscall::flag::{MAP_PRIVATE, O_CLOEXEC, PROT_READ, PROT_WRITE};
use redox_syscall::PAGE_SIZE; use redox_syscall::PAGE_SIZE;
use syscall as redox_syscall; use syscall as redox_syscall;
use crate::{DriverError, Result}; use crate::{DriverError, Result};
/// SAFETY: Cached FD for `/scheme/memory/physical`. -1 means uninitialized. #[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum DmaMemoryType {
Writeback,
Uncacheable,
}
impl DmaMemoryType {
const fn suffix(self) -> &'static str {
match self {
Self::Writeback => "wb",
Self::Uncacheable => "uc",
}
}
}
const DMA_MEMORY_TYPE: DmaMemoryType = if cfg!(any(target_arch = "x86", target_arch = "x86_64")) {
DmaMemoryType::Writeback
} else {
DmaMemoryType::Uncacheable
};
/// SAFETY: Cached FD for `/scheme/memory/scheme-root`. -1 means uninitialized.
/// This FD is process-lifetime cached for performance. If scheme:memory /// This FD is process-lifetime cached for performance. If scheme:memory
/// restarts (which should never happen — it's a kernel scheme), all /// restarts (which should never happen — it's a kernel scheme), all
/// in-flight DMA operations are already undefined behavior. /// in-flight DMA operations are already undefined behavior.
@@ -19,7 +40,7 @@ fn get_dma_memory_fd() -> Result<i32> {
return Ok(current); return Ok(current);
} }
let fd = libredox::call::open("/scheme/memory/physical", (O_CLOEXEC | O_RDWR) as i32, 0) let fd = libredox::call::open("/scheme/memory/scheme-root", O_CLOEXEC as i32, 0)
.map_err(|e| DriverError::Io(std::io::Error::from_raw_os_error(e.errno())))?; .map_err(|e| DriverError::Io(std::io::Error::from_raw_os_error(e.errno())))?;
let raw = fd as i32; let raw = fd as i32;
@@ -68,7 +89,11 @@ fn virt_to_phys_cached(virt: usize) -> Result<usize> {
enum DmaStorage { enum DmaStorage {
/// Allocated via scheme:memory — freed via munmap /// Allocated via scheme:memory — freed via munmap
SchemeMapped { ptr: NonNull<u8>, size: usize }, SchemeMapped {
ptr: NonNull<u8>,
size: usize,
region_fd: i32,
},
/// Allocated via heap — freed via dealloc /// Allocated via heap — freed via dealloc
Heap { Heap {
ptr: NonNull<u8>, ptr: NonNull<u8>,
@@ -126,10 +151,9 @@ impl DmaBuffer {
/// Allocate physically contiguous memory via scheme:memory/physical. /// Allocate physically contiguous memory via scheme:memory/physical.
fn allocate_via_scheme(mem_fd: i32, size: usize, _align: usize) -> Result<Self> { fn allocate_via_scheme(mem_fd: i32, size: usize, _align: usize) -> Result<Self> {
// Open a physical memory region of the requested size // Open a physical memory region of the requested size
let path = format!("zeroed@{}", size); let path = format!("zeroed@{}?phys_contiguous", DMA_MEMORY_TYPE.suffix());
let region_fd = let region_fd = libredox::call::openat(mem_fd as usize, &path, O_CLOEXEC as i32, 0)
libredox::call::openat(mem_fd as usize, &path, (O_CLOEXEC | O_RDWR) as i32, 0) .map_err(|e| DriverError::Io(std::io::Error::from_raw_os_error(e.errno())))?;
.map_err(|e| DriverError::Io(std::io::Error::from_raw_os_error(e.errno())))?;
// Map it into our address space // Map it into our address space
let ptr = unsafe { let ptr = unsafe {
@@ -137,7 +161,7 @@ impl DmaBuffer {
fd: region_fd as usize, fd: region_fd as usize,
offset: 0, offset: 0,
length: size, length: size,
flags: MAP_SHARED.bits() as u32, flags: MAP_PRIVATE.bits() as u32,
prot: (PROT_READ | PROT_WRITE).bits() as u32, prot: (PROT_READ | PROT_WRITE).bits() as u32,
addr: core::ptr::null_mut(), addr: core::ptr::null_mut(),
}) })
@@ -154,6 +178,17 @@ impl DmaBuffer {
let _ = libredox::call::close(region_fd as usize); let _ = libredox::call::close(region_fd as usize);
let phys_addr = virt_to_phys_cached(ptr as usize)?; let phys_addr = virt_to_phys_cached(ptr as usize)?;
for page in 1..size.div_ceil(PAGE_SIZE) {
let translated = virt_to_phys_cached(ptr as usize + page * PAGE_SIZE)?;
if translated != phys_addr + page * PAGE_SIZE {
return Err(DriverError::Other(format!(
"DMA mapping is not physically contiguous across page {}: expected {:#x}, got {:#x}",
page,
phys_addr + page * PAGE_SIZE,
translated
)));
}
}
let ptr = NonNull::new(ptr as *mut u8) let ptr = NonNull::new(ptr as *mut u8)
.ok_or_else(|| DriverError::Other("DMA mmap returned null".into()))?; .ok_or_else(|| DriverError::Other("DMA mmap returned null".into()))?;
@@ -165,7 +200,11 @@ impl DmaBuffer {
); );
Ok(Self { Ok(Self {
storage: DmaStorage::SchemeMapped { ptr, size }, storage: DmaStorage::SchemeMapped {
ptr,
size,
region_fd: region_fd as i32,
},
phys_addr, phys_addr,
size, size,
}) })
@@ -205,8 +244,13 @@ impl DmaBuffer {
impl Drop for DmaBuffer { impl Drop for DmaBuffer {
fn drop(&mut self) { fn drop(&mut self) {
match &self.storage { match &self.storage {
DmaStorage::SchemeMapped { ptr, size } => { DmaStorage::SchemeMapped {
ptr,
size,
region_fd,
} => {
let _ = unsafe { libredox::call::munmap(ptr.as_ptr() as *mut (), *size) }; let _ = unsafe { libredox::call::munmap(ptr.as_ptr() as *mut (), *size) };
let _ = libredox::call::close(*region_fd as usize);
} }
DmaStorage::Heap { ptr, layout } => { DmaStorage::Heap { ptr, layout } => {
unsafe { std::alloc::dealloc(ptr.as_ptr(), *layout) }; unsafe { std::alloc::dealloc(ptr.as_ptr(), *layout) };
@@ -149,6 +149,12 @@ impl MsixTable {
let (irq, fd) = allocate_irq_vector(cpu_id)?; let (irq, fd) = allocate_irq_vector(cpu_id)?;
self.program_x86_message(index, cpu_id, irq)?; self.program_x86_message(index, cpu_id, irq)?;
self.unmask_vector(index); self.unmask_vector(index);
log::info!(
"redox-driver-sys: allocated MSI-X vector {} -> irq {} on cpu {}",
index,
irq,
cpu_id
);
Ok(MsixVector { fd, index, irq }) Ok(MsixVector { fd, index, irq })
} }
@@ -288,7 +294,15 @@ fn allocate_irq_vector(cpu_id: u8) -> Result<(u32, File)> {
.create_new(true) .create_new(true)
.open(&path) .open(&path)
{ {
Ok(fd) => return Ok((irq, fd)), Ok(fd) => {
log::debug!(
"redox-driver-sys: reserved irq vector {} from {} for cpu {}",
irq,
path,
cpu_id
);
return Ok((irq, fd));
}
Err(err) if err.kind() == ErrorKind::AlreadyExists => continue, Err(err) if err.kind() == ErrorKind::AlreadyExists => continue,
Err(err) if err.kind() == ErrorKind::NotFound => continue, Err(err) if err.kind() == ErrorKind::NotFound => continue,
Err(err) => { Err(err) => {
+10 -7
View File
@@ -64,16 +64,19 @@ Driver access pattern:
- Register as scheme: daemon name becomes `/scheme/<name>` - Register as scheme: daemon name becomes `/scheme/<name>`
- PCI devices discovered via `pcid` daemon → spawns drivers - PCI devices discovered via `pcid` daemon → spawns drivers
## POSIX GAPS IN RELIBC (blocking Wayland) ## HISTORICAL POSIX GAPS IN RELIBC (Wayland-facing)
| Missing API | Location to implement | | Missing API | Location to implement |
|-------------|----------------------| |-------------|----------------------|
| signalfd/signalfd4 | `relibc/source/src/header/signal/` | | signalfd/signalfd4 | `relibc/source/src/header/signal/` — now source-visible in the current Red Bear tree |
| timerfd_create/settime/gettime | `relibc/source/src/header/sys_timerfd/` (NEW) | | timerfd_create/settime/gettime | `relibc/source/src/header/sys_timerfd/` — now source-visible in the current Red Bear tree |
| eventfd | `relibc/source/src/header/sys_eventfd/` (NEW) | | eventfd | `relibc/source/src/header/sys_eventfd/` — now source-visible in the current Red Bear tree |
| F_DUPFD_CLOEXEC | `relibc/source/src/header/fcntl/` | | F_DUPFD_CLOEXEC | `relibc/source/src/header/fcntl/` — now source-visible in the current Red Bear tree |
| MSG_CMSG_CLOEXEC, MSG_NOSIGNAL | `relibc/source/src/header/sys_socket/` | | MSG_CMSG_CLOEXEC, MSG_NOSIGNAL | `relibc/source/src/header/sys_socket/` — now source-visible in the current Red Bear tree |
| open_memstream | `relibc/source/src/header/stdio/` | | open_memstream | `relibc/source/src/header/stdio/` — now source-visible in the current Red Bear tree |
The current relibc work is therefore no longer just “add the missing Wayland APIs.” The higher-value
remaining work is completeness depth, downstream cleanup, and runtime validation.
## ANTI-PATTERNS ## ANTI-PATTERNS
+1 -1
View File
@@ -46,7 +46,7 @@ BINS=(
# depending on the target architecture # depending on the target architecture
case "${TARGET}" in case "${TARGET}" in
i586-unknown-redox | i686-unknown-redox | x86_64-unknown-redox) i586-unknown-redox | i686-unknown-redox | x86_64-unknown-redox)
BINS+=(ac97d bgad sb16d vboxd) BINS+=(ac97d sb16d vboxd)
;; ;;
*) *)
;; ;;