From a10c615b95c6b0c360eb4c70749cbf98071bc550 Mon Sep 17 00:00:00 2001 From: Drew Galbraith Date: Fri, 5 Dec 2025 22:01:13 -0800 Subject: [PATCH] Rust XHCI Implementation. --- ' | 64 +++++ rust/Cargo.lock | 37 ++- rust/lib/pci/src/device.rs | 10 +- rust/sys/voyageurs/Cargo.toml | 6 + rust/sys/voyageurs/src/main.rs | 20 +- .../src/xhci/device_context_base_array.rs | 22 ++ rust/sys/voyageurs/src/xhci/driver.rs | 227 ++++++++++++++++++ rust/sys/voyageurs/src/xhci/event_ring.rs | 25 ++ rust/sys/voyageurs/src/xhci/mod.rs | 8 +- rust/sys/voyageurs/src/xhci/trb_ring.rs | 49 ++++ 10 files changed, 438 insertions(+), 30 deletions(-) create mode 100644 ' create mode 100644 rust/sys/voyageurs/src/xhci/device_context_base_array.rs create mode 100644 rust/sys/voyageurs/src/xhci/driver.rs create mode 100644 rust/sys/voyageurs/src/xhci/event_ring.rs create mode 100644 rust/sys/voyageurs/src/xhci/trb_ring.rs diff --git a/' b/' new file mode 100644 index 0000000..8a445e9 --- /dev/null +++ b/' @@ -0,0 +1,64 @@ +use core::{ + ops::{Deref, Index, IndexMut}, + slice::SliceIndex, +}; + +use alloc::{boxed::Box, vec}; +use bitfield_struct::bitfield; +use mammoth::physical_box::PhysicalBox; + +#[bitfield(u128)] +pub struct TransferRequestBlock { + parameter: u64, + status: u32, + cycle: bool, + evaluate_next: bool, + flag_2: bool, + flag_3: bool, + flag_4: bool, + flag_5: bool, + flag_6: bool, + flag_7: bool, + flag_8: bool, + flag_9: bool, + #[bits(6)] + trb_type: u8, + control: u16, +} + +#[repr(transparent)] +pub struct TrbRing(PhysicalBox<[TransferRequestBlock]>); + +impl TrbRing { + pub fn new(size: usize) -> Self { + Self(PhysicalBox::default_with_count( + TransferRequestBlock::default(), + size, + )) + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn physical_address(&self) -> usize { + self.0.physical_address() + } +} + +impl Index for TrbRing { + type Output = TransferRequestBlock; + + fn index(&self, index: I) -> &Self::Output { + self.0[index] + } +} + +impl IndexMut for TrbRing +where + I: SliceIndex<[TransferRequestBlock]>, +{ + fn index_mut(&mut self, index: I) -> &mut Self::Output { + &mut self.0[index] + } +} diff --git a/rust/Cargo.lock b/rust/Cargo.lock index aec4469..ced63d5 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -2,12 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "autocfg" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" - [[package]] name = "bitfield-struct" version = "0.8.0" @@ -80,11 +74,10 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] @@ -105,9 +98,9 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.20" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", "syn", @@ -115,18 +108,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] @@ -148,9 +141,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.72" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", @@ -178,15 +171,15 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "victoriafalls" @@ -206,6 +199,8 @@ version = "0.1.0" dependencies = [ "bitfield-struct 0.12.1", "mammoth", + "pci", + "yellowstone-yunq", ] [[package]] diff --git a/rust/lib/pci/src/device.rs b/rust/lib/pci/src/device.rs index ab1b25d..948cd04 100644 --- a/rust/lib/pci/src/device.rs +++ b/rust/lib/pci/src/device.rs @@ -72,15 +72,15 @@ impl PciDevice { control.capable_address_64(), "We don't handle the non-64bit case for MSI yet." ); - assert!( - control.multi_message_capable() == 0, - "We don't yet handle multi-message capable devices." - ); + + if control.multi_message_capable() != 0 { + mammoth::debug!("WARN: We don't yet handle multi-message capable devices."); + } // FIXME: These probably need to be volatile writes. let header: &mut PciDeviceHeader = self.memory_region.as_mut(); header.command = header.command.with_interrupt_disable(true); - msi_cap.msi_control = control.with_msi_enable(true); + msi_cap.msi_control = control.with_msi_enable(true).with_multi_message_enable(0); // For setting addr and data field, see intel ref // Vol 3. Section 11.11 diff --git a/rust/sys/voyageurs/Cargo.toml b/rust/sys/voyageurs/Cargo.toml index 084d083..ddd2996 100644 --- a/rust/sys/voyageurs/Cargo.toml +++ b/rust/sys/voyageurs/Cargo.toml @@ -6,3 +6,9 @@ edition = "2024" [dependencies] bitfield-struct = "0.12" mammoth = { path = "../../lib/mammoth/" } +pci = { path = "../../lib/pci" } +yellowstone-yunq = { version = "0.1.0", path = "../../lib/yellowstone" } + +[features] +default = ["debug"] +debug = [] diff --git a/rust/sys/voyageurs/src/main.rs b/rust/sys/voyageurs/src/main.rs index e9e5dd6..adf75de 100644 --- a/rust/sys/voyageurs/src/main.rs +++ b/rust/sys/voyageurs/src/main.rs @@ -5,12 +5,28 @@ extern crate alloc; mod xhci; -use mammoth::{debug, define_entry, zion::z_err_t}; +use mammoth::{cap::Capability, debug, define_entry, zion::z_err_t}; +use pci::PciDevice; +use xhci::driver::XHCIDriver; define_entry!(); #[unsafe(no_mangle)] extern "C" fn main() -> z_err_t { - debug!("In Voyageurs"); + #[cfg(feature = "debug")] + debug!("Voyageurs Starting."); + + let yellowstone = yellowstone_yunq::from_init_endpoint(); + + let xhci_info = yellowstone + .get_xhci_info() + .expect("Failed to get XHCI info from yellowstone."); + + let pci_device = PciDevice::from_cap(Capability::take(xhci_info.xhci_region)).unwrap(); + + let xhci_driver = XHCIDriver::from_pci_device(pci_device); + + loop {} + 0 } diff --git a/rust/sys/voyageurs/src/xhci/device_context_base_array.rs b/rust/sys/voyageurs/src/xhci/device_context_base_array.rs new file mode 100644 index 0000000..efddd4e --- /dev/null +++ b/rust/sys/voyageurs/src/xhci/device_context_base_array.rs @@ -0,0 +1,22 @@ +use mammoth::mem::MemoryRegion; + +pub struct DeviceContextBaseArray { + #[allow(dead_code)] + region: MemoryRegion, + physical_addr: usize, +} + +impl DeviceContextBaseArray { + pub fn new() -> Self { + let (region, physical_addr) = MemoryRegion::contiguous_physical(0x1000).unwrap(); + region.zero_region(); + Self { + region, + physical_addr: physical_addr as usize, + } + } + + pub fn physical_addr(&self) -> usize { + self.physical_addr + } +} diff --git a/rust/sys/voyageurs/src/xhci/driver.rs b/rust/sys/voyageurs/src/xhci/driver.rs new file mode 100644 index 0000000..0482e6d --- /dev/null +++ b/rust/sys/voyageurs/src/xhci/driver.rs @@ -0,0 +1,227 @@ +use core::slice; + +use mammoth::{map_unaligned_volatile, mem::MemoryRegion}; +use mammoth::{read_unaligned_volatile, write_unaligned_volatile}; + +use super::registers::{self}; +use crate::xhci::device_context_base_array::DeviceContextBaseArray; +use crate::xhci::event_ring::EventRing; +use crate::xhci::registers::HCSParams1; +use crate::xhci::trb_ring::{InputTrbRing, OutputTrbRing}; + +pub struct XHCIDriver { + #[allow(dead_code)] + pci_device: pci::PciDevice, + registers_region: MemoryRegion, + command_ring: OutputTrbRing, + event_ring: EventRing, + device_context_base_array: DeviceContextBaseArray, +} + +impl XHCIDriver { + pub fn from_pci_device(mut pci_device: pci::PciDevice) -> Self { + // BAR0 Size Allocation + // If virtualization is supported, the Capability and Operational Register sets, and + // the Extended Capabilities may reside in a single page of virtual memory, + // however the RTSOFF and DBOFF Registers shall position the Runtime and + // Doorbell Registers to reside on their own respective virtual memory pages. The + // BAR0 size shall provide space that is sufficient to cover the offset between the + // respective register spaces (Capability, Operational, Runtime, etc.) and the + // register spaces themselves (e.g. a minimum of 3 virtual memory pages). + // If virtualization is not supported, all xHCI register spaces may reside on a single + // page pointed to by the BAR0. + let three_pages = 0x3000; + let address = + ((pci_device.header().bars[1] as u64) << 32) | (pci_device.header().bars[0] as u64); + let registers_region = MemoryRegion::direct_physical(address, three_pages).unwrap(); + + let port_cap = pci_device.register_msi().unwrap(); + + let driver = Self { + pci_device, + registers_region, + command_ring: OutputTrbRing::new(), + event_ring: EventRing::new(), + device_context_base_array: DeviceContextBaseArray::new(), + }; + driver.reset(); + driver + } + + fn capabilities(&self) -> ®isters::HostControllerCapabilities { + self.registers_region.as_ref() + } + + fn operational(&self) -> &mut registers::HostControllerOperational { + let cap_length: registers::HostControllerCapabilitiesLengthAndVersion = + read_unaligned_volatile!(self.capabilities(), cap_length_and_version); + let offset = cap_length.cap_length(); + + // TODO: Ensure exclusive access. + unsafe { self.registers_region.as_mut_ref_at_offset(offset as usize) } + } + + fn interrupters(&self) -> &[registers::InterrupterRegisterSet] { + // See Table 5-35: Host Controller Runtime Registers + const INTERRUPTER_OFFSET_FROM_RUNTIME: u32 = 0x20; + let runtime = read_unaligned_volatile!(self.capabilities(), runtime_register_space_offset); + + let interrupter_offset: usize = (runtime + INTERRUPTER_OFFSET_FROM_RUNTIME) as usize; + + let params1: registers::HCSParams1 = + read_unaligned_volatile!(self.capabilities(), params_1); + + // SAFETY: The XHCI spec says so? + unsafe { + slice::from_raw_parts( + self.registers_region + .raw_ptr_at_offset(interrupter_offset as u64), + params1.max_interrupters() as usize, + ) + } + } + + fn doorbells(&self) -> &[registers::Doorbell] { + let doorbell_offset = read_unaligned_volatile!(self.capabilities(), doorbell_offset); + + let params1: registers::HCSParams1 = + read_unaligned_volatile!(self.capabilities(), params_1); + + // SAFETY: The XHCI spec says so? + unsafe { + slice::from_raw_parts( + self.registers_region + .raw_ptr_at_offset(doorbell_offset as u64), + params1.max_device_slots() as usize, + ) + } + } + + fn reset(&self) { + #[cfg(feature = "debug")] + mammoth::debug!("Stopping XHCI Controller."); + + // Stop the host controller. + // TODO: Make this volatile. + self.operational().usb_command = self.operational().usb_command.with_run_stop(false); + + #[cfg(feature = "debug")] + mammoth::debug!("Waiting for controller to halt."); + + // Sleep until the controller is halted. + let mut status: registers::UsbStatus = + read_unaligned_volatile!(self.operational(), usb_status); + while !status.host_controller_halted() { + // TODO: Sleep for how long? + mammoth::syscall::thread_sleep(50).unwrap(); + status = read_unaligned_volatile!(self.operational(), usb_status); + } + + #[cfg(feature = "debug")] + mammoth::debug!("Resetting Controller."); + + map_unaligned_volatile!( + self.operational(), + usb_command, + |c: registers::UsbCommand| c.with_host_controller_reset(true) + ); + + let mut command: registers::UsbCommand = + read_unaligned_volatile!(self.operational(), usb_command); + while command.host_controller_reset() { + // TODO: Sleep for how long? + mammoth::syscall::thread_sleep(50).unwrap(); + command = read_unaligned_volatile!(self.operational(), usb_command); + } + + #[cfg(feature = "debug")] + mammoth::debug!("XHCI Controller Reset, waiting ready."); + + status = read_unaligned_volatile!(self.operational(), usb_status); + while status.controller_not_ready() { + // TODO: Sleep for how long? + mammoth::syscall::thread_sleep(50).unwrap(); + status = read_unaligned_volatile!(self.operational(), usb_status); + } + + #[cfg(feature = "debug")] + mammoth::debug!("XHCI Controller Ready."); + + #[cfg(feature = "debug")] + mammoth::debug!("Setting Command Ring"); + + // TODO: We should reset the command ring here. + write_unaligned_volatile!( + self.operational(), + command_ring_control, + self.command_ring.physical_addr() + ); + + #[cfg(feature = "debug")] + mammoth::debug!("Setting DCBA."); + + write_unaligned_volatile!( + self.operational(), + device_context_base_address_array_pointer, + self.device_context_base_array.physical_addr() + ); + + // We tell the controller that we can support as many slots as it does because + // we allocate a full 4K page to the DCBA, which is 256 entries and the max + // slots are 255. + let params1: registers::HCSParams1 = + read_unaligned_volatile!(self.capabilities(), params_1); + map_unaligned_volatile!( + self.operational(), + configure, + |c: registers::UsbConfigure| c + .with_max_device_slots_enabled(params1.max_device_slots()) + ); + + let params2: registers::HCSParams2 = + read_unaligned_volatile!(self.capabilities(), params_2); + + let max_scratchpad_buffers = + (params2.max_scratchpad_buffers_hi() << 5) | params2.max_scratchpad_buffers_lo(); + assert!( + max_scratchpad_buffers == 0, + "Unsupported scratchpad buffers." + ); + + #[cfg(feature = "debug")] + mammoth::debug!("Setting up initial event ring."); + + let interrupter0 = &self.interrupters()[0]; + write_unaligned_volatile!( + interrupter0, + event_ring_segment_table_base_address, + self.event_ring.segment_table().physical_address() + ); + write_unaligned_volatile!( + interrupter0, + event_ring_segement_table_size, + self.event_ring.segment_table().len() + ); + write_unaligned_volatile!( + interrupter0, + interrupter_moderation, + registers::InterrupterModeration::new() + .with_interrupt_moderation_interval(4000) + .with_interrupt_moderation_counter(0) + ); + write_unaligned_volatile!( + interrupter0, + interrupter_management, + registers::InterrupterManagement::new().with_interrupt_enabled(true) + ); + + map_unaligned_volatile!( + self.operational(), + usb_command, + |c: registers::UsbCommand| c.with_run_stop(true) + ); + + #[cfg(feature = "debug")] + mammoth::debug!("Enabled interrupts and controller."); + } +} diff --git a/rust/sys/voyageurs/src/xhci/event_ring.rs b/rust/sys/voyageurs/src/xhci/event_ring.rs new file mode 100644 index 0000000..c6d2fe5 --- /dev/null +++ b/rust/sys/voyageurs/src/xhci/event_ring.rs @@ -0,0 +1,25 @@ +use alloc::vec::Vec; + +use crate::xhci::data_structures::{EventRingSegmentTable, TrbRing}; + +pub struct EventRing { + segment_table: EventRingSegmentTable, + segments: Vec, +} + +impl EventRing { + pub fn new() -> Self { + let mut event_ring = Self { + segment_table: EventRingSegmentTable::new(1), + segments: [TrbRing::new(100)].into(), + }; + + event_ring.segment_table[0].from_trb_fing(&event_ring.segments[0]); + + event_ring + } + + pub fn segment_table(&self) -> &EventRingSegmentTable { + &self.segment_table + } +} diff --git a/rust/sys/voyageurs/src/xhci/mod.rs b/rust/sys/voyageurs/src/xhci/mod.rs index 008e36e..64bc6a9 100644 --- a/rust/sys/voyageurs/src/xhci/mod.rs +++ b/rust/sys/voyageurs/src/xhci/mod.rs @@ -1,2 +1,6 @@ -pub mod data_structures; -pub mod registers; +mod data_structures; +mod device_context_base_array; +pub mod driver; +mod event_ring; +mod registers; +mod trb_ring; diff --git a/rust/sys/voyageurs/src/xhci/trb_ring.rs b/rust/sys/voyageurs/src/xhci/trb_ring.rs new file mode 100644 index 0000000..116b8ea --- /dev/null +++ b/rust/sys/voyageurs/src/xhci/trb_ring.rs @@ -0,0 +1,49 @@ +use mammoth::mem::MemoryRegion; + +struct TrbRing { + region: MemoryRegion, + physical_addr: u64, +} + +impl TrbRing { + fn new() -> Self { + let (region, physical_addr) = MemoryRegion::contiguous_physical(0x1000).unwrap(); + region.zero_region(); + Self { + region, + physical_addr, + } + } +} + +pub struct OutputTrbRing { + ring: TrbRing, +} + +impl OutputTrbRing { + pub fn new() -> Self { + Self { + ring: TrbRing::new(), + } + } + + pub fn physical_addr(&self) -> u64 { + self.ring.physical_addr + } +} + +pub struct InputTrbRing { + ring: TrbRing, +} + +impl InputTrbRing { + pub fn new() -> Self { + Self { + ring: TrbRing::new(), + } + } + + pub fn physical_addr(&self) -> u64 { + self.ring.physical_addr + } +}