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

View file

@ -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 = []

View file

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

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;
pub mod registers;
mod data_structures;
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
}
}