Rust XHCI Implementation.

This commit is contained in:
Drew 2025-12-05 22:01:13 -08:00
parent 0b95098748
commit a10c615b95
10 changed files with 438 additions and 30 deletions

64
' Normal file
View file

@ -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<I> Index<I> for TrbRing {
type Output = TransferRequestBlock;
fn index(&self, index: I) -> &Self::Output {
self.0[index]
}
}
impl<I> IndexMut<I> for TrbRing
where
I: SliceIndex<[TransferRequestBlock]>,
{
fn index_mut(&mut self, index: I) -> &mut Self::Output {
&mut self.0[index]
}
}

37
rust/Cargo.lock generated
View file

@ -2,12 +2,6 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 4 version = 4
[[package]]
name = "autocfg"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]] [[package]]
name = "bitfield-struct" name = "bitfield-struct"
version = "0.8.0" version = "0.8.0"
@ -80,11 +74,10 @@ dependencies = [
[[package]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.4.12" version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [ dependencies = [
"autocfg",
"scopeguard", "scopeguard",
] ]
@ -105,9 +98,9 @@ dependencies = [
[[package]] [[package]]
name = "prettyplease" name = "prettyplease"
version = "0.2.20" version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"syn", "syn",
@ -115,18 +108,18 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.86" version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.36" version = "1.0.42"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@ -148,9 +141,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.72" version = "2.0.111"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -178,15 +171,15 @@ dependencies = [
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.12" version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]] [[package]]
name = "unicode-segmentation" name = "unicode-segmentation"
version = "1.11.0" version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]] [[package]]
name = "victoriafalls" name = "victoriafalls"
@ -206,6 +199,8 @@ version = "0.1.0"
dependencies = [ dependencies = [
"bitfield-struct 0.12.1", "bitfield-struct 0.12.1",
"mammoth", "mammoth",
"pci",
"yellowstone-yunq",
] ]
[[package]] [[package]]

View file

@ -72,15 +72,15 @@ impl PciDevice {
control.capable_address_64(), control.capable_address_64(),
"We don't handle the non-64bit case for MSI yet." "We don't handle the non-64bit case for MSI yet."
); );
assert!(
control.multi_message_capable() == 0, if control.multi_message_capable() != 0 {
"We don't yet handle multi-message capable devices." mammoth::debug!("WARN: We don't yet handle multi-message capable devices.");
); }
// FIXME: These probably need to be volatile writes. // FIXME: These probably need to be volatile writes.
let header: &mut PciDeviceHeader = self.memory_region.as_mut(); let header: &mut PciDeviceHeader = self.memory_region.as_mut();
header.command = header.command.with_interrupt_disable(true); 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 // For setting addr and data field, see intel ref
// Vol 3. Section 11.11 // Vol 3. Section 11.11

View file

@ -6,3 +6,9 @@ edition = "2024"
[dependencies] [dependencies]
bitfield-struct = "0.12" bitfield-struct = "0.12"
mammoth = { path = "../../lib/mammoth/" } mammoth = { path = "../../lib/mammoth/" }
pci = { path = "../../lib/pci" }
yellowstone-yunq = { version = "0.1.0", path = "../../lib/yellowstone" }
[features]
default = ["debug"]
debug = []

View file

@ -5,12 +5,28 @@ extern crate alloc;
mod xhci; 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!(); define_entry!();
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
extern "C" fn main() -> z_err_t { 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 0
} }

View file

@ -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
}
}

View file

@ -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) -> &registers::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.");
}
}

View file

@ -0,0 +1,25 @@
use alloc::vec::Vec;
use crate::xhci::data_structures::{EventRingSegmentTable, TrbRing};
pub struct EventRing {
segment_table: EventRingSegmentTable,
segments: Vec<TrbRing>,
}
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
}
}

View file

@ -1,2 +1,6 @@
pub mod data_structures; mod data_structures;
pub mod registers; mod device_context_base_array;
pub mod driver;
mod event_ring;
mod registers;
mod trb_ring;

View file

@ -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
}
}