From 2c271360ce9024ee6195e1d5a8686078ddad909b Mon Sep 17 00:00:00 2001 From: Drew Galbraith Date: Fri, 5 Dec 2025 22:01:13 -0800 Subject: [PATCH] Rust XHCI Implementation. --- ' | 51 +++ rust/Cargo.lock | 44 +-- rust/lib/mammoth/src/mem.rs | 8 +- rust/lib/mammoth/src/physical_box.rs | 16 +- rust/lib/pci/src/device.rs | 10 +- rust/sys/denali/src/ahci/controller.rs | 5 +- rust/sys/voyageurs/Cargo.toml | 7 + rust/sys/voyageurs/src/main.rs | 39 ++- .../event_ring_segment_table.rs | 13 +- .../voyageurs/src/xhci/data_structures/mod.rs | 6 +- .../voyageurs/src/xhci/data_structures/trb.rs | 234 +++++++++++++ .../src/xhci/data_structures/trb_ring.rs | 113 ------ .../xhci/data_structures/trb_ring_segment.rs | 48 +++ .../src/xhci/device_context_base_array.rs | 22 ++ rust/sys/voyageurs/src/xhci/driver.rs | 331 ++++++++++++++++++ rust/sys/voyageurs/src/xhci/event_ring.rs | 75 ++++ rust/sys/voyageurs/src/xhci/mod.rs | 9 +- .../src/xhci/registers/capabilities.rs | 1 + .../voyageurs/src/xhci/registers/doorbell.rs | 16 + .../src/xhci/registers/host_controller.rs | 110 ++++++ .../xhci/registers/host_controller_port.rs | 86 +++-- .../src/xhci/registers/interrupter.rs | 79 ++++- rust/sys/voyageurs/src/xhci/trb_ring.rs | 163 +++++++++ scripts/qemu.sh | 2 +- zion/interrupt/driver_manager.cpp | 2 + 25 files changed, 1288 insertions(+), 202 deletions(-) create mode 100644 ' create mode 100644 rust/sys/voyageurs/src/xhci/data_structures/trb.rs delete mode 100644 rust/sys/voyageurs/src/xhci/data_structures/trb_ring.rs create mode 100644 rust/sys/voyageurs/src/xhci/data_structures/trb_ring_segment.rs 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..d69da02 --- /dev/null +++ b/' @@ -0,0 +1,51 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +mod xhci; + +use alloc::sync::Arc; +use mammoth::{ + cap::Capability, + debug, define_entry, + sync::Mutex, + task::{Executor, Task}, + zion::z_err_t, +}; +use pci::PciDevice; +use xhci::driver::XHCIDriver; + +define_entry!(); + +#[unsafe(no_mangle)] +extern "C" fn main() -> z_err_t { + #[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 = Arc::new(XHCIDriver::from_pci_device(pci_device)); + + let executor = Arc::new(Mutex::new(Executor::new())); + + let driver_clone = xhci_driver.clone(); + let interrupt_thread = mammoth::thread::spawn(move || driver_clone.interrupt_loop()); + + let driver_clone = xhci_driver.clone(); + executor + .clone() + .lock() + .spawn(Task::new((|| xhci_driver.clone().startup())())); + + executor.clone().lock().run(); + interrupt_thread.join().unwrap(); + + 0 +} diff --git a/rust/Cargo.lock b/rust/Cargo.lock index aec4469..33e3fc5 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" @@ -200,12 +193,21 @@ dependencies = [ "yunqc", ] +[[package]] +name = "volatile" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af8ca9a5d4debca0633e697c88269395493cebf2e10db21ca2dbde37c1356452" + [[package]] name = "voyageurs" version = "0.1.0" dependencies = [ "bitfield-struct 0.12.1", "mammoth", + "pci", + "volatile", + "yellowstone-yunq", ] [[package]] diff --git a/rust/lib/mammoth/src/mem.rs b/rust/lib/mammoth/src/mem.rs index 7b81806..d35097b 100644 --- a/rust/lib/mammoth/src/mem.rs +++ b/rust/lib/mammoth/src/mem.rs @@ -78,6 +78,10 @@ impl MemoryRegion { }) } + pub fn vaddr(&self) -> usize { + self.virt_addr as usize + } + pub fn slice(&self) -> &[T] { unsafe { slice::from_raw_parts( @@ -246,11 +250,11 @@ pub fn map_cap_and_leak(mem_cap: Capability) -> u64 { vaddr } -pub fn map_direct_physical_and_leak(paddr: u64, size: u64) -> u64 { +pub fn map_direct_physical_and_leak(paddr: u64, size: u64) -> *mut T { let mem_cap = syscall::memory_object_direct_physical(paddr, size).unwrap(); let vaddr = syscall::address_space_map(&mem_cap).unwrap(); mem_cap.release(); - vaddr + vaddr as *mut T } pub fn map_physical_and_leak(size: u64) -> (u64, u64) { diff --git a/rust/lib/mammoth/src/physical_box.rs b/rust/lib/mammoth/src/physical_box.rs index 692eed1..b3a11c3 100644 --- a/rust/lib/mammoth/src/physical_box.rs +++ b/rust/lib/mammoth/src/physical_box.rs @@ -4,12 +4,13 @@ use core::{ ptr::NonNull, }; -use alloc::{slice, vec::Vec}; +use alloc::{boxed::Box, slice, vec::Vec}; use crate::mem::MemoryRegion; pub struct PhysicalBox { data: NonNull, + #[allow(dead_code)] region: MemoryRegion, physical_address: usize, _marker: PhantomData, @@ -54,6 +55,12 @@ impl PhysicalBox<[T]> { let (memory_region, paddr) = MemoryRegion::contiguous_physical(layout.size() as u64).expect("Failed to allocate"); + crate::debug!( + "Physical box allocated: v {:0x} p {:0x}", + memory_region.vaddr(), + paddr + ); + let ptr: *mut T = memory_region.mut_ptr_at_offset(0); for i in 0..len { unsafe { @@ -122,6 +129,13 @@ where } } +/// SAFETY: We are the only owner of this pointer. +unsafe impl Send for PhysicalBox where Box: Send {} + +/// SAFETY: You must have a mutable reference to this +/// type to modify the data at the pointer. +unsafe impl Sync for PhysicalBox where Box: Sync {} + impl Drop for PhysicalBox { fn drop(&mut self) { // SAFETY: 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/denali/src/ahci/controller.rs b/rust/sys/denali/src/ahci/controller.rs index 2a402f0..6289793 100644 --- a/rust/sys/denali/src/ahci/controller.rs +++ b/rust/sys/denali/src/ahci/controller.rs @@ -1,3 +1,5 @@ +use core::ffi::c_void; + use alloc::sync::Arc; use mammoth::{ cap::Capability, @@ -27,7 +29,8 @@ impl AhciController { let pci_device = PciDevice::from_cap(pci_memory).unwrap(); let hba_vaddr = - mem::map_direct_physical_and_leak(pci_device.header().bars[5] as u64, 0x1100); + mem::map_direct_physical_and_leak(pci_device.header().bars[5] as u64, 0x1100) + as *mut c_void as u64; let hba = unsafe { (hba_vaddr as *mut AhciHba).as_mut().unwrap() }; let mut controller = Self { pci_device: Mutex::new(pci_device), diff --git a/rust/sys/voyageurs/Cargo.toml b/rust/sys/voyageurs/Cargo.toml index 084d083..bd65fff 100644 --- a/rust/sys/voyageurs/Cargo.toml +++ b/rust/sys/voyageurs/Cargo.toml @@ -6,3 +6,10 @@ edition = "2024" [dependencies] bitfield-struct = "0.12" mammoth = { path = "../../lib/mammoth/" } +pci = { path = "../../lib/pci" } +volatile = "0.6.1" +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..92a3ee1 100644 --- a/rust/sys/voyageurs/src/main.rs +++ b/rust/sys/voyageurs/src/main.rs @@ -5,12 +5,47 @@ extern crate alloc; mod xhci; -use mammoth::{debug, define_entry, zion::z_err_t}; +use alloc::sync::Arc; +use mammoth::{ + cap::Capability, + debug, define_entry, + sync::Mutex, + task::{Executor, Task}, + 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 = Arc::new(XHCIDriver::from_pci_device(pci_device)); + + let executor = Arc::new(Mutex::new(Executor::new())); + + let driver_clone = xhci_driver.clone(); + let spawner = executor.clone().lock().new_spawner(); + let interrupt_thread = mammoth::thread::spawn(move || driver_clone.interrupt_loop(spawner)); + + executor + .clone() + .lock() + .spawn(Task::new(async move { xhci_driver.startup().await })); + + executor.clone().lock().run(); + interrupt_thread.join().unwrap(); + 0 } diff --git a/rust/sys/voyageurs/src/xhci/data_structures/event_ring_segment_table.rs b/rust/sys/voyageurs/src/xhci/data_structures/event_ring_segment_table.rs index 89ce405..e4985d8 100644 --- a/rust/sys/voyageurs/src/xhci/data_structures/event_ring_segment_table.rs +++ b/rust/sys/voyageurs/src/xhci/data_structures/event_ring_segment_table.rs @@ -1,9 +1,8 @@ use core::ops::{Index, IndexMut}; -use alloc::{boxed::Box, vec}; use mammoth::physical_box::PhysicalBox; -use crate::xhci::data_structures::TrbRing; +use crate::xhci::data_structures::TrbRingSegment; #[repr(align(64))] #[derive(Default, Clone)] @@ -19,10 +18,16 @@ pub struct EventRingSegmentTableEntry { } impl EventRingSegmentTableEntry { - pub fn from_trb_fing(&mut self, trb_ring: &TrbRing) { + pub fn from_trb_ring(&mut self, trb_ring: &TrbRingSegment) { + mammoth::debug!("RSTE: {:0x}", self as *const _ as usize); self.ring_segment_base_address = trb_ring.physical_address() as u64; assert!(self.ring_segment_base_address % 64 == 0); - self.ring_segment_size = trb_ring.len() as u64; + unsafe { + core::ptr::write_volatile( + &mut self.ring_segment_size as *mut u64, + trb_ring.len() as u64, + ) + }; assert!(self.ring_segment_size >= 16); assert!(self.ring_segment_size <= 4096); } diff --git a/rust/sys/voyageurs/src/xhci/data_structures/mod.rs b/rust/sys/voyageurs/src/xhci/data_structures/mod.rs index 781cbc1..445d62a 100644 --- a/rust/sys/voyageurs/src/xhci/data_structures/mod.rs +++ b/rust/sys/voyageurs/src/xhci/data_structures/mod.rs @@ -1,8 +1,10 @@ mod endpoint_context; mod event_ring_segment_table; mod slot_context; -mod trb_ring; +mod trb; +mod trb_ring_segment; pub use event_ring_segment_table::*; pub use slot_context::*; -pub use trb_ring::*; +pub use trb::*; +pub use trb_ring_segment::*; diff --git a/rust/sys/voyageurs/src/xhci/data_structures/trb.rs b/rust/sys/voyageurs/src/xhci/data_structures/trb.rs new file mode 100644 index 0000000..92d8e4e --- /dev/null +++ b/rust/sys/voyageurs/src/xhci/data_structures/trb.rs @@ -0,0 +1,234 @@ +use bitfield_struct::{bitenum, bitfield}; + +#[bitenum] +#[repr(u8)] +#[derive(Debug, Eq, PartialEq)] +pub enum TrbType { + #[fallback] + Reserved = 0, + Normal = 1, + SetupStage = 2, + DataStage = 3, + StatusStage = 4, + Isoch = 5, + Link = 6, + EventData = 7, + NoOp = 8, + EnableSlotCommand = 9, + DisableSlotCommand = 10, + AddressDeviceCommand = 11, + ConfigureEndpointCommand = 12, + EvaluateContextCommand = 13, + ResetEndpointCommand = 14, + StopEndpointCommand = 15, + SetTRDequeuePointerCommand = 16, + ResetDeviceCommand = 17, + ForceEventCommand = 18, + NegotiateBandwidthCommand = 19, + SetLatencyToleranceValueCommand = 20, + GetPortBandwidthCommand = 21, + ForceHeaderCommand = 22, + NoOpCommand = 23, + GetExtendedPropertyCommand = 24, + SetExtendedPropertyCommand = 25, + TransferEvent = 32, + CommandCompletionEvent = 33, + PortStatusChangeEvent = 34, + BandwidthRequestEvent = 35, + DoorbellEvent = 36, + HostControllerEvent = 37, + DeviceNotificationEvent = 38, + MFINDEXWrapEvent = 39, +} + +#[bitfield(u128)] +pub struct TransferRequestBlock { + pub parameter: u64, + pub status: u32, + pub 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)] + pub trb_type: TrbType, + control: u16, +} + +impl TransferRequestBlock {} + +pub trait TypedTrb +where + Self: Into + From + Copy, +{ + fn from_trb(trb: TransferRequestBlock) -> Self { + trb.into_bits().into() + } + + fn to_trb(self) -> TransferRequestBlock { + Into::::into(self).into() + } +} + +#[bitfield(u128)] +pub struct TrbNoOp { + __: u64, + #[bits(22)] + __: u32, + #[bits(10, default = 0)] + interrupter_target: u16, + cycle: bool, + evaluate_next: bool, + __: bool, + __: bool, + chain: bool, + #[bits(default = true)] + interrupt_on_completion: bool, + #[bits(4)] + __: u8, + #[bits(6, default = TrbType::NoOpCommand)] + trb_type: TrbType, + __: u16, +} + +impl TypedTrb for TrbNoOp {} + +#[bitfield(u128)] +pub struct TrbLink { + /// Ring Segment Pointer Hi and Lo. These fields represent the high order bits of the 64-bit base + /// address of the next Ring Segment. + /// The memory structure referenced by this physical memory pointer shall begin on a 16-byte + /// address boundary. + pub ring_segment_pointer: u64, + #[bits(22)] + __: u32, + /// Interrupter Target. This field defines the index of the Interrupter that will receive Transfer + /// Events generated by this TRB. Valid values are between 0 and MaxIntrs-1. + /// This field is ignored by the xHC on Command Rings. + #[bits(10)] + pub interrupter_target: u16, + /// Cycle bit (C). This bit is used to mark the Enqueue Pointer location of a Transfer or Command + /// Ring. + pub cycle: bool, + /// Toggle Cycle (TC). When set to ‘1’, the xHC shall toggle its interpretation of the Cycle bit. When + /// cleared to ‘0’, the xHC shall continue to the next segment using its current interpretation of the + /// Cycle bit. + pub toggle_cycle: bool, + __: bool, + __: bool, + /// Chain bit (CH). Set to ‘1’ by software to associate this TRB with the next TRB on the Ring. A + /// Transfer Descriptor (TD) is defined as one or more TRBs. The Chain bit is used to identify the + /// TRBs that comprise a TD. Refer to section 4.11.7 for more information on Link TRB placement + /// within a TD. On a Command Ring this bit is ignored by the xHC. + #[bits(default = true)] + chain: bool, + /// Interrupt On Completion (IOC). If this bit is set to ‘1’, it specifies that when this TRB completes, + /// the Host Controller shall notify the system of the completion by placing an Event TRB on the + /// Event ring and sending an interrupt at the next interrupt threshold. + pub interrupt_on_completion: bool, + #[bits(4)] + __: u8, + /// TRB Type. This field is set to Link TRB type. Refer to Table 6-91 for the definition of the Type + /// TRB IDs. + #[bits(6, default = TrbType::Link)] + trb_type: TrbType, + __: u16, +} + +impl TypedTrb for TrbLink {} + +#[bitenum] +#[repr(u8)] +pub enum CommandCompletionCode { + #[fallback] + Invalid = 0, + Success = 1, +} + +#[bitfield(u128)] +pub struct TrbCommandCompletion { + /// Command TRB Pointer Hi and Lo. This field represents the high order bits of the 64-bit address + /// of the Command TRB that generated this event. Note that this field is not valid for some + /// Completion Code values. Refer to Table 6-90 for specific cases. + /// + /// The memory structure referenced by this physical memory pointer shall be aligned on a 16-byte + /// address boundary. + pub command_trb_pointer: u64, + /// Command Completion Parameter. This field may optionally be set by a command. Refer to + /// section 4.6.6.1 for specific usage. If a command does not utilize this field it shall be treated as + /// RsvdZ. + #[bits(24)] + pub command_completion_parameter: u64, + /// Completion Code. This field encodes the completion status of the command that generated the + /// event. Refer to the respective command definition for a list of the possible Completion Codes + /// associated with the command. Refer to section 6.4.5 for an enumerated list of possible error + /// conditions. + pub completion_code: u8, + /// Cycle bit (C). This bit is used to mark the Dequeue Pointer of an Event Ring + pub cycle_bit: bool, + #[bits(9)] + __: u16, + /// TRB Type. This field identifies the type of the TRB. Refer to Table 6-91 for the definition of the + /// Command Completion Event TRB type ID + #[bits(6, default=TrbType::CommandCompletionEvent)] + pub trb_type: TrbType, + /// VF ID. The ID of the Virtual Function that generated the event. Note that this field is valid only if + /// Virtual Functions are enabled. If they are not enabled this field shall be cleared to ‘0’. + pub vf_id: u8, + /// Slot ID. The Slot ID field shall be updated by the xHC to reflect the slot associated with the + /// command that generated the event, with the following exceptions: + /// + /// - The Slot ID shall be cleared to ‘0’ for No Op, Set Latency Tolerance Value, Get Port Bandwidth, + /// and Force Event Commands. + /// + /// - The Slot ID shall be set to the ID of the newly allocated Device Slot for the Enable Slot + /// Command. + /// + /// - The value of Slot ID shall be vendor defined when generated by a vendor defined command. + /// + /// This value is used as an index in the Device Context Base Address Array to select the Device + /// Context of the source device. If this Event is due to a Host Controller Command, then this field + /// shall be cleared to ‘0’. + pub slot_id: u8, +} + +impl TypedTrb for TrbCommandCompletion {} + +#[bitfield(u128)] +pub struct TrbPortStatusChangeEvent { + #[bits(24)] + __: u32, + pub port_id: u8, + __: u32, + #[bits(24)] + __: u32, + pub completion_code: u8, + #[bits(10)] + __: u16, + #[bits(6, default=TrbType::PortStatusChangeEvent)] + trb_type: TrbType, + __: u16, +} + +impl TypedTrb for TrbPortStatusChangeEvent {} + +#[bitfield(u128)] +pub struct TrbEnableSlotCommand { + __: u64, + __: u32, + #[bits(10)] + __: u16, + #[bits(6, default=TrbType::EnableSlotCommand)] + trb_type: TrbType, + #[bits(5)] + slot_type: u8, + #[bits(11)] + __: u16, +} + +impl TypedTrb for TrbEnableSlotCommand {} diff --git a/rust/sys/voyageurs/src/xhci/data_structures/trb_ring.rs b/rust/sys/voyageurs/src/xhci/data_structures/trb_ring.rs deleted file mode 100644 index 2ded382..0000000 --- a/rust/sys/voyageurs/src/xhci/data_structures/trb_ring.rs +++ /dev/null @@ -1,113 +0,0 @@ -use core::{ - ops::{Index, IndexMut}, - slice::SliceIndex, -}; - -use bitfield_struct::{bitenum, bitfield}; -use mammoth::physical_box::PhysicalBox; - -#[bitenum] -#[repr(u8)] -#[derive(Debug)] -pub enum TrbType { - #[fallback] - Reserved = 0, - NoOp = 8, -} - -#[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, -} - -impl TransferRequestBlock {} - -trait TypedTrb { - fn from_trb(trb: TransferRequestBlock) -> Self - where - Self: From, - { - trb.into_bits().into() - } - - fn to_trb(self) -> TransferRequestBlock - where - Self: Into, - { - Into::::into(self).into() - } -} - -#[bitfield(u128)] -pub struct TrbNoOp { - __: u64, - __: u32, - cycle: bool, - evaluate_next: bool, - __: bool, - __: bool, - chain: bool, - #[bits(default = true)] - interrupt_on_completion: bool, - #[bits(4)] - __: u8, - #[bits(6, default = TrbType::NoOp)] - trb_type: TrbType, - __: u16, -} - -impl TypedTrb for TrbNoOp {} - -#[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 -where - I: SliceIndex<[TransferRequestBlock]>, -{ - type Output = I::Output; - - 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/sys/voyageurs/src/xhci/data_structures/trb_ring_segment.rs b/rust/sys/voyageurs/src/xhci/data_structures/trb_ring_segment.rs new file mode 100644 index 0000000..6ac6093 --- /dev/null +++ b/rust/sys/voyageurs/src/xhci/data_structures/trb_ring_segment.rs @@ -0,0 +1,48 @@ +use core::{ + ops::{Index, IndexMut}, + slice::SliceIndex, +}; + +use mammoth::physical_box::PhysicalBox; + +use crate::xhci::data_structures::TransferRequestBlock; + +#[repr(transparent)] +pub struct TrbRingSegment(PhysicalBox<[TransferRequestBlock]>); + +impl TrbRingSegment { + 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 TrbRingSegment +where + I: SliceIndex<[TransferRequestBlock]>, +{ + type Output = I::Output; + + fn index(&self, index: I) -> &Self::Output { + &self.0[index] + } +} + +impl IndexMut for TrbRingSegment +where + I: SliceIndex<[TransferRequestBlock]>, +{ + fn index_mut(&mut self, index: I) -> &mut Self::Output { + &mut self.0[index] + } +} 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..ed347a2 --- /dev/null +++ b/rust/sys/voyageurs/src/xhci/driver.rs @@ -0,0 +1,331 @@ +use core::slice; + +use alloc::sync::Arc; +use mammoth::cap::Capability; +use mammoth::mem::MemoryRegion; +use mammoth::sync::Mutex; +use mammoth::task::Spawner; +use mammoth::task::Task; +use mammoth::write_unaligned_volatile; + +use super::registers::{self}; +use crate::xhci::data_structures::CommandCompletionCode; +use crate::xhci::data_structures::TrbCommandCompletion; +use crate::xhci::data_structures::TrbEnableSlotCommand; +use crate::xhci::data_structures::TrbNoOp; +use crate::xhci::data_structures::TrbPortStatusChangeEvent; +use crate::xhci::data_structures::TrbType; +use crate::xhci::data_structures::TypedTrb; +use crate::xhci::device_context_base_array::DeviceContextBaseArray; +use crate::xhci::event_ring::EventRing; +use crate::xhci::registers::HostControllerOperationalWrapper; +use crate::xhci::registers::PortStatusAndControl; +use crate::xhci::trb_ring::TrbRing; + +pub struct XHCIDriver { + #[allow(dead_code)] + pci_device: pci::PciDevice, + capabilities: registers::HostControllerCapabilities, + operational: HostControllerOperationalWrapper, + registers_region: MemoryRegion, + command_ring: Mutex>, + event_ring: Mutex, + device_context_base_array: DeviceContextBaseArray, + irq_port_cap: Capability, +} + +impl XHCIDriver { + pub fn from_pci_device(mut pci_device: pci::PciDevice) -> Self { + // 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 irq_port_cap = pci_device.register_msi().unwrap(); + + let (operational, capabilities) = HostControllerOperationalWrapper::new(address as usize); + + let p1 = capabilities.params_1; + mammoth::debug!("Num Port: {:?}", p1); + + let mut driver = Self { + pci_device, + capabilities, + operational, + registers_region, + command_ring: Mutex::new(TrbRing::new()), + event_ring: Mutex::new(EventRing::new()), + device_context_base_array: DeviceContextBaseArray::new(), + irq_port_cap, + }; + driver.initialize(); + driver + } + + fn interrupters(&self) -> &mut [registers::InterrupterRegisterSet] { + // See Table 5-35: Host Controller Runtime Registers + const INTERRUPTER_OFFSET_FROM_RUNTIME: u32 = 0x20; + let runtime = self.capabilities.runtime_register_space_offset; + + let interrupter_offset = (runtime + INTERRUPTER_OFFSET_FROM_RUNTIME) as usize; + + let params1 = self.capabilities.params_1; + + // SAFETY: The XHCI spec says so? + unsafe { + slice::from_raw_parts_mut( + self.registers_region.mut_ptr_at_offset(interrupter_offset), + params1.max_interrupters() as usize, + ) + } + } + + fn doorbells(&self) -> &mut [registers::Doorbell] { + let doorbell_offset = self.capabilities.doorbell_offset; + + let params1 = self.capabilities.params_1; + + // SAFETY: The XHCI spec says so? + unsafe { + slice::from_raw_parts_mut( + self.registers_region + .mut_ptr_at_offset(doorbell_offset as usize), + params1.max_device_slots() as usize, + ) + } + } + + fn initialize(&mut self) { + #[cfg(feature = "debug")] + mammoth::debug!("Stopping XHCI Controller."); + + // Stop the host controller. + self.operational.update(|mut o| { + o.usb_command = o.usb_command.with_run_stop(false); + o + }); + + #[cfg(feature = "debug")] + mammoth::debug!("Waiting for controller to halt."); + + // Sleep until the controller is halted. + let mut status = self.operational.status(); + while !status.host_controller_halted() { + // TODO: Sleep for how long? + mammoth::syscall::thread_sleep(50).unwrap(); + status = self.operational.status(); + } + + #[cfg(feature = "debug")] + mammoth::debug!("Resetting Controller."); + + self.operational.update(|mut o| { + o.usb_command = o.usb_command.with_host_controller_reset(false); + o + }); + + let mut command: registers::UsbCommand = self.operational.command(); + while command.host_controller_reset() { + // TODO: Sleep for how long? + mammoth::syscall::thread_sleep(50).unwrap(); + command = self.operational.command(); + } + + #[cfg(feature = "debug")] + mammoth::debug!("XHCI Controller Reset, waiting ready."); + + let mut status = self.operational.status(); + while status.controller_not_ready() { + // TODO: Sleep for how long? + mammoth::syscall::thread_sleep(50).unwrap(); + status = self.operational.status(); + } + + #[cfg(feature = "debug")] + mammoth::debug!("XHCI Controller Ready."); + + #[cfg(feature = "debug")] + mammoth::debug!("Setting Command Ring"); + + self.operational.update(|mut o| { + // TODO: Split this struct to make it clearer that we are setting the cycle bit here. + o.command_ring_control = (self.command_ring.lock().physical_base_address() | 1) as u64; + o + }); + + #[cfg(feature = "debug")] + mammoth::debug!("Setting DCBA."); + + let params1 = self.capabilities.params_1; + self.operational.update(|mut o| { + o.device_context_base_address_array_pointer = + self.device_context_base_array.physical_addr() as u64; + // 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. + o.configure = o + .configure + .with_max_device_slots_enabled(params1.max_device_slots()); + o + }); + + let params2 = 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 = &mut self.interrupters()[0]; + // SAFETY: + // - The HC was halted above. + // - THe segment table is size 1. + unsafe { + let event_ring = self.event_ring.lock(); + interrupter0.set_event_ring( + event_ring.segment_table(), + event_ring.erdp_physical_address(), + ); + } + 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) + ); + + self.operational.update(|mut o| { + o.usb_command = o + .usb_command + .with_run_stop(true) + .with_interrupter_enable(true); + o + }); + + #[cfg(feature = "debug")] + mammoth::debug!("Enabled interrupts and controller."); + } + + pub fn interrupt_loop(self: Arc, spawner: Spawner) { + loop { + let _ = mammoth::syscall::port_recv(&self.irq_port_cap, &mut [], &mut []).unwrap(); + #[cfg(feature = "debug")] + mammoth::debug!("Received Interrupt."); + self.interrupters()[0].interrupter_management = self.interrupters()[0] + .interrupter_management + .with_interrupt_pending(true); + + // TODO: Make event ring own its interrupter. + let mut event_ring = self.event_ring.lock(); + + while let Some(trb) = event_ring.get_next() { + event_ring.update_dequeue_pointer(&mut self.interrupters()[0]); + match trb.trb_type() { + TrbType::TransferEvent => { + todo!("Handle Transfer") + } + TrbType::CommandCompletionEvent => { + self.command_ring + .lock() + .handle_commpletion(TrbCommandCompletion::from_trb(trb)); + } + TrbType::PortStatusChangeEvent => { + let trb = TrbPortStatusChangeEvent::from_trb(trb); + let self_clone = self.clone(); + spawner.spawn(Task::new(async move { + self_clone.port_status_change(trb).await + })); + } + _ => { + panic!("Unhandled event type: {:?}", trb.trb_type()); + } + } + } + } + } + + async fn send_command(&self, trb: impl TypedTrb) -> TrbCommandCompletion { + let fut = self.command_ring.lock().enqueue_trb(trb.to_trb()); + self.doorbells()[0].ring_command(); + fut.await + } + + pub async fn startup(&self) { + #[cfg(feature = "debug")] + mammoth::debug!("Sending no op command."); + + let result = self.send_command(TrbNoOp::new()).await; + + assert!(result.completion_code() == CommandCompletionCode::Success.into_bits()); + + #[cfg(feature = "debug")] + mammoth::debug!("Successfully tested no op command."); + + #[cfg(feature = "debug")] + mammoth::debug!("Resetting all connected ports."); + for port_index in 0..self.operational.num_ports() { + self.operational + .update_port_status(port_index, |p| p.clear_change_bits()); + } + + for port_index in 0..self.operational.num_ports() { + let status = self.operational.get_port(port_index).status_and_control; + if status.port_power() && status.current_connect_status() { + mammoth::debug!("Resetting port {}", port_index); + self.operational.update_port_status(port_index, |_| { + PortStatusAndControl::new() + .with_port_reset(true) + .with_port_power(true) + }); + } + } + } + + async fn port_status_change(self: Arc, status_change: TrbPortStatusChangeEvent) { + // Ports are indexed from 1. + let port_id = status_change.port_id(); + let port_index = (port_id - 1) as usize; + + let port_status = self + .operational + .get_port(port_index as usize) + .status_and_control; + + #[cfg(feature = "debug")] + mammoth::debug!( + "Port status change for port {}, status= {:?}", + port_id, + port_status + ); + + if !port_status.port_reset_change() { + mammoth::debug!("Unknown port status event, not handling."); + return; + } + self.operational + .update_port_status(port_index, |s| s.clear_change_bits()); + + #[cfg(feature = "debug")] + mammoth::debug!("Enabling slot."); + + let resp = self.send_command(TrbEnableSlotCommand::new()).await; + assert!(resp.completion_code() == CommandCompletionCode::Success.into_bits()); + } +} 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..6c60bfb --- /dev/null +++ b/rust/sys/voyageurs/src/xhci/event_ring.rs @@ -0,0 +1,75 @@ +use alloc::vec::Vec; + +use crate::xhci::{ + data_structures::{EventRingSegmentTable, TransferRequestBlock, TrbRingSegment, TrbType}, + registers::InterrupterRegisterSet, + trb_ring::TrbPointer, +}; + +pub struct EventRing { + segment_table: EventRingSegmentTable, + segments: Vec, + cycle_bit: bool, + trb_pointer: TrbPointer, +} + +impl EventRing { + pub fn new() -> Self { + // Software maintains an Event Ring Consumer Cycle State (CCS) bit, initializing it + // to ‘1’... + let cycle_bit = true; + let mut event_ring = Self { + segment_table: EventRingSegmentTable::new(1), + segments: [TrbRingSegment::new(100)].into(), + cycle_bit, + trb_pointer: TrbPointer::default(), + }; + + event_ring.segment_table[0].from_trb_ring(&event_ring.segments[0]); + + event_ring + } + + pub fn segment_table(&self) -> &EventRingSegmentTable { + &self.segment_table + } + + pub fn erdp_physical_address(&self) -> usize { + self.segments[self.trb_pointer.segment_index].physical_address() + + self.trb_pointer.segment_physical_offset() + } + + fn current_trb(&self) -> TransferRequestBlock { + // TODO: These should be volatile reads. + self.segments[self.trb_pointer.segment_index][self.trb_pointer.segment_offset] + } + + fn increment_pointer(&mut self) { + self.trb_pointer.segment_offset += 1; + + if self.trb_pointer.segment_offset == self.segments[self.trb_pointer.segment_index].len() { + self.trb_pointer.segment_index += 1; + self.trb_pointer.segment_offset = 0; + + if self.trb_pointer.segment_index == self.segments.len() { + // Wrap around to front. + self.trb_pointer.segment_index = 0; + self.cycle_bit = !self.cycle_bit; + } + } + } + + pub fn get_next(&mut self) -> Option { + let curr = self.current_trb(); + if curr.cycle() != self.cycle_bit { + None + } else { + self.increment_pointer(); + Some(curr) + } + } + + pub fn update_dequeue_pointer(&self, interrupter: &mut InterrupterRegisterSet) { + interrupter.update_dequeue_pointer(self.erdp_physical_address()); + } +} diff --git a/rust/sys/voyageurs/src/xhci/mod.rs b/rust/sys/voyageurs/src/xhci/mod.rs index 008e36e..f42fa09 100644 --- a/rust/sys/voyageurs/src/xhci/mod.rs +++ b/rust/sys/voyageurs/src/xhci/mod.rs @@ -1,2 +1,7 @@ -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/registers/capabilities.rs b/rust/sys/voyageurs/src/xhci/registers/capabilities.rs index 751fb9c..8e50c2f 100644 --- a/rust/sys/voyageurs/src/xhci/registers/capabilities.rs +++ b/rust/sys/voyageurs/src/xhci/registers/capabilities.rs @@ -321,6 +321,7 @@ pub struct HCCParams2 { /// /// These registers are located at the addresses specified in BAR0 and BAR1 in the PCI Header. #[repr(C, packed)] +#[derive(Copy, Clone)] pub struct HostControllerCapabilities { pub cap_length_and_version: HostControllerCapabilitiesLengthAndVersion, pub params_1: HCSParams1, diff --git a/rust/sys/voyageurs/src/xhci/registers/doorbell.rs b/rust/sys/voyageurs/src/xhci/registers/doorbell.rs index af59cc2..276a5c0 100644 --- a/rust/sys/voyageurs/src/xhci/registers/doorbell.rs +++ b/rust/sys/voyageurs/src/xhci/registers/doorbell.rs @@ -63,3 +63,19 @@ pub struct Doorbell { /// This field returns ‘0’ when read db_stream_id: u16, } + +impl Doorbell { + pub fn ring(&mut self, target: u8) { + // SAFETY: + // - We know this is a valid reference. + unsafe { + core::ptr::write_volatile( + self as *mut _, + Doorbell::new().with_db_target(target).with_db_stream_id(0), + ); + } + } + pub fn ring_command(&mut self) { + self.ring(0) + } +} diff --git a/rust/sys/voyageurs/src/xhci/registers/host_controller.rs b/rust/sys/voyageurs/src/xhci/registers/host_controller.rs index db609af..e9887d8 100644 --- a/rust/sys/voyageurs/src/xhci/registers/host_controller.rs +++ b/rust/sys/voyageurs/src/xhci/registers/host_controller.rs @@ -1,4 +1,13 @@ +use core::ptr::NonNull; + +use alloc::vec::Vec; use bitfield_struct::bitfield; +use mammoth::{mem::map_direct_physical_and_leak, sync::Mutex}; +use volatile::{VolatilePtr, VolatileRef, map_field}; + +use crate::xhci::registers::{ + HostControllerCapabilities, HostControllerUsbPort, PortStatusAndControl, +}; #[bitfield(u32)] pub struct UsbCommand { @@ -273,6 +282,7 @@ pub struct UsbConfigure { /// of the Capability Registers Length (CAPLENGTH) register (refer to Section 5.3.1) /// to the Capability Base address. All registers are multiples of 32 bits in length #[repr(C, packed)] +#[derive(Copy, Clone)] pub struct HostControllerOperational { pub usb_command: UsbCommand, pub usb_status: UsbStatus, @@ -296,3 +306,103 @@ pub struct HostControllerOperational { } const _: () = assert!(size_of::() == 0x3C); + +pub struct HostControllerOperationalWrapper { + // TODO: Fix alignment of this type so we can do more targetted reads and writes. + operational: Mutex>, + // TODO: This should maybe be its own structure. + ports: Vec>>, +} + +impl HostControllerOperationalWrapper { + pub fn new(mmio_address: usize) -> (Self, HostControllerCapabilities) { + const MAP_SIZE: usize = 0x1000; + let caps_ptr: *mut HostControllerCapabilities = + map_direct_physical_and_leak(mmio_address as u64, MAP_SIZE as u64); + + // SAFETY: + // - The pointer is valid. + // - No other thread has access in this block. + let capabilities = unsafe { + VolatilePtr::new( + // UNWRAP: We just constructed this object with a non-null value. + NonNull::new(caps_ptr).unwrap(), + ) + .read() + }; + + let cap_length_and_version = capabilities.cap_length_and_version; + // TODO: I don't think we acutally handle this properly. + // SAFETY: XHCI Spec says that this resides in a single page of memory which we mapped + // above. + // + // 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, + let operational_ptr = unsafe { + (caps_ptr as *mut u8).add(cap_length_and_version.cap_length() as usize) + as *mut HostControllerOperational + }; + + const PORT_OFFSET: usize = 0x400; + + // FIXME: This calculation is cursed. + let ports_addr = unsafe { (operational_ptr as *mut u8).add(PORT_OFFSET) as usize }; + let ports_space = MAP_SIZE - cap_length_and_version.cap_length() as usize - PORT_OFFSET; + let max_ports_we_support = ports_space / size_of::(); + let params_1 = capabilities.params_1; + assert!( + params_1.max_ports() as usize <= max_ports_we_support, + "TODO: Support more ports." + ); + + let mut ports = Vec::new(); + let ports_addr = ports_addr as *mut HostControllerUsbPort; + for port_index in 0..params_1.max_ports() { + ports.push(unsafe { + Mutex::new(VolatileRef::new( + NonNull::new(ports_addr.add(port_index as usize)).unwrap(), + )) + }); + } + + let operational = Self { + operational: Mutex::new(unsafe { + VolatileRef::new(NonNull::new(operational_ptr).unwrap()) + }), + ports, + }; + + (operational, capabilities) + } + + pub fn update(&self, f: impl Fn(HostControllerOperational) -> HostControllerOperational) { + self.operational.lock().as_mut_ptr().update(f); + } + + pub fn status(&self) -> UsbStatus { + self.operational.lock().as_ptr().read().usb_status + } + + pub fn command(&self) -> UsbCommand { + self.operational.lock().as_ptr().read().usb_command + } + + pub fn get_port(&self, index: usize) -> HostControllerUsbPort { + self.ports[index].lock().as_ptr().read() + } + + pub fn update_port_status( + &self, + index: usize, + f: impl Fn(PortStatusAndControl) -> PortStatusAndControl, + ) { + let mut port_ref = self.ports[index].lock(); + let ptr = port_ref.as_mut_ptr(); + map_field!(ptr.status_and_control).update(f); + } + + pub fn num_ports(&self) -> usize { + self.ports.len() + } +} diff --git a/rust/sys/voyageurs/src/xhci/registers/host_controller_port.rs b/rust/sys/voyageurs/src/xhci/registers/host_controller_port.rs index 369c4dc..27f1f45 100644 --- a/rust/sys/voyageurs/src/xhci/registers/host_controller_port.rs +++ b/rust/sys/voyageurs/src/xhci/registers/host_controller_port.rs @@ -9,13 +9,15 @@ use bitfield_struct::bitfield; /// XHCI Spec 5.4.8 #[bitfield(u32)] pub struct PortStatusAndControl { - /// A host controller shall implement one or more port registers. The number of - /// port registers implemented by a particular instantiation of a host controller is - /// documented in the HCSPARAMS1 register (Section 5.3.3). Software uses this - /// information as an input parameter to determine how many ports need to be - /// serviced. All ports have the structure defined below. + /// Current Connect Status (CCS) – ROS. Default = ‘0’. ‘1’ = A device is connected81 to the port. ‘0’ = + /// A device is not connected. This value reflects the current state of the port, and may not + /// correspond directly to the event that caused the Connect Status Change (CSC) bit to be set to ‘1’. + /// + /// Refer to sections 4.19.3 and 4.19.4 for more details on the Connect Status Change (CSC) + /// assertion conditions. + /// This flag is ‘0’ if PP is ‘0’. #[bits(access=RO)] - current_connect_status: bool, + pub current_connect_status: bool, /// Port Enabled/Disabled (PED) – RW1CS. Default = ‘0’. ‘1’ = Enabled. ‘0’ = Disabled. /// Ports may only be enabled by the xHC. Software cannot enable a port by writing a ‘1’ to this flag. @@ -43,9 +45,9 @@ pub struct PortStatusAndControl { /// from ‘1’ to ‘0’ after a successful reset. Refer to Port Reset (PR) bit for more information on how /// the PED bit is managed. /// - /// Note that when software writes this bit to a ‘1’, it shall also write a ‘0’ to the PR bit82. + /// Note that when software writes this bit to a ‘1’, it shall also write a ‘0’ to the PR bit. /// This flag is ‘0’ if PP is ‘0’. - port_enabled_disabled: bool, + pub port_enabled_disabled: bool, __: bool, @@ -53,7 +55,7 @@ pub struct PortStatusAndControl { /// condition. ‘0’ = This port does not have an over-current condition. This bit shall automatically /// transition from a ‘1’ to a ‘0’ when the over-current condition is removed. #[bits(access=RO)] - over_current_active: bool, + pub over_current_active: bool, /// Port Reset (PR) – RW1S. Default = ‘0’. ‘1’ = Port Reset signaling is asserted. ‘0’ = Port is not in /// Reset. When software writes a ‘1’ to this bit generating a ‘0’ to ‘1’ transition, the bus reset @@ -65,7 +67,7 @@ pub struct PortStatusAndControl { /// the Enabled state. Refer to sections 4.15.2.3 and 4.19.1.1. /// /// This flag is ‘0’ if PP is ‘0’. - port_reset: bool, + pub port_reset: bool, /// Port Link State (PLS) – RWS. Default = RxDetect (‘5’). This field is used to power manage the port /// and reflects its current link state. @@ -128,7 +130,7 @@ pub struct PortStatusAndControl { /// USB2 LPM ECR for more information on USB link power management operation. Refer to section /// 7.2 for supported USB protocols #[bits(4)] - port_link_status: u8, + pub port_link_status: u8, /// Port Power (PP) – RWS. Default = ‘1’. This flag reflects a port's logical, power control state. /// Because host controllers can implement different methods of port power switching, this flag may /// or may not represent whether (VBus) power is actually applied to the port. When PP equals a '0' @@ -156,7 +158,7 @@ pub struct PortStatusAndControl { /// /// Refer to section 5.1.2 in the SSIC Spec for more information. /// Refer to section 4.19.4 for more information. - port_power: bool, + pub port_power: bool, /// Port Speed (Port Speed) – ROS. Default = ‘0’. This field identifies the speed of the connected /// USB Device. This field is only relevant if a device is connected (CCS = ‘1’) in all other cases this /// field shall indicate Undefined Speed. Refer to section 4.19.3. @@ -168,7 +170,7 @@ pub struct PortStatusAndControl { /// /// Note: This field is invalid on a USB2 protocol port until after the port is reset. #[bits(4)] - port_speed: u8, + pub port_speed: u8, /// Port Indicator Control (PIC) – RWS. Default = 0. Writing to these bits has no effect if the Port /// Indicators (PIND) bit in the HCCPARAMS1 register is a ‘0’. If PIND bit is a ‘1’, then the bit /// encodings are: @@ -182,11 +184,11 @@ pub struct PortStatusAndControl { /// Refer to the USB2 Specification section 11.5.3 for a description on how these bits shall be used. /// This field is ‘0’ if PP is ‘0’ #[bits(2)] - port_indicator_control: u8, + pub port_indicator_control: u8, /// Port Link State Write Strobe (LWS) – RW. Default = ‘0’. When this bit is set to ‘1’ on a write /// reference to this register, this flag enables writes to the PLS field. When ‘0’, write data in PLS field /// is ignored. Reads to this bit return ‘0’ - port_link_state_write_strobe: bool, + pub port_link_state_write_strobe: bool, /// Connect Status Change (CSC) – RW1CS. Default = ‘0’. ‘1’ = Change in CCS. ‘0’ = No change. This /// flag indicates a change has occurred in the port’s Current Connect Status (CCS) or Cold Attach /// Status (CAS) bits. Note that this flag shall not be set if the CCS transition was due to software @@ -196,7 +198,7 @@ pub struct PortStatusAndControl { /// before system software has cleared the changed condition, root hub hardware will be “setting” /// an already-set bit (i.e., the bit will remain ‘1’). Software shall clear this bit by writing a ‘1’ to it. /// Refer to section 4.19.2 for more information on change bit usage. - connect_status_change: bool, + pub connect_status_change: bool, /// Port Enabled/Disabled Change (PEC) – RW1CS. Default = ‘0’. ‘1’ = change in PED. ‘0’ = No /// change. Note that this flag shall not be set if the PED transition was due to software setting PP to /// ‘0’. Software shall clear this bit by writing a ‘1’ to it. Refer to section 4.19.2 for more information @@ -207,7 +209,7 @@ pub struct PortStatusAndControl { /// /// Specification for the definition of a Port Error). /// For a USB3 protocol port, this bit shall never be set to ‘1’. - port_enabled_disabled_change: bool, + pub port_enabled_disabled_change: bool, /// Warm Port Reset Change (WRC) – RW1CS/RsvdZ. Default = ‘0’. This bit is set when Warm Reset /// processing on this port completes. ‘0’ = No change. ‘1’ = Warm Reset complete. Note that this /// flag shall not be set to ‘1’ if the Warm Reset processing was forced to terminate due to software @@ -215,18 +217,18 @@ pub struct PortStatusAndControl { /// Refer to section 4.19.2 for more information on change bit usage. /// /// This bit only applies to USB3 protocol ports. For USB2 protocol ports it shall be RsvdZ. - warm_port_reset_change: bool, + pub warm_port_reset_change: bool, /// Over-current Change (OCC) – RW1CS. Default = ‘0’. This bit shall be set to a ‘1’ when there is a ‘0’ /// to ‘1’ or ‘1’ to ‘0’ transition of Over-current Active (OCA). Software shall clear this bit by writing a /// ‘1’ to it. Refer to section 4.19.2 for more information on change bit usage. - over_current_change: bool, + pub over_current_change: bool, /// Port Reset Change (PRC) – RW1CS. Default = ‘0’. This flag is set to ‘1’ due to a '1' to '0' transition /// of Port Reset (PR). e.g. when any reset processing (Warm or Hot) on this port is complete. Note /// that this flag shall not be set to ‘1’ if the reset processing was forced to terminate due to software /// clearing PP or PED to '0'. ‘0’ = No change. ‘1’ = Reset complete. Software shall clear this bit by /// writing a '1' to it. Refer to section 4.19.5. Refer to section 4.19.2 for more information on change /// bit usage - port_reset_change: bool, + pub port_reset_change: bool, /// Port Link State Change (PLC) – RW1CS. Default = ‘0’. This flag is set to ‘1’ due to the following /// PLS transitions: /// @@ -256,7 +258,7 @@ pub struct PortStatusAndControl { /// writing a '1' to it. Refer to “PLC Condition:” references in section 4.19.1 /// for the specific port state transitions that set this flag. Refer to section /// 4.19.2 for more information on change bit usage. - port_link_state_change: bool, + pub port_link_state_change: bool, /// Port Config Error Change (CEC) – RW1CS/RsvdZ. Default = ‘0’. This flag indicates that the port /// failed to configure its link partner. 0 = No change. 1 = Port Config Error detected. Software shall /// clear this bit by writing a '1' to it. Refer to section 4.19.2 for more information on change bit @@ -264,7 +266,7 @@ pub struct PortStatusAndControl { /// /// Note: This flag is valid only for USB3 protocol ports. For USB2 protocol ports this bit shall be /// RsvdZ. - port_config_error_change: bool, + pub port_config_error_change: bool, /// Cold Attach Status (CAS) – RO. Default = ‘0’. ‘1’ = Far-end Receiver Terminations were detected /// in the Disconnected state and the Root Hub Port State Machine was unable to advance to the /// Enabled state. Refer to sections 4.19.8 for more details on the Cold Attach Status (CAS) assertion @@ -272,31 +274,48 @@ pub struct PortStatusAndControl { /// transitions to ‘1’. /// This flag is ‘0’ if PP is ‘0’ or for USB2 protocol ports #[bits(access=RO)] - cold_attach_status: bool, + pub cold_attach_status: bool, /// Wake on Connect Enable (WCE) – RWS. Default = ‘0’. Writing this bit to a ‘1’ enables the port to /// be sensitive to device connects as system wake-up events96. Refer to section 4.15 for operational /// model. - wake_on_connect_enable: bool, + pub wake_on_connect_enable: bool, /// Wake on Disconnect Enable (WDE) – RWS. Default = ‘0’. Writing this bit to a ‘1’ enables the port /// to be sensitive to device disconnects as system wake-up events. Refer to section 4.15 for /// operational model. - wake_on_disconnect_enable: bool, + pub wake_on_disconnect_enable: bool, /// Wake on Over-current Enable (WOE) – RWS. Default = ‘0’. Writing this bit to a ‘1’ enables the /// port to be sensitive to over-current conditions as system wake-up events96. Refer to section 4.15 /// for operational model. - wake_on_overcurrent_enable: bool, + pub wake_on_overcurrent_enable: bool, __: bool, __: bool, /// Device Removable97 (DR) - RO. This flag indicates if this port has a removable device attached. /// ‘1’ = Device is non-removable. ‘0’ = Device is removable. #[bits(access=RO)] - device_removable: bool, + pub device_removable: bool, /// Warm Port Reset (WPR) – RW1S/RsvdZ. Default = ‘0’. When software writes a ‘1’ to this bit, the /// Warm Reset sequence as defined in the USB3 Specification is initiated and the PR flag is set to ‘1’. /// Once initiated, the PR, PRC, and WRC flags shall reflect the progress of the Warm Reset /// sequence. This flag shall always return ‘0’ when read. Refer to section 4.19.5.1. /// This flag only applies to USB3 protocol ports. For USB2 protocol ports it shall be RsvdZ. - warm_port_reset: bool, + pub warm_port_reset: bool, +} + +impl PortStatusAndControl { + pub fn clear_change_bits(&self) -> Self { + PortStatusAndControl::new() + .with_connect_status_change(true) + .with_port_enabled_disabled_change(true) + .with_warm_port_reset_change(true) + .with_over_current_change(true) + .with_port_reset_change(true) + .with_port_link_state_change(true) + .with_port_config_error_change(true) + .with_port_power(self.port_power()) + .with_wake_on_connect_enable(self.wake_on_connect_enable()) + .with_wake_on_disconnect_enable(self.wake_on_disconnect_enable()) + .with_wake_on_overcurrent_enable(self.wake_on_overcurrent_enable()) + } } /// XHCI Spec 5.4.9 @@ -384,12 +403,13 @@ pub struct PortLinkInfo { /// from the HostControllerOperation address. /// /// Where MAX_PORTS is HostControllerCapabilities.params_1.max_ports -#[repr(C, packed)] +#[repr(C)] +#[derive(Copy, Clone)] pub struct HostControllerUsbPort { - status_and_control: PortStatusAndControl, - power_management_status_and_control: PortPowerManagementStatusAndControl, - link_info: PortLinkInfo, - hardware_lpm_control: u32, + pub status_and_control: PortStatusAndControl, + pub power_management_status_and_control: PortPowerManagementStatusAndControl, + pub link_info: PortLinkInfo, + pub hardware_lpm_control: u32, } const _: () = assert!(size_of::() == 0x10); diff --git a/rust/sys/voyageurs/src/xhci/registers/interrupter.rs b/rust/sys/voyageurs/src/xhci/registers/interrupter.rs index 69fc188..8ff8981 100644 --- a/rust/sys/voyageurs/src/xhci/registers/interrupter.rs +++ b/rust/sys/voyageurs/src/xhci/registers/interrupter.rs @@ -1,5 +1,7 @@ use bitfield_struct::bitfield; +use crate::xhci::data_structures::EventRingSegmentTable; + /// The Interrupter Management register allows system software to enable, disable, /// and detect xHC interrupts. /// @@ -49,16 +51,6 @@ pub struct InterrupterModeration { /// XHCI 5.5.2.3.1 #[bitfield(u32)] pub struct EventRingSegmentTableSize { - /// Event Ring Segment Table Size – RW. Default = ‘0’. This field identifies the number of valid - /// Event Ring Segment Table entries in the Event Ring Segment Table pointed to by the Event Ring - /// Segment Table Base Address register. The maximum value supported by an xHC - /// implementation for this register is defined by the ERST Max field in the HCSPARAMS2 register - /// (5.3.4). - /// For Secondary Interrupters: Writing a value of ‘0’ to this field disables the Event Ring. Any events - /// targeted at this Event Ring when it is disabled shall result in undefined behavior of the Event - /// Ring. - /// For the Primary Interrupter: Writing a value of ‘0’ to this field shall result in undefined behavior - /// of the Event Ring. The Primary Event Ring cannot be disabled. pub event_ring_segment_table_size: u16, _reserved: u16, } @@ -98,9 +90,18 @@ pub struct EventRingDequePointer { pub struct InterrupterRegisterSet { pub interrupter_management: InterrupterManagement, pub interrupter_moderation: InterrupterModeration, - pub event_ring_segement_table_size: EventRingSegmentTableSize, - _reserved: u32, - + /// Event Ring Segment Table Size – RW. Default = ‘0’. This field identifies the number of valid + /// Event Ring Segment Table entries in the Event Ring Segment Table pointed to by the Event Ring + /// Segment Table Base Address register. The maximum value supported by an xHC + /// implementation for this register is defined by the ERST Max field in the HCSPARAMS2 register + /// (5.3.4). + /// For Secondary Interrupters: Writing a value of ‘0’ to this field disables the Event Ring. Any events + /// targeted at this Event Ring when it is disabled shall result in undefined behavior of the Event + /// Ring. + /// For the Primary Interrupter: Writing a value of ‘0’ to this field shall result in undefined behavior + /// of the Event Ring. The Primary Event Ring cannot be disabled. + event_ring_segment_table_size: u32, + ___: u32, /// Event Ring Segment Table Base Address Register – RW. Default = ‘0’. This field defines the /// high order bits of the start address of the Event Ring Segment Table. /// Writing this register sets the Event Ring State Machine:EREP Advancement to the Start state. @@ -111,6 +112,54 @@ pub struct InterrupterRegisterSet { /// NOTE: This must be aligned such that bits 0:5 are 0. /// /// XHCI 5.5.2.3.2 - pub event_ring_segment_table_base_address: u64, - pub event_ring_deque_pointer: u64, + event_ring_segment_table_base_address: u64, + event_ring_deque_pointer: u64, } + +impl InterrupterRegisterSet { + /// SAFETY: + /// - For the primary interrupter HC must be halted. + /// - The event rings size must be at most ERST_MAX from HCSPARAMS2 + pub unsafe fn set_event_ring( + &mut self, + event_ring_segment_table: &EventRingSegmentTable, + event_ring_dequeue_pointer: usize, + ) { + // NOTE: We must write the size before the base address otherwise qemu is unhappy. + // Not sure if this is required by the spec. + + // SAFETY: + // - We know this address is valid and we have a mut reference to it. + unsafe { + core::ptr::write_volatile( + core::ptr::addr_of!(self.event_ring_segment_table_size) as *mut _, + event_ring_segment_table.len() as u32, + ); + + core::ptr::write_volatile( + core::ptr::addr_of!(self.event_ring_segment_table_base_address) as *mut _, + event_ring_segment_table.physical_address(), + ); + + core::ptr::write_volatile( + core::ptr::addr_of!(self.event_ring_deque_pointer) as *mut _, + event_ring_dequeue_pointer, + ); + } + } + + pub fn update_dequeue_pointer(&mut self, event_ring_dequeue_pointer: usize) { + // SAFETY: + // - We know this address is valid and we have a mut pointer to it. + unsafe { + // TODO: Preserve lower bits, also update it to make it clear that we + // are clearing the EHB bit. + core::ptr::write_volatile( + core::ptr::addr_of!(self.event_ring_deque_pointer) as *mut _, + event_ring_dequeue_pointer | (1 << 3), + ) + } + } +} + +const _: () = assert!(size_of::() == 0x20); 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..64df389 --- /dev/null +++ b/rust/sys/voyageurs/src/xhci/trb_ring.rs @@ -0,0 +1,163 @@ +use core::task::{Poll, Waker}; + +use alloc::{collections::vec_deque::VecDeque, sync::Arc, vec::Vec}; +use mammoth::sync::Mutex; + +use crate::xhci::data_structures::{TransferRequestBlock, TrbLink, TrbRingSegment, TypedTrb}; + +struct TrbFutureState { + /// Physical Address for the enqueued TRB. + /// Used for sanity checking. + physical_address: usize, + + waker: Option, + response: Option, +} + +#[derive(Clone)] +pub struct TrbFuture { + state: Arc>>, +} + +impl TrbFuture { + fn new(paddr: usize) -> Self { + Self { + state: Arc::new(Mutex::new(TrbFutureState { + physical_address: paddr, + waker: None, + response: None, + })), + } + } +} + +impl Future for TrbFuture { + type Output = T; + + fn poll( + self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> core::task::Poll { + let mut state = self.state.lock(); + match state.response { + Some(trb) => Poll::Ready(trb), + None => { + state.waker = Some(cx.waker().clone()); + Poll::Pending + } + } + } +} + +#[derive(Default, Copy, Clone, Debug)] +pub struct TrbPointer { + /// Index into the vector of trb segments. + pub segment_index: usize, + /// Index into the specific segment. + /// This is a TransferRequestBlock index, + /// to get the physical_offset use segment_physical_offset() + pub segment_offset: usize, +} + +impl TrbPointer { + pub fn segment_physical_offset(&self) -> usize { + self.segment_offset * size_of::() + } +} + +pub struct TrbRing { + segments: Vec, + enqueue_pointer: TrbPointer, + cycle_bit: bool, + pending_futures: VecDeque>, +} + +impl TrbRing { + pub fn new() -> Self { + Self { + // TODO: What size and count should this be. + segments: alloc::vec![TrbRingSegment::new(100)], + enqueue_pointer: TrbPointer::default(), + // Start with this as true so we are flipping bits from 0 (default) to 1 + // to mark the enqueue pointer. + cycle_bit: true, + pending_futures: VecDeque::new(), + } + } + + pub fn physical_base_address(&self) -> usize { + self.segments[0].physical_address() + } + + fn physical_address_of_enqueue_pointer(&self) -> usize { + self.segments[self.enqueue_pointer.segment_index].physical_address() + + self.enqueue_pointer.segment_physical_offset() + } + + pub fn enqueue_trb(&mut self, trb: TransferRequestBlock) -> TrbFuture { + let paddr = self.physical_address_of_enqueue_pointer(); + *self.next_trb_ref() = trb.with_cycle(self.cycle_bit); + self.advance_enqueue_pointer(); + let future = TrbFuture::new(paddr); + self.pending_futures.push_back(future.clone()); + future + } + + fn next_trb_ref(&mut self) -> &mut TransferRequestBlock { + &mut self.segments[self.enqueue_pointer.segment_index][self.enqueue_pointer.segment_offset] + } + + fn advance_enqueue_pointer(&mut self) { + self.enqueue_pointer.segment_offset += 1; + + if self.enqueue_pointer.segment_offset + == self.segments[self.enqueue_pointer.segment_index].len() - 1 + { + // We have reached the end of the segment, insert a link trb. + + // Increment the segment index with wrapping. + let next_segment_index = + if self.enqueue_pointer.segment_index + 1 == self.segments.len() { + 0 + } else { + self.enqueue_pointer.segment_index + 1 + }; + + let next_segment_pointer = self.segments[next_segment_index].physical_address(); + let toggle_cycle = next_segment_index == 0; + + *self.next_trb_ref() = TrbLink::new() + .with_ring_segment_pointer(next_segment_pointer as u64) + .with_cycle(self.cycle_bit) + .with_toggle_cycle(toggle_cycle) + .to_trb(); + + // Flip toggle cycle bit if necessary. + self.cycle_bit ^= toggle_cycle; + + self.enqueue_pointer = TrbPointer { + segment_index: next_segment_index, + segment_offset: 0, + }; + } + } + + pub fn handle_commpletion(&mut self, completion_trb: T) { + let trb = completion_trb.to_trb(); + let paddr = trb.parameter() as usize; + let completion = self.pending_futures.pop_front().unwrap(); + let mut completion = completion.state.lock(); + // TODO: Handle recovery scenarios here. + assert!( + completion.physical_address == paddr, + "Got an unexpected command completion. Expected: {:0x}, Got: {:0x}", + completion.physical_address, + paddr + ); + completion.response = Some(completion_trb); + + if let Some(waker) = &completion.waker { + waker.wake_by_ref(); + } + } +} diff --git a/scripts/qemu.sh b/scripts/qemu.sh index 22d6f98..a68570d 100755 --- a/scripts/qemu.sh +++ b/scripts/qemu.sh @@ -18,7 +18,7 @@ if [[ $1 == "debug" ]]; then fi # Use machine q35 to access PCI devices. -qemu-system-x86_64 -machine q35 -d guest_errors -m 1G -serial stdio -hda ${BUILD_DIR}/disk.img ${QEMU_ARGS} -device nec-usb-xhci,id=xhci -device usb-kbd,bus=xhci.0 +~/.local/bin/qemu-system-x86_64 -machine q35 -d guest_errors -m 1G -serial stdio -hda ${BUILD_DIR}/disk.img ${QEMU_ARGS} -device nec-usb-xhci,id=xhci -device usb-kbd,bus=xhci.0 popd # Extra options to add to this script in the future. diff --git a/zion/interrupt/driver_manager.cpp b/zion/interrupt/driver_manager.cpp index 3aa2bc5..b991004 100644 --- a/zion/interrupt/driver_manager.cpp +++ b/zion/interrupt/driver_manager.cpp @@ -18,6 +18,8 @@ void DriverManager::WriteMessage(uint64_t irq_num, IpcMessage&& message) { return; } + dbgln("IRQ offset {x}", offset); + driver_list_[offset]->Send(glcr::Move(message)); }