diff --git a/local/patches/base/redox.patch b/local/patches/base/redox.patch index f9c30896..943be899 100644 --- a/local/patches/base/redox.patch +++ b/local/patches/base/redox.patch @@ -61,6 +61,23 @@ index 94a1eb17..3fd91156 100644 + Ok(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) => { + log::warn!("Failed to power on {} with _PS0: {:?}", device_path, error); + } @@ -1039,3 +1056,300 @@ index 670a5526..24ce3d68 100644 } UnitKind::Target {} => { 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 "; + +- let scheme = args.next().expect(USAGE); +- let port = args +- .next() +- .expect(USAGE) +- .parse::() +- .expect("Expected port ID"); +- let protocol = args +- .next() +- .expect(USAGE) +- .parse::() +- .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::() ++ .map_err(|err| format!("invalid port id `{port_arg}`: {err}"))?; ++ let protocol = protocol_arg ++ .parse::() ++ .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> { ++) -> Result, 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), ++ _ => 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::(true).unwrap(); ++ let mut ring = EventRing::new::<{ super::super::CONTEXT_64 }>(true).unwrap(); +@@ +- ring.grow::(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 ++ ); ++ } diff --git a/local/recipes/drivers/redox-driver-sys/source/src/dma.rs b/local/recipes/drivers/redox-driver-sys/source/src/dma.rs index aa863789..ea26e538 100644 --- a/local/recipes/drivers/redox-driver-sys/source/src/dma.rs +++ b/local/recipes/drivers/redox-driver-sys/source/src/dma.rs @@ -1,13 +1,34 @@ use core::ptr::NonNull; 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 syscall as redox_syscall; 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 /// restarts (which should never happen — it's a kernel scheme), all /// in-flight DMA operations are already undefined behavior. @@ -19,7 +40,7 @@ fn get_dma_memory_fd() -> Result { 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())))?; let raw = fd as i32; @@ -68,7 +89,11 @@ fn virt_to_phys_cached(virt: usize) -> Result { enum DmaStorage { /// Allocated via scheme:memory — freed via munmap - SchemeMapped { ptr: NonNull, size: usize }, + SchemeMapped { + ptr: NonNull, + size: usize, + region_fd: i32, + }, /// Allocated via heap — freed via dealloc Heap { ptr: NonNull, @@ -126,10 +151,9 @@ impl DmaBuffer { /// Allocate physically contiguous memory via scheme:memory/physical. fn allocate_via_scheme(mem_fd: i32, size: usize, _align: usize) -> Result { // Open a physical memory region of the requested size - let path = format!("zeroed@{}", size); - let region_fd = - 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())))?; + let path = format!("zeroed@{}?phys_contiguous", DMA_MEMORY_TYPE.suffix()); + let region_fd = libredox::call::openat(mem_fd as usize, &path, O_CLOEXEC as i32, 0) + .map_err(|e| DriverError::Io(std::io::Error::from_raw_os_error(e.errno())))?; // Map it into our address space let ptr = unsafe { @@ -137,7 +161,7 @@ impl DmaBuffer { fd: region_fd as usize, offset: 0, length: size, - flags: MAP_SHARED.bits() as u32, + flags: MAP_PRIVATE.bits() as u32, prot: (PROT_READ | PROT_WRITE).bits() as u32, addr: core::ptr::null_mut(), }) @@ -154,6 +178,17 @@ impl DmaBuffer { let _ = libredox::call::close(region_fd 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) .ok_or_else(|| DriverError::Other("DMA mmap returned null".into()))?; @@ -165,7 +200,11 @@ impl DmaBuffer { ); Ok(Self { - storage: DmaStorage::SchemeMapped { ptr, size }, + storage: DmaStorage::SchemeMapped { + ptr, + size, + region_fd: region_fd as i32, + }, phys_addr, size, }) @@ -205,8 +244,13 @@ impl DmaBuffer { impl Drop for DmaBuffer { fn drop(&mut self) { 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 _ = libredox::call::close(*region_fd as usize); } DmaStorage::Heap { ptr, layout } => { unsafe { std::alloc::dealloc(ptr.as_ptr(), *layout) }; diff --git a/local/recipes/drivers/redox-driver-sys/source/src/irq.rs b/local/recipes/drivers/redox-driver-sys/source/src/irq.rs index aee1d611..4f9295df 100644 --- a/local/recipes/drivers/redox-driver-sys/source/src/irq.rs +++ b/local/recipes/drivers/redox-driver-sys/source/src/irq.rs @@ -149,6 +149,12 @@ impl MsixTable { let (irq, fd) = allocate_irq_vector(cpu_id)?; self.program_x86_message(index, cpu_id, irq)?; 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 }) } @@ -288,7 +294,15 @@ fn allocate_irq_vector(cpu_id: u8) -> Result<(u32, File)> { .create_new(true) .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::NotFound => continue, Err(err) => { diff --git a/recipes/core/AGENTS.md b/recipes/core/AGENTS.md index bb2964e3..c1141d51 100644 --- a/recipes/core/AGENTS.md +++ b/recipes/core/AGENTS.md @@ -64,16 +64,19 @@ Driver access pattern: - Register as scheme: daemon name becomes `/scheme/` - 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 | |-------------|----------------------| -| signalfd/signalfd4 | `relibc/source/src/header/signal/` | -| timerfd_create/settime/gettime | `relibc/source/src/header/sys_timerfd/` (NEW) | -| eventfd | `relibc/source/src/header/sys_eventfd/` (NEW) | -| F_DUPFD_CLOEXEC | `relibc/source/src/header/fcntl/` | -| MSG_CMSG_CLOEXEC, MSG_NOSIGNAL | `relibc/source/src/header/sys_socket/` | -| open_memstream | `relibc/source/src/header/stdio/` | +| 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/` — now source-visible in the current Red Bear tree | +| eventfd | `relibc/source/src/header/sys_eventfd/` — now source-visible in the current Red Bear tree | +| 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/` — now source-visible in the current Red Bear tree | +| 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 diff --git a/recipes/core/base/recipe.toml b/recipes/core/base/recipe.toml index 2b895506..174318f7 100644 --- a/recipes/core/base/recipe.toml +++ b/recipes/core/base/recipe.toml @@ -46,7 +46,7 @@ BINS=( # depending on the target architecture case "${TARGET}" in i586-unknown-redox | i686-unknown-redox | x86_64-unknown-redox) - BINS+=(ac97d bgad sb16d vboxd) + BINS+=(ac97d sb16d vboxd) ;; *) ;;