Rust XHCI Register Types.

This commit is contained in:
Drew 2025-05-18 19:26:06 -07:00
parent b9cd550a63
commit 22b43de6dc
10 changed files with 1596 additions and 0 deletions

View file

@ -0,0 +1,505 @@
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 {
/// Run/Stop (R/S) RW. Default = 0. 1 = Run. 0 = Stop. When set to a 1, the xHC proceeds with
/// execution of the schedule. The xHC continues execution as long as this bit is set to a 1. When
/// this bit is cleared to 0, the xHC completes any current or queued commands or TDs, and any
/// USB transactions associated with them, then halts.
///
/// Refer to section 5.4.1.1 for more information on how R/S shall be managed.
///
/// The xHC shall halt within 16 ms. after software clears the Run/Stop bit if the above conditions
/// have been met.
///
/// The HCHalted (HCH) bit in the USBSTS register indicates when the xHC has finished its pending
/// pipelined transactions and has entered the stopped state. Software shall not write a 1 to this
/// flag unless the xHC is in the Halted state (i.e. HCH in the USBSTS register is 1). Doing so may
/// yield undefined results. Writing a 0 to this flag when the xHC is in the Running state (i.e. HCH =
/// 0) and any Event Rings are in the Event Ring Full state (refer to section 4.9.4) may result in lost
/// events.
///
/// When this register is exposed by a Virtual Function (VF), this bit only controls the run state of
/// the xHC instance presented by the selected VF. Refer to section 8 for more information.
pub run_stop: bool,
/// Host Controller Reset (HCRST) RW. Default = 0. This control bit is used by software to reset
/// the host controller. The effects of this bit on the xHC and the Root Hub registers are similar to a
/// Chip Hardware Reset.
///
/// When software writes a 1 to this bit, the Host Controller resets its internal pipelines, timers,
/// counters, state machines, etc. to their initial value. Any transaction currently in progress on the
/// USB is immediately terminated. A USB reset shall not be driven on USB2 downstream ports,
/// however a Hot or Warm Reset79 shall be initiated on USB3 Root Hub downstream ports.
///
/// PCI Configuration registers are not affected by this reset. All operational registers, including port
/// registers and port state machines are set to their initial values. Software shall reinitialize the
/// host controller as described in Section 4.2 in order to return the host controller to an
/// operational state.
///
/// This bit is cleared to 0 by the Host Controller when the reset process is complete. Software
/// cannot terminate the reset process early by writing a 0 to this bit and shall not write any xHC
/// Operational or Runtime registers until while HCRST is 1. Note, the completion of the xHC reset
/// process is not gated by the Root Hub port reset process.
///
/// Software shall not set this bit to 1 when the HCHalted (HCH) bit in the USBSTS register is a 0.
/// Attempting to reset an actively running host controller may result in undefined behavior.
///
/// When this register is exposed by a Virtual Function (VF), this bit only resets the xHC instance
/// presented by the selected VF. Refer to section 8 for more information
pub host_controller_reset: bool,
/// Interrupter Enable (INTE) RW. Default = 0. This bit provides system software with a means of
/// enabling or disabling the host system interrupts generated by Interrupters. When this bit is a 1,
/// then Interrupter host system interrupt generation is allowed, e.g. the xHC shall issue an interrupt
/// at the next interrupt threshold if the host system interrupt mechanism (e.g. MSI, MSI-X, etc.) is
/// enabled. The interrupt is acknowledged by a host system interrupt specific mechanism.
///
/// When this register is exposed by a Virtual Function (VF), this bit only enables the set of
/// Interrupters assigned to the selected VF. Refer to section 7.7.2 for more information.
pub interrupter_enable: bool,
/// Host System Error Enable (HSEE) RW. Default = 0. When this bit is a 1, and the HSE bit in
/// the USBSTS register is a 1, the xHC shall assert out-of-band error signaling to the host. The
/// signaling is acknowledged by software clearing the HSE bit. Refer to section 4.10.2.6 for more
/// information.
/// When this register is exposed by a Virtual Function (VF), the effect of the assertion of this bit on
/// the Physical Function (PF0) is determined by the VMM. Refer to section 8 for more information
pub host_system_error_enable: bool,
#[bits(3)]
__: u8,
/// Light Host Controller Reset (LHCRST) RO or RW. Optional normative. Default = 0. If the Light
/// HC Reset Capability (LHRC) bit in the HCCPARAMS1 register is 1, then this flag allows the driver
/// to reset the xHC without affecting the state of the ports.
///
/// A system software read of this bit as 0 indicates the Light Host Controller Reset has completed
/// and it is safe for software to re-initialize the xHC. A software read of this bit as a 1 indicates the
/// Light Host Controller Reset has not yet completed.
///
/// If not implemented, a read of this flag shall always return a 0.
///
/// All registers in the Aux Power well shall maintain the values that had been asserted prior to the
/// Light Host Controller Reset. Refer to section 4.23.1 for more information.
///
/// When this register is exposed by a Virtual Function (VF), this bit only generates a Light Reset to
/// the xHC instance presented by the selected VF, e.g. Disable the VFs device slots and set the
/// associated VF Run bit to Stopped. Refer to section 8 for more information.
pub light_host_controller_reset: bool,
/// Controller Save State (CSS) - RW. Default = 0. When written by software with 1 and HCHalted
/// (HCH) = 1, then the xHC shall save any internal state (that may be restored by a subsequent
/// Restore State operation) and if FSC = '1' any cached Slot, Endpoint, Stream, or other Context
/// information (so that software may save it). When written by software with 1 and HCHalted
/// (HCH) = 0, or written with 0, no Save State operation shall be performed. This flag always
/// returns 0 when read. Refer to the Save State Status (SSS) flag in the USBSTS register for
/// information on Save State completion. Refer to section 4.23.2 for more information on xHC
///
/// Save/Restore operation. Note that undefined behavior may occur if a Save State operation is
/// initiated while Restore State Status (RSS) = 1.
///
/// When this register is exposed by a Virtual Function (VF), this bit only controls saving the state of
/// the xHC instance presented by the selected VF. Refer to section 8 for more information.
pub controller_save_state: bool,
/// Controller Restore State (CRS) - RW. Default = 0. When set to 1, and HCHalted (HCH) = 1,
/// then the xHC shall perform a Restore State operation and restore its internal state. When set to
/// 1 and Run/Stop (R/S) = 1 or HCHalted (HCH) = 0, or when cleared to 0, no Restore State
/// operation shall be performed. This flag always returns 0 when read. Refer to the Restore State
/// Status (RSS) flag in the USBSTS register for information on Restore State completion. Refer to
/// section 4.23.2 for more information. Note that undefined behavior may occur if a Restore State
/// operation is initiated while Save State Status (SSS) = 1.
/// When this register is exposed by a Virtual Function (VF), this bit only controls restoring the state
/// of the xHC instance presented by the selected VF. Refer to section 8 for more information.
pub controller_restore_state: bool,
/// Enable Wrap Event (EWE) - RW. Default = 0. When set to 1, the xHC shall generate a MFINDEX
/// Wrap Event every time the MFINDEX register transitions from 03FFFh to 0. When cleared to 0
/// no MFINDEX Wrap Events are generated. Refer to section 4.14.2 for more information.
///
/// When this register is exposed by a Virtual Function (VF), the generation of MFINDEX Wrap
/// Events to VFs shall be emulated by the VMM.
pub enable_wrap_event: bool,
/// Enable U3 MFINDEX Stop (EU3S) - RW. Default = 0. When set to 1, the xHC may stop the
/// MFINDEX counting action if all Root Hub ports are in the U3, Disconnected, Disabled, or
/// Powered-off state. When cleared to 0 the xHC may stop the MFINDEX counting action if all
/// Root Hub ports are in the Disconnected, Disabled, Training, or Powered-off state. Refer to
/// section 4.14.2 for more information
pub enable_u3_mfindex_stop: bool,
___: bool,
/// CEM Enable (CME) - RW. Default = '0'. When set to '1', a Max Exit Latency Too Large Capability
/// Error may be returned by a Configure Endpoint Command. When cleared to '0', a Max Exit
/// Latency Too Large Capability Error shall not be returned by a Configure Endpoint Command.
/// This bit is Reserved if CMC = 0. Refer to section 4.23.5.2.2 for more information.
pub cem_enable: bool,
/// Extended TBC Enable (ETE). This flag indicates that the host controller implementation is
/// enabled to support Transfer Burst Count (TBC) values greater that 4 in isoch TDs. When this bit
/// is 1, the Isoch TRB TD Size/TBC field presents the TBC value, and the TBC/RsvdZ field is RsvdZ.
/// When this bit is 0, the TDSize/TCB field presents the TD Size value, and the TBC/RsvdZ field
/// presents the TBC value. This bit may be set only if ETC = 1. Refer to section 4.11.2.3 for more
/// information.
pub extended_tbc_enable: bool,
/// Extended TBC TRB Status Enable (TSC_EN). This flag indicates that the host controller
/// implementation is enabled to support ETC_TSC capability. When this is 1, TRBSts field in the
/// TRB updated to indicate if it is last transfer TRB in the TD. This bit may be set only if
/// ETC_TSC=1. Refer to section 4.11.2.3 for more information.
pub extended_tbc_trb_status_enable: bool,
/// VTIO Enable (VTIOE) RW. Default = 0. When set to 1, XHCI HW will enable its VTIO
/// capability and begin to use the information provided via that VTIO Registers to determine its
/// DMA-ID. When cleared to 0, XHCI HW will use the Primary DMA-ID for all accesses. This bit
/// may be set only if VTC = 1.
pub vtio_enable: bool,
#[bits(15)]
____: u16,
}
#[bitfield(u32)]
pub struct UsbStatus {
/// HCHalted (HCH) RO. Default = 1. This bit is a 0 whenever the Run/Stop (R/S) bit is a 1. The
/// xHC sets this bit to 1 after it has stopped executing as a result of the Run/Stop (R/S) bit being
/// cleared to 0, either by software or by the xHC hardware (e.g. internal error).
///
/// If this bit is '1', then SOFs, microSOFs, or Isochronous Timestamp Packets (ITP) shall not be
/// generated by the xHC, and any received Transaction Packet shall be dropped.
///
/// When this register is exposed by a Virtual Function (VF), this bit only reflects the Halted state of
/// the xHC instance presented by the selected VF. Refer to section 8 for more information
#[bits(access=RO)]
pub host_controller_halted: bool,
__: bool,
/// Host System Error (HSE) RW1C. Default = 0. The xHC sets this bit to 1 when a serious error
/// is detected, either internal to the xHC or during a host system access involving the xHC module.
/// (In a PCI system, conditions that set this bit to 1 include PCI Parity error, PCI Master Abort, and
/// PCI Target Abort.) When this error occurs, the xHC clears the Run/Stop (R/S) bit in the USBCMD
/// register to prevent further execution of the scheduled TDs. If the HSEE bit in the USBCMD
/// register is a 1, the xHC shall also assert out-of-band error signaling to the host. Refer to section
/// 4.10.2.6 for more information.
/// When this register is exposed by a Virtual Function (VF), the assertion of this bit affects all VFs
/// and reflects the Host System Error state of the Physical Function (PF0). Refer to section 8 for
/// more information.
pub host_system_error: bool,
/// Event Interrupt (EINT) RW1C. Default = 0. The xHC sets this bit to 1 when the Interrupt
/// Pending (IP) bit of any Interrupter transitions from 0 to 1. Refer to section 7.1.2 for use.
/// Software that uses EINT shall clear it prior to clearing any IP flags. A race condition may occur if
/// software clears the IP flags then clears the EINT flag, and between the operations another IP 0
/// to '1' transition occurs. In this case the new IP transition shall be lost.
/// When this register is exposed by a Virtual Function (VF), this bit is the logical 'OR' of the IP bits
/// for the Interrupters assigned to the selected VF. And it shall be cleared to 0 when all associated
/// interrupter IP bits are cleared, i.e. all the VFs Interrupter Event Ring(s) are empty. Refer to
/// section 8 for more information
pub event_interrupt: bool,
/// Port Change Detect (PCD) RW1C. Default = 0. The xHC sets this bit to a 1 when any port has
/// a change bit transition from a 0 to a 1.
///
/// This bit is allowed to be maintained in the Aux Power well. Alternatively, it is also acceptable
/// that on a D3 to D0 transition of the xHC, this bit is loaded with the OR of all of the PORTSC
/// change bits. Refer to section 4.19.3.
///
/// This bit provides system software an efficient means of determining if there has been Root Hub
/// port activity. Refer to section 4.15.2.3 for more information.
///
/// When this register is exposed by a Virtual Function (VF), the VMM determines the state of this
/// bit as a function of the Root Hub Ports associated with the Device Slots assigned to the selected
/// VF. Refer to section 8 for more information.
pub port_change_detect: bool,
#[bits(3)]
__: u8,
/// Save State Status (SSS) - RO. Default = 0. When the Controller Save State (CSS) flag in the
/// USBCMD register is written with 1 this bit shall be set to 1 and remain 1 while the xHC saves
/// its internal state. When the Save State operation is complete, this bit shall be cleared to 0.
/// Refer to section 4.23.2 for more information.
///
/// When this register is exposed by a Virtual Function (VF), the VMM determines the state of this
/// bit as a function of the saving the state for the selected VF. Refer to section 8 for more
/// information.
#[bits(access=RO)]
pub save_state_status: bool,
/// Restore State Status (RSS) - RO. Default = 0. When the Controller Restore State (CRS) flag in
/// the USBCMD register is written with 1 this bit shall be set to 1 and remain 1 while the xHC
/// restores its internal state. When the Restore State operation is complete, this bit shall be
/// cleared to 0. Refer to section 4.23.2 for more information.
///
/// When this register is exposed by a Virtual Function (VF), the VMM determines the state of this
/// bit as a function of the restoring the state for the selected VF. Refer to section 8 for more
/// information.
#[bits(access=RO)]
pub restore_state_status: bool,
/// Save/Restore Error (SRE) - RW1C. Default = 0. If an error occurs during a Save or Restore
/// operation this bit shall be set to 1. This bit shall be cleared to 0 when a Save or Restore
/// operation is initiated or when written with 1. Refer to section 4.23.2 for more information.
/// When this register is exposed by a Virtual Function (VF), the VMM determines the state of this
/// bit as a function of the Save/Restore completion status for the selected VF. Refer to section 8
/// for more information.
pub save_restore_error: bool,
/// Controller Not Ready (CNR) RO. Default = 1. 0 = Ready and 1 = Not Ready. Software shall
/// not write any Doorbell or Operational register of the xHC, other than the USBSTS register, until
/// CNR = 0. This flag is set by the xHC after a Chip Hardware Reset and cleared when the xHC is
/// ready to begin accepting register writes. This flag shall remain cleared (0) until the next Chip
/// Hardware Reset.
#[bits(access=RO)]
pub controller_not_ready: bool,
/// Host Controller Error (HCE) RO. Default = 0. 0 = No internal xHC error conditions exist and 1
/// = Internal xHC error condition. This flag shall be set to indicate that an internal error condition
/// has been detected which requires software to reset and reinitialize the xHC. Refer to section
/// 4.24.1 for more information.
#[bits(access=RO)]
pub host_controller_error: bool,
#[bits(19)]
__: u32,
}
impl UsbStatus {
// Returns a copy of this object that can be written without overwritting flags that are RW1C.
fn preserving_flags(&self) -> UsbStatus {
self.with_host_system_error(false)
.with_event_interrupt(false)
.with_port_change_detect(false)
.with_save_restore_error(false)
}
}
/// Internal data structure to ensure 64 bit reads and writes.
#[bitfield(u64)]
struct CommandAndStatus {
#[bits(32)]
usb_command: UsbCommand,
#[bits(32)]
usb_status: UsbStatus,
}
impl CommandAndStatus {
fn update_command(&self, f: impl Fn(UsbCommand) -> UsbCommand) -> CommandAndStatus {
CommandAndStatus::new()
.with_usb_command(f(self.usb_command()))
.with_usb_status(self.usb_status().preserving_flags())
}
fn update_status(&self, f: impl Fn(UsbStatus) -> UsbStatus) -> CommandAndStatus {
self.with_usb_status(f(self.usb_status()).preserving_flags())
}
}
#[bitfield(u64)]
struct PageSize {
///Page Size RO. Default = Implementation defined. This field defines the page size supported by
/// the xHC implementation. This xHC supports a page size of 2^(n+12) if bit n is Set. For example, if
/// bit 0 is Set, the xHC supports 4k byte page sizes.
///
/// For a Virtual Function, this register reflects the page size selected in the System Page Size field
/// of the SR-IOV Extended Capability structure. For the Physical Function 0, this register reflects
/// the implementation dependent default xHC page size.
///
/// Various xHC resources reference PAGESIZE to describe their minimum alignment requirements.
///
/// The maximum possible page size is 128M.
#[bits(access=RO)]
page_size: u32,
__: u32,
}
#[bitfield(u64)]
struct DeviceNotificationControl {
__: u32,
/// This register is used by software to enable or disable the reporting of the
/// reception of specific USB Device Notification Transaction Packets. A Notification
/// Enable (Nx, where x = 0 to 15) flag is defined for each of the 16 possible de vice
/// notification types. If a flag is set for a specific notification type, a Device
/// Notification Event shall be generated when the respective notification packet is
/// received. After reset all notifications are disabled. Refer to section 6.4.2.7
device_notification_control: u32,
}
#[bitfield(u64)]
pub struct UsbConfigure {
/// Max Device Slots Enabled (MaxSlotsEn) RW. Default = 0. This field specifies the maximum
/// number of enabled Device Slots. Valid values are in the range of 0 to MaxSlots. Enabled Devices
/// Slots are allocated contiguously. e.g. A value of 16 specifies that Device Slots 1 to 16 are active.
///
/// A value of 0 disables all Device Slots. A disabled Device Slot shall not respond to Doorbell
/// Register references.
///
/// This field shall not be modified by software if the xHC is running (Run/Stop (R/S) = 1)
pub max_device_slots_enabled: u8,
/// U3 Entry Enable (U3E) RW. Default = '0'. When set to '1', the xHC shall assert the PLC flag ('1')
/// when a Root Hub port transitions to the U3 State. Refer to section 4.15.1 for more information.
pub u3_entry_enable: bool,
/// Configuration Information Enable (CIE) - RW. Default = '0'. When set to '1', the software shall
/// initialize the Configuration Value, Interface Number, and Alternate Setting fields in the Input
/// Control Context when it is associated with a Configure Endpoint Command. When this bit is '0',
/// the extended Input Control Context fields are not supported. Refer to section 6.2.5.1 for more
/// information.
pub configuration_information_enable: bool,
#[bits(22)]
__: u32,
// Pad to 64 bits for the purposes of reads and writes.
__: u32,
}
/// XHCI Spec Section 5.4
///
/// The base address of this register space is referred to as Operational Base. The
/// Operational Base shall be Dword aligned and is calculated by adding the value
/// 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)]
#[derive(Copy, Clone)]
pub struct HostControllerOperational {
command_and_status: CommandAndStatus,
page_size: PageSize,
device_notification_control: DeviceNotificationControl,
/// Bit 0: Ring Cycle State (RW)
/// Bit 1: Command Stop (RW1S)
/// Bit 2: Command Abort (RW1S)
/// Bit 3: Command Ring Running (RO)
command_ring_control: u64,
__: u64,
___: u64,
/// The Device Context Base Address Array Pointer Register identifies the base
/// address of the Device Context Base Address Array.
/// The memory structure referenced by this physical memory pointer is assumed to
/// be physically contiguous and 64-byte aligned.
device_context_base_address_array_pointer: u64,
configure: UsbConfigure,
}
const _: () = assert!(size_of::<HostControllerOperational>() == 0x40);
pub struct HostControllerOperationalWrapper {
operational: Mutex<VolatileRef<'static, HostControllerOperational>>,
// TODO: This should maybe be its own structure.
ports: Vec<Mutex<VolatileRef<'static, HostControllerUsbPort>>>,
}
#[allow(dead_code)]
impl HostControllerOperationalWrapper {
pub fn new(mmio_address: usize) -> (Self, HostControllerCapabilities) {
const MAP_SIZE: usize = 0x1000;
let caps_ptr: NonNull<HostControllerCapabilities> =
map_direct_physical_and_leak(mmio_address, MAP_SIZE);
// SAFETY:
// - The pointer is valid.
// - No other thread has access in this block.
let capabilities = unsafe { VolatilePtr::new(caps_ptr).read() };
assert!(
capabilities.cap_params_1.supports_64_bit(),
"We only support 64 bit XHCI"
);
// 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 cap_length_and_version = capabilities.cap_length_and_version;
let operational_ptr = unsafe {
(caps_ptr.as_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::<HostControllerUsbPort>();
let max_ports = capabilities.params_1.max_ports();
assert!(
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..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 read_command(&self) -> UsbCommand {
let locked = self.operational.lock();
let op = locked.as_ptr();
map_field!(op.command_and_status).read().usb_command()
}
pub fn update_command(&self, f: impl Fn(UsbCommand) -> UsbCommand) {
let mut locked = self.operational.lock();
let op = locked.as_mut_ptr();
map_field!(op.command_and_status).update(|c_and_s| c_and_s.update_command(f));
}
pub fn read_status(&self) -> UsbStatus {
let locked = self.operational.lock();
let op = locked.as_ptr();
map_field!(op.command_and_status).read().usb_status()
}
pub fn update_status(&self, f: impl Fn(UsbStatus) -> UsbStatus) {
let mut locked = self.operational.lock();
let op = locked.as_mut_ptr();
map_field!(op.command_and_status).update(|c_and_s| c_and_s.update_status(f));
}
pub fn set_device_context_base_address_array_pointer(&self, pointer: usize) {
let mut locked = self.operational.lock();
let op = locked.as_mut_ptr();
map_field!(op.device_context_base_address_array_pointer).write(pointer as u64);
}
pub fn set_command_ring_dequeue_pointer(&self, pointer: usize, cycle_bit: bool) {
// TODO: Assert that the command ring is not running here.
let mut locked = self.operational.lock();
let op = locked.as_mut_ptr();
map_field!(op.command_ring_control).write(pointer as u64 | cycle_bit as u64);
}
pub fn read_configure(&self) -> UsbConfigure {
let locked = self.operational.lock();
let op = locked.as_ptr();
map_field!(op.configure).read()
}
pub fn update_configure(&self, f: impl Fn(UsbConfigure) -> UsbConfigure) {
let mut locked = self.operational.lock();
let op = locked.as_mut_ptr();
map_field!(op.configure).update(f);
}
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()
}
}