499 lines
16 KiB
Rust
499 lines
16 KiB
Rust
use core::sync::atomic::{AtomicU8, Ordering};
|
|
|
|
use alloc::{boxed::Box, collections::btree_map::BTreeMap, sync::Arc, vec::Vec};
|
|
use async_trait::async_trait;
|
|
use device_api::{device::Device, interrupt::InterruptHandler};
|
|
use libk::{error::Error, task::runtime};
|
|
use libk_mm::{
|
|
address::{AsPhysicalAddress, PhysicalAddress},
|
|
PageBox,
|
|
};
|
|
use libk_util::{
|
|
sync::{spin_rwlock::IrqSafeRwLock, IrqSafeSpinlock},
|
|
OneTimeInit,
|
|
};
|
|
use tock_registers::{
|
|
interfaces::{ReadWriteable, Readable, Writeable},
|
|
LocalRegisterCopy,
|
|
};
|
|
use ygg_driver_pci::device::PciDeviceInfo;
|
|
use ygg_driver_usb::{
|
|
bus::UsbBusManager,
|
|
device::{UsbBusAddress, UsbDeviceAccess, UsbSpeed},
|
|
error::UsbError,
|
|
info::UsbVersion,
|
|
pipe::control::UsbControlPipeAccess,
|
|
UsbHostController,
|
|
};
|
|
|
|
use crate::{
|
|
context::{XhciDeviceContext, XhciInputContext},
|
|
device::XhciBusDevice,
|
|
pipe::ControlPipe,
|
|
regs::{
|
|
self,
|
|
extended::ExtendedCapability,
|
|
operational::{PortRegs, CONFIG, PORTSC, USBCMD, USBSTS},
|
|
PortNumber, Regs,
|
|
},
|
|
ring::{
|
|
command::CommandRing,
|
|
event::{Event, EventRing, EventRingSegmentTable},
|
|
transfer::TransferRing,
|
|
CommandExecutor, GenericRing,
|
|
},
|
|
util::EventBitmap,
|
|
};
|
|
|
|
#[allow(unused)]
|
|
struct ScratchpadArray {
|
|
buffers: Vec<PageBox<[u8]>>,
|
|
array: PageBox<[PhysicalAddress]>,
|
|
}
|
|
|
|
struct RootHubPort {
|
|
version: UsbVersion,
|
|
slot_type: u8,
|
|
}
|
|
|
|
pub struct Xhci {
|
|
pub(crate) regs: Regs,
|
|
#[allow(unused)]
|
|
pci: PciDeviceInfo,
|
|
|
|
dcbaa: IrqSafeRwLock<PageBox<[PhysicalAddress]>>,
|
|
#[allow(unused)]
|
|
scratchpads: Option<ScratchpadArray>,
|
|
pub(crate) command_ring: CommandRing,
|
|
event_ring: EventRing,
|
|
erst: EventRingSegmentTable,
|
|
|
|
root_hub_ports: Vec<Option<RootHubPort>>,
|
|
pub(crate) endpoints: IrqSafeRwLock<BTreeMap<(u8, u8), Arc<TransferRing>>>,
|
|
pub(crate) slots: Vec<IrqSafeRwLock<Option<Arc<XhciBusDevice>>>>,
|
|
pub(crate) port_slot_map: Vec<AtomicU8>,
|
|
bus_index: OneTimeInit<u16>,
|
|
port_event_map: EventBitmap,
|
|
}
|
|
|
|
impl ScratchpadArray {
|
|
pub fn new(capacity: usize, element_size: usize) -> Result<Self, UsbError> {
|
|
let buffers = (0..capacity)
|
|
.map(|_| PageBox::new_slice(0, element_size))
|
|
.collect::<Result<Vec<_>, _>>()
|
|
.map_err(UsbError::MemoryError)?;
|
|
let array =
|
|
PageBox::new_slice_with(|i| unsafe { buffers[i].as_physical_address() }, capacity)
|
|
.map_err(UsbError::MemoryError)?;
|
|
Ok(Self { buffers, array })
|
|
}
|
|
}
|
|
|
|
impl Xhci {
|
|
pub fn new(pci: PciDeviceInfo, regs: Regs) -> Result<Self, UsbError> {
|
|
let mut dcbaa = PageBox::new_slice(PhysicalAddress::ZERO, regs.slot_count + 1)
|
|
.map_err(UsbError::MemoryError)?;
|
|
let command_ring = CommandRing::new(128)?;
|
|
let event_ring = EventRing::new(128)?;
|
|
let erst = EventRingSegmentTable::for_event_rings(&[&event_ring])?;
|
|
|
|
// Setup scratch buffers
|
|
// TODO: Linux seems to just ignore the PAGESIZE, it's (1 << 0) everywhere
|
|
let scratchpads = if regs.scratch_count != 0 {
|
|
let array = ScratchpadArray::new(regs.scratch_count, 0x1000)?;
|
|
dcbaa[0] = unsafe { array.array.as_physical_address() };
|
|
Some(array)
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let mut root_hub_ports: Vec<Option<_>> = (0..regs.port_count).map(|_| None).collect();
|
|
|
|
for cap in regs.extended_capabilities.iter() {
|
|
match cap {
|
|
ExtendedCapability::ProtocolSupport(support) => {
|
|
let Some(version) = support.usb_revision() else {
|
|
continue;
|
|
};
|
|
|
|
for port in support.port_range() {
|
|
log::info!("* Port {port}: {version}");
|
|
root_hub_ports[port as usize - 1] = Some(RootHubPort {
|
|
version,
|
|
slot_type: support.slot_type(),
|
|
});
|
|
}
|
|
}
|
|
ExtendedCapability::LegacySupport(legsup) => {
|
|
legsup.write().perform_bios_handoff(10000000)?;
|
|
}
|
|
}
|
|
}
|
|
|
|
let port_slot_map = (0..regs.port_count).map(|_| AtomicU8::new(0)).collect();
|
|
let slots = (0..regs.slot_count)
|
|
.map(|_| IrqSafeRwLock::new(None))
|
|
.collect();
|
|
|
|
Ok(Self {
|
|
regs,
|
|
pci,
|
|
|
|
dcbaa: IrqSafeRwLock::new(dcbaa),
|
|
scratchpads,
|
|
command_ring,
|
|
event_ring,
|
|
erst,
|
|
|
|
root_hub_ports,
|
|
bus_index: OneTimeInit::new(),
|
|
endpoints: IrqSafeRwLock::new(BTreeMap::new()),
|
|
slots,
|
|
port_slot_map,
|
|
port_event_map: EventBitmap::new(),
|
|
})
|
|
}
|
|
|
|
fn notify_endpoint(&self, slot_id: u8, endpoint_id: u8, address: PhysicalAddress, status: u32) {
|
|
if let Some(endpoint) = self.endpoints.read().get(&(slot_id, endpoint_id)) {
|
|
endpoint.notify(address, status);
|
|
} else {
|
|
log::warn!("Endpoint {slot_id}:{endpoint_id} does not exist");
|
|
}
|
|
}
|
|
|
|
fn kill_endpoint(&self, slot_id: u8, endpoint_id: u8) {
|
|
let endpoint = self.endpoints.write().remove(&(slot_id, endpoint_id));
|
|
if let Some(endpoint) = endpoint {
|
|
endpoint.shutdown();
|
|
}
|
|
}
|
|
|
|
async fn allocate_device_slot(
|
|
self: &Arc<Self>,
|
|
port_id: PortNumber,
|
|
slot_type: u8,
|
|
speed: UsbSpeed,
|
|
) -> Result<(u8, Arc<XhciBusDevice>, Arc<TransferRing>), UsbError> {
|
|
let device_context = XhciDeviceContext::new(self.regs.context_size)?;
|
|
let slot_id = self.command_ring.enable_slot(&**self, slot_type).await?;
|
|
let (control_pipe, control_ring) = ControlPipe::new(self.clone(), slot_id, 1, 128)?;
|
|
let control_pipe = UsbControlPipeAccess(Box::new(control_pipe));
|
|
|
|
self.dcbaa.write()[slot_id as usize] = device_context.physical_address();
|
|
self.endpoints
|
|
.write()
|
|
.insert((slot_id, 1), control_ring.clone());
|
|
|
|
let slot = Arc::new(XhciBusDevice {
|
|
port_id,
|
|
slot_id,
|
|
device_context,
|
|
control_pipe,
|
|
speed,
|
|
detach_handler: IrqSafeSpinlock::new(None),
|
|
endpoints: IrqSafeRwLock::new(BTreeMap::from_iter([(1, control_ring.clone())])),
|
|
xhci: self.clone(),
|
|
bus_address: OneTimeInit::new(),
|
|
});
|
|
|
|
*self.slots[slot_id as usize].write() = Some(slot.clone());
|
|
self.port_slot_map[port_id.index()].store(slot_id, Ordering::Release);
|
|
|
|
Ok((slot_id, slot, control_ring))
|
|
}
|
|
|
|
// TODO proper timeout
|
|
async fn reset_port(&self, regs: &PortRegs) -> Result<(), UsbError> {
|
|
// Reset the port
|
|
regs.modify_portsc_preserving(PORTSC::PR::SET);
|
|
|
|
let mut timeout = 10000000;
|
|
while timeout > 0 {
|
|
let status = regs.PORTSC.extract();
|
|
|
|
if status.matches_all(PORTSC::PR::CLEAR) {
|
|
break;
|
|
}
|
|
|
|
timeout -= 1;
|
|
}
|
|
|
|
// Clear PRC
|
|
regs.modify_portsc_preserving(PORTSC::PRC::SET);
|
|
Ok(())
|
|
}
|
|
|
|
// TODO clean up resources if init fails
|
|
async fn setup_port(
|
|
self: &Arc<Self>,
|
|
regs: &PortRegs,
|
|
number: PortNumber,
|
|
) -> Result<(), UsbError> {
|
|
let root_hub_port = self.root_hub_ports[number.index()]
|
|
.as_ref()
|
|
.ok_or(UsbError::PortInitFailed)?;
|
|
|
|
let need_reset = !root_hub_port.version.is_version_3();
|
|
|
|
if need_reset {
|
|
self.reset_port(regs).await?;
|
|
}
|
|
|
|
let status = regs.PORTSC.extract();
|
|
|
|
let speed = status.read(PORTSC::PS) as u8;
|
|
let pls = status.read(PORTSC::PLS);
|
|
|
|
let (usb_speed, max_packet_size) = match speed {
|
|
1 => (UsbSpeed::Full, 8),
|
|
2 => (UsbSpeed::Low, 8),
|
|
3 => (UsbSpeed::High, 64),
|
|
4 => (UsbSpeed::Super, 512),
|
|
_ => {
|
|
log::error!("Port {number} invalid speed value {speed}");
|
|
return Err(UsbError::DeviceDisconnected);
|
|
}
|
|
};
|
|
|
|
// PLS != 0 after reset
|
|
if pls != 0 {
|
|
log::error!("Port {number} pls!=0 after reset");
|
|
return Err(UsbError::DeviceDisconnected);
|
|
}
|
|
|
|
let (slot_id, slot, control_ring) = self
|
|
.allocate_device_slot(number, root_hub_port.slot_type, usb_speed)
|
|
.await?;
|
|
|
|
let input_cx = XhciInputContext::new_address_device(
|
|
self.regs.context_size,
|
|
number,
|
|
max_packet_size,
|
|
speed,
|
|
control_ring.base(),
|
|
)?;
|
|
self.command_ring
|
|
.address_device(&**self, slot_id, input_cx.physical_address(), false)
|
|
.await
|
|
.inspect_err(|error| {
|
|
log::error!("Port {number} Address Device TRB (BSR=0) failed: {error:?}")
|
|
})?;
|
|
|
|
// TODO update max_packet_size for default control endpoint after fetching the device
|
|
// descriptor
|
|
|
|
let bus_address = slot
|
|
.device_context
|
|
.map(|device| device.slot().usb_device_address());
|
|
slot.bus_address.init(UsbBusAddress {
|
|
bus: *self.bus_index.get(),
|
|
device: bus_address,
|
|
});
|
|
|
|
let device = UsbDeviceAccess::setup(slot).await?;
|
|
UsbBusManager::register_device(device.into());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn handle_disconnect(&self, number: PortNumber) -> Result<(), UsbError> {
|
|
let slot_id = self.port_slot_map[number.index()].swap(0, Ordering::Acquire);
|
|
if slot_id == 0 {
|
|
return Ok(());
|
|
}
|
|
log::info!("Port {number} disconnected");
|
|
|
|
let slot = self.slots[slot_id as usize].write().take();
|
|
if let Some(slot) = slot {
|
|
UsbBusManager::detach_device(*slot.bus_address.get());
|
|
|
|
// Clean up xHC resources
|
|
for (&endpoint_id, endpoint) in slot.endpoints.read().iter() {
|
|
endpoint.shutdown();
|
|
self.kill_endpoint(slot_id, endpoint_id);
|
|
}
|
|
} else {
|
|
log::warn!("No device in slot {slot_id}");
|
|
}
|
|
|
|
// TODO stop endpoints on the xHC side?
|
|
self.command_ring.disable_slot(self, slot_id).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn port_handler_task(self: Arc<Self>) -> Result<(), Error> {
|
|
// Inject notify for ports that are already connected
|
|
{
|
|
let ports = self.regs.ports.write();
|
|
for (index, port) in self.root_hub_ports.iter().enumerate() {
|
|
if port.is_none() {
|
|
continue;
|
|
}
|
|
|
|
if ports[index].PORTSC.matches_all(PORTSC::CCS::SET) {
|
|
self.port_event_map.signal(index);
|
|
}
|
|
}
|
|
}
|
|
|
|
loop {
|
|
let events = self.port_event_map.wait_any(self.regs.port_count).await;
|
|
|
|
for index in events {
|
|
let number = PortNumber::from_index(index);
|
|
let ports = self.regs.ports.write();
|
|
let regs = &ports[index];
|
|
|
|
let status = regs.PORTSC.extract();
|
|
let neutral = regs::portsc_to_neutral(status);
|
|
let mut clear = LocalRegisterCopy::<u32, PORTSC::Register>::new(0);
|
|
|
|
// Clear affected change bits
|
|
let connected = if status.matches_all(PORTSC::CSC::SET) {
|
|
clear.modify(PORTSC::CSC::SET);
|
|
Some(status.matches_all(PORTSC::CCS::SET))
|
|
} else {
|
|
None
|
|
};
|
|
if status.matches_all(PORTSC::PEC::SET) {
|
|
clear.modify(PORTSC::PEC::SET);
|
|
}
|
|
if status.matches_all(PORTSC::PLC::SET) {
|
|
clear.modify(PORTSC::PLC::SET);
|
|
}
|
|
|
|
regs.PORTSC.set(neutral.get() | clear.get());
|
|
|
|
if let Some(connected) = connected {
|
|
if connected {
|
|
log::info!("Port {number} connected");
|
|
if let Err(error) = self.setup_port(regs, number).await {
|
|
log::error!("Port {number} setup error: {error:?}");
|
|
}
|
|
}
|
|
|
|
if !connected {
|
|
if let Err(error) = self.handle_disconnect(number).await {
|
|
log::error!("Port {number} disconnect error: {error:?}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn handle_event(&self) {
|
|
while let Some(event) = self.event_ring.try_dequeue() {
|
|
match event {
|
|
Event::PortChange(number) => {
|
|
if number > 0 {
|
|
self.port_event_map.signal(number - 1);
|
|
}
|
|
}
|
|
Event::CommandCompletion { address, reply } => {
|
|
self.command_ring.notify(address, reply);
|
|
}
|
|
Event::Transfer {
|
|
address,
|
|
slot_id,
|
|
endpoint_id,
|
|
status,
|
|
} => {
|
|
self.notify_endpoint(slot_id, endpoint_id, address, status);
|
|
}
|
|
}
|
|
}
|
|
|
|
self.regs
|
|
.runtime
|
|
.write()
|
|
.set_interrupter_dequeue_pointer(0, self.event_ring.dequeue_pointer());
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl CommandExecutor for Xhci {
|
|
fn ring_doorbell(&self, index: usize, target: u8) {
|
|
self.regs.doorbells[index].store(target as u32, Ordering::Release);
|
|
}
|
|
|
|
async fn reset_endpoint(
|
|
&self,
|
|
slot_id: u8,
|
|
endpoint_id: u8,
|
|
dequeue_pointer: PhysicalAddress,
|
|
dequeue_cycle: bool,
|
|
) -> Result<(), UsbError> {
|
|
log::warn!("xhci: reset stalled endpoint {slot_id}:{endpoint_id}");
|
|
|
|
self.command_ring
|
|
.reset_endpoint(&*self, slot_id, endpoint_id, false)
|
|
.await?;
|
|
self.command_ring
|
|
.set_tr_dequeue_pointer(&*self, slot_id, endpoint_id, dequeue_pointer, dequeue_cycle)
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl Device for Xhci {
|
|
unsafe fn init(self: Arc<Self>) -> Result<(), Error> {
|
|
self.regs.hc_reset(10000000)?;
|
|
log::info!("xHC reset complete");
|
|
|
|
// Configure the HC
|
|
let dcbaap = unsafe { self.dcbaa.read().as_physical_address() };
|
|
let cr_base = self.command_ring.base();
|
|
|
|
let op = self.regs.operational.write();
|
|
let rt = self.regs.runtime.write();
|
|
|
|
log::info!("xhci: configure HC");
|
|
op.CONFIG
|
|
.modify(CONFIG::MaxSlotsEn.val(self.regs.slot_count as u32));
|
|
op.set_dcbaap(dcbaap);
|
|
op.set_crcr(cr_base, true);
|
|
|
|
log::info!("xhci: configure interrupter");
|
|
rt.configure_interrupter(0, &self.event_ring, &self.erst);
|
|
|
|
// Enable interrupts and start the HC
|
|
log::info!("xhci: start HC");
|
|
op.USBCMD.modify(USBCMD::INTE::SET + USBCMD::HSEE::SET);
|
|
op.USBCMD.modify(USBCMD::RS::SET);
|
|
op.wait_usbsts_bit(USBSTS::CNR::CLEAR, 100000000)?;
|
|
|
|
let bus = UsbBusManager::register_bus(self.clone());
|
|
self.bus_index.init(bus);
|
|
|
|
runtime::spawn(self.clone().port_handler_task()).ok();
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn display_name(&self) -> &str {
|
|
"xHCI"
|
|
}
|
|
}
|
|
|
|
impl InterruptHandler for Xhci {
|
|
fn handle_irq(self: Arc<Self>, _vector: Option<usize>) -> bool {
|
|
let status = self.regs.handle_interrupt(0);
|
|
|
|
if status.matches_all(USBSTS::HSE::SET) {
|
|
log::error!("xhci: Host System Error occurred");
|
|
}
|
|
|
|
if status.matches_all(USBSTS::EINT::SET) {
|
|
self.handle_event();
|
|
}
|
|
|
|
true
|
|
}
|
|
}
|
|
|
|
impl UsbHostController for Xhci {}
|