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 VF’s 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::() == 0x40); pub struct HostControllerOperationalWrapper { operational: Mutex>, // TODO: This should maybe be its own structure. ports: Vec>>, } #[allow(dead_code)] impl HostControllerOperationalWrapper { pub fn new(mmio_address: usize) -> (Self, HostControllerCapabilities) { const MAP_SIZE: usize = 0x1000; let caps_ptr: NonNull = 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::(); 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() } }