382 lines
11 KiB
Rust
382 lines
11 KiB
Rust
use core::{future::poll_fn, sync::atomic::Ordering, task::Poll, time::Duration};
|
|
|
|
use alloc::{boxed::Box, collections::BTreeMap, sync::Arc, vec, vec::Vec};
|
|
use atomic_enum::atomic_enum;
|
|
use device_api::{device::Device, interrupt::InterruptHandler};
|
|
use futures_util::task::AtomicWaker;
|
|
use libk::task::runtime;
|
|
use libk_mm::{
|
|
address::{AsPhysicalAddress, PhysicalAddress},
|
|
PageBox,
|
|
};
|
|
use libk_util::{sync::spin_rwlock::IrqSafeRwLock, OneTimeInit};
|
|
use ygg_driver_usb::{
|
|
bus::UsbBusManager,
|
|
device::{UsbBusAddress, UsbDevice, UsbDeviceAccess},
|
|
error::UsbError,
|
|
pipe::control::UsbControlPipeAccess,
|
|
util::UsbAddressAllocator,
|
|
UsbHostController,
|
|
};
|
|
use yggdrasil_abi::error::Error;
|
|
|
|
use crate::{
|
|
device::XhciBusDevice,
|
|
pipe::ControlPipe,
|
|
regs::{Mapper, PortSpeed, Regs},
|
|
ring::{
|
|
CommandExecutor, CommandRing, ControlTransferRing, Event, EventRing, EventRingSegmentTable,
|
|
GenericTransferRing,
|
|
},
|
|
XhciContext,
|
|
};
|
|
|
|
#[atomic_enum]
|
|
#[derive(PartialEq, Eq)]
|
|
pub enum PortState {
|
|
Disconnected, // Default + set by "handle detach"
|
|
Init, // Set by "port task"
|
|
Running, // Set by "port task"
|
|
}
|
|
|
|
struct PortStruct {
|
|
state: AtomicPortState,
|
|
notify: AtomicWaker,
|
|
address: IrqSafeRwLock<Option<UsbBusAddress>>,
|
|
}
|
|
|
|
pub struct Xhci {
|
|
regs: Regs,
|
|
|
|
bus_address: OneTimeInit<u16>,
|
|
address_allocator: UsbAddressAllocator,
|
|
|
|
port_count: usize,
|
|
// TODO use to allocate proper contexts
|
|
#[allow(unused)]
|
|
context_size: usize,
|
|
|
|
dcbaa: IrqSafeRwLock<PageBox<[PhysicalAddress]>>,
|
|
endpoints: IrqSafeRwLock<BTreeMap<(u8, u8), Arc<dyn GenericTransferRing>>>,
|
|
event_ring: EventRing,
|
|
pub(crate) command_ring: CommandRing,
|
|
|
|
port_states: Vec<PortStruct>,
|
|
}
|
|
|
|
impl Xhci {
|
|
pub fn new(regs: xhci_lib::Registers<Mapper>) -> Result<Self, UsbError> {
|
|
let event_ring = EventRing::new(128)?;
|
|
let command_ring = CommandRing::new(128)?;
|
|
|
|
let regs = Regs::from(regs);
|
|
|
|
let port_count = regs.port_count();
|
|
let slot_count = regs.max_slot_count();
|
|
let context_size = regs.context_size();
|
|
|
|
let dcbaa = PageBox::new_slice(PhysicalAddress::ZERO, slot_count + 1)
|
|
.map_err(UsbError::MemoryError)?;
|
|
|
|
Ok(Self {
|
|
regs,
|
|
|
|
bus_address: OneTimeInit::new(),
|
|
address_allocator: UsbAddressAllocator::new(),
|
|
|
|
port_count,
|
|
context_size,
|
|
|
|
event_ring,
|
|
command_ring,
|
|
|
|
dcbaa: IrqSafeRwLock::new(dcbaa),
|
|
endpoints: IrqSafeRwLock::new(BTreeMap::new()),
|
|
|
|
port_states: Vec::from_iter((0..port_count).map(|_| PortStruct {
|
|
state: AtomicPortState::new(PortState::Disconnected),
|
|
notify: AtomicWaker::new(),
|
|
address: IrqSafeRwLock::new(None),
|
|
})),
|
|
})
|
|
}
|
|
|
|
pub fn register_device_context(&self, slot_id: u8, context: PhysicalAddress) {
|
|
self.dcbaa.write()[slot_id as usize] = context;
|
|
}
|
|
|
|
pub fn register_endpoint(
|
|
&self,
|
|
slot_id: u8,
|
|
endpoint_id: u8,
|
|
ring: Arc<dyn GenericTransferRing>,
|
|
) {
|
|
self.endpoints.write().insert((slot_id, endpoint_id), ring);
|
|
}
|
|
|
|
pub fn shutdown_endpoint(&self, slot_id: u8, endpoint_id: u8) {
|
|
if let Some(endpoint) = self.endpoints.write().remove(&(slot_id, endpoint_id)) {
|
|
endpoint.shutdown();
|
|
} else {
|
|
log::warn!(
|
|
"Endpoint {}:{} does not exist, maybe already shut down?",
|
|
slot_id,
|
|
endpoint_id
|
|
);
|
|
}
|
|
}
|
|
|
|
pub fn notify_transfer(
|
|
&self,
|
|
slot_id: u8,
|
|
endpoint_id: u8,
|
|
address: PhysicalAddress,
|
|
status: u32,
|
|
) {
|
|
if let Some(ep) = self.endpoints.read().get(&(slot_id, endpoint_id)) {
|
|
ep.notify(address, status);
|
|
}
|
|
}
|
|
|
|
async fn assign_device(
|
|
self: Arc<Self>,
|
|
speed: PortSpeed,
|
|
slot_id: u8,
|
|
root_hub_port_number: u8,
|
|
) -> Result<Box<dyn UsbDevice>, UsbError> {
|
|
let address = self.address_allocator.allocate().unwrap();
|
|
let ring = Arc::new(ControlTransferRing::new(slot_id, 1, 128)?);
|
|
|
|
let context =
|
|
XhciContext::new_32byte_address_device(&ring, speed, address, root_hub_port_number)?;
|
|
let mut input = context.input.write();
|
|
|
|
self.register_device_context(slot_id, unsafe { context.output.as_physical_address() });
|
|
|
|
self.command_ring
|
|
.address_device(&*self, slot_id, &mut input)
|
|
.await?;
|
|
|
|
self.register_endpoint(slot_id, 1, ring.clone());
|
|
|
|
let pipe = ControlPipe::new(self.clone(), slot_id, ring.clone());
|
|
|
|
// TODO: If the device is a Full-speed one, determine its max packet size for the control
|
|
// endpoint
|
|
if speed == PortSpeed::Full {
|
|
todo!()
|
|
}
|
|
|
|
drop(input);
|
|
|
|
let bus_address = UsbBusAddress {
|
|
bus: *self.bus_address.get(),
|
|
device: address,
|
|
};
|
|
|
|
let device = XhciBusDevice {
|
|
xhci: self.clone(),
|
|
slot_id,
|
|
port_id: root_hub_port_number,
|
|
bus_address,
|
|
speed,
|
|
context: Arc::new(context),
|
|
rings: IrqSafeRwLock::new(vec![ring]),
|
|
control_pipe: UsbControlPipeAccess(Box::new(pipe)),
|
|
};
|
|
|
|
Ok(Box::new(device))
|
|
}
|
|
|
|
async fn port_task(self: Arc<Self>, index: usize) -> Result<(), UsbError> {
|
|
let state = &self.port_states[index];
|
|
|
|
self.reset_port(index).await?;
|
|
|
|
let regs = self.regs.ports.read(index);
|
|
let speed =
|
|
PortSpeed::try_from(regs.portsc.port_speed()).map_err(|_| UsbError::PortInitFailed)?;
|
|
|
|
let slot_id = self.command_ring.enable_slot(self.as_ref()).await?;
|
|
|
|
let device = self
|
|
.clone()
|
|
.assign_device(speed, slot_id, (index + 1) as _)
|
|
.await?;
|
|
let device = UsbDeviceAccess::setup(device).await?;
|
|
|
|
let old = state.address.write().replace(device.bus_address());
|
|
assert!(old.is_none());
|
|
|
|
UsbBusManager::register_device(Arc::new(device));
|
|
|
|
state.state.store(PortState::Running, Ordering::Release);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn handle_device_attached(self: Arc<Self>, port: usize) -> Result<(), UsbError> {
|
|
log::info!("Port {}: device attached", port);
|
|
|
|
if let Err(err) = self.port_states[port].state.compare_exchange(
|
|
PortState::Disconnected,
|
|
PortState::Init,
|
|
Ordering::Acquire,
|
|
Ordering::Relaxed,
|
|
) {
|
|
log::warn!("Could not start port init task: port state is {:?}", err);
|
|
return Err(UsbError::DeviceBusy);
|
|
}
|
|
|
|
runtime::spawn(async move { self.port_task(port).await }).map_err(UsbError::SystemError)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn handle_device_detached(&self, port: usize) -> Result<(), UsbError> {
|
|
let state = &self.port_states[port];
|
|
|
|
match state.state.swap(PortState::Disconnected, Ordering::Release) {
|
|
PortState::Init => todo!(),
|
|
PortState::Running => {
|
|
log::info!("Port {}: device detached", port);
|
|
let address = state
|
|
.address
|
|
.write()
|
|
.take()
|
|
.expect("Port marked as Running, but has no address");
|
|
|
|
UsbBusManager::detach_device(address);
|
|
state.notify.wake();
|
|
|
|
Ok(())
|
|
}
|
|
// Already disconnected
|
|
PortState::Disconnected => Ok(()),
|
|
}
|
|
}
|
|
|
|
fn handle_port_event(self: Arc<Self>, port: usize) -> Result<(), UsbError> {
|
|
let state = &self.port_states[port];
|
|
let port_regs = self.regs.ports.read(port);
|
|
|
|
if port_regs.portsc.connect_status_change() {
|
|
if port_regs.portsc.current_connect_status() {
|
|
self.handle_device_attached(port)
|
|
} else {
|
|
self.handle_device_detached(port)
|
|
}
|
|
} else {
|
|
// Some other event
|
|
state.notify.wake();
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
async fn reset_port(&self, port: usize) -> Result<(), UsbError> {
|
|
log::debug!("Reset port {}", port);
|
|
|
|
self.regs.ports.update(port, |u| {
|
|
u.portsc.set_port_reset();
|
|
});
|
|
|
|
// Wait for port reset
|
|
// TODO handle disconnect during reset?
|
|
let result = runtime::with_timeout(
|
|
poll_fn(|cx| {
|
|
let state = &self.port_states[port];
|
|
|
|
state.notify.register(cx.waker());
|
|
if !self.regs.ports.read(port).portsc.port_reset() {
|
|
Poll::Ready(())
|
|
} else {
|
|
Poll::Pending
|
|
}
|
|
}),
|
|
Duration::from_secs(1),
|
|
)
|
|
.await;
|
|
|
|
match result {
|
|
Ok(()) => Ok(()),
|
|
Err(_) => Err(UsbError::PortResetFailed),
|
|
}
|
|
}
|
|
|
|
fn handle_event(self: Arc<Self>) {
|
|
while let Some(event) = self.event_ring.try_dequeue() {
|
|
match event {
|
|
Event::PortChange(port) => {
|
|
self.clone().handle_port_event(port - 1).ok();
|
|
}
|
|
Event::CommandCompletion { address, reply } => {
|
|
self.command_ring.notify(address, reply);
|
|
}
|
|
Event::Transfer {
|
|
address,
|
|
slot_id,
|
|
endpoint_id,
|
|
status,
|
|
} => {
|
|
self.notify_transfer(slot_id, endpoint_id, address, status);
|
|
}
|
|
}
|
|
}
|
|
|
|
self.regs
|
|
.set_interrupter_0_dequeue_pointer(self.event_ring.dequeue_pointer());
|
|
}
|
|
}
|
|
|
|
impl UsbHostController for Xhci {}
|
|
|
|
impl CommandExecutor for Xhci {
|
|
fn ring_doorbell(&self, index: usize, target: u8) {
|
|
self.regs.ring_doorbell(index, target);
|
|
}
|
|
}
|
|
|
|
impl Device for Xhci {
|
|
unsafe fn init(self: Arc<Self>) -> Result<(), Error> {
|
|
log::info!("Init USB xHCI");
|
|
|
|
self.regs.reset();
|
|
self.regs.set_max_slot_count();
|
|
|
|
let erst = EventRingSegmentTable::for_event_rings(&[&self.event_ring])?;
|
|
let dcbaa = self.dcbaa.read();
|
|
|
|
self.regs
|
|
.configure(&dcbaa, &self.command_ring, &self.event_ring, &erst);
|
|
|
|
let bus = UsbBusManager::register_bus(self.clone());
|
|
self.bus_address.init(bus);
|
|
|
|
for port in 0..self.port_count {
|
|
let p = self.regs.ports.read(port);
|
|
if p.portsc.current_connect_status() {
|
|
self.clone().handle_device_attached(port).ok();
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn display_name(&self) -> &str {
|
|
"USB xHCI"
|
|
}
|
|
}
|
|
|
|
impl InterruptHandler for Xhci {
|
|
fn handle_irq(self: Arc<Self>, _vector: Option<usize>) -> bool {
|
|
if let Some(status) = self.regs.handle_interrupt() {
|
|
if status.event_interrupt() {
|
|
self.handle_event();
|
|
}
|
|
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
}
|