use std::sync::{
atomic::{AtomicU32, AtomicU64, Ordering},
Arc,
};
use tokio::sync::Notify;
use tracing::{info, trace, warn};
use crate::device::{
pci::constants::xhci::{
operational::{portsc, usbcmd, usbsts},
MAX_SLOTS,
},
xhci::{interrupter::EventSender, port::UsbVersion, trb::EventTrb},
};
#[derive(Debug)]
pub struct PortscRegister {
value: AtomicU64,
event_sender: EventSender,
usb_version: UsbVersion,
port_id: u8,
}
const BITMASK_RW1C: u64 = 0x00260000;
impl PortscRegister {
pub const fn new(event_sender: EventSender, usb_version: UsbVersion, port_id: u8) -> Self {
Self {
value: AtomicU64::new(portsc::PP | portsc::value::PLS_RXDETECT),
event_sender,
usb_version,
port_id,
}
}
pub fn set(&self, value: u64) {
self.value
.store(value, std::sync::atomic::Ordering::Relaxed);
}
pub const fn usb_version(&self) -> UsbVersion {
self.usb_version
}
pub fn read(&self) -> u64 {
self.value.load(std::sync::atomic::Ordering::Relaxed)
}
pub fn write(&self, new_value: u64) -> anyhow::Result<()> {
let bits_to_clear = new_value & BITMASK_RW1C;
let port_reset_bit = new_value & portsc::PR != 0;
match self.value.fetch_update(
std::sync::atomic::Ordering::Relaxed,
std::sync::atomic::Ordering::Relaxed,
|reg| {
let mut new_reg = reg & !bits_to_clear;
if port_reset_bit {
Self::port_reset(&mut new_reg, self.usb_version);
}
Some(new_reg)
},
) {
Ok(_) => {
if port_reset_bit {
let event = EventTrb::new_port_status_change_event_trb(self.port_id);
self.event_sender.send(event)?;
}
Ok(())
}
Err(_) => unreachable!("update function never returns None"),
}
}
fn port_reset(register: &mut u64, usb_version: UsbVersion) {
match usb_version {
UsbVersion::USB2 => {
trace!("driver attempted to write portsc::PR on USB 2");
let portsc_update_mask = portsc::PRC | portsc::PED | portsc::PLS;
Self::update_with_mask(
register,
portsc::value::PLS_U0 | portsc::PED | portsc::PRC,
portsc_update_mask,
);
}
UsbVersion::USB3 => {
Self::update_with_mask(register, portsc::PRC, portsc::PRC);
}
}
}
const fn update_with_mask(register: &mut u64, value: u64, mask: u64) {
let register_clear = *register & !mask;
let value_checked = value & mask;
*register = value_checked | register_clear;
}
}
#[derive(Debug, Default, Clone)]
pub struct PortpmscRegister {
value: Arc<AtomicU32>,
}
impl PortpmscRegister {
pub fn read(&self) -> u32 {
self.value.load(Ordering::Relaxed)
}
pub fn write(&self, value: u32) {
self.value.store(value, Ordering::Relaxed);
}
}
#[derive(Debug, Default, Clone)]
pub struct ConfigureRegister {
value: Arc<AtomicU32>,
}
impl ConfigureRegister {
pub fn read(&self) -> u32 {
self.value.load(Ordering::Relaxed)
}
pub fn write(&self, value: u32) {
let slots_enabled = (value & 0xff) as u8;
assert!(slots_enabled <= MAX_SLOTS as u8);
self.value.store(value, Ordering::Relaxed);
}
pub fn num_slots_enabled(&self) -> u8 {
(self.read() & 0xff) as u8
}
}
#[derive(Debug, Default, Clone)]
pub struct DcbaapRegister {
value: Arc<AtomicU64>,
}
impl DcbaapRegister {
pub fn read(&self) -> u64 {
self.value.load(Ordering::Relaxed)
}
pub fn write(&self, new_value: u64) {
self.value.store(new_value & !0x1f, Ordering::Relaxed);
}
}
#[derive(Debug)]
pub struct UsbcmdRegister {
value: Arc<AtomicU32>,
}
impl UsbcmdRegister {
pub fn new() -> Self {
Self {
value: Arc::new(AtomicU32::new(0)),
}
}
pub fn read(&self) -> u64 {
self.value.load(std::sync::atomic::Ordering::Relaxed).into()
}
pub fn write(&self, value: u64) {
const BITMASK_PRESERVED: u64 = usbcmd::RS | usbcmd::INTE;
if value & usbcmd::HCRST == usbcmd::HCRST {
info!("Host Controller Reset attempted; no action");
}
if value & !(BITMASK_PRESERVED | usbcmd::HCRST) != 0 {
warn!(
"received at least one bit that is ignored for USBCMD: {}",
value & !BITMASK_PRESERVED
);
}
let value = value & BITMASK_PRESERVED;
self.value
.store(value.try_into().unwrap(), Ordering::Relaxed);
}
pub fn value_reference(&self) -> Arc<AtomicU32> {
self.value.clone()
}
}
#[derive(Debug)]
pub struct UsbstsRegister {
usbcmd: Arc<AtomicU32>,
}
impl UsbstsRegister {
pub const fn new(usbcmd: Arc<AtomicU32>) -> Self {
Self { usbcmd }
}
pub fn read(&self) -> u64 {
let is_running = (self.usbcmd.load(Ordering::Relaxed) as u64 & usbcmd::RS) == usbcmd::RS;
let hch = if is_running { 0 } else { usbsts::HCH };
hch | usbsts::EINT | usbsts::PCD
}
}
#[derive(Debug, Default, Clone)]
pub struct GenericRwRegister {
value: Arc<AtomicU64>,
}
impl GenericRwRegister {
pub fn new(value: u64) -> Self {
Self {
value: Arc::new(AtomicU64::new(value)),
}
}
pub fn read(&self) -> u64 {
self.value.load(Ordering::Relaxed)
}
pub fn write(&self, new_value: u64) {
self.value.store(new_value, Ordering::Relaxed);
}
}
#[derive(Debug, Default, Clone)]
pub struct ErstbaRegister {
value: Arc<AtomicU64>,
notify: Arc<Notify>,
}
impl ErstbaRegister {
pub fn read(&self) -> u64 {
self.value.load(Ordering::Relaxed)
}
pub fn erstba(&self) -> u64 {
self.value.load(Ordering::Relaxed) & !0x1f
}
pub fn write(&self, new_value: u64) {
self.value.store(new_value, Ordering::Relaxed);
self.notify.notify_waiters();
}
pub async fn write_notification(&self) {
self.notify.notified().await;
}
}
#[cfg(test)]
mod tests {
use crate::device::xhci::interrupter::Interrupter;
use crate::dynamic_bus::DynamicBus;
use super::*;
#[tokio::test]
async fn portsc_read_write() {
let async_runtime = tokio::runtime::Handle::current();
let dma_bus = Arc::new(DynamicBus::new());
let interrupter = Interrupter::new(dma_bus, &async_runtime);
let reg = PortscRegister::new(interrupter.create_event_sender(), UsbVersion::USB3, 1);
reg.set(0x00260203);
assert_eq!(reg.read(), 0x00260203);
reg.write(0x0).unwrap();
assert_eq!(
reg.read(),
0x00260203,
"writing 0 should affect neither the read-only nor the RW1C bits."
);
reg.write(0x00200000).unwrap();
assert_eq!(
reg.read(),
0x00060203,
"writing 1 to bit 21 should clear the bit."
);
reg.write(0x00040000).unwrap();
assert_eq!(
reg.read(),
0x00020203,
"writing 1 to bit 18 should clear the bit."
);
reg.write(0x00020000).unwrap();
assert_eq!(
reg.read(),
0x00000203,
"writing 1 to bit 17 should clear the bit."
);
}
#[test]
fn usbcmd_read_write() {
let reg = UsbcmdRegister::new();
reg.write(0x5);
assert_eq!(reg.read(), 0x5, "Writing to allowed bits should work.");
reg.write(0xd);
assert_eq!(
reg.read(),
0x5,
"Writing any bit besides the allowed ones should be ignored."
);
reg.write(0x1);
assert_eq!(
reg.read(),
0x1,
"Not writing to allowed and already set bit 2 should clear it."
);
}
}