milestone: desktop path Phases 1-5
Phase 1 (Runtime Substrate): 4 check binaries, --probe, POSIX tests Phase 2 (Wayland Compositor): bounded scaffold, zero warnings Phase 3 (KWin Session): preflight checker (KWin stub, gated on Qt6Quick) Phase 4 (KDE Plasma): 18 KF6 enabled, preflight checker Phase 5 (Hardware GPU): DRM/firmware/Mesa preflight checker Build: zero warnings, all scripts syntax-clean. Oracle-verified.
This commit is contained in:
@@ -0,0 +1 @@
|
||||
/target
|
||||
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "usbctl"
|
||||
version = "0.1.0"
|
||||
authors = ["4lDO2 <4lDO2@protonmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
clap.workspace = true
|
||||
xhcid = { path = "../xhcid" }
|
||||
common = { path = "../../common" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,54 @@
|
||||
use clap::{Arg, Command};
|
||||
use xhcid_interface::{PortId, XhciClientHandle};
|
||||
|
||||
fn main() {
|
||||
common::init();
|
||||
let matches = Command::new("usbctl")
|
||||
.arg(
|
||||
Arg::new("SCHEME")
|
||||
.num_args(1)
|
||||
.required(true)
|
||||
.long("scheme")
|
||||
.short('s'),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("port")
|
||||
.arg(Arg::new("PORT").num_args(1).required(true))
|
||||
.subcommand(Command::new("status"))
|
||||
.subcommand(
|
||||
Command::new("endpoint")
|
||||
.arg(Arg::new("ENDPOINT_NUM").num_args(1).required(true))
|
||||
.subcommand(Command::new("status")),
|
||||
),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
let scheme = matches.get_one::<String>("SCHEME").expect("no scheme");
|
||||
|
||||
if let Some(port_scmd_matches) = matches.subcommand_matches("port") {
|
||||
let port = port_scmd_matches
|
||||
.get_one::<String>("PORT")
|
||||
.expect("invalid utf-8 for PORT argument")
|
||||
.parse::<PortId>()
|
||||
.expect("expected PORT ID");
|
||||
|
||||
let handle = XhciClientHandle::new(scheme.to_owned(), port)
|
||||
.expect("Failed to open XhciClientHandle");
|
||||
|
||||
if let Some(_status_scmd_matches) = port_scmd_matches.subcommand_matches("status") {
|
||||
let state = handle.port_state().expect("Failed to get port state");
|
||||
println!("{}", state.as_str());
|
||||
} else if let Some(endp_scmd_matches) = port_scmd_matches.subcommand_matches("endpoint") {
|
||||
let endp_num = endp_scmd_matches
|
||||
.get_one::<String>("ENDPOINT_NUM")
|
||||
.expect("no valid ENDPOINT_NUM")
|
||||
.parse::<u8>()
|
||||
.expect("expected ENDPOINT_NUM to be an 8-bit integer");
|
||||
let mut endp_handle = handle
|
||||
.open_endpoint(endp_num)
|
||||
.expect("Failed to open endpoint");
|
||||
let state = endp_handle.status().expect("Failed to get endpoint state");
|
||||
println!("{}", state.as_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
/target
|
||||
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "usbhubd"
|
||||
description = "USB Hub driver"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
log.workspace = true
|
||||
redox_syscall.workspace = true
|
||||
xhcid = { path = "../xhcid" }
|
||||
|
||||
common = { path = "../../common" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,249 @@
|
||||
use std::{env, thread, time};
|
||||
|
||||
use xhcid_interface::{
|
||||
plain, usb, ConfigureEndpointsReq, DevDesc, DeviceReqData, PortId, PortReqRecipient, PortReqTy,
|
||||
XhciClientHandle,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
common::init();
|
||||
let mut args = env::args().skip(1);
|
||||
|
||||
const USAGE: &'static str = "usbhubd <scheme> <port> <interface>";
|
||||
|
||||
let scheme = args.next().expect(USAGE);
|
||||
let port_id = args
|
||||
.next()
|
||||
.expect(USAGE)
|
||||
.parse::<PortId>()
|
||||
.expect("Expected port ID");
|
||||
let interface_num = args
|
||||
.next()
|
||||
.expect(USAGE)
|
||||
.parse::<u8>()
|
||||
.expect("Expected integer as input of interface");
|
||||
|
||||
log::info!(
|
||||
"USB HUB driver spawned with scheme `{}`, port {}, interface {}",
|
||||
scheme,
|
||||
port_id,
|
||||
interface_num
|
||||
);
|
||||
|
||||
let name = format!("{}_{}_{}_hub", scheme, port_id, interface_num);
|
||||
common::setup_logging(
|
||||
"usb",
|
||||
"device",
|
||||
&name,
|
||||
common::output_level(),
|
||||
common::file_level(),
|
||||
);
|
||||
|
||||
let handle =
|
||||
XhciClientHandle::new(scheme.clone(), port_id).expect("Failed to open XhciClientHandle");
|
||||
let desc: DevDesc = handle
|
||||
.get_standard_descs()
|
||||
.expect("Failed to get standard descriptors");
|
||||
|
||||
let (conf_desc, if_desc) = desc
|
||||
.config_descs
|
||||
.iter()
|
||||
.find_map(|conf_desc| {
|
||||
let if_desc = conf_desc.interface_descs.iter().find_map(|if_desc| {
|
||||
if if_desc.number == interface_num {
|
||||
Some(if_desc.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})?;
|
||||
Some((conf_desc.clone(), if_desc))
|
||||
})
|
||||
.expect("Failed to find suitable configuration");
|
||||
|
||||
// Read hub descriptor
|
||||
let (ports, usb_3) = if desc.major_version() >= 3 {
|
||||
// USB 3.0 hubs
|
||||
let mut hub_desc = usb::HubDescriptorV3::default();
|
||||
handle
|
||||
.device_request(
|
||||
PortReqTy::Class,
|
||||
PortReqRecipient::Device,
|
||||
usb::SetupReq::GetDescriptor as u8,
|
||||
u16::from(usb::HubDescriptorV3::DESCRIPTOR_KIND) << 8,
|
||||
0,
|
||||
DeviceReqData::In(unsafe { plain::as_mut_bytes(&mut hub_desc) }),
|
||||
)
|
||||
.expect("Failed to read hub descriptor");
|
||||
(hub_desc.ports, true)
|
||||
} else {
|
||||
// USB 2.0 and earlier hubs
|
||||
let mut hub_desc = usb::HubDescriptorV2::default();
|
||||
handle
|
||||
.device_request(
|
||||
PortReqTy::Class,
|
||||
PortReqRecipient::Device,
|
||||
usb::SetupReq::GetDescriptor as u8,
|
||||
u16::from(usb::HubDescriptorV2::DESCRIPTOR_KIND) << 8,
|
||||
0,
|
||||
DeviceReqData::In(unsafe { plain::as_mut_bytes(&mut hub_desc) }),
|
||||
)
|
||||
.expect("Failed to read hub descriptor");
|
||||
(hub_desc.ports, false)
|
||||
};
|
||||
|
||||
// Configure as hub device
|
||||
handle
|
||||
.configure_endpoints(&ConfigureEndpointsReq {
|
||||
config_desc: conf_desc.configuration_value,
|
||||
interface_desc: None, //TODO: stalls on USB 3 hub: Some(interface_num),
|
||||
alternate_setting: None, //TODO: stalls on USB 3 hub: Some(if_desc.alternate_setting),
|
||||
hub_ports: Some(ports),
|
||||
})
|
||||
.expect("Failed to configure endpoints after reading hub descriptor");
|
||||
|
||||
if usb_3 {
|
||||
handle
|
||||
.device_request(
|
||||
PortReqTy::Class,
|
||||
PortReqRecipient::Device,
|
||||
0x0c, // SET_HUB_DEPTH
|
||||
port_id.hub_depth().into(),
|
||||
0,
|
||||
DeviceReqData::NoData,
|
||||
)
|
||||
.expect("Failed to set hub depth");
|
||||
}
|
||||
|
||||
// Initialize states
|
||||
struct PortState {
|
||||
port_id: PortId,
|
||||
port_sts: usb::HubPortStatus,
|
||||
handle: XhciClientHandle,
|
||||
attached: bool,
|
||||
}
|
||||
|
||||
impl PortState {
|
||||
pub fn ensure_attached(&mut self, attached: bool) {
|
||||
if attached == self.attached {
|
||||
return;
|
||||
}
|
||||
|
||||
if attached {
|
||||
self.handle.attach().expect("Failed to attach");
|
||||
} else {
|
||||
self.handle.detach().expect("Failed to detach");
|
||||
}
|
||||
|
||||
self.attached = attached;
|
||||
}
|
||||
}
|
||||
|
||||
let mut states = Vec::new();
|
||||
for port in 1..=ports {
|
||||
let child_port_id = port_id.child(port).expect("Cannot get child port ID");
|
||||
states.push(PortState {
|
||||
port_id: child_port_id,
|
||||
port_sts: if usb_3 {
|
||||
usb::HubPortStatus::V3(usb::HubPortStatusV3::default())
|
||||
} else {
|
||||
usb::HubPortStatus::V2(usb::HubPortStatusV2::default())
|
||||
},
|
||||
handle: XhciClientHandle::new(scheme.clone(), child_port_id)
|
||||
.expect("Failed to open XhciClientHandle"),
|
||||
attached: false,
|
||||
});
|
||||
}
|
||||
|
||||
//TODO: use change flags?
|
||||
loop {
|
||||
for port in 1..=ports {
|
||||
let port_idx: usize = port.checked_sub(1).unwrap().into();
|
||||
let state = states.get_mut(port_idx).unwrap();
|
||||
|
||||
let port_sts = if usb_3 {
|
||||
let mut port_sts = usb::HubPortStatusV3::default();
|
||||
handle
|
||||
.device_request(
|
||||
PortReqTy::Class,
|
||||
PortReqRecipient::Other,
|
||||
usb::SetupReq::GetStatus as u8,
|
||||
0,
|
||||
port as u16,
|
||||
DeviceReqData::In(unsafe { plain::as_mut_bytes(&mut port_sts) }),
|
||||
)
|
||||
.expect("Failed to retrieve port status");
|
||||
usb::HubPortStatus::V3(port_sts)
|
||||
} else {
|
||||
let mut port_sts = usb::HubPortStatusV2::default();
|
||||
handle
|
||||
.device_request(
|
||||
PortReqTy::Class,
|
||||
PortReqRecipient::Other,
|
||||
usb::SetupReq::GetStatus as u8,
|
||||
0,
|
||||
port as u16,
|
||||
DeviceReqData::In(unsafe { plain::as_mut_bytes(&mut port_sts) }),
|
||||
)
|
||||
.expect("Failed to retrieve port status");
|
||||
usb::HubPortStatus::V2(port_sts)
|
||||
};
|
||||
if state.port_sts != port_sts {
|
||||
state.port_sts = port_sts;
|
||||
log::info!("port {} status {:X?}", port, port_sts);
|
||||
}
|
||||
|
||||
// Ensure port is powered on
|
||||
if !port_sts.is_powered() {
|
||||
log::info!("power on port {port}");
|
||||
handle
|
||||
.device_request(
|
||||
PortReqTy::Class,
|
||||
PortReqRecipient::Other,
|
||||
usb::SetupReq::SetFeature as u8,
|
||||
usb::HubPortFeature::PortPower as u16,
|
||||
port as u16,
|
||||
DeviceReqData::NoData,
|
||||
)
|
||||
.expect("Failed to set port power");
|
||||
state.ensure_attached(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ignore disconnected port
|
||||
if !port_sts.is_connected() {
|
||||
state.ensure_attached(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ignore port in reset
|
||||
if port_sts.is_resetting() {
|
||||
state.ensure_attached(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ensure port is enabled
|
||||
if !port_sts.is_enabled() {
|
||||
log::info!("reset port {port}");
|
||||
handle
|
||||
.device_request(
|
||||
PortReqTy::Class,
|
||||
PortReqRecipient::Other,
|
||||
usb::SetupReq::SetFeature as u8,
|
||||
usb::HubPortFeature::PortReset as u16,
|
||||
port as u16,
|
||||
DeviceReqData::NoData,
|
||||
)
|
||||
.expect("Failed to set port enable");
|
||||
state.ensure_attached(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
state.ensure_attached(true);
|
||||
}
|
||||
|
||||
//TODO: use interrupts or poll faster?
|
||||
thread::sleep(time::Duration::new(1, 0));
|
||||
}
|
||||
|
||||
//TODO: read interrupt port for changes
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
/target
|
||||
@@ -0,0 +1,41 @@
|
||||
[package]
|
||||
name = "xhcid"
|
||||
description = "xHCI controller driver"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
[[bin]]
|
||||
name = "xhcid"
|
||||
path = "src/main.rs"
|
||||
|
||||
[lib]
|
||||
name = "xhcid_interface"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
bitflags.workspace = true
|
||||
chashmap = "2.2.2"
|
||||
crossbeam-channel = "0.4"
|
||||
futures = "0.3"
|
||||
plain.workspace = true
|
||||
lazy_static = "1.4"
|
||||
log.workspace = true
|
||||
redox_event.workspace = true
|
||||
redox-scheme.workspace = true
|
||||
scheme-utils = { path = "../../../scheme-utils" }
|
||||
redox_syscall.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
smallvec = { workspace = true, features = ["serde"] }
|
||||
thiserror.workspace = true
|
||||
toml.workspace = true
|
||||
|
||||
config = { path = "../../../config" }
|
||||
common = { path = "../../common" }
|
||||
daemon = { path = "../../../daemon" }
|
||||
pcid = { path = "../../pcid" }
|
||||
libredox.workspace = true
|
||||
regex = "1.10.6"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,7 @@
|
||||
[[drivers]]
|
||||
name = "XHCI"
|
||||
class = 0x0C
|
||||
subclass = 0x03
|
||||
interface = 0x30
|
||||
command = ["xhcid"]
|
||||
use_channel = true
|
||||
@@ -0,0 +1,18 @@
|
||||
#TODO: causes XHCI errors
|
||||
#[[drivers]]
|
||||
#name = "SCSI over USB"
|
||||
#class = 8 # Mass Storage class
|
||||
#subclass = 6 # SCSI transparent command set
|
||||
#command = ["usbscsid", "$SCHEME", "$PORT", "$IF_PROTO"]
|
||||
|
||||
[[drivers]]
|
||||
name = "USB HUB"
|
||||
class = 9 # HUB class
|
||||
subclass = -1
|
||||
command = ["usbhubd", "$SCHEME", "$PORT", "$IF_NUM"]
|
||||
|
||||
[[drivers]]
|
||||
name = "USB HID"
|
||||
class = 3 # HID class
|
||||
subclass = -1
|
||||
command = ["usbhidd", "$SCHEME", "$PORT", "$IF_NUM"]
|
||||
@@ -0,0 +1,889 @@
|
||||
pub extern crate serde;
|
||||
pub extern crate smallvec;
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::num::NonZeroU8;
|
||||
use std::os::fd::{FromRawFd, RawFd};
|
||||
use std::string::FromUtf8Error;
|
||||
use std::{fmt, io, result, str};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smallvec::SmallVec;
|
||||
use syscall::{Error, Result, EINVAL};
|
||||
use thiserror::Error;
|
||||
|
||||
pub use crate::usb::{EndpointTy, ENDP_ATTR_TY_MASK};
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct ConfigureEndpointsReq {
|
||||
/// Index into the configuration descriptors of the device descriptor.
|
||||
pub config_desc: u8,
|
||||
pub interface_desc: Option<u8>,
|
||||
pub alternate_setting: Option<u8>,
|
||||
pub hub_ports: Option<u8>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct DevDesc {
|
||||
pub kind: u8,
|
||||
pub usb: u16,
|
||||
pub class: u8,
|
||||
pub sub_class: u8,
|
||||
pub protocol: u8,
|
||||
pub packet_size: u8,
|
||||
pub vendor: u16,
|
||||
pub product: u16,
|
||||
pub release: u16,
|
||||
pub manufacturer_str: Option<String>,
|
||||
pub product_str: Option<String>,
|
||||
pub serial_str: Option<String>,
|
||||
pub config_descs: SmallVec<[ConfDesc; 1]>,
|
||||
}
|
||||
|
||||
impl DevDesc {
|
||||
pub fn major_version(&self) -> u8 {
|
||||
((self.usb & 0xFF00) >> 8) as u8
|
||||
}
|
||||
pub fn minor_version(&self) -> u8 {
|
||||
self.usb as u8
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct ConfDesc {
|
||||
pub kind: u8,
|
||||
pub configuration_value: u8,
|
||||
pub configuration: Option<String>,
|
||||
pub attributes: u8,
|
||||
pub max_power: u8,
|
||||
pub interface_descs: SmallVec<[IfDesc; 1]>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
pub struct EndpDesc {
|
||||
pub kind: u8,
|
||||
pub address: u8,
|
||||
pub attributes: u8,
|
||||
pub max_packet_size: u16,
|
||||
pub interval: u8,
|
||||
pub ssc: Option<SuperSpeedCmp>,
|
||||
pub sspc: Option<SuperSpeedPlusIsochCmp>,
|
||||
}
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum EndpDirection {
|
||||
Out,
|
||||
In,
|
||||
Bidirectional,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum EndpBinaryDirection {
|
||||
Out,
|
||||
In,
|
||||
}
|
||||
|
||||
impl From<PortReqDirection> for EndpBinaryDirection {
|
||||
fn from(d: PortReqDirection) -> Self {
|
||||
match d {
|
||||
PortReqDirection::DeviceToHost => Self::In,
|
||||
PortReqDirection::HostToDevice => Self::Out,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EndpBinaryDirection> for EndpDirection {
|
||||
fn from(b: EndpBinaryDirection) -> Self {
|
||||
match b {
|
||||
EndpBinaryDirection::In => Self::In,
|
||||
EndpBinaryDirection::Out => Self::Out,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PortReqDirection> for EndpDirection {
|
||||
fn from(d: PortReqDirection) -> Self {
|
||||
match d {
|
||||
PortReqDirection::HostToDevice => Self::Out,
|
||||
PortReqDirection::DeviceToHost => Self::In,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EndpDesc {
|
||||
pub fn ty(self) -> EndpointTy {
|
||||
match self.attributes & ENDP_ATTR_TY_MASK {
|
||||
0 => EndpointTy::Ctrl,
|
||||
1 => EndpointTy::Isoch,
|
||||
2 => EndpointTy::Bulk,
|
||||
3 => EndpointTy::Interrupt,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
pub fn is_control(&self) -> bool {
|
||||
self.ty() == EndpointTy::Ctrl
|
||||
}
|
||||
pub fn is_interrupt(&self) -> bool {
|
||||
self.ty() == EndpointTy::Interrupt
|
||||
}
|
||||
pub fn is_bulk(&self) -> bool {
|
||||
self.ty() == EndpointTy::Bulk
|
||||
}
|
||||
pub fn is_isoch(&self) -> bool {
|
||||
self.ty() == EndpointTy::Isoch
|
||||
}
|
||||
pub fn direction(&self) -> EndpDirection {
|
||||
if self.is_control() {
|
||||
return EndpDirection::Bidirectional;
|
||||
}
|
||||
if self.address & 0x80 != 0 {
|
||||
EndpDirection::In
|
||||
} else {
|
||||
EndpDirection::Out
|
||||
}
|
||||
}
|
||||
pub fn xhci_ep_type(&self) -> Result<u8> {
|
||||
Ok(match self.direction() {
|
||||
EndpDirection::Out if self.is_isoch() => 1,
|
||||
EndpDirection::Out if self.is_bulk() => 2,
|
||||
EndpDirection::Out if self.is_interrupt() => 3,
|
||||
EndpDirection::Bidirectional if self.is_control() => 4,
|
||||
EndpDirection::In if self.is_isoch() => 5,
|
||||
EndpDirection::In if self.is_bulk() => 6,
|
||||
EndpDirection::In if self.is_interrupt() => 7,
|
||||
_ => return Err(Error::new(EINVAL)),
|
||||
})
|
||||
}
|
||||
pub fn is_superspeed(&self) -> bool {
|
||||
self.ssc.is_some()
|
||||
}
|
||||
pub fn is_superspeedplus(&self) -> bool {
|
||||
self.sspc.is_some()
|
||||
}
|
||||
fn interrupt_usage_bits(&self) -> u8 {
|
||||
assert!(self.is_interrupt());
|
||||
(self.attributes & 0x20) >> 4
|
||||
}
|
||||
pub fn is_periodic(&self) -> bool {
|
||||
#[repr(u8)]
|
||||
enum InterruptUsageBits {
|
||||
Periodic,
|
||||
Notification,
|
||||
Rsvd2,
|
||||
Rsvd3,
|
||||
}
|
||||
|
||||
if self.is_interrupt() {
|
||||
self.interrupt_usage_bits() == InterruptUsageBits::Periodic as u8
|
||||
} else {
|
||||
self.is_isoch()
|
||||
}
|
||||
}
|
||||
pub fn log_max_streams(&self) -> Option<NonZeroU8> {
|
||||
self.ssc
|
||||
.as_ref()
|
||||
.map(|ssc| {
|
||||
if self.is_bulk() {
|
||||
let raw = ssc.attributes & 0x1F;
|
||||
NonZeroU8::new(raw)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
}
|
||||
pub fn isoch_mult(&self, lec: bool) -> u8 {
|
||||
if !lec && self.is_isoch() {
|
||||
if self.is_superspeedplus() {
|
||||
return 0;
|
||||
}
|
||||
self.ssc
|
||||
.as_ref()
|
||||
.map(|ssc| ssc.attributes & 0x3)
|
||||
.unwrap_or(0)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
pub fn max_burst(&self) -> u8 {
|
||||
self.ssc.map(|ssc| ssc.max_burst).unwrap_or(0)
|
||||
}
|
||||
pub fn has_ssp_companion(&self) -> bool {
|
||||
self.ssc
|
||||
.map(|ssc| ssc.attributes & (1 << 7) != 0)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct IfDesc {
|
||||
pub kind: u8,
|
||||
pub number: u8,
|
||||
pub alternate_setting: u8,
|
||||
pub class: u8,
|
||||
pub sub_class: u8,
|
||||
pub protocol: u8,
|
||||
pub interface_str: Option<String>,
|
||||
pub endpoints: SmallVec<[EndpDesc; 4]>,
|
||||
pub hid_descs: SmallVec<[HidDesc; 1]>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
pub struct SuperSpeedCmp {
|
||||
pub kind: u8,
|
||||
pub max_burst: u8,
|
||||
pub attributes: u8,
|
||||
pub bytes_per_interval: u16,
|
||||
}
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
pub struct SuperSpeedPlusIsochCmp {
|
||||
pub kind: u8,
|
||||
pub bytes_per_interval: u32,
|
||||
}
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
pub struct HidDesc {
|
||||
pub kind: u8,
|
||||
pub hid_spec_release: u16,
|
||||
pub country: u8,
|
||||
pub desc_count: u8,
|
||||
pub desc_ty: u8,
|
||||
pub desc_len: u16,
|
||||
pub optional_desc_ty: u8,
|
||||
pub optional_desc_len: u16,
|
||||
}
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
pub struct PortReq {
|
||||
pub direction: PortReqDirection,
|
||||
pub req_type: PortReqTy,
|
||||
pub req_recipient: PortReqRecipient,
|
||||
pub request: u8,
|
||||
pub value: u16,
|
||||
pub index: u16,
|
||||
pub length: u16,
|
||||
pub transfers_data: bool,
|
||||
}
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
|
||||
pub enum PortReqDirection {
|
||||
HostToDevice,
|
||||
DeviceToHost,
|
||||
}
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
pub enum PortReqTy {
|
||||
Class,
|
||||
Vendor,
|
||||
Standard,
|
||||
}
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
pub enum PortReqRecipient {
|
||||
Device,
|
||||
Interface,
|
||||
Endpoint,
|
||||
Other,
|
||||
VendorSpecific,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub struct PortId {
|
||||
pub root_hub_port_num: u8,
|
||||
pub route_string: u32,
|
||||
}
|
||||
|
||||
impl PortId {
|
||||
pub fn root_hub_port_index(&self) -> usize {
|
||||
self.root_hub_port_num.checked_sub(1).unwrap().into()
|
||||
}
|
||||
|
||||
pub fn hub_depth(&self) -> u8 {
|
||||
let mut hub_depth = 0;
|
||||
let mut route_string = self.route_string;
|
||||
while route_string != 0 {
|
||||
route_string >>= 4;
|
||||
hub_depth += 1;
|
||||
}
|
||||
hub_depth
|
||||
}
|
||||
|
||||
pub fn child(&self, value: u8) -> Result<Self, String> {
|
||||
let depth = self.hub_depth();
|
||||
if depth >= 5 {
|
||||
return Err(format!("too many route string components"));
|
||||
}
|
||||
if value & 0xF0 != 0 {
|
||||
return Err(format!(
|
||||
"value {:?} is too large for route string component",
|
||||
value
|
||||
));
|
||||
}
|
||||
Ok(Self {
|
||||
root_hub_port_num: self.root_hub_port_num,
|
||||
route_string: self.route_string | u32::from(value) << (depth * 4),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parent(&self) -> Option<(Self, u8)> {
|
||||
let depth = self.hub_depth();
|
||||
let parent_depth = depth.checked_sub(1)?;
|
||||
let parent_shift = parent_depth * 4;
|
||||
let parent_mask = 0xF << parent_shift;
|
||||
Some((
|
||||
Self {
|
||||
root_hub_port_num: self.root_hub_port_num,
|
||||
route_string: self.route_string & !parent_mask,
|
||||
},
|
||||
u8::try_from((self.route_string & parent_mask) >> parent_shift).unwrap(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for PortId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.root_hub_port_num)?;
|
||||
// USB 3.1 Revision 1.1 Specification Section 8.9 Route String Field
|
||||
// The Route String is a 20-bit field in downstream directed packets that the hub uses to route
|
||||
// each packet to the designated downstream port. It is composed of a concatenation of the
|
||||
// downstream port numbers (4 bits per hub) for each hub traversed to reach a device.
|
||||
let mut route_string = self.route_string;
|
||||
while route_string != 0 {
|
||||
write!(f, ".{}", route_string & 0xF)?;
|
||||
route_string >>= 4;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl str::FromStr for PortId {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut root_hub_port_num = 0;
|
||||
let mut route_string = 0;
|
||||
for (i, part) in s.split('.').enumerate() {
|
||||
let value: u8 = part
|
||||
.parse()
|
||||
.map_err(|e| format!("failed to parse {:?}: {}", part, e))?;
|
||||
|
||||
// Neither root hub port number nor route string support 0 components
|
||||
// to identify downstream ports
|
||||
if value == 0 {
|
||||
return Err(format!("zero is not a valid port ID component"));
|
||||
}
|
||||
|
||||
// Parse root hub port number
|
||||
if i == 0 {
|
||||
root_hub_port_num = value;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse route string component
|
||||
let depth = i - 1;
|
||||
if depth >= 5 {
|
||||
return Err(format!("too many route string components"));
|
||||
}
|
||||
if value & 0xF0 != 0 {
|
||||
return Err(format!(
|
||||
"value {:?} is too large for route string component",
|
||||
value
|
||||
));
|
||||
}
|
||||
route_string |= u32::from(value) << (depth * 4);
|
||||
}
|
||||
Ok(Self {
|
||||
root_hub_port_num,
|
||||
route_string,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct XhciClientHandle {
|
||||
fd: libredox::Fd,
|
||||
scheme: String,
|
||||
port: PortId,
|
||||
}
|
||||
impl fmt::Debug for XhciClientHandle {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("XhciClientHandle")
|
||||
.field("scheme", &self.scheme)
|
||||
.field("port", &self.port)
|
||||
.field("fd", &"libredox::Fd")
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum PortState {
|
||||
EnabledOrDisabled,
|
||||
Default,
|
||||
Addressed,
|
||||
Configured,
|
||||
}
|
||||
impl PortState {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::EnabledOrDisabled => "enabled_or_disabled",
|
||||
Self::Default => "default",
|
||||
Self::Addressed => "addressed",
|
||||
Self::Configured => "configured",
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Error)]
|
||||
#[error("invalid input")]
|
||||
pub struct Invalid(pub &'static str);
|
||||
|
||||
impl str::FromStr for PortState {
|
||||
type Err = Invalid;
|
||||
|
||||
fn from_str(s: &str) -> result::Result<Self, Self::Err> {
|
||||
Ok(match s {
|
||||
"enabled_or_disabled" | "enabled/disabled" => Self::EnabledOrDisabled,
|
||||
"default" => Self::Default,
|
||||
"addressed" => Self::Addressed,
|
||||
"configured" => Self::Configured,
|
||||
_ => return Err(Invalid("read reserved port state")),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
|
||||
pub enum EndpointStatus {
|
||||
Disabled,
|
||||
Enabled,
|
||||
Halted,
|
||||
Stopped,
|
||||
Error,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Serialize, Deserialize)]
|
||||
pub struct PortTransferStatus {
|
||||
pub kind: PortTransferStatusKind,
|
||||
pub bytes_transferred: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
|
||||
pub enum PortTransferStatusKind {
|
||||
Success,
|
||||
ShortPacket,
|
||||
Stalled,
|
||||
Unknown,
|
||||
}
|
||||
impl Default for PortTransferStatusKind {
|
||||
fn default() -> Self {
|
||||
Self::Success
|
||||
}
|
||||
}
|
||||
|
||||
impl EndpointStatus {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Disabled => "disabled",
|
||||
Self::Enabled => "enabled",
|
||||
Self::Halted => "halted",
|
||||
Self::Stopped => "stopped",
|
||||
Self::Error => "error",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl str::FromStr for EndpointStatus {
|
||||
type Err = Invalid;
|
||||
|
||||
fn from_str(s: &str) -> result::Result<Self, Self::Err> {
|
||||
Ok(match s {
|
||||
"disabled" => Self::Disabled,
|
||||
"enabled" => Self::Enabled,
|
||||
"halted" => Self::Halted,
|
||||
"stopped" => Self::Stopped,
|
||||
"error" => Self::Error,
|
||||
_ => return Err(Invalid("read reserved endpoint state")),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub enum DeviceReqData<'a> {
|
||||
In(&'a mut [u8]),
|
||||
Out(&'a [u8]),
|
||||
NoData,
|
||||
}
|
||||
impl DeviceReqData<'_> {
|
||||
pub fn len(&self) -> usize {
|
||||
match self {
|
||||
Self::In(buf) => buf.len(),
|
||||
Self::Out(buf) => buf.len(),
|
||||
Self::NoData => 0,
|
||||
}
|
||||
}
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
pub fn map_buf<T, F: Fn(&[u8]) -> T>(&self, f: F) -> Option<T> {
|
||||
match self {
|
||||
Self::In(sbuf) => Some(f(sbuf)),
|
||||
Self::Out(dbuf) => Some(f(dbuf)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub fn direction(&self) -> PortReqDirection {
|
||||
match self {
|
||||
DeviceReqData::Out(_) => PortReqDirection::HostToDevice,
|
||||
DeviceReqData::NoData => PortReqDirection::HostToDevice,
|
||||
DeviceReqData::In(_) => PortReqDirection::DeviceToHost,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl XhciClientHandle {
|
||||
pub fn new(scheme: String, port: PortId) -> result::Result<Self, XhciClientHandleError> {
|
||||
let path = format!("/scheme/{}/port{}", scheme, port);
|
||||
let fd = libredox::Fd::open(&path, libredox::flag::O_DIRECTORY, 0)?;
|
||||
Ok(Self { fd, scheme, port })
|
||||
}
|
||||
fn read(&self, path: &str) -> result::Result<Vec<u8>, XhciClientHandleError> {
|
||||
let target_fd = self.fd.openat(path, libredox::flag::O_RDONLY, 0)?;
|
||||
let stat = target_fd.stat()?;
|
||||
let mut buf: Vec<u8> = vec![0u8; stat.st_size as usize];
|
||||
let count = target_fd.read(&mut buf)?;
|
||||
buf.truncate(count);
|
||||
Ok(buf)
|
||||
}
|
||||
fn read_to_string(&self, path: &str) -> result::Result<String, XhciClientHandleError> {
|
||||
let buf = self.read(path)?;
|
||||
Ok(String::from_utf8(buf)?)
|
||||
}
|
||||
pub fn attach(&self) -> result::Result<(), XhciClientHandleError> {
|
||||
let file = self.fd.openat("attach", libredox::flag::O_WRONLY, 0)?;
|
||||
let _bytes_written = file.write(&[])?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn detach(&self) -> result::Result<(), XhciClientHandleError> {
|
||||
let file = self.fd.openat("detach", libredox::flag::O_WRONLY, 0)?;
|
||||
let _bytes_written = file.write(&[])?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn get_standard_descs(&self) -> result::Result<DevDesc, XhciClientHandleError> {
|
||||
let json = self.read("descriptors")?;
|
||||
Ok(serde_json::from_slice(&json)?)
|
||||
}
|
||||
pub fn configure_endpoints(
|
||||
&self,
|
||||
req: &ConfigureEndpointsReq,
|
||||
) -> result::Result<(), XhciClientHandleError> {
|
||||
let json = serde_json::to_vec(req)?;
|
||||
let file = self.fd.openat("configure", libredox::flag::O_WRONLY, 0)?;
|
||||
let json_bytes_written = file.write(&json)?;
|
||||
if json_bytes_written != json.len() {
|
||||
return Err(XhciClientHandleError::InvalidResponse(Invalid(
|
||||
"configure_endpoints didn't read as many bytes as were requested",
|
||||
)));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn port_state(&self) -> result::Result<PortState, XhciClientHandleError> {
|
||||
let string = self.read_to_string("state")?;
|
||||
Ok(string.parse()?)
|
||||
}
|
||||
pub fn open_endpoint_ctl(&self, num: u8) -> result::Result<File, XhciClientHandleError> {
|
||||
let path = format!("endpoints/{}/ctl", num);
|
||||
let fd = self.fd.openat(&path, libredox::flag::O_RDWR, 0)?;
|
||||
Ok(unsafe { File::from_raw_fd(fd.into_raw() as RawFd) })
|
||||
}
|
||||
pub fn open_endpoint_data(&self, num: u8) -> result::Result<File, XhciClientHandleError> {
|
||||
let path = format!("endpoints/{}/data", num);
|
||||
let fd = self.fd.openat(&path, libredox::flag::O_RDWR, 0)?;
|
||||
Ok(unsafe { File::from_raw_fd(fd.into_raw() as RawFd) })
|
||||
}
|
||||
pub fn open_endpoint(&self, num: u8) -> result::Result<XhciEndpHandle, XhciClientHandleError> {
|
||||
Ok(XhciEndpHandle {
|
||||
ctl: self.open_endpoint_ctl(num)?,
|
||||
data: self.open_endpoint_data(num)?,
|
||||
})
|
||||
}
|
||||
pub fn device_request<'a>(
|
||||
&self,
|
||||
req_type: PortReqTy,
|
||||
req_recipient: PortReqRecipient,
|
||||
request: u8,
|
||||
value: u16,
|
||||
index: u16,
|
||||
data: DeviceReqData<'a>,
|
||||
) -> result::Result<(), XhciClientHandleError> {
|
||||
let length = u16::try_from(data.len())
|
||||
.or(Err(XhciClientHandleError::TransferBufTooLarge(data.len())))?;
|
||||
|
||||
let req = PortReq {
|
||||
direction: data.direction(),
|
||||
req_type,
|
||||
req_recipient,
|
||||
request,
|
||||
value,
|
||||
index,
|
||||
length,
|
||||
transfers_data: !matches!(data, DeviceReqData::NoData),
|
||||
};
|
||||
let json = serde_json::to_vec(&req)?;
|
||||
|
||||
let file = self.fd.openat("request", libredox::flag::O_RDWR, 0)?;
|
||||
|
||||
let json_bytes_written = file.write(&json)?;
|
||||
if json_bytes_written != json.len() {
|
||||
return Err(XhciClientHandleError::InvalidResponse(Invalid(
|
||||
"device_request didn't return the same number of bytes as were written",
|
||||
)));
|
||||
}
|
||||
|
||||
match data {
|
||||
DeviceReqData::In(buf) => {
|
||||
let bytes_read = file.read(buf)?;
|
||||
|
||||
if bytes_read != buf.len() {
|
||||
return Err(XhciClientHandleError::InvalidResponse(Invalid(
|
||||
"device_request didn't transfer (host2dev) all bytes",
|
||||
)));
|
||||
}
|
||||
}
|
||||
DeviceReqData::Out(buf) => {
|
||||
let bytes_read = file.write(&buf)?;
|
||||
|
||||
if bytes_read != buf.len() {
|
||||
return Err(XhciClientHandleError::InvalidResponse(Invalid(
|
||||
"device_request didn't transfer (dev2host) all bytes",
|
||||
)));
|
||||
}
|
||||
}
|
||||
DeviceReqData::NoData => (),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn get_descriptor(
|
||||
&self,
|
||||
recipient: PortReqRecipient,
|
||||
ty: u8,
|
||||
idx: u8,
|
||||
windex: u16,
|
||||
buffer: &mut [u8],
|
||||
) -> result::Result<(), XhciClientHandleError> {
|
||||
self.device_request(
|
||||
PortReqTy::Standard,
|
||||
recipient,
|
||||
0x06,
|
||||
(u16::from(ty) << 8) | u16::from(idx),
|
||||
windex,
|
||||
DeviceReqData::In(buffer),
|
||||
)
|
||||
}
|
||||
pub fn clear_feature(
|
||||
&self,
|
||||
recipient: PortReqRecipient,
|
||||
index: u16,
|
||||
feature_sel: u16,
|
||||
) -> result::Result<(), XhciClientHandleError> {
|
||||
self.device_request(
|
||||
PortReqTy::Standard,
|
||||
recipient,
|
||||
0x01,
|
||||
feature_sel,
|
||||
index,
|
||||
DeviceReqData::NoData,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct XhciEndpHandle {
|
||||
data: File,
|
||||
ctl: File,
|
||||
}
|
||||
|
||||
/// The direction of a transfer.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
|
||||
pub enum XhciEndpCtlDirection {
|
||||
/// Host to device
|
||||
Out,
|
||||
/// Device to host
|
||||
In,
|
||||
/// No data, and hence no I/O on the Data interface file at all.
|
||||
NoData,
|
||||
}
|
||||
|
||||
/// A request to an endpoint Ctl interface file. Currently serialized with JSON.
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
#[non_exhaustive]
|
||||
pub enum XhciEndpCtlReq {
|
||||
// TODO: Reduce the number of direction enums from 5 to perhaps 2.
|
||||
// TODO: Allow to send multiple buffers in one transfer.
|
||||
/// Tells xhcid that a buffer is about to be sent from the Data interface file, to the
|
||||
/// endpoint.
|
||||
Transfer {
|
||||
/// The direction of the transfer. If the direction is `XhciEndpCtlDirection::NoData`, no
|
||||
/// bytes will be transferred, and therefore no reads or writes shall be done to the Data
|
||||
/// driver interface file.
|
||||
direction: XhciEndpCtlDirection,
|
||||
|
||||
/// The number of bytes to be read or written. This field must be set to zero if the
|
||||
/// direction is `XhciEndpCtlDirection::NoData`. When all bytes have been read or written,
|
||||
/// the transfer will be considered complete by xhcid, and a non-pending status will be
|
||||
/// returned.
|
||||
count: u32,
|
||||
},
|
||||
// TODO: Allow clients to specify what to reset.
|
||||
/// Tells xhcid that the endpoint is going to be reset.
|
||||
Reset {
|
||||
/// Only issue the Reset Endpoint and Set TR Dequeue Pointer commands, and let the client
|
||||
/// itself send a potential ClearFeature(ENDPOINT_HALT).
|
||||
no_clear_feature: bool,
|
||||
},
|
||||
|
||||
/// Tells xhcid that the endpoint status is going to be retrieved from the Ctl interface file.
|
||||
Status,
|
||||
}
|
||||
/// A response from an endpoint Ctl interface file. Currently serialized with JSON.
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
#[non_exhaustive]
|
||||
pub enum XhciEndpCtlRes {
|
||||
/// Xhcid responded with the current state of an endpoint.
|
||||
Status(EndpointStatus),
|
||||
|
||||
/// Xhci sent the result of a transfer.
|
||||
TransferResult(PortTransferStatus),
|
||||
|
||||
/// Xhcid is waiting for data to be sent or received on the Data interface file.
|
||||
Pending,
|
||||
|
||||
/// No Ctl request is currently being processed by xhcid.
|
||||
Idle,
|
||||
}
|
||||
|
||||
impl XhciEndpHandle {
|
||||
fn ctl_req(&mut self, ctl_req: &XhciEndpCtlReq) -> result::Result<(), XhciClientHandleError> {
|
||||
let ctl_buffer = serde_json::to_vec(ctl_req)?;
|
||||
|
||||
let ctl_bytes_written = self.ctl.write(&ctl_buffer)?;
|
||||
if ctl_bytes_written != ctl_buffer.len() {
|
||||
return Err(Invalid("xhcid didn't process all of the ctl bytes").into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
fn ctl_res(&mut self) -> result::Result<XhciEndpCtlRes, XhciClientHandleError> {
|
||||
// a response must never exceed 256 bytes
|
||||
let mut ctl_buffer = [0u8; 256];
|
||||
let ctl_bytes_read = self.ctl.read(&mut ctl_buffer)?;
|
||||
|
||||
let ctl_res = serde_json::from_slice(&ctl_buffer[..ctl_bytes_read as usize])?;
|
||||
Ok(ctl_res)
|
||||
}
|
||||
pub fn reset(&mut self, no_clear_feature: bool) -> result::Result<(), XhciClientHandleError> {
|
||||
self.ctl_req(&XhciEndpCtlReq::Reset { no_clear_feature })
|
||||
}
|
||||
pub fn status(&mut self) -> result::Result<EndpointStatus, XhciClientHandleError> {
|
||||
self.ctl_req(&XhciEndpCtlReq::Status)?;
|
||||
match self.ctl_res()? {
|
||||
XhciEndpCtlRes::Status(s) => Ok(s),
|
||||
_ => Err(Invalid("expected status response").into()),
|
||||
}
|
||||
}
|
||||
fn generic_transfer<F: FnOnce(&mut File) -> io::Result<usize>>(
|
||||
&mut self,
|
||||
direction: XhciEndpCtlDirection,
|
||||
f: F,
|
||||
expected_len: u32,
|
||||
) -> result::Result<PortTransferStatus, XhciClientHandleError> {
|
||||
let req = XhciEndpCtlReq::Transfer {
|
||||
direction,
|
||||
count: expected_len,
|
||||
};
|
||||
self.ctl_req(&req)?;
|
||||
|
||||
let bytes_read = f(&mut self.data)?;
|
||||
let res = self.ctl_res()?;
|
||||
|
||||
match res {
|
||||
XhciEndpCtlRes::TransferResult(PortTransferStatus {
|
||||
kind: PortTransferStatusKind::Success,
|
||||
..
|
||||
}) if bytes_read != expected_len as usize => {
|
||||
Err(Invalid("no short packet, but fewer bytes were read/written").into())
|
||||
}
|
||||
XhciEndpCtlRes::TransferResult(r) => Ok(r),
|
||||
_ => Err(Invalid("expected transfer result").into()),
|
||||
}
|
||||
}
|
||||
pub fn transfer_write(
|
||||
&mut self,
|
||||
buf: &[u8],
|
||||
) -> result::Result<PortTransferStatus, XhciClientHandleError> {
|
||||
self.generic_transfer(
|
||||
XhciEndpCtlDirection::Out,
|
||||
|data| data.write(buf),
|
||||
buf.len() as u32,
|
||||
)
|
||||
}
|
||||
pub fn transfer_read(
|
||||
&mut self,
|
||||
buf: &mut [u8],
|
||||
) -> result::Result<PortTransferStatus, XhciClientHandleError> {
|
||||
let len = buf.len() as u32;
|
||||
self.generic_transfer(XhciEndpCtlDirection::In, |data| data.read(buf), len)
|
||||
}
|
||||
pub fn transfer_nodata(&mut self) -> result::Result<PortTransferStatus, XhciClientHandleError> {
|
||||
self.generic_transfer(XhciEndpCtlDirection::NoData, |_| Ok(0), 0)
|
||||
}
|
||||
fn transfer_stream(&mut self, total_len: u32) -> TransferStream<'_> {
|
||||
TransferStream {
|
||||
bytes_to_transfer: total_len,
|
||||
bytes_transferred: 0,
|
||||
bytes_per_transfer: 32768, // TODO
|
||||
endp_handle: self,
|
||||
}
|
||||
}
|
||||
pub fn transfer_write_stream(&mut self, total_len: u32) -> TransferWriteStream<'_> {
|
||||
TransferWriteStream {
|
||||
inner: self.transfer_stream(total_len),
|
||||
}
|
||||
}
|
||||
pub fn transfer_read_stream(&mut self, total_len: u32) -> TransferReadStream<'_> {
|
||||
TransferReadStream {
|
||||
inner: self.transfer_stream(total_len),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TransferWriteStream<'a> {
|
||||
inner: TransferStream<'a>,
|
||||
}
|
||||
pub struct TransferReadStream<'a> {
|
||||
inner: TransferStream<'a>,
|
||||
}
|
||||
struct TransferStream<'a> {
|
||||
bytes_to_transfer: u32,
|
||||
bytes_transferred: u32,
|
||||
bytes_per_transfer: u32,
|
||||
endp_handle: &'a mut XhciEndpHandle,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum XhciClientHandleError {
|
||||
#[error("i/o error: {0}")]
|
||||
IoError(#[from] io::Error),
|
||||
|
||||
#[error("serialization error: {0}")]
|
||||
SerializationError(#[from] serde_json::Error),
|
||||
|
||||
#[error("invalid response")]
|
||||
InvalidResponse(#[from] Invalid),
|
||||
|
||||
#[error("transfer buffer too large ({0} > 65536)")]
|
||||
TransferBufTooLarge(usize),
|
||||
|
||||
#[error("unexpected short packet of size {0}")]
|
||||
UnexpectedShortPacket(usize),
|
||||
|
||||
#[error("utf8 error: {0}")]
|
||||
Utf8Error(#[from] FromUtf8Error),
|
||||
}
|
||||
|
||||
impl From<libredox::error::Error> for XhciClientHandleError {
|
||||
fn from(error: libredox::error::Error) -> Self {
|
||||
Self::IoError(error.into())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
//! The eXtensible Host Controller Interface (XHCI) Daemon Interface
|
||||
//!
|
||||
//! This crate implements the driver interface for interacting with the Redox xhcid daemon from
|
||||
//! another userspace process.
|
||||
//!
|
||||
//! XHCI is a standard for the USB Host Controller interface specified by Intel that provides a
|
||||
//! common register interface for systems to use to interact with the Universal Serial Bus (USB)
|
||||
//! subsystem.
|
||||
//!
|
||||
//! USB consists of three types of devices: The Host Controller/Root Hub, USB Hubs, and Endpoints.
|
||||
//! Endpoints represent actual devices connected to the USB fabric. USB Hubs are intermediaries
|
||||
//! between the Host Controller and the endpoints that report when devices have been connected/disconnected.
|
||||
//! The Host Controller provides the interface to the USB subsystem that software running on the
|
||||
//! system's CPU can interact with. It's a tree-like structure, which the Host Controller enumerating
|
||||
//! and addressing all the hubs and endpoints in the tree. Data then flows through the fabric
|
||||
//! using the USB protocol (2.0 or 3.2) as packets. Hubs have multiple ports that endpoints can
|
||||
//! connect to, and they notify the Host Controller/Root Hub when devices are hot plugged or removed.
|
||||
//!
|
||||
//! This documentation will refer directly to the relevant standards, which are as follows:
|
||||
//!
|
||||
//! - XHCI - [eXtensible Host Controller Interface for Universal Serial Bus (xHCI) Requirements Specification](https://www.intel.com/content/dam/www/public/us/en/documents/technical-specifications/extensible-host-controler-interface-usb-xhci.pdf)
|
||||
//! - USB2 - [Universal Serial Bus Specification](https://www.usb.org/document-library/usb-20-specification)
|
||||
//! - USB32 - [Universal Serial Bus 3.2 Specification Revision 1.1](https://usb.org/document-library/usb-32-revision-11-june-2022)
|
||||
//!
|
||||
pub extern crate plain;
|
||||
|
||||
mod driver_interface;
|
||||
pub mod usb;
|
||||
|
||||
pub use driver_interface::*;
|
||||
@@ -0,0 +1,181 @@
|
||||
//! The eXtensible Host Controller Interface (XHCI) Daemon
|
||||
//!
|
||||
//! This crate provides the executable xhcid daemon that implements the driver for interacting with
|
||||
//! a PCIe XHCI device
|
||||
//!
|
||||
//! XHCI is a standard for the USB Host Controller interface specified by Intel that provides a
|
||||
//! common register interface for systems to use to interact with the Universal Serial Bus (USB)
|
||||
//! subsystem.
|
||||
//!
|
||||
//! USB consists of three types of devices: The Host Controller/Root Hub, USB Hubs, and Endpoints.
|
||||
//! Endpoints represent actual devices connected to the USB fabric. USB Hubs are intermediaries
|
||||
//! between the Host Controller and the endpoints that report when devices have been connected/disconnected.
|
||||
//! The Host Controller provides the interface to the USB subsystem that software running on the
|
||||
//! system's CPU can interact with. It's a tree-like structure, which the Host Controller enumerating
|
||||
//! and addressing all the hubs and endpoints in the tree. Data then flows through the fabric
|
||||
//! using the USB protocol (2.0 or 3.2) as packets. Hubs have multiple ports that endpoints can
|
||||
//! connect to, and they notify the Host Controller/Root Hub when devices are hot plugged or removed.
|
||||
//!
|
||||
//! This documentation will refer directly to the relevant standards, which are as follows:
|
||||
//!
|
||||
//! - XHCI - [eXtensible Host Controller Interface for Universal Serial Bus (xHCI) Requirements Specification](https://www.intel.com/content/dam/www/public/us/en/documents/technical-specifications/extensible-host-controler-interface-usb-xhci.pdf)
|
||||
//! - USB2 - [Universal Serial Bus Specification](https://www.usb.org/document-library/usb-20-specification)
|
||||
//! - USB32 - [Universal Serial Bus 3.2 Specification Revision 1.1](https://usb.org/document-library/usb-32-revision-11-june-2022)
|
||||
//!
|
||||
#![allow(warnings)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
|
||||
use std::fs::File;
|
||||
use std::sync::Arc;
|
||||
|
||||
use pcid_interface::irq_helpers::read_bsp_apic_id;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use pcid_interface::irq_helpers::{
|
||||
allocate_first_msi_interrupt_on_bsp, allocate_single_interrupt_vector_for_msi,
|
||||
};
|
||||
use pcid_interface::{PciFeature, PciFeatureInfo, PciFunctionHandle};
|
||||
|
||||
use redox_scheme::{scheme::register_sync_scheme, Socket};
|
||||
use scheme_utils::Blocking;
|
||||
|
||||
use crate::xhci::{InterruptMethod, Xhci};
|
||||
|
||||
// Declare as pub so that no warnings appear due to parts of the interface code not being used by
|
||||
// the driver. Since there's also a dedicated crate for the driver interface, those warnings don't
|
||||
// mean anything.
|
||||
pub mod driver_interface;
|
||||
|
||||
mod usb;
|
||||
mod xhci;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn get_int_method(pcid_handle: &mut PciFunctionHandle) -> (Option<File>, InterruptMethod) {
|
||||
let pci_config = pcid_handle.config();
|
||||
|
||||
let all_pci_features = pcid_handle.fetch_all_features();
|
||||
log::debug!("XHCI PCI FEATURES: {:?}", all_pci_features);
|
||||
|
||||
let has_msi = all_pci_features.iter().any(|feature| feature.is_msi());
|
||||
let has_msix = all_pci_features.iter().any(|feature| feature.is_msix());
|
||||
|
||||
if has_msix {
|
||||
let msix_info = match pcid_handle.feature_info(PciFeature::MsiX) {
|
||||
PciFeatureInfo::Msi(_) => panic!(),
|
||||
PciFeatureInfo::MsiX(s) => s,
|
||||
};
|
||||
let mut info = unsafe { msix_info.map_and_mask_all(pcid_handle) };
|
||||
|
||||
// Allocate one msi vector.
|
||||
|
||||
let method = {
|
||||
// primary interrupter
|
||||
let k = 0;
|
||||
|
||||
let table_entry_pointer = info.table_entry_pointer(k);
|
||||
|
||||
let destination_id = read_bsp_apic_id().expect("xhcid: failed to read BSP apic id");
|
||||
let (msg_addr_and_data, interrupt_handle) =
|
||||
allocate_single_interrupt_vector_for_msi(destination_id);
|
||||
table_entry_pointer.write_addr_and_data(msg_addr_and_data);
|
||||
table_entry_pointer.unmask();
|
||||
|
||||
(Some(interrupt_handle), InterruptMethod::Msi)
|
||||
};
|
||||
|
||||
pcid_handle.enable_feature(PciFeature::MsiX);
|
||||
log::debug!("Enabled MSI-X");
|
||||
|
||||
method
|
||||
} else if has_msi {
|
||||
let interrupt_handle = allocate_first_msi_interrupt_on_bsp(pcid_handle);
|
||||
(Some(interrupt_handle), InterruptMethod::Msi)
|
||||
} else if let Some(irq) = pci_config.func.legacy_interrupt_line {
|
||||
log::debug!("Legacy IRQ {}", irq);
|
||||
|
||||
// legacy INTx# interrupt pins.
|
||||
(Some(irq.irq_handle("xhcid")), InterruptMethod::Intx)
|
||||
} else {
|
||||
// no interrupts at all
|
||||
(None, InterruptMethod::Polling)
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: MSI on non-x86_64?
|
||||
#[cfg(not(target_arch = "x86_64"))]
|
||||
fn get_int_method(pcid_handle: &mut PciFunctionHandle) -> (Option<File>, InterruptMethod) {
|
||||
let pci_config = pcid_handle.config();
|
||||
|
||||
if let Some(irq) = pci_config.func.legacy_interrupt_line {
|
||||
// legacy INTx# interrupt pins.
|
||||
(Some(irq.irq_handle("xhcid")), InterruptMethod::Intx)
|
||||
} else {
|
||||
// no interrupts at all
|
||||
(None, InterruptMethod::Polling)
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: cleanup CSZ support
|
||||
fn daemon_with_context_size<const N: usize>(
|
||||
daemon: daemon::Daemon,
|
||||
mut pcid_handle: PciFunctionHandle,
|
||||
) -> ! {
|
||||
let pci_config = pcid_handle.config();
|
||||
|
||||
let mut name = pci_config.func.name();
|
||||
name.push_str("_xhci");
|
||||
|
||||
common::setup_logging(
|
||||
"usb",
|
||||
"host",
|
||||
&name,
|
||||
common::output_level(),
|
||||
common::file_level(),
|
||||
);
|
||||
|
||||
log::debug!("XHCI PCI CONFIG: {:?}", pci_config);
|
||||
|
||||
let address = unsafe { pcid_handle.map_bar(0) }.ptr.as_ptr() as usize;
|
||||
|
||||
let (irq_file, interrupt_method) = (None, InterruptMethod::Polling); //get_int_method(&mut pcid_handle);
|
||||
//TODO: Fix interrupts.
|
||||
|
||||
log::info!("XHCI {}", pci_config.func.display());
|
||||
|
||||
let scheme_name = format!("usb.{}", name);
|
||||
let socket = Socket::create().expect("xhcid: failed to create usb scheme");
|
||||
let handler = Blocking::new(&socket, 16);
|
||||
|
||||
let hci = Arc::new(
|
||||
Xhci::<N>::new(scheme_name.clone(), address, interrupt_method, pcid_handle)
|
||||
.expect("xhcid: failed to allocate device"),
|
||||
);
|
||||
register_sync_scheme(&socket, &scheme_name, &mut &*hci)
|
||||
.expect("xhcid: failed to regsiter scheme to namespace");
|
||||
|
||||
daemon.ready();
|
||||
|
||||
xhci::start_irq_reactor(&hci, irq_file);
|
||||
xhci::start_device_enumerator(&hci);
|
||||
|
||||
hci.poll();
|
||||
|
||||
handler
|
||||
.process_requests_blocking(&*hci)
|
||||
.expect("xhcid: failed to process requests");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
pcid_interface::pci_daemon(daemon);
|
||||
}
|
||||
|
||||
fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||
let address = unsafe { pcid_handle.map_bar(0) }.ptr.as_ptr() as usize;
|
||||
let cap = unsafe { &mut *(address as *mut xhci::CapabilityRegs) };
|
||||
if cap.csz() {
|
||||
daemon_with_context_size::<{ xhci::CONTEXT_64 }>(daemon, pcid_handle)
|
||||
} else {
|
||||
daemon_with_context_size::<{ xhci::CONTEXT_32 }>(daemon, pcid_handle)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
use std::slice;
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct BosDescriptor {
|
||||
pub len: u8,
|
||||
pub kind: u8,
|
||||
pub total_len: u16,
|
||||
pub cap_count: u8,
|
||||
}
|
||||
|
||||
unsafe impl plain::Plain for BosDescriptor {}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct BosDevDescriptorBase {
|
||||
pub len: u8,
|
||||
pub kind: u8,
|
||||
pub cap_ty: u8,
|
||||
}
|
||||
|
||||
unsafe impl plain::Plain for BosDevDescriptorBase {}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct BosSuperSpeedDesc {
|
||||
pub len: u8,
|
||||
pub kind: u8,
|
||||
pub cap_ty: u8,
|
||||
|
||||
pub attrs: u8,
|
||||
pub speed_supp: u16,
|
||||
pub func_supp: u8,
|
||||
pub u1_dev_exit_lat: u8,
|
||||
pub u2_dev_exit_lat: u16,
|
||||
}
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct BosSuperSpeedPlusDesc {
|
||||
pub len: u8,
|
||||
pub kind: u8,
|
||||
pub cap_ty: u8,
|
||||
pub _rsvd0: u8,
|
||||
pub attrs: u32,
|
||||
pub func_supp: u32,
|
||||
pub _rsvd1: u16,
|
||||
}
|
||||
|
||||
unsafe impl plain::Plain for BosSuperSpeedPlusDesc {}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct BosUsb2ExtDesc {
|
||||
pub len: u8,
|
||||
pub kind: u8,
|
||||
pub cap_ty: u8,
|
||||
|
||||
pub attrs: u32,
|
||||
}
|
||||
|
||||
unsafe impl plain::Plain for BosUsb2ExtDesc {}
|
||||
|
||||
#[repr(u8)]
|
||||
pub enum DeviceCapability {
|
||||
Usb2Ext = 0x02,
|
||||
SuperSpeed,
|
||||
SuperSpeedPlus = 0x0A,
|
||||
}
|
||||
|
||||
unsafe impl plain::Plain for BosSuperSpeedDesc {}
|
||||
|
||||
impl BosSuperSpeedPlusDesc {
|
||||
pub fn ssac(&self) -> u8 {
|
||||
(self.attrs & 0x0000_000F) as u8
|
||||
}
|
||||
pub fn sublink_speed_attr(&self) -> &[u32] {
|
||||
unsafe {
|
||||
slice::from_raw_parts(
|
||||
(self as *const Self).add(1) as *const u32,
|
||||
self.ssac() as usize + 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BosDevDescIter<'a> {
|
||||
bytes: &'a [u8],
|
||||
}
|
||||
impl<'a> BosDevDescIter<'a> {
|
||||
pub fn new(bytes: &'a [u8]) -> Self {
|
||||
Self { bytes }
|
||||
}
|
||||
}
|
||||
impl<'a> From<&'a [u8]> for BosDevDescIter<'a> {
|
||||
fn from(slice: &'a [u8]) -> Self {
|
||||
Self::new(slice)
|
||||
}
|
||||
}
|
||||
impl<'a> Iterator for BosDevDescIter<'a> {
|
||||
type Item = (BosDevDescriptorBase, &'a [u8]);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if let Some(desc) = plain::from_bytes::<BosDevDescriptorBase>(self.bytes).ok() {
|
||||
if desc.len as usize > self.bytes.len() {
|
||||
return None;
|
||||
};
|
||||
let bytes_ret = &self.bytes[..desc.len as usize];
|
||||
self.bytes = &self.bytes[desc.len as usize..];
|
||||
Some((*desc, bytes_ret))
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum BosAnyDevDesc {
|
||||
Usb2Ext(BosUsb2ExtDesc),
|
||||
SuperSpeed(BosSuperSpeedDesc),
|
||||
SuperSpeedPlus(BosSuperSpeedPlusDesc),
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl BosAnyDevDesc {
|
||||
pub fn is_superspeed(&self) -> bool {
|
||||
match self {
|
||||
Self::SuperSpeed(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
pub fn is_superspeedplus(&self) -> bool {
|
||||
match self {
|
||||
Self::SuperSpeedPlus(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BosAnyDevDescIter<'a> {
|
||||
inner: BosDevDescIter<'a>,
|
||||
}
|
||||
impl<'a> From<BosDevDescIter<'a>> for BosAnyDevDescIter<'a> {
|
||||
fn from(ll: BosDevDescIter<'a>) -> Self {
|
||||
Self { inner: ll }
|
||||
}
|
||||
}
|
||||
impl<'a> From<&'a [u8]> for BosAnyDevDescIter<'a> {
|
||||
fn from(slice: &'a [u8]) -> Self {
|
||||
Self::from(BosDevDescIter::from(slice))
|
||||
}
|
||||
}
|
||||
impl<'a> Iterator for BosAnyDevDescIter<'a> {
|
||||
type Item = BosAnyDevDesc;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let (base, slice) = self.inner.next()?;
|
||||
|
||||
if base.cap_ty == DeviceCapability::Usb2Ext as u8 {
|
||||
Some(BosAnyDevDesc::Usb2Ext(*plain::from_bytes(slice).ok()?))
|
||||
} else if base.cap_ty == DeviceCapability::SuperSpeed as u8 {
|
||||
Some(BosAnyDevDesc::SuperSpeed(*plain::from_bytes(slice).ok()?))
|
||||
} else if base.cap_ty == DeviceCapability::SuperSpeedPlus as u8 {
|
||||
Some(BosAnyDevDesc::SuperSpeedPlus(
|
||||
*plain::from_bytes(slice).ok()?,
|
||||
))
|
||||
} else if base.cap_ty == 0 {
|
||||
// TODO
|
||||
return None;
|
||||
} else {
|
||||
log::warn!("unknown USB device capability of type: {:#x}", base.cap_ty);
|
||||
Some(BosAnyDevDesc::Unknown)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bos_capability_descs<'a>(
|
||||
desc: BosDescriptor,
|
||||
data: &'a [u8],
|
||||
) -> impl Iterator<Item = BosAnyDevDesc> + 'a {
|
||||
BosAnyDevDescIter::from(&data[..desc.total_len as usize - std::mem::size_of_val(&desc)])
|
||||
.take(desc.cap_count as usize)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct ConfigDescriptor {
|
||||
pub length: u8,
|
||||
pub kind: u8,
|
||||
pub total_length: u16,
|
||||
pub interfaces: u8,
|
||||
pub configuration_value: u8,
|
||||
pub configuration_str: u8,
|
||||
pub attributes: u8,
|
||||
pub max_power: u8,
|
||||
}
|
||||
|
||||
unsafe impl plain::Plain for ConfigDescriptor {}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct OtherSpeedConfig {
|
||||
pub length: u8,
|
||||
pub kind: u8,
|
||||
pub total_length: u16,
|
||||
pub interfaces: u8,
|
||||
pub configuration_value: u8,
|
||||
pub configuration_str: u8,
|
||||
pub attributes: u8,
|
||||
pub max_power: u8,
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
//! Implements the "Device" USB Descriptor.
|
||||
//!
|
||||
//! This descriptor is described in USB32 section 9.6.1
|
||||
|
||||
/// A USB Device Descriptor.
|
||||
///
|
||||
/// This is common to all USB standards, and "provides information that applies globally to the
|
||||
/// device and all the device's configurations" (USB32 9.6.1)
|
||||
///
|
||||
/// A given device will only have one device descriptor.
|
||||
///
|
||||
/// USB32 Table 9-11 describes the USB packet offsets of the fields described by this structure.
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct DeviceDescriptor {
|
||||
/// The length of this descriptor in bytes.
|
||||
/// The bLength field in USB32 Table 9-11
|
||||
pub length: u8,
|
||||
/// The descriptor type. See [DescriptorKind]
|
||||
/// The bDescriptorType field in USB32 Table 9-11.
|
||||
pub kind: u8,
|
||||
/// The USB standard version in binary-coded decimal.
|
||||
///
|
||||
/// USB 2.1 would be encoded as 210H, 3.2 would be 320H.
|
||||
/// The bcdUSB field in USB32 Table 9-11
|
||||
pub usb: u16,
|
||||
/// The USB Class Code.
|
||||
///
|
||||
/// bDeviceClass in USB32 Table 9-11.
|
||||
///
|
||||
/// These are values assigned by USB-IF that describes the type of device connected via USB.
|
||||
///
|
||||
/// A value of FF indicates a vendor-specific class. A value of 0 indicates that all the
|
||||
/// interfaces in a configuration will provide their own class information.
|
||||
pub class: u8,
|
||||
/// The USB Sub Device Class Code.
|
||||
///
|
||||
/// bDeviceSubClass in USB32 Table 9-11
|
||||
///
|
||||
/// These specify subclasses of a device class specified by the 'class' field.
|
||||
pub sub_class: u8,
|
||||
/// The USB Protocol code.
|
||||
///
|
||||
/// bDeviceProtocol in USB32 Table 9-11
|
||||
///
|
||||
/// This qualified by the class and sub_class fields, and specifies the application-layer protocol
|
||||
/// (the protocol encapsulated by USB) of this device.
|
||||
pub protocol: u8,
|
||||
/// The maximum packet size for endpoint 0.
|
||||
///
|
||||
/// bMaxPacketSize0 in USB32 Table 9-11
|
||||
pub packet_size: u8,
|
||||
/// The USB Vendor ID
|
||||
///
|
||||
/// idVendor in USB32 Table 9-11
|
||||
pub vendor: u16,
|
||||
/// The USB Product ID
|
||||
///
|
||||
/// idProduct in USB32 Table 9-11
|
||||
pub product: u16,
|
||||
/// The device release number in binary-coded decimal.
|
||||
///
|
||||
/// bcdDevice in USB32 Table 9-11
|
||||
pub release: u16,
|
||||
/// Index of the String Descriptor describing the device manufacturer
|
||||
///
|
||||
/// iManufacturer in USB32 Table 9-11
|
||||
pub manufacturer_str: u8,
|
||||
/// Index of the String Descriptor describing the product
|
||||
///
|
||||
/// iProduct in Table 9-11
|
||||
pub product_str: u8,
|
||||
/// Index of the string descriptor describing the device's serial number
|
||||
///
|
||||
/// iSerialNumber in USB32 Table 9-11
|
||||
pub serial_str: u8,
|
||||
/// The number of possible configurations (Configuration Descriptors) for this device.
|
||||
///
|
||||
/// bNumConfigurations in USB32 Table 9-11
|
||||
pub configurations: u8,
|
||||
}
|
||||
|
||||
unsafe impl plain::Plain for DeviceDescriptor {}
|
||||
|
||||
impl DeviceDescriptor {
|
||||
/// Gets the USB Minor Version
|
||||
pub fn minor_usb_vers(&self) -> u8 {
|
||||
(self.usb & 0xFF) as u8
|
||||
}
|
||||
/// Gets the USB Major Version
|
||||
pub fn major_usb_vers(&self) -> u8 {
|
||||
((self.usb >> 8) & 0xFF) as u8
|
||||
}
|
||||
}
|
||||
|
||||
/// The 8-byte version of the Device Descriptor
|
||||
///
|
||||
/// This is a subset of the full Device Descriptor. When the system is first performing device
|
||||
/// enumeration, it will request only the first eight bytes of the DeviceDescriptor from each
|
||||
/// device as this contains the crucial information, and then it will request the full descriptor
|
||||
/// at a later point.
|
||||
///
|
||||
/// See [DeviceDescriptor]
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct DeviceDescriptor8Byte {
|
||||
/// See [DeviceDescriptor]
|
||||
pub length: u8,
|
||||
/// See [DeviceDescriptor]
|
||||
pub kind: u8,
|
||||
/// See [DeviceDescriptor]
|
||||
pub usb: u16,
|
||||
/// See [DeviceDescriptor]
|
||||
pub class: u8,
|
||||
/// See [DeviceDescriptor]
|
||||
pub sub_class: u8,
|
||||
/// See [DeviceDescriptor]
|
||||
pub protocol: u8,
|
||||
/// See [DeviceDescriptor]
|
||||
pub packet_size: u8,
|
||||
}
|
||||
|
||||
unsafe impl plain::Plain for DeviceDescriptor8Byte {}
|
||||
|
||||
impl DeviceDescriptor8Byte {
|
||||
/// Gets the USB Minor Version
|
||||
pub fn minor_usb_vers(&self) -> u8 {
|
||||
(self.usb & 0xFF) as u8
|
||||
}
|
||||
|
||||
/// Gets the USB Major Version
|
||||
pub fn major_usb_vers(&self) -> u8 {
|
||||
((self.usb >> 8) & 0xFF) as u8
|
||||
}
|
||||
}
|
||||
|
||||
/// A Device Qualifier Descriptor
|
||||
///
|
||||
/// This is a descriptor specific to the USB2 standard, and was deprecated in USB3. USB2 devices
|
||||
/// will still provide this value.
|
||||
///
|
||||
/// A Device Qualifier is sent by a high-speed capable USB2 device to describe information in its
|
||||
/// descriptor that would change if it was operating at the other speed. If it was at low speed,
|
||||
/// the qualifier would describe the device at high speed. If it was at high speed, the qualifier
|
||||
/// would describe the device at low speed.
|
||||
///
|
||||
/// See USB2 section 9.6.2
|
||||
///
|
||||
/// The packet offsets are described in USB2 Table 9-9
|
||||
#[repr(C, packed)]
|
||||
pub struct DeviceQualifier {
|
||||
/// The size of the descriptor.
|
||||
///
|
||||
/// bLength in USB2 Table 9-9
|
||||
pub length: u8,
|
||||
/// The Device Descriptor Type (see [xhci_interface::usb::DescriptorKind])
|
||||
///
|
||||
/// bDescriptorType in USB2 Table 9-9
|
||||
pub kind: u8,
|
||||
/// The USB specification version number in binary-coded decimal
|
||||
///
|
||||
/// bDeviceClass in USB2 Table 9-9
|
||||
pub usb: u16,
|
||||
/// The USB Device Class Code
|
||||
///
|
||||
/// bDeviceClass in USB2 Table 9-9
|
||||
pub class: u8,
|
||||
/// The USB Device Sub Class Code
|
||||
///
|
||||
/// bDeviceSubClass in USB2 Table 9-9
|
||||
pub sub_class: u8,
|
||||
/// The USB Device Protocol Code
|
||||
///
|
||||
/// bDeviceProtocol in USB2 Table 9-9
|
||||
pub protocol: u8,
|
||||
/// The maximum packet size for the other speed\
|
||||
///
|
||||
/// bMaxPacketSize0 in USB2 Table9-9
|
||||
pub pkgsz_other_speed: u8,
|
||||
/// The number of device configurations for the other speed
|
||||
///
|
||||
/// bNumConfiguration in USB2 Table 9-9
|
||||
pub num_other_speed_cfgs: u8,
|
||||
/// Reserved for future use by the USB2 standard
|
||||
///
|
||||
/// (DeviceQualifier was dropped in USB3, so it was never used!)
|
||||
/// bReserved in USB2 Table 9-9
|
||||
pub _rsvd: u8,
|
||||
}
|
||||
|
||||
unsafe impl plain::Plain for DeviceQualifier {}
|
||||
@@ -0,0 +1,86 @@
|
||||
use plain::Plain;
|
||||
|
||||
/// The descriptor for a USB Endpoint.
|
||||
///
|
||||
/// Each endpoint for a particular interface has its own descriptor. The information in this
|
||||
/// structure is used by the host to determine the bandwidth requirements of the endpoint.
|
||||
///
|
||||
/// This is returned automatically when you send a request for a ConfigurationDescriptor,
|
||||
/// and cannot be requested individually.
|
||||
///
|
||||
/// See USB32 9.6.6
|
||||
///
|
||||
/// The offsets for the fields in the packet are described in USB32 Table 9-26
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct EndpointDescriptor {
|
||||
pub length: u8,
|
||||
pub kind: u8,
|
||||
pub address: u8,
|
||||
pub attributes: u8,
|
||||
pub max_packet_size: u16,
|
||||
pub interval: u8,
|
||||
}
|
||||
|
||||
/// Mask that is ANDed to the [EndpointDescriptor].attributes buffer to get the endpoint type.
|
||||
pub const ENDP_ATTR_TY_MASK: u8 = 0x3;
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum EndpointTy {
|
||||
Ctrl = 0,
|
||||
Isoch = 1,
|
||||
Bulk = 2,
|
||||
Interrupt = 3,
|
||||
}
|
||||
|
||||
impl EndpointDescriptor {
|
||||
fn ty(self) -> EndpointTy {
|
||||
match self.attributes & ENDP_ATTR_TY_MASK {
|
||||
0 => EndpointTy::Ctrl,
|
||||
1 => EndpointTy::Isoch,
|
||||
2 => EndpointTy::Bulk,
|
||||
3 => EndpointTy::Interrupt,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Plain for EndpointDescriptor {}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct SuperSpeedCompanionDescriptor {
|
||||
pub length: u8,
|
||||
pub kind: u8,
|
||||
pub max_burst: u8,
|
||||
pub attributes: u8,
|
||||
pub bytes_per_interval: u16,
|
||||
}
|
||||
unsafe impl Plain for SuperSpeedCompanionDescriptor {}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct SuperSpeedPlusIsochCmpDescriptor {
|
||||
pub length: u8,
|
||||
pub kind: u8,
|
||||
pub reserved: u16,
|
||||
pub bytes_per_interval: u32,
|
||||
}
|
||||
unsafe impl Plain for SuperSpeedPlusIsochCmpDescriptor {}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct HidDescriptor {
|
||||
pub length: u8,
|
||||
pub kind: u8,
|
||||
pub hid_spec_release: u16,
|
||||
pub country_code: u8,
|
||||
pub num_descriptors: u8,
|
||||
pub report_desc_ty: u8,
|
||||
pub report_desc_len: u16,
|
||||
pub optional_desc_ty: u8,
|
||||
pub optional_desc_len: u16,
|
||||
}
|
||||
|
||||
unsafe impl Plain for HidDescriptor {}
|
||||
@@ -0,0 +1,187 @@
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct HubDescriptorV2 {
|
||||
pub length: u8,
|
||||
pub kind: u8,
|
||||
pub ports: u8,
|
||||
pub characteristics: u16,
|
||||
pub power_on_good: u8,
|
||||
pub current: u8,
|
||||
/*TODO: USB 2 and 3 disagree on the descriptor, so some fields are disabled
|
||||
// device_removable: bitmap of ports, maximum of 256 bits (32 bytes)
|
||||
// power_control_mask: bitmap of ports, maximum of 256 bits (32 bytes)
|
||||
bitmaps: [u8; 64],
|
||||
*/
|
||||
}
|
||||
|
||||
unsafe impl plain::Plain for HubDescriptorV2 {}
|
||||
|
||||
impl HubDescriptorV2 {
|
||||
pub const DESCRIPTOR_KIND: u8 = 0x29;
|
||||
}
|
||||
|
||||
impl Default for HubDescriptorV2 {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
length: 0,
|
||||
kind: 0,
|
||||
ports: 0,
|
||||
characteristics: 0,
|
||||
power_on_good: 0,
|
||||
current: 0,
|
||||
/*
|
||||
bitmaps: [0; 64],
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct HubDescriptorV3 {
|
||||
pub length: u8,
|
||||
pub kind: u8,
|
||||
pub ports: u8,
|
||||
pub characteristics: u16,
|
||||
pub power_on_good: u8,
|
||||
pub current: u8,
|
||||
pub decode_latency: u8,
|
||||
pub delay: u16,
|
||||
/*TODO: USB 2 and 3 disagree on the descriptor, so some fields are disabled
|
||||
// device_removable: bitmap of ports, maximum of 256 bits (32 bytes)
|
||||
// power_control_mask: bitmap of ports, maximum of 256 bits (32 bytes)
|
||||
bitmaps: [u8; 64],
|
||||
*/
|
||||
}
|
||||
|
||||
unsafe impl plain::Plain for HubDescriptorV3 {}
|
||||
|
||||
impl HubDescriptorV3 {
|
||||
pub const DESCRIPTOR_KIND: u8 = 0x2A;
|
||||
}
|
||||
|
||||
impl Default for HubDescriptorV3 {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
length: 0,
|
||||
kind: 0,
|
||||
ports: 0,
|
||||
characteristics: 0,
|
||||
power_on_good: 0,
|
||||
current: 0,
|
||||
decode_latency: 0,
|
||||
delay: 0,
|
||||
/*
|
||||
bitmaps: [0; 64],
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This only includes matching features from both USB 2.0 and 3.0 specs
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum HubPortFeature {
|
||||
PortConnection = 0,
|
||||
PortOverCurrent = 3,
|
||||
PortReset = 4,
|
||||
PortLinkState = 5,
|
||||
PortPower = 8,
|
||||
CPortConnection = 16,
|
||||
CPortOverCurrent = 19,
|
||||
CPortReset = 20,
|
||||
}
|
||||
|
||||
bitflags::bitflags! {
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
#[repr(transparent)]
|
||||
pub struct HubPortStatusV2: u32 {
|
||||
const CONNECTION = 1 << 0;
|
||||
const ENABLE = 1 << 1;
|
||||
const SUSPEND = 1 << 2;
|
||||
const OVER_CURRENT = 1 << 3;
|
||||
const RESET = 1 << 4;
|
||||
// bits 5-7 reserved
|
||||
const POWER = 1 << 8;
|
||||
const LOW_SPEED = 1 << 9;
|
||||
const HIGH_SPEED = 1 << 10;
|
||||
const TEST = 1 << 11;
|
||||
const INDICATOR = 1 << 12;
|
||||
// bits 13-15 reserved
|
||||
const CONNECTION_CHANGED = 1 << 16;
|
||||
const ENABLE_CHANGED = 1 << 17;
|
||||
const SUSPEND_CHANGED = 1 << 18;
|
||||
const OVER_CURRENT_CHANGED = 1 << 19;
|
||||
const RESET_CHANGED = 1 << 20;
|
||||
// bits 21 - 31 reserved
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl plain::Plain for HubPortStatusV2 {}
|
||||
|
||||
bitflags::bitflags! {
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
#[repr(transparent)]
|
||||
pub struct HubPortStatusV3: u32 {
|
||||
const CONNECTION = 1 << 0;
|
||||
const ENABLE = 1 << 1;
|
||||
// bit 2 reserved
|
||||
const OVER_CURRENT = 1 << 3;
|
||||
const RESET = 1 << 4;
|
||||
const LINK_STATE_0 = 1 << 5;
|
||||
const LINK_STATE_1 = 1 << 6;
|
||||
const LINK_STATE_2 = 1 << 7;
|
||||
const LINK_STATE_3 = 1 << 8;
|
||||
const POWER = 1 << 9;
|
||||
const SPEED_0 = 1 << 10;
|
||||
const SPEED_1 = 1 << 11;
|
||||
const SPEED_2 = 1 << 12;
|
||||
// bits 13 - 15 reserved
|
||||
const CONNECTION_CHANGED = 1 << 16;
|
||||
// bits 17-18
|
||||
const OVER_CURRENT_CHANGED = 1 << 19;
|
||||
const RESET_CHANGED = 1 << 20;
|
||||
const BH_RESET_CHANGED = 1 << 21;
|
||||
const LINK_STATE_CHANGED = 1 << 22;
|
||||
const CONFIG_ERROR = 1 << 23;
|
||||
// bits 24 - 31 reserved
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl plain::Plain for HubPortStatusV3 {}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum HubPortStatus {
|
||||
V2(HubPortStatusV2),
|
||||
V3(HubPortStatusV3),
|
||||
}
|
||||
|
||||
impl HubPortStatus {
|
||||
pub fn is_powered(&self) -> bool {
|
||||
match self {
|
||||
Self::V2(x) => x.contains(HubPortStatusV2::POWER),
|
||||
Self::V3(x) => x.contains(HubPortStatusV3::POWER),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_connected(&self) -> bool {
|
||||
match self {
|
||||
Self::V2(x) => x.contains(HubPortStatusV2::CONNECTION),
|
||||
Self::V3(x) => x.contains(HubPortStatusV3::CONNECTION),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_resetting(&self) -> bool {
|
||||
match self {
|
||||
Self::V2(x) => x.contains(HubPortStatusV2::RESET),
|
||||
Self::V3(x) => x.contains(HubPortStatusV3::RESET),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
match self {
|
||||
Self::V2(x) => x.contains(HubPortStatusV2::ENABLE),
|
||||
Self::V3(x) => x.contains(HubPortStatusV3::ENABLE),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
use plain::Plain;
|
||||
|
||||
///
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct InterfaceDescriptor {
|
||||
pub length: u8,
|
||||
pub kind: u8,
|
||||
pub number: u8,
|
||||
pub alternate_setting: u8,
|
||||
pub endpoints: u8,
|
||||
pub class: u8,
|
||||
pub sub_class: u8,
|
||||
pub protocol: u8,
|
||||
pub interface_str: u8,
|
||||
}
|
||||
|
||||
unsafe impl Plain for InterfaceDescriptor {}
|
||||
@@ -0,0 +1,63 @@
|
||||
//! The Universal Serial Bus (USB) Module
|
||||
//!
|
||||
//! The implementations in this module are common to all USB interfaces (though individual elements
|
||||
//! may be specific to only 2.0 or 3.2), and are used by specialized driver components like [xhci]
|
||||
//! to implement the driver interface.
|
||||
//!
|
||||
//! The [Universal Serial Bus Specification](https://www.usb.org/document-library/usb-20-specification) and the [Universal Serial Bus 3.2 Specification](https://usb.org/document-library/usb-32-revision-11-june-2022) are
|
||||
//! the documents that inform this implementation.
|
||||
//!
|
||||
//! See the crate-level documentation for the acronyms used to refer to specific documents.
|
||||
pub use self::bos::{bos_capability_descs, BosAnyDevDesc, BosDescriptor, BosSuperSpeedDesc};
|
||||
pub use self::config::ConfigDescriptor;
|
||||
pub use self::device::{DeviceDescriptor, DeviceDescriptor8Byte};
|
||||
pub use self::endpoint::{
|
||||
EndpointDescriptor, EndpointTy, HidDescriptor, SuperSpeedCompanionDescriptor,
|
||||
SuperSpeedPlusIsochCmpDescriptor, ENDP_ATTR_TY_MASK,
|
||||
};
|
||||
pub use self::hub::*;
|
||||
pub use self::interface::InterfaceDescriptor;
|
||||
pub use self::setup::{Setup, SetupReq};
|
||||
|
||||
/// Enumerates the list of descriptor kinds that can be reported by a USB device to report its
|
||||
/// attributes to the system. (See USB32 Sections 9.5 and 9.6)
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum DescriptorKind {
|
||||
/// No Descriptor TODO: Determine why this state exists, and what it does in the code.
|
||||
None = 0,
|
||||
/// A Device Descriptor. See [DeviceDescriptor]
|
||||
Device = 1,
|
||||
/// A Configuration Descriptor. See [ConfigDescriptor]
|
||||
Configuration = 2,
|
||||
/// A String Descriptor. See (USB32 Section 9.6.9).
|
||||
String = 3,
|
||||
/// An Interface Descriptor. See [InterfaceDescriptor]
|
||||
Interface = 4,
|
||||
/// An Endpoint Descriptor. See [EndpointDescriptor]
|
||||
Endpoint = 5,
|
||||
/// A Device Qualifier. USB2-specific. See [DeviceQualifier]
|
||||
DeviceQualifier = 6,
|
||||
/// The "Other Speed Configuration" descriptor. USB2-specific. See (USB2 9.6.4]
|
||||
OtherSpeedConfiguration = 7,
|
||||
/// TODO: Determine the standard that specifies this
|
||||
InterfacePower = 8,
|
||||
/// TODO: Determine the standard that specifies this (Possibly USB-C?)
|
||||
OnTheGo = 9,
|
||||
/// A Binary Device Object Store Descriptor. See [BosDescriptor]
|
||||
BinaryObjectStorage = 15,
|
||||
/// TODO: Track down the HID standard for references
|
||||
Hid = 33,
|
||||
/// A USB Hub Device Descriptor. See [HubDescriptor]
|
||||
Hub = 41,
|
||||
/// A Super Speed Endpoint Companion Descriptor. See [SuperSpeedCompanionDescriptor]
|
||||
SuperSpeedCompanion = 48,
|
||||
}
|
||||
|
||||
pub(crate) mod bos;
|
||||
pub(crate) mod config;
|
||||
pub(crate) mod device;
|
||||
pub(crate) mod endpoint;
|
||||
pub(crate) mod hub;
|
||||
pub(crate) mod interface;
|
||||
pub(crate) mod setup;
|
||||
@@ -0,0 +1,209 @@
|
||||
use super::DescriptorKind;
|
||||
use crate::driver_interface::*;
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct Setup {
|
||||
pub kind: u8,
|
||||
pub request: u8,
|
||||
pub value: u16,
|
||||
pub index: u16,
|
||||
pub length: u16,
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
pub enum ReqDirection {
|
||||
HostToDevice = 0,
|
||||
DeviceToHost = 1,
|
||||
}
|
||||
impl From<PortReqDirection> for ReqDirection {
|
||||
fn from(d: PortReqDirection) -> Self {
|
||||
match d {
|
||||
PortReqDirection::DeviceToHost => Self::DeviceToHost,
|
||||
PortReqDirection::HostToDevice => Self::HostToDevice,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
pub enum ReqType {
|
||||
/// Standard device requests, such as SET_ADDRESS and SET_CONFIGURATION. These aren't directly
|
||||
/// accessible using the API, but are sent from xhcid when required.
|
||||
Standard = 0,
|
||||
|
||||
/// Class specific requests that are directly accessible from the API.
|
||||
Class = 1,
|
||||
|
||||
/// Vendor specific requests that are accessible using the API.
|
||||
Vendor = 2,
|
||||
|
||||
/// Reserved
|
||||
Reserved = 3,
|
||||
}
|
||||
impl From<PortReqTy> for ReqType {
|
||||
fn from(d: PortReqTy) -> Self {
|
||||
match d {
|
||||
PortReqTy::Standard => Self::Standard,
|
||||
PortReqTy::Class => Self::Class,
|
||||
PortReqTy::Vendor => Self::Vendor,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
pub enum ReqRecipient {
|
||||
Device = 0,
|
||||
Interface = 1,
|
||||
Endpoint = 2,
|
||||
Other = 3,
|
||||
// 4..=30 are reserved
|
||||
VendorSpecific = 31,
|
||||
}
|
||||
impl From<PortReqRecipient> for ReqRecipient {
|
||||
fn from(d: PortReqRecipient) -> Self {
|
||||
match d {
|
||||
PortReqRecipient::Device => Self::Device,
|
||||
PortReqRecipient::Interface => Self::Interface,
|
||||
PortReqRecipient::Endpoint => Self::Endpoint,
|
||||
PortReqRecipient::Other => Self::Other,
|
||||
PortReqRecipient::VendorSpecific => Self::VendorSpecific,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
pub enum SetupReq {
|
||||
GetStatus = 0x00,
|
||||
ClearFeature = 0x01,
|
||||
SetFeature = 0x03,
|
||||
SetAddress = 0x05,
|
||||
GetDescriptor = 0x06,
|
||||
SetDescriptor = 0x07,
|
||||
GetConfiguration = 0x08,
|
||||
SetConfiguration = 0x09,
|
||||
GetInterface = 0x0A,
|
||||
SetInterface = 0x0B,
|
||||
SynchFrame = 0x0C,
|
||||
}
|
||||
|
||||
pub const USB_SETUP_DIR_BIT: u8 = 1 << 7;
|
||||
pub const USB_SETUP_DIR_SHIFT: u8 = 7;
|
||||
pub const USB_SETUP_REQ_TY_MASK: u8 = 0x60;
|
||||
pub const USB_SETUP_REQ_TY_SHIFT: u8 = 5;
|
||||
pub const USB_SETUP_RECIPIENT_MASK: u8 = 0x1F;
|
||||
pub const USB_SETUP_RECIPIENT_SHIFT: u8 = 0;
|
||||
|
||||
impl Setup {
|
||||
pub fn direction(&self) -> ReqDirection {
|
||||
if self.kind & USB_SETUP_DIR_BIT == 0 {
|
||||
ReqDirection::HostToDevice
|
||||
} else {
|
||||
ReqDirection::DeviceToHost
|
||||
}
|
||||
}
|
||||
pub const fn req_ty(&self) -> u8 {
|
||||
(self.kind & USB_SETUP_REQ_TY_MASK) >> USB_SETUP_REQ_TY_SHIFT
|
||||
}
|
||||
|
||||
pub const fn req_recipient(&self) -> u8 {
|
||||
(self.kind & USB_SETUP_RECIPIENT_MASK) >> USB_SETUP_RECIPIENT_SHIFT
|
||||
}
|
||||
pub fn is_allowed_from_api(&self) -> bool {
|
||||
self.req_ty() == ReqType::Class as u8 || self.req_ty() == ReqType::Vendor as u8
|
||||
}
|
||||
|
||||
pub const fn get_status() -> Self {
|
||||
Self {
|
||||
kind: 0b1000_0000,
|
||||
request: 0x00,
|
||||
value: 0,
|
||||
index: 0,
|
||||
length: 2,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn clear_feature(feature: u16) -> Self {
|
||||
Self {
|
||||
kind: 0b0000_0000,
|
||||
request: 0x01,
|
||||
value: feature,
|
||||
index: 0,
|
||||
length: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn set_feature(feature: u16) -> Self {
|
||||
Self {
|
||||
kind: 0b0000_0000,
|
||||
request: 0x03,
|
||||
value: feature,
|
||||
index: 0,
|
||||
length: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn set_address(address: u16) -> Self {
|
||||
Self {
|
||||
kind: 0b0000_0000,
|
||||
request: 0x05,
|
||||
value: address,
|
||||
index: 0,
|
||||
length: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn get_descriptor(
|
||||
kind: DescriptorKind,
|
||||
index: u8,
|
||||
language: u16,
|
||||
length: u16,
|
||||
) -> Self {
|
||||
Self {
|
||||
kind: 0b1000_0000,
|
||||
request: 0x06,
|
||||
value: ((kind as u16) << 8) | (index as u16),
|
||||
index: language,
|
||||
length: length,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn set_descriptor(kind: u8, index: u8, language: u16, length: u16) -> Self {
|
||||
Self {
|
||||
kind: 0b0000_0000,
|
||||
request: 0x07,
|
||||
value: ((kind as u16) << 8) | (index as u16),
|
||||
index: language,
|
||||
length: length,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn get_configuration() -> Self {
|
||||
Self {
|
||||
kind: 0b1000_0000,
|
||||
request: 0x08,
|
||||
value: 0,
|
||||
index: 0,
|
||||
length: 1,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn set_configuration(value: u8) -> Self {
|
||||
Self {
|
||||
kind: 0b0000_0000,
|
||||
request: 0x09,
|
||||
value: value as u16,
|
||||
index: 0,
|
||||
length: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn set_interface(interface: u8, alternate_setting: u8) -> Self {
|
||||
Self {
|
||||
kind: 0b0000_0001,
|
||||
request: 0x0B,
|
||||
value: alternate_setting as u16,
|
||||
index: interface as u16,
|
||||
length: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
use common::io::{Io, Mmio};
|
||||
|
||||
/// Represents the memory-mapped Capability Registers of the XHCI
|
||||
///
|
||||
/// These are read-only registers that specify the capabilities
|
||||
/// of the host controller implementation.
|
||||
///
|
||||
/// They are used by the driver to determine what subsystems to
|
||||
/// configure during initialization.
|
||||
///
|
||||
/// See XHCI Section 5.3. Table 5-9 describes the offsets of the registers
|
||||
/// in memory.
|
||||
#[repr(C, packed)]
|
||||
pub struct CapabilityRegs {
|
||||
/// The length of the Capability Registers data structure in XHCI memory.
|
||||
///
|
||||
/// While only the registers in this structure are defined by the XHCI standard,
|
||||
/// the standard defines an arbitrary amount of space following those registers that
|
||||
/// are reserved for the standard. As such, you need to know the offset to the operational
|
||||
/// registers, which immediately follow.
|
||||
///
|
||||
/// CAPLENGTH in XHC Table 5-9. See XHC 5.3.1
|
||||
pub len: Mmio<u8>,
|
||||
/// Reserved byte
|
||||
///
|
||||
/// Rsvd in XHC Table 5-9
|
||||
_rsvd: Mmio<u8>,
|
||||
/// The XHCI interface version number in Binary-Encoded Decimal.
|
||||
///
|
||||
/// This specifies the version of the XHCI specification that is supported by this controller.
|
||||
/// HCIVERSION in XHC Table 5-9
|
||||
pub hci_ver: Mmio<u16>,
|
||||
/// The HCI Structural Parameters 1 Register.
|
||||
///
|
||||
/// -Bits 0 - 7 describe the number of device slots supported by this controller
|
||||
/// -Bits 8 - 18 describe the number of interrupters supported by this controller
|
||||
/// -Bits 19-23 are reserved
|
||||
/// -Bits 24-31 specify the maximum number of ports supported by this controller.
|
||||
///
|
||||
/// HCPARAMS1 in XHC Table 5-9. See 5.3.3
|
||||
pub hcs_params1: Mmio<u32>,
|
||||
/// The HCI Structural Parameters 2 Register.
|
||||
///
|
||||
/// - Bits 0-3 describe the Isochronus Scheduling Threshold (IST)
|
||||
/// - Bits 4-7 describe the Event Ring Segment Table Max (ERST Max). The maximum number of event
|
||||
/// ring segment table entries is 2^(ERST Max)
|
||||
/// - Bits 8-20 are reserved
|
||||
/// - Bits 25-21 describe the high order five bits of the maximum number of scratchpad buffers
|
||||
/// - Bit 26 is the Scratchpad Restore Buffer (SPR). (See XHC 4.23.2)
|
||||
/// - Bits 26-31 describe the low order five bits of the maximum number of scratchpad buffers
|
||||
///
|
||||
/// HCPARAMS2 in XHC Table 5-9. See 5.3.4
|
||||
pub hcs_params2: Mmio<u32>,
|
||||
/// The HCI Structural Parameters 3 Register.
|
||||
///
|
||||
/// - Bits 0-7 describes the worst-case U1 Device Exit Latency. Values are in microseconds, from 00h to 0Ah. 0B-FFh are reserved
|
||||
/// - Bits 8-15 are reserved
|
||||
/// - Bits 16-31 describe the worst-case U2 Device Exit Latency. Values are in microseconds, from 0000h to 07FFh. 0800-FFFFh are reserved
|
||||
///
|
||||
/// HCPARAMS3 in XHC Table 5-9. See XHC 5.3.5
|
||||
pub hcs_params3: Mmio<u32>,
|
||||
/// The HCI Capability Parameters 1 Register.
|
||||
///
|
||||
/// This register defines optional capabilities supported by the xHCI
|
||||
///
|
||||
/// - Bit 0 is the 64-bit Address Capability Flag (AC64). 0 = 32-bit pointers, 1 = 64-bit pointers.
|
||||
/// - Bit 1 is the Bandwidth Negotation Capability Flag (BNC)
|
||||
/// - Bit 2 is the Context Size Flag (CSZ). 0 = 32-byte, 1 = 64-byte Context Data Structures
|
||||
/// - Bit 3 is the Port Power Control Flag (PPC). Indicates whether the implementation supports port power control.
|
||||
/// - Bit 4 is the Port Indicators Flag (PIND). Indicates whether the XHC root hub supports port indicator control
|
||||
/// - Bit 5 is the Light Host Controller Reset Capability Flag (LHRC). Indicates whether the implementation supports a light reset
|
||||
/// - Bit 6 is the Latency Tolerance Messaging Capability Flag (LTC). Indicates whether the implementation supports Latency Tolerance Messaging
|
||||
/// - Bit 7 is the no Secondary SID Support Flag (NSS). Indicates whether secondary stream ids is supported. 1 = NO, 0 = YES
|
||||
/// - Bit 8 is the Parse All Event Data Flag (PAE). (See XHC Table 5-13)
|
||||
/// - Bit 9 is the Stopped - Short Packet Capability Flag (SPC). (See XHC 4.6.9)
|
||||
/// - Bit 10 is the Stopped EDTLA Capability Flag (SEC). (See XHC 4.6.9, 4.12, and 6.4.4.1)
|
||||
/// - Bit 11 is the Contiguous Frame ID Capability Flag (CFC). (See XHC 4.11.2.5)
|
||||
/// - Bits 12-15 are the Maximum Primary Stream Array Size (MaxPSASize). Identifies the maximum size of PSA that the implementation supports.
|
||||
/// - Bits 16-31 The xHCI Extended Capabilities Pointer (xECP). Points to an extended capabilities list. (See XHC Table 5-13 to see how to process this value)
|
||||
///
|
||||
/// HCCPARAMS1 in XHC Table 5-9. See XHC 5.3.6
|
||||
pub hcc_params1: Mmio<u32>,
|
||||
/// The Doorbell Offset Register
|
||||
///
|
||||
/// This register defines the offset of the Doorbell Array base address from the Base.
|
||||
///
|
||||
/// Bits 0-1 are reserved.
|
||||
/// Bits 2-31 contain the offset.
|
||||
///
|
||||
/// DBOFF in XHC Table 5-9. See XHC 5.3.7
|
||||
pub db_offset: Mmio<u32>,
|
||||
/// The Runtime Register Space Offset
|
||||
///
|
||||
/// The offset of the xHCI Runtime Registers from the Base.
|
||||
///
|
||||
/// - Bits 0-4 are reserved.
|
||||
/// - Bits 5-31 contain the offset.
|
||||
///
|
||||
/// RTSOFF in XHC Table 5-9. See XHC 5.3.8
|
||||
pub rts_offset: Mmio<u32>,
|
||||
/// The HC Capability Parameters 2 Register
|
||||
///
|
||||
/// This register defines optional capabilities supported by the xHCI
|
||||
///
|
||||
/// - Bit 0 is the UC3 Entry Capability Flag (U3C). See XHC 4.15.1
|
||||
/// - Bit 1 is the Configure Endpoint Command Max Latency Too Large Capability Flag (CMC). See XHC 4.23.5.2 and 5.4.1
|
||||
/// - Bit 2 is the Force Save Context Capability (FCS). See XHC 4.23.2 and 5.4.1
|
||||
/// - Bit 3 is the Compliance Transition Capability (CTC). See XHC 4.19.2.4.1
|
||||
/// - Bit 4 is the Large ESIT Payload Capability (LEC). See XHC 6.2.3.8
|
||||
/// - Bit 5 is the Configuration Information Capability (CIC). See XHC 6.2.5.1
|
||||
/// - Bit 6 is the Extended TBC Capability (ETC). See XHC 4.11.2.3
|
||||
/// - Bit 7 is the Extended TBC TRB Status Capability (ETC_TSC). See XHC 4.11.2.3
|
||||
/// - Bit 8 is the Get/Set Extended Property Capability (GSC). See Sections XHC 4.6.17 and 4.6.18
|
||||
/// - Bits 10-31 are reserved.
|
||||
pub hcc_params2: Mmio<u32>,
|
||||
//TODO: VTIOSOFF register for I/O virtualization
|
||||
}
|
||||
|
||||
/// The mask to use to get the AC64 bit from HCCPARAMS1. See [CapabilityRegs]
|
||||
pub const HCC_PARAMS1_AC64_BIT: u32 = 1 << HCC_PARAMS1_AC64_SHIFT;
|
||||
/// The shift to use to get the AC64 bit from HCCParams1. See [CapabilityRegs]
|
||||
pub const HCC_PARAMS1_AC64_SHIFT: u8 = 0;
|
||||
/// The mask to use to get the CSZ bit from HCCPARAMS1. See [CapabilityRegs]
|
||||
pub const HCC_PARAMS1_CSZ_BIT: u32 = 1 << HCC_PARAMS1_CSZ_SHIFT;
|
||||
/// The shift to use to get the CSZ bit from HCCParams1. See [CapabilityRegs]
|
||||
pub const HCC_PARAMS1_CSZ_SHIFT: u8 = 2;
|
||||
/// The Mask to use to get the MAXPSASIZE value from HCCParams1. See [CapabilityRegs]
|
||||
pub const HCC_PARAMS1_MAXPSASIZE_MASK: u32 = 0xF000; // 15:12
|
||||
/// The shift to use to get the MAXPSASIZE value from HCCParams1. See [CapabilityRegs]
|
||||
pub const HCC_PARAMS1_MAXPSASIZE_SHIFT: u8 = 12;
|
||||
/// The mask to use to get the XECP value from HCCParams1. See [CapabilityRegs]
|
||||
pub const HCC_PARAMS1_XECP_MASK: u32 = 0xFFFF_0000;
|
||||
/// The shift to use to get the XECP value from HCCParams1. See [CapabilityRegs]
|
||||
pub const HCC_PARAMS1_XECP_SHIFT: u8 = 16;
|
||||
|
||||
/// The mask to use to get the LEC bit from HCCParams2. See [CapabilityRegs]
|
||||
pub const HCC_PARAMS2_LEC_BIT: u32 = 1 << 4;
|
||||
/// The mask to use to get the CIC bit from HCCParams2. See [CapabilityRegs]
|
||||
pub const HCC_PARAMS2_CIC_BIT: u32 = 1 << 5;
|
||||
/// The mask to use to get MAXPORTS from HCSParams1. See [CapabilityRegs]
|
||||
pub const HCS_PARAMS1_MAX_PORTS_MASK: u32 = 0xFF00_0000;
|
||||
/// The shift to use to get MAXPORTS from HCSParams1. See [CapabilityRegs]
|
||||
pub const HCS_PARAMS1_MAX_PORTS_SHIFT: u8 = 24;
|
||||
/// The shift to use to get MAXSLOTS from HCSParams1. See [CapabilityRegs]
|
||||
pub const HCS_PARAMS1_MAX_SLOTS_MASK: u32 = 0x0000_00FF;
|
||||
/// The shift to use to get MAXSLOTS from HCSParams1. See [CapabilityRegs]
|
||||
pub const HCS_PARAMS1_MAX_SLOTS_SHIFT: u8 = 0;
|
||||
/// The mask to use to get MAXSCRATPADBUFS_LO from HCSParams2. See [CapabilityRegs]
|
||||
pub const HCS_PARAMS2_MAX_SCRATCHPAD_BUFS_LO_MASK: u32 = 0xF800_0000;
|
||||
/// The shift to use to get MAXSCRATCHPADBUFS_LO from HCSParams2. See [CapabilityRegs]
|
||||
pub const HCS_PARAMS2_MAX_SCRATCHPAD_BUFS_LO_SHIFT: u8 = 27;
|
||||
/// The mask to use to get the SPR bit from HCSParams2. See [CapabilityRegs]
|
||||
pub const HCS_PARAMS2_SPR_BIT: u32 = 1 << HCS_PARAMS2_SPR_SHIFT;
|
||||
/// The shift to use to get the SPR bit from HCSParams2. See [CapabilityRegs]
|
||||
pub const HCS_PARAMS2_SPR_SHIFT: u8 = 26;
|
||||
/// The mask to use to get MAXSCRATCHPADBUFS_HI from HCSParams2. See [CapabilityRegs]
|
||||
pub const HCS_PARAMS2_MAX_SCRATCHPAD_BUFS_HI_MASK: u32 = 0x03E0_0000;
|
||||
/// The shift to use to get MAXSCRATCHPADBUFS_HI from HCSParams2. See [CapabilityRegs]
|
||||
|
||||
pub const HCS_PARAMS2_MAX_SCRATCHPAD_BUFS_HI_SHIFT: u8 = 21;
|
||||
|
||||
impl CapabilityRegs {
|
||||
/// Gets the ACS64 bit from HCCParams1.
|
||||
pub fn ac64(&self) -> bool {
|
||||
self.hcc_params1.readf(HCC_PARAMS1_AC64_BIT)
|
||||
}
|
||||
|
||||
/// Gets the context size (CSZ) bit from HCCParams1.
|
||||
pub fn csz(&self) -> bool {
|
||||
self.hcc_params1.readf(HCC_PARAMS1_CSZ_BIT)
|
||||
}
|
||||
|
||||
/// Gets the LEC bit from HCCParams2.
|
||||
pub fn lec(&self) -> bool {
|
||||
self.hcc_params2.readf(HCC_PARAMS2_LEC_BIT)
|
||||
}
|
||||
/// Gets the CIC bit from HCCParams2.
|
||||
pub fn cic(&self) -> bool {
|
||||
self.hcc_params2.readf(HCC_PARAMS2_CIC_BIT)
|
||||
}
|
||||
|
||||
/// Gets the Max PSA Size from HCCParams1
|
||||
pub fn max_psa_size(&self) -> u8 {
|
||||
((self.hcc_params1.read() & HCC_PARAMS1_MAXPSASIZE_MASK) >> HCC_PARAMS1_MAXPSASIZE_SHIFT)
|
||||
as u8
|
||||
}
|
||||
|
||||
/// Gets the maximum number of ports from HCCParams1
|
||||
pub fn max_ports(&self) -> u8 {
|
||||
((self.hcs_params1.read() & HCS_PARAMS1_MAX_PORTS_MASK) >> HCS_PARAMS1_MAX_PORTS_SHIFT)
|
||||
as u8
|
||||
}
|
||||
|
||||
/// Gets the maximum number of ports from HCCParams 2
|
||||
pub fn max_slots(&self) -> u8 {
|
||||
(self.hcs_params1.read() & HCS_PARAMS1_MAX_SLOTS_MASK) as u8
|
||||
}
|
||||
|
||||
/// Gets the extended capability pointer from HCCParams1 in DWORDs.
|
||||
pub fn ext_caps_ptr_in_dwords(&self) -> u16 {
|
||||
((self.hcc_params1.read() & HCC_PARAMS1_XECP_MASK) >> HCC_PARAMS1_XECP_SHIFT) as u16
|
||||
}
|
||||
|
||||
/// Gets the lower five bits from the Max Scratchpad Buffer Lo Register in HCSParams2
|
||||
pub fn max_scratchpad_bufs_lo(&self) -> u8 {
|
||||
((self.hcs_params2.read() & HCS_PARAMS2_MAX_SCRATCHPAD_BUFS_LO_MASK)
|
||||
>> HCS_PARAMS2_MAX_SCRATCHPAD_BUFS_LO_SHIFT) as u8
|
||||
}
|
||||
|
||||
/// Gets the SPR register from HCSParams2
|
||||
pub fn spr(&self) -> bool {
|
||||
self.hcs_params2.readf(HCS_PARAMS2_SPR_BIT)
|
||||
}
|
||||
|
||||
/// Gets the higher five bits from the Max Scratchpad Buffer Hi Register in HCSParams2
|
||||
pub fn max_scratchpad_bufs_hi(&self) -> u8 {
|
||||
((self.hcs_params2.read() & HCS_PARAMS2_MAX_SCRATCHPAD_BUFS_HI_MASK)
|
||||
>> HCS_PARAMS2_MAX_SCRATCHPAD_BUFS_HI_SHIFT) as u8
|
||||
}
|
||||
|
||||
/// Gets the maximum number of scratchpad buffers supported by this implementation.
|
||||
pub fn max_scratchpad_bufs(&self) -> u16 {
|
||||
u16::from(self.max_scratchpad_bufs_lo()) | (u16::from(self.max_scratchpad_bufs_hi()) << 5)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use common::io::{Io, Mmio};
|
||||
use log::debug;
|
||||
use syscall::error::Result;
|
||||
use syscall::PAGE_SIZE;
|
||||
|
||||
use common::dma::Dma;
|
||||
|
||||
use super::ring::Ring;
|
||||
use super::Xhci;
|
||||
|
||||
pub const CONTEXT_32: usize = 0;
|
||||
pub const CONTEXT_64: usize = 1;
|
||||
|
||||
#[repr(C, packed)]
|
||||
struct Rsvd64<const N: usize>([[Mmio<u32>; 8]; N]);
|
||||
|
||||
#[repr(C, packed)]
|
||||
pub struct SlotContext<const N: usize> {
|
||||
pub a: Mmio<u32>,
|
||||
pub b: Mmio<u32>,
|
||||
pub c: Mmio<u32>,
|
||||
pub d: Mmio<u32>,
|
||||
_rsvd: [Mmio<u32>; 4],
|
||||
_rsvd64: Rsvd64<N>,
|
||||
}
|
||||
|
||||
pub const SLOT_CONTEXT_STATE_MASK: u32 = 0xF800_0000;
|
||||
pub const SLOT_CONTEXT_STATE_SHIFT: u8 = 27;
|
||||
|
||||
#[repr(u8)]
|
||||
pub enum SlotState {
|
||||
EnabledOrDisabled = 0,
|
||||
Default = 1,
|
||||
Addressed = 2,
|
||||
Configured = 3,
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
pub struct EndpointContext<const N: usize> {
|
||||
pub a: Mmio<u32>,
|
||||
pub b: Mmio<u32>,
|
||||
pub trl: Mmio<u32>,
|
||||
pub trh: Mmio<u32>,
|
||||
pub c: Mmio<u32>,
|
||||
_rsvd: [Mmio<u32>; 3],
|
||||
_rsvd64: Rsvd64<N>,
|
||||
}
|
||||
|
||||
pub const ENDPOINT_CONTEXT_STATUS_MASK: u32 = 0x7;
|
||||
|
||||
#[repr(C, packed)]
|
||||
pub struct DeviceContext<const N: usize> {
|
||||
pub slot: SlotContext<N>,
|
||||
pub endpoints: [EndpointContext<N>; 31],
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
pub struct InputContext<const N: usize> {
|
||||
pub drop_context: Mmio<u32>,
|
||||
pub add_context: Mmio<u32>,
|
||||
_rsvd: [Mmio<u32>; 5],
|
||||
pub control: Mmio<u32>,
|
||||
_rsvd64: Rsvd64<N>,
|
||||
pub device: DeviceContext<N>,
|
||||
}
|
||||
impl<const N: usize> InputContext<N> {
|
||||
pub fn dump_control(&self) {
|
||||
debug!(
|
||||
"INPUT CONTEXT: {} {} [{} {} {} {} {}] {}",
|
||||
self.drop_context.read(),
|
||||
self.add_context.read(),
|
||||
self._rsvd[0].read(),
|
||||
self._rsvd[1].read(),
|
||||
self._rsvd[2].read(),
|
||||
self._rsvd[3].read(),
|
||||
self._rsvd[4].read(),
|
||||
self.control.read()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DeviceContextList<const N: usize> {
|
||||
pub dcbaa: Dma<[u64; 256]>,
|
||||
pub contexts: Box<[Dma<DeviceContext<N>>]>,
|
||||
}
|
||||
|
||||
impl<const N: usize> DeviceContextList<N> {
|
||||
pub fn new(ac64: bool, max_slots: u8) -> Result<Self> {
|
||||
let mut dcbaa = unsafe { Xhci::<N>::alloc_dma_zeroed_raw::<[u64; 256]>(ac64)? };
|
||||
let mut contexts = vec![];
|
||||
|
||||
// Create device context buffers for each slot
|
||||
for i in 0..max_slots as usize {
|
||||
let context: Dma<DeviceContext<N>> = unsafe { Xhci::<N>::alloc_dma_zeroed_raw(ac64) }?;
|
||||
dcbaa[i] = context.physical() as u64;
|
||||
contexts.push(context);
|
||||
}
|
||||
|
||||
Ok(DeviceContextList {
|
||||
dcbaa,
|
||||
contexts: contexts.into_boxed_slice(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn dcbaap(&self) -> u64 {
|
||||
self.dcbaa.physical() as u64
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
pub struct StreamContext {
|
||||
trl: Mmio<u32>,
|
||||
trh: Mmio<u32>,
|
||||
edtla: Mmio<u32>,
|
||||
rsvd: Mmio<u32>,
|
||||
}
|
||||
|
||||
unsafe impl plain::Plain for StreamContext {}
|
||||
|
||||
#[repr(u8)]
|
||||
pub enum StreamContextType {
|
||||
SecondaryRing,
|
||||
PrimaryRing,
|
||||
PrimarySsa8,
|
||||
PrimarySsa16,
|
||||
PrimarySsa32,
|
||||
PrimarySsa64,
|
||||
PrimarySsa128,
|
||||
PrimarySsa256,
|
||||
}
|
||||
|
||||
pub struct StreamContextArray {
|
||||
pub contexts: Dma<[StreamContext]>,
|
||||
pub rings: BTreeMap<u16, Ring>,
|
||||
}
|
||||
|
||||
impl StreamContextArray {
|
||||
pub fn new<const N: usize>(ac64: bool, count: usize) -> Result<Self> {
|
||||
unsafe {
|
||||
Ok(Self {
|
||||
contexts: Xhci::<N>::alloc_dma_zeroed_unsized_raw(ac64, count)?,
|
||||
rings: BTreeMap::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
pub fn add_ring<const N: usize>(
|
||||
&mut self,
|
||||
ac64: bool,
|
||||
stream_id: u16,
|
||||
link: bool,
|
||||
) -> Result<()> {
|
||||
// NOTE: stream_id 0 is reserved
|
||||
assert_ne!(stream_id, 0);
|
||||
|
||||
let ring = Ring::new::<N>(ac64, 16, link)?;
|
||||
let pointer = ring.register();
|
||||
let sct = StreamContextType::PrimaryRing;
|
||||
|
||||
assert_eq!(pointer & (!0xE), pointer);
|
||||
{
|
||||
let context = &mut self.contexts[stream_id as usize];
|
||||
context.trl.write((pointer as u32) | ((sct as u32) << 1));
|
||||
context.trh.write((pointer >> 32) as u32);
|
||||
// TODO: stopped edtla
|
||||
}
|
||||
self.rings.insert(stream_id, ring);
|
||||
Ok(())
|
||||
}
|
||||
pub fn register(&self) -> u64 {
|
||||
self.contexts.physical() as u64
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
pub struct ScratchpadBufferEntry {
|
||||
pub value_low: Mmio<u32>,
|
||||
pub value_high: Mmio<u32>,
|
||||
}
|
||||
impl ScratchpadBufferEntry {
|
||||
pub fn set_addr(&mut self, addr: u64) {
|
||||
self.value_low.write(addr as u32);
|
||||
self.value_high.write((addr >> 32) as u32);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ScratchpadBufferArray {
|
||||
pub entries: Dma<[ScratchpadBufferEntry]>,
|
||||
pub pages: Vec<Dma<[u8; PAGE_SIZE]>>,
|
||||
}
|
||||
impl ScratchpadBufferArray {
|
||||
pub fn new<const N: usize>(ac64: bool, entries: u16) -> Result<Self> {
|
||||
let mut entries =
|
||||
unsafe { Xhci::<N>::alloc_dma_zeroed_unsized_raw(ac64, entries as usize)? };
|
||||
|
||||
let pages = entries
|
||||
.iter_mut()
|
||||
.map(
|
||||
|entry: &mut ScratchpadBufferEntry| -> Result<_, syscall::Error> {
|
||||
let dma = unsafe { Dma::<[u8; PAGE_SIZE]>::zeroed()?.assume_init() };
|
||||
assert_eq!(dma.physical() % PAGE_SIZE, 0);
|
||||
entry.set_addr(dma.physical() as u64);
|
||||
Ok(dma)
|
||||
},
|
||||
)
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Ok(Self { entries, pages })
|
||||
}
|
||||
pub fn register(&self) -> usize {
|
||||
self.entries.physical()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use core::mem;
|
||||
|
||||
#[test]
|
||||
fn context_size() {
|
||||
assert_eq!(mem::size_of::<SlotContext<CONTEXT_32>>(), 32);
|
||||
assert_eq!(mem::size_of::<SlotContext<CONTEXT_64>>(), 64);
|
||||
assert_eq!(mem::size_of::<EndpointContext<CONTEXT_32>>(), 32);
|
||||
assert_eq!(mem::size_of::<EndpointContext<CONTEXT_64>>(), 64);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
use crate::xhci::port::PortFlags;
|
||||
use crate::xhci::{PortId, Xhci};
|
||||
use common::io::Io;
|
||||
use crossbeam_channel;
|
||||
use log::{debug, info, warn};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use syscall::EAGAIN;
|
||||
|
||||
pub struct DeviceEnumerationRequest {
|
||||
pub port_id: PortId,
|
||||
}
|
||||
|
||||
pub struct DeviceEnumerator<const N: usize> {
|
||||
hci: Arc<Xhci<N>>,
|
||||
request_queue: crossbeam_channel::Receiver<DeviceEnumerationRequest>,
|
||||
}
|
||||
|
||||
impl<const N: usize> DeviceEnumerator<N> {
|
||||
pub fn new(hci: Arc<Xhci<N>>) -> Self {
|
||||
let request_queue = hci.device_enumerator_receiver.clone();
|
||||
DeviceEnumerator { hci, request_queue }
|
||||
}
|
||||
|
||||
pub fn run(&mut self) {
|
||||
loop {
|
||||
debug!("Start Device Enumerator Loop");
|
||||
let request = match self.request_queue.recv() {
|
||||
Ok(req) => req,
|
||||
Err(err) => {
|
||||
panic!("Failed to received an enumeration request! error: {}", err)
|
||||
}
|
||||
};
|
||||
|
||||
let port_id = request.port_id;
|
||||
let port_array_index = port_id.root_hub_port_index();
|
||||
|
||||
debug!("Device Enumerator request for port {}", port_id);
|
||||
|
||||
let (len, flags) = {
|
||||
let ports = self.hci.ports.lock().unwrap();
|
||||
|
||||
let len = ports.len();
|
||||
|
||||
if port_array_index >= len {
|
||||
warn!(
|
||||
"Received out of bounds Device Enumeration request for port {}",
|
||||
port_id
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
(len, ports[port_array_index].flags())
|
||||
};
|
||||
|
||||
if flags.contains(PortFlags::CCS) {
|
||||
debug!(
|
||||
"Received Device Connect Port Status Change Event with port flags {:?}",
|
||||
flags
|
||||
);
|
||||
//If the port isn't enabled (i.e. it's a USB2 port), we need to reset it if it isn't resetting already
|
||||
//A USB3 port won't generate a Connect Status Change until it's already enabled, so this check
|
||||
//will always be skipped for USB3 ports
|
||||
if !flags.contains(PortFlags::PED) {
|
||||
let disabled_state = flags.contains(PortFlags::PP)
|
||||
&& flags.contains(PortFlags::CCS)
|
||||
&& !flags.contains(PortFlags::PED)
|
||||
&& !flags.contains(PortFlags::PR);
|
||||
|
||||
if !disabled_state {
|
||||
panic!(
|
||||
"Port {} isn't in the disabled state! Current flags: {:?}",
|
||||
port_id, flags
|
||||
);
|
||||
} else {
|
||||
debug!("Port {} has entered the disabled state.", port_id);
|
||||
}
|
||||
|
||||
//THIS LOCKS THE PORTS. DO NOT LOCK PORTS BEFORE THIS POINT
|
||||
debug!("Received a device connect on port {}, but it's not enabled. Resetting the port.", port_id);
|
||||
let _ = self.hci.reset_port(port_id);
|
||||
|
||||
let mut ports = self.hci.ports.lock().unwrap();
|
||||
let port = &mut ports[port_array_index];
|
||||
|
||||
port.clear_prc();
|
||||
|
||||
std::thread::sleep(Duration::from_millis(16)); //Some controllers need some extra time to make the transition.
|
||||
|
||||
let flags = port.flags();
|
||||
|
||||
let enabled_state = flags.contains(PortFlags::PP)
|
||||
&& flags.contains(PortFlags::CCS)
|
||||
&& flags.contains(PortFlags::PED)
|
||||
&& !flags.contains(PortFlags::PR);
|
||||
|
||||
if !enabled_state {
|
||||
warn!(
|
||||
"Port {} isn't in the enabled state! Current flags: {:?}",
|
||||
port_id, flags
|
||||
);
|
||||
} else {
|
||||
debug!(
|
||||
"Port {} is in the enabled state. Proceeding with enumeration",
|
||||
port_id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let result = futures::executor::block_on(self.hci.attach_device(port_id));
|
||||
match result {
|
||||
Ok(_) => {
|
||||
info!("Device on port {} was attached", port_id);
|
||||
}
|
||||
Err(err) => {
|
||||
if err.errno == EAGAIN {
|
||||
debug!("Received a device connect notification for an already connected device. Ignoring...")
|
||||
} else {
|
||||
warn!("processing of device attach request failed! Error: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
debug!(
|
||||
"Device Enumerator received Detach request on port {} which is in state {}",
|
||||
port_id,
|
||||
self.hci.get_pls(port_id)
|
||||
);
|
||||
let result = futures::executor::block_on(self.hci.detach_device(port_id));
|
||||
match result {
|
||||
Ok(was_connected) => {
|
||||
if was_connected {
|
||||
info!("Device on port {} was detached", port_id);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("processing of device attach request failed! Error: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
use common::io::{Io, Mmio};
|
||||
|
||||
#[repr(C, packed)]
|
||||
pub struct Doorbell(Mmio<u32>);
|
||||
|
||||
impl Doorbell {
|
||||
pub fn read(&self) -> u32 {
|
||||
self.0.read()
|
||||
}
|
||||
|
||||
pub fn write(&mut self, data: u32) {
|
||||
self.0.write(data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
use common::io::{Io, Mmio};
|
||||
use syscall::error::Result;
|
||||
|
||||
use common::dma::Dma;
|
||||
|
||||
use super::ring::Ring;
|
||||
use super::trb::Trb;
|
||||
use super::Xhci;
|
||||
|
||||
#[repr(C, packed)]
|
||||
pub struct EventRingSte {
|
||||
pub address_low: Mmio<u32>,
|
||||
pub address_high: Mmio<u32>,
|
||||
pub size: Mmio<u16>,
|
||||
_rsvd: Mmio<u16>,
|
||||
_rsvd2: Mmio<u32>,
|
||||
}
|
||||
|
||||
// TODO: Use atomic operations, and perhaps an occasional lock for reallocating.
|
||||
pub struct EventRing {
|
||||
pub ste: Dma<[EventRingSte]>,
|
||||
pub ring: Ring,
|
||||
}
|
||||
|
||||
impl EventRing {
|
||||
pub fn new<const N: usize>(ac64: bool) -> Result<EventRing> {
|
||||
let mut ring = EventRing {
|
||||
ste: unsafe { Xhci::<N>::alloc_dma_zeroed_unsized_raw(ac64, 1)? },
|
||||
ring: Ring::new::<N>(ac64, 256, false)?,
|
||||
};
|
||||
|
||||
ring.ste[0]
|
||||
.address_low
|
||||
.write(ring.ring.trbs.physical() as u32);
|
||||
ring.ste[0]
|
||||
.address_high
|
||||
.write((ring.ring.trbs.physical() as u64 >> 32) as u32);
|
||||
ring.ste[0].size.write(ring.ring.trbs.len() as u16);
|
||||
|
||||
Ok(ring)
|
||||
}
|
||||
|
||||
pub fn next(&mut self) -> &mut Trb {
|
||||
self.ring.next().0
|
||||
}
|
||||
pub fn erdp(&self) -> u64 {
|
||||
self.ring.register() & 0xFFFF_FFFF_FFFF_FFF0
|
||||
}
|
||||
pub fn erstba(&self) -> u64 {
|
||||
self.ste.physical() as u64
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
use common::io::{Io, Mmio};
|
||||
use std::ops::Range;
|
||||
use std::ptr::NonNull;
|
||||
use std::{fmt, mem, ptr, slice};
|
||||
|
||||
pub struct ExtendedCapabilitiesIter {
|
||||
base: *const u8,
|
||||
}
|
||||
impl ExtendedCapabilitiesIter {
|
||||
pub unsafe fn new(base: *const u8) -> Self {
|
||||
Self { base }
|
||||
}
|
||||
}
|
||||
impl Iterator for ExtendedCapabilitiesIter {
|
||||
type Item = (NonNull<u8>, u8); // pointer, capability id
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
unsafe {
|
||||
let current = NonNull::new(self.base as *mut _)?;
|
||||
|
||||
let reg = current.cast::<Mmio<u32>>().as_ref().read();
|
||||
let capability_id = (reg & 0xFF) as u8;
|
||||
let next_rel_in_dwords = ((reg & 0xFF00) >> 8) as u8;
|
||||
|
||||
let next_rel = u16::from(next_rel_in_dwords) << 2;
|
||||
|
||||
self.base = if next_rel != 0 {
|
||||
self.base.offset(next_rel as isize)
|
||||
} else {
|
||||
ptr::null()
|
||||
};
|
||||
|
||||
Some((current, capability_id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
pub enum CapabilityId {
|
||||
// bit 0 is reserved
|
||||
UsbLegacySupport = 1,
|
||||
SupportedProtocol,
|
||||
ExtendedPowerManagement,
|
||||
IoVirtualization,
|
||||
MessageInterrupt,
|
||||
LocalMem,
|
||||
// bits 7-9 are reserved
|
||||
UsbDebugCapability = 10,
|
||||
// bits 11-16 are reserved
|
||||
ExtendedMessageInterrupt = 17,
|
||||
// bits 18-191 are reserved
|
||||
// bits 192-255 are vendor-defined
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
pub struct SupportedProtoCap {
|
||||
a: Mmio<u32>,
|
||||
b: Mmio<u32>,
|
||||
c: Mmio<u32>,
|
||||
d: Mmio<u32>,
|
||||
protocol_speeds: [u8; 0],
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
pub struct ProtocolSpeed {
|
||||
a: Mmio<u32>,
|
||||
}
|
||||
|
||||
pub const PROTO_SPEED_PSIV_MASK: u32 = 0x0000_000F;
|
||||
pub const PROTO_SPEED_PSIV_SHIFT: u8 = 0;
|
||||
|
||||
pub const PROTO_SPEED_PSIE_MASK: u32 = 0x0000_0030;
|
||||
pub const PROTO_SPEED_PSIE_SHIFT: u8 = 4;
|
||||
|
||||
pub const PROTO_SPEED_PLT_MASK: u32 = 0x0000_00C0;
|
||||
pub const PROTO_SPEED_PLT_SHIFT: u8 = 6;
|
||||
|
||||
pub const PROTO_SPEED_PFD_BIT: u32 = 1 << PROTO_SPEED_PFD_SHIFT;
|
||||
pub const PROTO_SPEED_PFD_SHIFT: u8 = 8;
|
||||
|
||||
pub const PROTO_SPEED_LP_MASK: u32 = 0x0000_C000;
|
||||
pub const PROTO_SPEED_LP_SHIFT: u8 = 14;
|
||||
|
||||
pub const PROTO_SPEED_PSIM_MASK: u32 = 0xFFFF_0000;
|
||||
pub const PROTO_SPEED_PSIM_SHIFT: u8 = 16;
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub enum Psie {
|
||||
Bps,
|
||||
Kbps,
|
||||
Mbps,
|
||||
Gbps,
|
||||
}
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum Plt {
|
||||
Symmetric,
|
||||
Reserved,
|
||||
AsymmetricRx,
|
||||
AsymmetricTx,
|
||||
}
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum Lp {
|
||||
SuperSpeed,
|
||||
SuperSpeedPlus,
|
||||
Rsvd2,
|
||||
Rsvd3,
|
||||
}
|
||||
|
||||
impl ProtocolSpeed {
|
||||
pub const fn from_raw(raw: u32) -> Self {
|
||||
Self { a: Mmio::new(raw) }
|
||||
}
|
||||
pub fn is_lowspeed(&self) -> bool {
|
||||
self.psim() == 1500 && self.psie() == Psie::Kbps && !self.pfd()
|
||||
}
|
||||
pub fn is_fullspeed(&self) -> bool {
|
||||
self.psim() == 12 && self.psie() == Psie::Mbps && !self.pfd()
|
||||
}
|
||||
pub fn is_highspeed(&self) -> bool {
|
||||
self.psim() == 480 && self.psie() == Psie::Mbps && !self.pfd()
|
||||
}
|
||||
pub fn is_superspeed_gen1x1(&self) -> bool {
|
||||
self.psim() == 5 && self.psie() == Psie::Gbps && self.pfd() && self.lp() == Lp::SuperSpeed
|
||||
}
|
||||
pub fn is_superspeedplus_gen2x1(&self) -> bool {
|
||||
self.psim() == 10
|
||||
&& self.psie() == Psie::Gbps
|
||||
&& self.pfd()
|
||||
&& self.lp() == Lp::SuperSpeedPlus
|
||||
}
|
||||
pub fn is_superspeedplus_gen1x2(&self) -> bool {
|
||||
self.psim() == 10
|
||||
&& self.psie() == Psie::Gbps
|
||||
&& self.pfd()
|
||||
&& self.lp() == Lp::SuperSpeedPlus
|
||||
}
|
||||
pub fn is_superspeedplus_gen2x2(&self) -> bool {
|
||||
self.psim() == 20
|
||||
&& self.psie() == Psie::Gbps
|
||||
&& self.pfd()
|
||||
&& self.lp() == Lp::SuperSpeedPlus
|
||||
}
|
||||
pub fn is_superspeed_gen_x(&self) -> bool {
|
||||
self.is_superspeed_gen1x1()
|
||||
|| self.is_superspeedplus_gen2x1()
|
||||
|| self.is_superspeedplus_gen1x2()
|
||||
|| self.is_superspeedplus_gen2x2()
|
||||
}
|
||||
/// Protocol speed ID value
|
||||
pub fn psiv(&self) -> u8 {
|
||||
((self.a.read() & PROTO_SPEED_PSIV_MASK) >> PROTO_SPEED_PSIV_SHIFT) as u8
|
||||
}
|
||||
pub fn psie_raw(&self) -> u8 {
|
||||
((self.a.read() & PROTO_SPEED_PSIE_MASK) >> PROTO_SPEED_PSIE_SHIFT) as u8
|
||||
}
|
||||
/// Protocol speed ID exponent
|
||||
pub fn psie(&self) -> Psie {
|
||||
// safe because psie_raw can only return values in 0..=3
|
||||
unsafe { mem::transmute(self.psie_raw()) }
|
||||
}
|
||||
pub fn plt_raw(&self) -> u8 {
|
||||
((self.a.read() & PROTO_SPEED_PLT_MASK) >> PROTO_SPEED_PLT_SHIFT) as u8
|
||||
}
|
||||
/// PSI type
|
||||
pub fn plt(&self) -> Plt {
|
||||
// safe because plt_raw can only return values in 0..=3
|
||||
unsafe { mem::transmute(self.plt_raw()) }
|
||||
}
|
||||
/// PSI Full-duplex
|
||||
pub fn pfd(&self) -> bool {
|
||||
self.a.readf(PROTO_SPEED_PFD_BIT)
|
||||
}
|
||||
pub fn lp_raw(&self) -> u8 {
|
||||
((self.a.read() & PROTO_SPEED_LP_MASK) >> PROTO_SPEED_LP_SHIFT) as u8
|
||||
}
|
||||
/// Link protocol
|
||||
pub fn lp(&self) -> Lp {
|
||||
// safe because lp_raw can only return values in 0..=3
|
||||
unsafe { mem::transmute(self.lp_raw()) }
|
||||
}
|
||||
/// Protocol speed ID mantissa
|
||||
pub fn psim(&self) -> u16 {
|
||||
((self.a.read() & PROTO_SPEED_PSIM_MASK) >> PROTO_SPEED_PSIM_SHIFT) as u16
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ProtocolSpeed {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("ProtocolSpeed")
|
||||
.field("psiv", &self.psiv())
|
||||
.field("psie", &self.psie())
|
||||
.field("plt", &self.plt())
|
||||
.field("pfd", &self.pfd())
|
||||
.field("lp", &self.lp())
|
||||
.field("psim", &self.psim())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
pub const SUPP_PROTO_CAP_REV_MIN_MASK: u32 = 0x00FF_0000;
|
||||
pub const SUPP_PROTO_CAP_REV_MIN_SHIFT: u8 = 16;
|
||||
|
||||
pub const SUPP_PROTO_CAP_REV_MAJ_MASK: u32 = 0xFF00_0000;
|
||||
pub const SUPP_PROTO_CAP_REV_MAJ_SHIFT: u8 = 24;
|
||||
|
||||
pub const SUPP_PROTO_CAP_COMPAT_PORT_OFF_MASK: u32 = 0x0000_00FF;
|
||||
pub const SUPP_PROTO_CAP_COMPAT_PORT_OFF_SHIFT: u8 = 0;
|
||||
|
||||
pub const SUPP_PROTO_CAP_COMPAT_PORT_CNT_MASK: u32 = 0x0000_FF00;
|
||||
pub const SUPP_PROTO_CAP_COMPAT_PORT_CNT_SHIFT: u8 = 8;
|
||||
|
||||
pub const SUPP_PROTO_CAP_PROTO_DEF_MASK: u32 = 0x0FFF_0000;
|
||||
pub const SUPP_PROTO_CAP_PROTO_DEF_SHIFT: u8 = 16;
|
||||
|
||||
pub const SUPP_PROTO_CAP_PSIC_MASK: u32 = 0xF000_0000;
|
||||
pub const SUPP_PROTO_CAP_PSIC_SHIFT: u8 = 28;
|
||||
|
||||
pub const SUPP_PROTO_CAP_PORT_SLOT_TYPE_MASK: u32 = 0x0000_001F;
|
||||
pub const SUPP_PROTO_CAP_PORT_SLOT_TYPE_SHIFT: u8 = 0;
|
||||
|
||||
impl SupportedProtoCap {
|
||||
pub unsafe fn protocol_speeds(&self) -> &[ProtocolSpeed] {
|
||||
slice::from_raw_parts(
|
||||
&self.protocol_speeds as *const u8 as *const _,
|
||||
self.psic() as usize,
|
||||
)
|
||||
}
|
||||
pub unsafe fn protocol_speeds_mut(&mut self) -> &mut [ProtocolSpeed] {
|
||||
// XXX: Variance really is annoying sometimes.
|
||||
slice::from_raw_parts_mut(
|
||||
&self.protocol_speeds as *const u8 as *mut u8 as *mut _,
|
||||
self.psic() as usize,
|
||||
)
|
||||
}
|
||||
pub fn rev_minor(&self) -> u8 {
|
||||
((self.a.read() & SUPP_PROTO_CAP_REV_MIN_MASK) >> SUPP_PROTO_CAP_REV_MIN_SHIFT) as u8
|
||||
}
|
||||
pub fn rev_major(&self) -> u8 {
|
||||
((self.a.read() & SUPP_PROTO_CAP_REV_MAJ_MASK) >> SUPP_PROTO_CAP_REV_MAJ_SHIFT) as u8
|
||||
}
|
||||
pub fn name_string(&self) -> [u8; 4] {
|
||||
// TODO: Little endian, right?
|
||||
u32::to_le_bytes(self.b.read())
|
||||
}
|
||||
pub fn compat_port_offset(&self) -> u8 {
|
||||
((self.c.read() & SUPP_PROTO_CAP_COMPAT_PORT_OFF_MASK)
|
||||
>> SUPP_PROTO_CAP_COMPAT_PORT_OFF_SHIFT) as u8
|
||||
}
|
||||
pub fn compat_port_count(&self) -> u8 {
|
||||
((self.c.read() & SUPP_PROTO_CAP_COMPAT_PORT_CNT_MASK)
|
||||
>> SUPP_PROTO_CAP_COMPAT_PORT_CNT_SHIFT) as u8
|
||||
}
|
||||
pub fn compat_port_range(&self) -> Range<u8> {
|
||||
self.compat_port_offset()..self.compat_port_offset() + self.compat_port_count()
|
||||
}
|
||||
|
||||
pub fn proto_defined(&self) -> u16 {
|
||||
((self.c.read() & SUPP_PROTO_CAP_PROTO_DEF_MASK) >> SUPP_PROTO_CAP_PROTO_DEF_SHIFT) as u16
|
||||
}
|
||||
pub fn psic(&self) -> u8 {
|
||||
((self.c.read() & SUPP_PROTO_CAP_PSIC_MASK) >> SUPP_PROTO_CAP_PSIC_SHIFT) as u8
|
||||
}
|
||||
pub fn proto_slot_ty(&self) -> u8 {
|
||||
((self.d.read() & SUPP_PROTO_CAP_PORT_SLOT_TYPE_MASK)
|
||||
>> SUPP_PROTO_CAP_PORT_SLOT_TYPE_SHIFT) as u8
|
||||
}
|
||||
}
|
||||
impl fmt::Debug for SupportedProtoCap {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("SupportedProtoCap")
|
||||
.field("rev_minor", &self.rev_minor())
|
||||
.field("rev_major", &self.rev_major())
|
||||
.field("name_string", &String::from_utf8_lossy(&self.name_string()))
|
||||
.field("compat_port_offset", &self.compat_port_count())
|
||||
.field("compat_port_count", &self.compat_port_offset())
|
||||
.field("proto_defined", &self.proto_defined())
|
||||
.field("psic", &self.psic())
|
||||
.field("proto_slot_ty", &self.proto_slot_ty())
|
||||
.field("proto_speeds", unsafe {
|
||||
&self.protocol_speeds().to_owned()
|
||||
})
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,743 @@
|
||||
use std::fs::File;
|
||||
use std::future::Future;
|
||||
use std::io::prelude::*;
|
||||
use std::pin::Pin;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::task;
|
||||
|
||||
use std::os::unix::io::AsRawFd;
|
||||
|
||||
use crossbeam_channel::{Receiver, Sender};
|
||||
use log::{debug, error, info, trace, warn};
|
||||
|
||||
use super::doorbell::Doorbell;
|
||||
use super::event::EventRing;
|
||||
use super::ring::Ring;
|
||||
use super::trb::{Trb, TrbCompletionCode, TrbType};
|
||||
use super::{PortId, Xhci};
|
||||
use crate::xhci::device_enumerator::DeviceEnumerationRequest;
|
||||
use crate::xhci::port::PortFlags;
|
||||
use common::io::Io as _;
|
||||
use event::RawEventQueue;
|
||||
|
||||
/// Short-term states (as in, they are removed when the waker is consumed, but probably pushed back
|
||||
/// by the future unless it completed).
|
||||
#[derive(Debug)]
|
||||
pub struct State {
|
||||
waker: task::Waker,
|
||||
kind: StateKind,
|
||||
message: Arc<Mutex<Option<NextEventTrb>>>,
|
||||
is_isoch_or_vf: bool,
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn finish(self, message: Option<NextEventTrb>) {
|
||||
*self.message.lock().unwrap() = message;
|
||||
trace!("Waking up future with waker: {:?}", self.waker);
|
||||
self.waker.wake();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NextEventTrb {
|
||||
pub event_trb: Trb,
|
||||
pub src_trb: Option<Trb>,
|
||||
}
|
||||
|
||||
// TODO: Perhaps all of the transfer rings used by the xHC should be stored linearly, and then
|
||||
// indexed using this struct instead.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub struct RingId {
|
||||
pub port: PortId,
|
||||
pub endpoint_num: u8,
|
||||
pub stream_id: u16,
|
||||
}
|
||||
impl RingId {
|
||||
pub const fn default_control_pipe(port: PortId) -> Self {
|
||||
Self {
|
||||
port,
|
||||
endpoint_num: 0,
|
||||
stream_id: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The state specific to a TRB-type. Since some of the event TDs may asynchronously appear, for
|
||||
/// example the Command Completion Event and the Transfer Event TDs, they have to be
|
||||
/// distinguishable. Luckily, the xHC also gives us the actual (physical) pointer to the source
|
||||
/// TRB, from the command ring, unless the event TD has one the completion codes Ring Underrun,
|
||||
/// Ring Overrun, or VF Event Ring Full Error. When these errors are encountered, it simply
|
||||
/// indicates that the commands causing the errors continue to be pending, and thus no information
|
||||
/// is lost.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum StateKind {
|
||||
CommandCompletion {
|
||||
phys_ptr: u64,
|
||||
},
|
||||
Transfer {
|
||||
first_phys_ptr: u64,
|
||||
last_phys_ptr: u64,
|
||||
ring_id: RingId,
|
||||
},
|
||||
Other(TrbType),
|
||||
}
|
||||
|
||||
impl StateKind {
|
||||
pub fn trb_type(&self) -> TrbType {
|
||||
match self {
|
||||
&Self::CommandCompletion { .. } => TrbType::CommandCompletion,
|
||||
&Self::Transfer { .. } => TrbType::Transfer,
|
||||
&Self::Other(ty) => ty,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IrqReactor<const N: usize> {
|
||||
hci: Arc<Xhci<N>>,
|
||||
irq_file: Option<File>,
|
||||
irq_receiver: Receiver<NewPendingTrb>,
|
||||
device_enumerator_sender: Sender<DeviceEnumerationRequest>,
|
||||
states: Vec<State>,
|
||||
// TODO: Since the IRQ reactor is the only part of this driver that gets event TRBs, perhaps
|
||||
// the event ring should be owned here?
|
||||
}
|
||||
|
||||
pub type NewPendingTrb = State;
|
||||
|
||||
impl<const N: usize> IrqReactor<N> {
|
||||
pub fn new(hci: Arc<Xhci<N>>, irq_file: Option<File>) -> Self {
|
||||
let device_enumerator_sender = hci.device_enumerator_sender.clone();
|
||||
let irq_receiver = hci.irq_reactor_receiver.clone();
|
||||
|
||||
Self {
|
||||
hci,
|
||||
irq_file,
|
||||
irq_receiver,
|
||||
device_enumerator_sender,
|
||||
states: Vec::new(),
|
||||
}
|
||||
}
|
||||
// TODO: Configure the amount of time wait when no more work can be done (for IRQ-less polling).
|
||||
fn pause(&self) {
|
||||
std::thread::sleep(std::time::Duration::from_millis(2));
|
||||
}
|
||||
fn run_polling(mut self) -> ! {
|
||||
debug!("Running IRQ reactor in polling mode.");
|
||||
let hci_clone = Arc::clone(&self.hci);
|
||||
|
||||
let mut event_trb_index = {
|
||||
hci_clone
|
||||
.primary_event_ring
|
||||
.lock()
|
||||
.unwrap()
|
||||
.ring
|
||||
.next_index()
|
||||
};
|
||||
|
||||
'trb_loop: loop {
|
||||
self.pause();
|
||||
|
||||
let mut event_ring = hci_clone.primary_event_ring.lock().unwrap();
|
||||
|
||||
let event_trb = &mut event_ring.ring.trbs[event_trb_index];
|
||||
|
||||
if event_trb.completion_code() == TrbCompletionCode::Invalid as u8 {
|
||||
continue 'trb_loop;
|
||||
}
|
||||
|
||||
trace!(
|
||||
"Found event TRB at index {} with type {} and cycle bit {}: {:?}",
|
||||
event_trb_index,
|
||||
event_trb.trb_type(),
|
||||
event_trb.cycle() as u8,
|
||||
event_trb
|
||||
);
|
||||
|
||||
if self.check_event_ring_full(event_trb.clone()) {
|
||||
info!("Had to resize event TRB, retrying...");
|
||||
continue 'trb_loop;
|
||||
}
|
||||
|
||||
trace!("Handling requests");
|
||||
self.handle_requests();
|
||||
trace!("Requests handled");
|
||||
|
||||
match event_trb.trb_type() {
|
||||
_ if event_trb.trb_type() == TrbType::PortStatusChange as u8 => {
|
||||
trace!("Received a port status change!");
|
||||
self.handle_port_status_change(event_trb.clone())
|
||||
} //TODO Handle the other unprompted events
|
||||
_ => {
|
||||
self.acknowledge(event_trb.clone());
|
||||
}
|
||||
}
|
||||
|
||||
event_trb.reserved(false);
|
||||
|
||||
self.update_erdp(&*event_ring);
|
||||
hci_clone.event_handler_finished();
|
||||
|
||||
event_trb_index = event_ring.ring.next_index();
|
||||
}
|
||||
}
|
||||
|
||||
fn mask_interrupts(&mut self) {
|
||||
let mut run = self.hci.run.lock().unwrap();
|
||||
|
||||
debug!("Masking interrupts!");
|
||||
|
||||
if !run.ints[0].iman.readf(1 << 1) {
|
||||
warn!("Attempted to mask interrupts when they were already disabled!")
|
||||
}
|
||||
|
||||
run.ints[0].iman.writef(1 << 1, false);
|
||||
}
|
||||
|
||||
fn unmask_interrupts(&mut self) {
|
||||
let mut run = self.hci.run.lock().unwrap();
|
||||
|
||||
debug!("unmasking interrupts!");
|
||||
if run.ints[0].iman.readf(1 << 1) {
|
||||
warn!("Attempted to unmask interrupts when they were already enabled!")
|
||||
}
|
||||
|
||||
run.ints[0].iman.writef(1 << 1, true);
|
||||
}
|
||||
|
||||
fn run_with_irq_file(mut self) -> ! {
|
||||
debug!("Running IRQ reactor with IRQ file and event queue");
|
||||
|
||||
let hci_clone = Arc::clone(&self.hci);
|
||||
let event_queue =
|
||||
RawEventQueue::new().expect("xhcid irq_reactor: failed to create IRQ event queue");
|
||||
let irq_fd = self.irq_file.as_ref().unwrap().as_raw_fd();
|
||||
event_queue
|
||||
.subscribe(irq_fd as usize, 0, event::EventFlags::READ)
|
||||
.unwrap();
|
||||
|
||||
trace!("IRQ Reactor has created its event queue.");
|
||||
let mut event_trb_index = {
|
||||
hci_clone
|
||||
.primary_event_ring
|
||||
.lock()
|
||||
.unwrap()
|
||||
.ring
|
||||
.next_index()
|
||||
};
|
||||
|
||||
trace!("IRQ reactor has grabbed the next index in the event ring.");
|
||||
'trb_loop: loop {
|
||||
let _event = event_queue.next_event().unwrap();
|
||||
trace!("IRQ event queue notified");
|
||||
let mut buffer = [0u8; 8];
|
||||
|
||||
let _ = self
|
||||
.irq_file
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.read(&mut buffer)
|
||||
.expect("Failed to read from irq scheme");
|
||||
|
||||
if !self.hci.received_irq() {
|
||||
// continue only when an IRQ to this device was received
|
||||
trace!("no interrupt pending");
|
||||
continue 'trb_loop;
|
||||
}
|
||||
|
||||
self.mask_interrupts();
|
||||
|
||||
trace!("IRQ reactor received an IRQ");
|
||||
|
||||
let _ = self.irq_file.as_mut().unwrap().write(&buffer);
|
||||
|
||||
// TODO: More event rings, probably even with different IRQs.
|
||||
|
||||
let mut event_ring = hci_clone.primary_event_ring.lock().unwrap();
|
||||
|
||||
let mut count = 0;
|
||||
|
||||
loop {
|
||||
trace!("count: {}", count);
|
||||
let event_trb = &mut event_ring.ring.trbs[event_trb_index];
|
||||
|
||||
if event_trb.completion_code() == TrbCompletionCode::Invalid as u8 {
|
||||
if count == 0 {
|
||||
warn!("xhci: Received interrupt, but no event was found in the event ring. Ignoring interrupt.")
|
||||
}
|
||||
//hci_clone.event_handler_finished();
|
||||
self.unmask_interrupts();
|
||||
continue 'trb_loop;
|
||||
} else {
|
||||
count += 1
|
||||
}
|
||||
|
||||
info!(
|
||||
"Found event TRB at index {} with type {} and cycle bit {}: {:?}",
|
||||
event_trb_index,
|
||||
event_trb.trb_type(),
|
||||
event_trb.cycle() as u8,
|
||||
event_trb
|
||||
);
|
||||
|
||||
if self.check_event_ring_full(event_trb.clone()) {
|
||||
info!("Had to resize event TRB, retrying...");
|
||||
//hci_clone.event_handler_finished();
|
||||
if self.hci.interrupt_is_pending(0) {
|
||||
warn!("After incrementing the dequeue pointer, the interrupt bit is still pending.")
|
||||
} else {
|
||||
debug!("The interrupt bit is no longer pending.");
|
||||
}
|
||||
self.unmask_interrupts();
|
||||
continue 'trb_loop;
|
||||
}
|
||||
self.handle_requests();
|
||||
|
||||
match event_trb.trb_type() {
|
||||
_ if event_trb.trb_type() == TrbType::PortStatusChange as u8 => {
|
||||
trace!("Received a port status change!");
|
||||
self.handle_port_status_change(event_trb.clone())
|
||||
} //TODO Handle the other unprompted events
|
||||
_ => {
|
||||
trace!("Received a non-status trb");
|
||||
self.acknowledge(event_trb.clone());
|
||||
}
|
||||
}
|
||||
|
||||
event_trb.reserved(false);
|
||||
|
||||
self.update_erdp(&*event_ring);
|
||||
self.hci.event_handler_finished();
|
||||
|
||||
event_trb_index = event_ring.ring.next_index();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles device attach/detach events as indicated by a PortStatusChange
|
||||
fn handle_port_status_change(&mut self, trb: Trb) {
|
||||
if let Some(root_hub_port_num) = trb.port_status_change_port_id() {
|
||||
let port_id = PortId {
|
||||
root_hub_port_num,
|
||||
route_string: 0,
|
||||
};
|
||||
trace!("Received Port Status Change Request on port {}", port_id);
|
||||
self.device_enumerator_sender
|
||||
.send(DeviceEnumerationRequest { port_id })
|
||||
.expect(
|
||||
format!(
|
||||
"Failed to transmit device numeration request on port {}",
|
||||
port_id
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
{
|
||||
let mut ports = self.hci.ports.lock().unwrap();
|
||||
let root_port_index = port_id.root_hub_port_index();
|
||||
if root_port_index >= ports.len() {
|
||||
warn!(
|
||||
"Received out of bounds transmit device numeration request on root index {} at port {} [port len was: {}]",
|
||||
root_port_index, port_id, ports.len()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let port = &mut ports[root_port_index];
|
||||
port.clear_csc();
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
"Received a TRB of type {}, which was unexpected",
|
||||
trb.trb_type()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn update_erdp(&self, event_ring: &EventRing) {
|
||||
let dequeue_pointer_and_dcs = event_ring.erdp();
|
||||
let dequeue_pointer = dequeue_pointer_and_dcs & 0xFFFF_FFFF_FFFF_FFFE;
|
||||
assert_eq!(
|
||||
dequeue_pointer & 0xFFFF_FFFF_FFFF_FFF0,
|
||||
dequeue_pointer,
|
||||
"unaligned ERDP received from primary event ring"
|
||||
);
|
||||
|
||||
trace!("Updated ERDP to {:#0x}", dequeue_pointer);
|
||||
|
||||
self.hci.run.lock().unwrap().ints[0]
|
||||
.erdp_low
|
||||
.write(dequeue_pointer as u32);
|
||||
self.hci.run.lock().unwrap().ints[0]
|
||||
.erdp_high
|
||||
.write((dequeue_pointer >> 32) as u32);
|
||||
}
|
||||
fn handle_requests(&mut self) {
|
||||
self.states.extend(
|
||||
self.irq_receiver
|
||||
.try_iter()
|
||||
.inspect(|req| trace!("Received request: {:X?}", req)),
|
||||
);
|
||||
}
|
||||
fn acknowledge(&mut self, trb: Trb) {
|
||||
//TODO: handle TRBs without an attached state
|
||||
|
||||
trace!("ACK TRB {:X?}", trb);
|
||||
|
||||
let mut index = 0;
|
||||
while index < self.states.len() {
|
||||
trace!("ACK STATE {}: {:X?}", index, self.states[index].kind);
|
||||
|
||||
match self.states[index].kind {
|
||||
StateKind::CommandCompletion { phys_ptr }
|
||||
if trb.trb_type() == TrbType::CommandCompletion as u8 =>
|
||||
{
|
||||
if trb.completion_trb_pointer() == Some(phys_ptr) {
|
||||
trace!("Found matching command completion future");
|
||||
let state = self.states.remove(index);
|
||||
|
||||
// Before waking, it's crucial that the command TRB that generated this event
|
||||
// is fetched before removing this event TRB from the queue.
|
||||
let command_trb = match self
|
||||
.hci
|
||||
.cmd
|
||||
.lock()
|
||||
.unwrap()
|
||||
.phys_addr_to_entry_mut(self.hci.cap.ac64(), phys_ptr)
|
||||
{
|
||||
Some(command_trb) => {
|
||||
let t = command_trb.clone();
|
||||
command_trb.reserved(false);
|
||||
t
|
||||
}
|
||||
None => {
|
||||
warn!("The xHC supplied a pointer to a command TRB that was outside the known command ring bounds. Ignoring event TRB {:?}.", trb);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: Validate the command TRB.
|
||||
state.finish(Some(NextEventTrb {
|
||||
src_trb: Some(command_trb.clone()),
|
||||
event_trb: trb.clone(),
|
||||
}));
|
||||
|
||||
return;
|
||||
} else if trb.completion_trb_pointer().is_none() {
|
||||
warn!("Command TRB somehow resulted in an error that only can be caused by transfer TRBs. Ignoring event TRB: {:?}.", trb);
|
||||
}
|
||||
}
|
||||
|
||||
StateKind::Transfer {
|
||||
first_phys_ptr,
|
||||
last_phys_ptr,
|
||||
ring_id,
|
||||
} => {
|
||||
// Check if the TRB matches the transfer
|
||||
if trb.trb_type() == TrbType::Transfer as u8 {
|
||||
match trb.transfer_event_trb_pointer() {
|
||||
Some(phys_ptr) => {
|
||||
let matches = if first_phys_ptr <= last_phys_ptr {
|
||||
phys_ptr >= first_phys_ptr && phys_ptr <= last_phys_ptr
|
||||
} else {
|
||||
// Handle ring buffer wrap
|
||||
phys_ptr >= first_phys_ptr || phys_ptr <= last_phys_ptr
|
||||
};
|
||||
if matches {
|
||||
let src_trb = self.hci.get_transfer_trb(phys_ptr, ring_id);
|
||||
// Give the source transfer TRB together with the event TRB, to the future.
|
||||
let state = self.states.remove(index);
|
||||
state.finish(Some(NextEventTrb {
|
||||
src_trb: src_trb,
|
||||
event_trb: trb.clone(),
|
||||
}));
|
||||
return;
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// Ring Overrun, Ring Underrun, or Virtual Function Event Ring Full.
|
||||
//
|
||||
// These errors are caused when either an isoch transfer that shall write data, doesn't
|
||||
// have any data since the ring is empty, or if an isoch receive is impossible due to a
|
||||
// full ring. The Virtual Function Event Ring Full is only for Virtual Machine
|
||||
// Managers, and since this isn't implemented yet, they are irrelevant.
|
||||
//
|
||||
// The best solution here is to differentiate between isoch transfers (and
|
||||
// virtual function event rings when virtualization gets implemented), with
|
||||
// regular commands and transfers, and send the error TRB to all of them, or
|
||||
// possibly an error code wrapped in a Result.
|
||||
self.acknowledge_failed_transfer_trbs(trb);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Also check if the transfer is on a dead ring
|
||||
if self.hci.with_ring(ring_id, |_ring| ()).is_none() {
|
||||
log::debug!("State {} is a dead transfer", index);
|
||||
let state = self.states.remove(index);
|
||||
state.finish(Some(NextEventTrb {
|
||||
src_trb: None,
|
||||
//TODO: don't send this TRB as it may not be related
|
||||
event_trb: trb.clone(),
|
||||
}));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
StateKind::Other(trb_type) if trb_type as u8 == trb.trb_type() => {
|
||||
let state = self.states.remove(index);
|
||||
state.finish(None);
|
||||
return;
|
||||
}
|
||||
|
||||
_ => (),
|
||||
}
|
||||
|
||||
index += 1;
|
||||
}
|
||||
warn!(
|
||||
"Lost event TRB type {}, completion code: {}: {:X?}",
|
||||
trb.trb_type(),
|
||||
trb.completion_code(),
|
||||
trb
|
||||
);
|
||||
}
|
||||
fn acknowledge_failed_transfer_trbs(&mut self, trb: Trb) {
|
||||
let mut index = 0;
|
||||
|
||||
loop {
|
||||
if !self.states[index].is_isoch_or_vf {
|
||||
index += 1;
|
||||
if index >= self.states.len() {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let state = self.states.remove(index);
|
||||
state.finish(Some(NextEventTrb {
|
||||
event_trb: trb.clone(),
|
||||
src_trb: None,
|
||||
}));
|
||||
}
|
||||
}
|
||||
/// Checks if an event TRB is a Host Controller Event, with the completion code Event Ring
|
||||
/// Full. If so, it grows the event ring. The return value is whether the event ring was full,
|
||||
/// and then grown.
|
||||
fn check_event_ring_full(&mut self, event_trb: Trb) -> bool {
|
||||
let had_event_ring_full_error = event_trb.trb_type() == TrbType::HostController as u8
|
||||
&& event_trb.completion_code() == TrbCompletionCode::EventRingFull as u8;
|
||||
|
||||
if had_event_ring_full_error {
|
||||
self.grow_event_ring();
|
||||
}
|
||||
had_event_ring_full_error
|
||||
}
|
||||
/// Grows the event ring
|
||||
fn grow_event_ring(&mut self) {
|
||||
// TODO
|
||||
error!("TODO: grow event ring");
|
||||
}
|
||||
|
||||
pub fn run(self) -> ! {
|
||||
if self.irq_file.is_some() {
|
||||
self.run_with_irq_file();
|
||||
} else {
|
||||
self.run_polling();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct FutureState {
|
||||
message: Arc<Mutex<Option<NextEventTrb>>>,
|
||||
is_isoch_or_vf: bool,
|
||||
state_kind: StateKind,
|
||||
}
|
||||
|
||||
pub struct EventDoorbell {
|
||||
dbs: Arc<Mutex<&'static mut [Doorbell]>>,
|
||||
index: usize,
|
||||
data: u32,
|
||||
}
|
||||
|
||||
impl EventDoorbell {
|
||||
pub fn new<const N: usize>(hci: &Xhci<N>, index: usize, data: u32) -> Self {
|
||||
Self {
|
||||
//TODO: simplify this logic, maybe just use a raw pointer?
|
||||
dbs: hci.dbs.clone(),
|
||||
index,
|
||||
data,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ring(self) {
|
||||
trace!("Ring doorbell {} with data {}", self.index, self.data);
|
||||
self.dbs.lock().unwrap()[self.index].write(self.data);
|
||||
trace!("Doorbell was rung.");
|
||||
}
|
||||
}
|
||||
|
||||
enum EventTrbFuture {
|
||||
Pending {
|
||||
state: FutureState,
|
||||
sender: Sender<State>,
|
||||
doorbell_opt: Option<EventDoorbell>,
|
||||
},
|
||||
Finished,
|
||||
}
|
||||
|
||||
impl Future for EventTrbFuture {
|
||||
type Output = NextEventTrb;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, context: &mut task::Context) -> task::Poll<Self::Output> {
|
||||
let this = self.get_mut();
|
||||
trace!("Start poll!");
|
||||
let message = match this {
|
||||
&mut Self::Pending {
|
||||
ref state,
|
||||
ref sender,
|
||||
ref mut doorbell_opt,
|
||||
} => match state.message.lock().unwrap().take() {
|
||||
Some(message) => message,
|
||||
|
||||
None => {
|
||||
// Register state with IRQ reactor
|
||||
trace!("Send state {:X?}", state.state_kind);
|
||||
sender
|
||||
.send(State {
|
||||
message: Arc::clone(&state.message),
|
||||
is_isoch_or_vf: state.is_isoch_or_vf,
|
||||
kind: state.state_kind,
|
||||
waker: context.waker().clone(),
|
||||
})
|
||||
.expect("IRQ reactor thread unexpectedly stopped");
|
||||
|
||||
// Doorbell must be rung after sending state
|
||||
if let Some(doorbell) = doorbell_opt.take() {
|
||||
doorbell.ring();
|
||||
}
|
||||
return task::Poll::Pending;
|
||||
}
|
||||
},
|
||||
&mut Self::Finished => panic!("Polling finished EventTrbFuture again."),
|
||||
};
|
||||
trace!("finished!");
|
||||
*this = Self::Finished;
|
||||
task::Poll::Ready(message)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Xhci<N> {
|
||||
pub fn get_transfer_trb(&self, paddr: u64, id: RingId) -> Option<Trb> {
|
||||
self.with_ring(id, |ring| ring.phys_addr_to_entry(self.cap.ac64(), paddr))
|
||||
.flatten()
|
||||
}
|
||||
pub fn with_ring<T, F: FnOnce(&Ring) -> T>(&self, id: RingId, function: F) -> Option<T> {
|
||||
use super::RingOrStreams;
|
||||
|
||||
let slot_state = self.port_states.get(&id.port)?;
|
||||
let endpoint_state = slot_state.endpoint_states.get(&id.endpoint_num)?;
|
||||
|
||||
let ring_ref = match endpoint_state.transfer {
|
||||
RingOrStreams::Ring(ref ring) => ring,
|
||||
RingOrStreams::Streams(ref ctx_arr) => ctx_arr.rings.get(&id.stream_id)?,
|
||||
};
|
||||
|
||||
Some(function(ring_ref))
|
||||
}
|
||||
pub fn with_ring_mut<T, F: FnOnce(&mut Ring) -> T>(
|
||||
&self,
|
||||
id: RingId,
|
||||
function: F,
|
||||
) -> Option<T> {
|
||||
use super::RingOrStreams;
|
||||
|
||||
let mut slot_state = self.port_states.get_mut(&id.port)?;
|
||||
let mut endpoint_state = slot_state.endpoint_states.get_mut(&id.endpoint_num)?;
|
||||
|
||||
let ring_ref = match endpoint_state.transfer {
|
||||
RingOrStreams::Ring(ref mut ring) => ring,
|
||||
RingOrStreams::Streams(ref mut ctx_arr) => ctx_arr.rings.get_mut(&id.stream_id)?,
|
||||
};
|
||||
|
||||
Some(function(ring_ref))
|
||||
}
|
||||
pub fn next_transfer_event_trb(
|
||||
&self,
|
||||
ring_id: RingId,
|
||||
ring: &Ring,
|
||||
first_trb: &Trb,
|
||||
last_trb: &Trb,
|
||||
doorbell: EventDoorbell,
|
||||
) -> impl Future<Output = NextEventTrb> + Send + Sync + 'static {
|
||||
if !last_trb.is_transfer_trb() {
|
||||
panic!("Invalid TRB type given to next_transfer_event_trb(): {} (TRB {:?}. Expected transfer TRB.", last_trb.trb_type(), last_trb)
|
||||
}
|
||||
|
||||
let is_isoch_or_vf = last_trb.trb_type() == TrbType::Isoch as u8;
|
||||
let first_phys_ptr = ring.trb_phys_ptr(self.cap.ac64(), first_trb);
|
||||
let last_phys_ptr = ring.trb_phys_ptr(self.cap.ac64(), last_trb);
|
||||
EventTrbFuture::Pending {
|
||||
state: FutureState {
|
||||
is_isoch_or_vf,
|
||||
state_kind: StateKind::Transfer {
|
||||
ring_id,
|
||||
first_phys_ptr,
|
||||
last_phys_ptr,
|
||||
},
|
||||
message: Arc::new(Mutex::new(None)),
|
||||
},
|
||||
sender: self.irq_reactor_sender.clone(),
|
||||
doorbell_opt: Some(doorbell),
|
||||
}
|
||||
}
|
||||
pub fn next_command_completion_event_trb(
|
||||
&self,
|
||||
command_ring: &Ring,
|
||||
trb: &Trb,
|
||||
doorbell: EventDoorbell,
|
||||
) -> impl Future<Output = NextEventTrb> + Send + Sync + 'static {
|
||||
trace!(
|
||||
"Sending command at phys_ptr {:X}",
|
||||
command_ring.trb_phys_ptr(self.cap.ac64(), trb)
|
||||
);
|
||||
if !trb.is_command_trb() {
|
||||
panic!("Invalid TRB type given to next_command_completion_event_trb(): {} (TRB {:?}. Expected command TRB.", trb.trb_type(), trb)
|
||||
}
|
||||
EventTrbFuture::Pending {
|
||||
state: FutureState {
|
||||
// This is only possible for transfers if they are isochronous, or for Force Event TRBs (virtualization).
|
||||
is_isoch_or_vf: false,
|
||||
state_kind: StateKind::CommandCompletion {
|
||||
phys_ptr: command_ring.trb_phys_ptr(self.cap.ac64(), trb),
|
||||
},
|
||||
message: Arc::new(Mutex::new(None)),
|
||||
},
|
||||
sender: self.irq_reactor_sender.clone(),
|
||||
doorbell_opt: Some(doorbell),
|
||||
}
|
||||
}
|
||||
pub fn next_misc_event_trb(
|
||||
&self,
|
||||
trb_type: TrbType,
|
||||
) -> impl Future<Output = NextEventTrb> + Send + Sync + 'static {
|
||||
let valid_trb_types = [
|
||||
TrbType::PortStatusChange as u8,
|
||||
TrbType::BandwidthRequest as u8,
|
||||
TrbType::Doorbell as u8,
|
||||
TrbType::HostController as u8,
|
||||
TrbType::DeviceNotification as u8,
|
||||
TrbType::MfindexWrap as u8,
|
||||
];
|
||||
if !valid_trb_types.contains(&(trb_type as u8)) {
|
||||
panic!("Invalid TRB type given to next_misc_event_trb(): {:?}. Only event TRB types that are neither transfer events or command completion events can be used.", trb_type)
|
||||
}
|
||||
EventTrbFuture::Pending {
|
||||
state: FutureState {
|
||||
is_isoch_or_vf: false,
|
||||
state_kind: StateKind::Other(trb_type),
|
||||
message: Arc::new(Mutex::new(None)),
|
||||
},
|
||||
sender: self.irq_reactor_sender.clone(),
|
||||
doorbell_opt: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,101 @@
|
||||
use common::io::{Io, Mmio};
|
||||
|
||||
/// The XHCI Operational Registers
|
||||
///
|
||||
/// These registers specify the operational state of the XHCI device, and are used to receive status
|
||||
/// messages and transmit commands. These registers are offset from the XHCI base address by the
|
||||
/// "length" field of the [CapabilityRegs]
|
||||
///
|
||||
/// See XHCI section 5.4. Table 5-18 describes the offset of these registers in memory.
|
||||
#[repr(C, packed)]
|
||||
pub struct OperationalRegs {
|
||||
/// The USB Command Register (USBCMD)
|
||||
///
|
||||
/// Describes the command to be executed by the XHCI. Writes to this register case a command
|
||||
/// to be executed.
|
||||
///
|
||||
/// - Bit 0 is the Run/Stop bit (R/S). Writing a value of 1 stops the xHC from executing the schedule, 1 resumes. Latency is ~16ms at worst. (See XHCI Table 5-20)
|
||||
/// - Bit 1 is the Host Controller Reset Bit (HCRST). Used by software to reset the host controller (See XHCI Table 5-20)
|
||||
/// - Bit 2 is the Interrupter Enable Bit (INTE). Enables interrupting the host system.
|
||||
/// - Bit 3 is the Host System Error Enable Bit (HSEE). Enables out-of-band error signalling to the host.
|
||||
/// - Bits 4-6 are reserved.
|
||||
/// - Bit 7 is the Light Host Controller Reset Bit (LHCRST). Resets the driver without affecting the state of the ports. Affected by [CapabilityRegs]
|
||||
/// - Bit 8 is the Controller Save State Bit (CSS). See XHCI Table 5-20
|
||||
/// - Bit 9 is the Controller Restore State Bit (CRS). See XHCI Table 5-20
|
||||
/// - Bit 10 is the Enable Wrap Event Bit (EWE). See XHCI Table 5-20
|
||||
/// - Bit 11 is the Enable U3 MFINDEX Stop Bit (EU3S). See XHCI Table 5-20
|
||||
/// - Bit 12 is reserved.
|
||||
/// - Bit 13 is the CEM Enable Bit (CME). See XHCI Table 5-20
|
||||
/// - Bit 14 is the Extended TBC Enable Bit (ETE). See XHCI Table 5-20
|
||||
/// - Bit 15 is the Extended TBC TRB Status Enable Bit (TSC_En). See XHCI Table 5-20
|
||||
/// - Bit 16 is the VTIO Enable Bit (VTIOE). Controls the enable state of the VTIO capability.
|
||||
/// - Bits 17-31 are reserved.
|
||||
///
|
||||
pub usb_cmd: Mmio<u32>,
|
||||
/// The USB Status Register (USBSTS)
|
||||
///
|
||||
/// This register indicates pending interrupts and various states of the host controller.
|
||||
///
|
||||
/// Software sets a bit to '0' in this register by writing a 1 to it.
|
||||
///
|
||||
///
|
||||
pub usb_sts: Mmio<u32>,
|
||||
/// The PAGESIZE Register (PAGESIZE)
|
||||
///
|
||||
///
|
||||
pub page_size: Mmio<u32>,
|
||||
/// Reserved bits (RsvdZ)
|
||||
_rsvd: [Mmio<u32>; 2],
|
||||
/// The Device Notification Control Register (DNCTRL)
|
||||
///
|
||||
///
|
||||
pub dn_ctrl: Mmio<u32>,
|
||||
/// The Command Ring Control Register Lower 32 bits (CRCR)
|
||||
///
|
||||
///
|
||||
pub crcr_low: Mmio<u32>,
|
||||
/// The Command Ring Control Register Upper 32 bits (CRCR)
|
||||
///
|
||||
///
|
||||
pub crcr_high: Mmio<u32>,
|
||||
/// Reserved bits (RsvdZ)
|
||||
_rsvd2: [Mmio<u32>; 4],
|
||||
/// Device Context Base Address Array Pointer Lower 32 bits (DCBAAP)
|
||||
///
|
||||
///
|
||||
pub dcbaap_low: Mmio<u32>,
|
||||
/// Device Context Base Address Array Pointer Upper 32 bits (DCBAAP)
|
||||
///
|
||||
///
|
||||
pub dcbaap_high: Mmio<u32>,
|
||||
/// The Configure Register (CONFIG)
|
||||
///
|
||||
///
|
||||
pub config: Mmio<u32>,
|
||||
// The standard has another set of reserved bits from 3C-3FFh here
|
||||
// The standard has 400-13FFh has a Port Register Set here (likely defined in port.rs).
|
||||
}
|
||||
|
||||
// Run/stop
|
||||
pub const USB_CMD_RS: u32 = 1 << 0;
|
||||
/// Host controller reset
|
||||
pub const USB_CMD_HCRST: u32 = 1 << 1;
|
||||
// Interrupter enable
|
||||
pub const USB_CMD_INTE: u32 = 1 << 2;
|
||||
|
||||
/// Host controller halted
|
||||
pub const USB_STS_HCH: u32 = 1 << 0;
|
||||
/// Host controller not ready
|
||||
pub const USB_STS_CNR: u32 = 1 << 11;
|
||||
|
||||
/// The mask to get the CIE bit from the Config register. See [OperationalRegs]
|
||||
pub const OP_CONFIG_CIE_BIT: u32 = 1 << 9;
|
||||
|
||||
impl OperationalRegs {
|
||||
pub fn cie(&self) -> bool {
|
||||
self.config.readf(OP_CONFIG_CIE_BIT)
|
||||
}
|
||||
pub fn set_cie(&mut self, value: bool) {
|
||||
self.config.writef(OP_CONFIG_CIE_BIT, value)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
use common::io::{Io, Mmio};
|
||||
|
||||
// RO - read-only
|
||||
// ROS - read-only sticky
|
||||
// RW - read/write
|
||||
// RWS - read/write sticky
|
||||
// RW1CS - read/write-1-to-clear sticky
|
||||
// RW1S - read/write-1-to-set
|
||||
// Sticky register values may preserve values through chip hardware reset
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug)]
|
||||
pub struct PortFlags: u32 {
|
||||
const CCS = 1 << 0; // ROS
|
||||
const PED = 1 << 1; // RW1CS
|
||||
const RSVD_2 = 1 << 2; // RsvdZ
|
||||
const OCA = 1 << 3; // RO
|
||||
const PR = 1 << 4; // RW1S
|
||||
const PLS_0 = 1 << 5; // RWS
|
||||
const PLS_1 = 1 << 6; // RWS
|
||||
const PLS_2 = 1 << 7; // RWS
|
||||
const PLS_3 = 1 << 8; // RWS
|
||||
const PP = 1 << 9; // RWS
|
||||
const SPEED_0 = 1 << 10; // ROS
|
||||
const SPEED_1 = 1 << 11; // ROS
|
||||
const SPEED_2 = 1 << 12; // ROS
|
||||
const SPEED_3 = 1 << 13; // ROS
|
||||
const PIC_AMB = 1 << 14; // RWS
|
||||
const PIC_GRN = 1 << 15; // RWS
|
||||
const LWS = 1 << 16; // RW
|
||||
const CSC = 1 << 17; // RW1CS
|
||||
const PEC = 1 << 18; // RW1CS
|
||||
const WRC = 1 << 19; // RW1CS
|
||||
const OCC = 1 << 20; // RW1CS
|
||||
const PRC = 1 << 21; // RW1CS
|
||||
const PLC = 1 << 22; // RW1CS
|
||||
const CEC = 1 << 23; // RW1CS
|
||||
const CAS = 1 << 24; // RO
|
||||
const WCE = 1 << 25; // RWS
|
||||
const WDE = 1 << 26; // RWS
|
||||
const WOE = 1 << 27; // RWS
|
||||
const RSVD_28 = 1 << 28; // RsvdZ
|
||||
const RSVD_29 = 1 << 29; // RsvdZ
|
||||
const DR = 1 << 30; // RO
|
||||
const WPR = 1 << 31; // RW1S
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
pub struct Port {
|
||||
// This has write one to clear fields, do not expose it, handle writes carefully!
|
||||
portsc: Mmio<u32>,
|
||||
pub portpmsc: Mmio<u32>,
|
||||
pub portli: Mmio<u32>,
|
||||
pub porthlpmc: Mmio<u32>,
|
||||
}
|
||||
|
||||
impl Port {
|
||||
pub fn read(&self) -> u32 {
|
||||
self.portsc.read()
|
||||
}
|
||||
|
||||
pub fn clear_csc(&mut self) {
|
||||
self.portsc
|
||||
.write((self.flags_preserved() | PortFlags::CSC).bits());
|
||||
}
|
||||
|
||||
pub fn clear_prc(&mut self) {
|
||||
self.portsc
|
||||
.write((self.flags_preserved() | PortFlags::PRC).bits());
|
||||
}
|
||||
|
||||
pub fn set_pr(&mut self) {
|
||||
self.portsc
|
||||
.write((self.flags_preserved() | PortFlags::PR).bits());
|
||||
}
|
||||
|
||||
pub fn state(&self) -> u8 {
|
||||
((self.read() & (0b1111 << 5)) >> 5) as u8
|
||||
}
|
||||
|
||||
pub fn speed(&self) -> u8 {
|
||||
((self.read() & (0b1111 << 10)) >> 10) as u8
|
||||
}
|
||||
|
||||
pub fn flags(&self) -> PortFlags {
|
||||
PortFlags::from_bits_truncate(self.read())
|
||||
}
|
||||
|
||||
// Read only preserved flags
|
||||
pub fn flags_preserved(&self) -> PortFlags {
|
||||
// RO(S) and RW(S) bits should be preserved
|
||||
// RW1S and RW1CS bits should not
|
||||
let preserved = PortFlags::CCS
|
||||
| PortFlags::OCA
|
||||
| PortFlags::PLS_0
|
||||
| PortFlags::PLS_1
|
||||
| PortFlags::PLS_2
|
||||
| PortFlags::PLS_3
|
||||
| PortFlags::PP
|
||||
| PortFlags::SPEED_0
|
||||
| PortFlags::SPEED_1
|
||||
| PortFlags::SPEED_2
|
||||
| PortFlags::SPEED_3
|
||||
| PortFlags::PIC_AMB
|
||||
| PortFlags::PIC_GRN
|
||||
| PortFlags::WCE
|
||||
| PortFlags::WDE
|
||||
| PortFlags::WOE
|
||||
| PortFlags::DR;
|
||||
|
||||
self.flags() & preserved
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
use std::mem;
|
||||
|
||||
use syscall::error::Result;
|
||||
|
||||
use common::dma::Dma;
|
||||
|
||||
use super::trb::Trb;
|
||||
use super::Xhci;
|
||||
|
||||
pub struct Ring {
|
||||
pub link: bool,
|
||||
pub trbs: Dma<[Trb]>,
|
||||
pub i: usize,
|
||||
pub cycle: bool,
|
||||
}
|
||||
|
||||
impl Ring {
|
||||
pub fn new<const N: usize>(ac64: bool, length: usize, link: bool) -> Result<Ring> {
|
||||
Ok(Ring {
|
||||
link,
|
||||
trbs: unsafe { Xhci::<N>::alloc_dma_zeroed_unsized_raw(ac64, length)? },
|
||||
i: 0,
|
||||
cycle: link,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn register(&self) -> u64 {
|
||||
let base = self.trbs.physical() as *const Trb;
|
||||
let addr = unsafe { base.offset(self.i as isize) };
|
||||
addr as u64 | self.cycle as u64
|
||||
}
|
||||
|
||||
pub fn next_index(&mut self) -> usize {
|
||||
let mut i;
|
||||
loop {
|
||||
i = self.i;
|
||||
self.i += 1;
|
||||
if self.i >= self.trbs.len() {
|
||||
self.i = 0;
|
||||
|
||||
if self.link {
|
||||
let address = self.trbs.physical();
|
||||
self.trbs[i].link(address, true, self.cycle);
|
||||
self.cycle = !self.cycle;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
i
|
||||
}
|
||||
|
||||
pub fn next(&mut self) -> (&mut Trb, bool) {
|
||||
let i = self.next_index();
|
||||
(&mut self.trbs[i], self.cycle)
|
||||
}
|
||||
/// Endless iterator that iterates through the ring items, over and over again. The iterator
|
||||
/// doesn't enqueue or dequeue anything.
|
||||
pub fn iter(&self) -> impl Iterator<Item = &Trb> + '_ {
|
||||
Iter {
|
||||
ring: self,
|
||||
i: self.i,
|
||||
}
|
||||
}
|
||||
/// Takes a physical address and returns the index into this ring, that the index represents.
|
||||
/// Returns `None` if the address is outside the bounds of this ring.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if paddr is not a multiple of 16 bytes, i.e. the size of a TRB.
|
||||
pub fn phys_addr_to_index(&self, ac64: bool, paddr: u64) -> Option<usize> {
|
||||
let base = (self.trbs.physical() as u64)
|
||||
& if ac64 {
|
||||
0xFFFF_FFFF_FFFF_FFFF
|
||||
} else {
|
||||
0xFFFF_FFFF
|
||||
};
|
||||
let offset = paddr.checked_sub(base)? as usize;
|
||||
|
||||
assert_eq!(
|
||||
offset % mem::size_of::<Trb>(),
|
||||
0,
|
||||
"unaligned TRB physical address"
|
||||
);
|
||||
|
||||
let index = offset / mem::size_of::<Trb>();
|
||||
|
||||
if index > self.trbs.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(index)
|
||||
}
|
||||
pub fn phys_addr_to_entry_ref(&self, ac64: bool, paddr: u64) -> Option<&Trb> {
|
||||
Some(&self.trbs[self.phys_addr_to_index(ac64, paddr)?])
|
||||
}
|
||||
pub fn phys_addr_to_entry_mut(&mut self, ac64: bool, paddr: u64) -> Option<&mut Trb> {
|
||||
let index = self.phys_addr_to_index(ac64, paddr)?;
|
||||
Some(&mut self.trbs[index])
|
||||
}
|
||||
pub fn phys_addr_to_entry(&self, ac64: bool, paddr: u64) -> Option<Trb> {
|
||||
Some(self.trbs[self.phys_addr_to_index(ac64, paddr)?].clone())
|
||||
}
|
||||
pub(crate) fn start_virt_addr(&self) -> *const Trb {
|
||||
self.trbs.as_ptr()
|
||||
}
|
||||
pub(crate) fn end_virt_addr(&self) -> *const Trb {
|
||||
unsafe { self.start_virt_addr().offset(self.trbs.len() as isize) }
|
||||
}
|
||||
pub fn trb_phys_ptr(&self, ac64: bool, trb: &Trb) -> u64 {
|
||||
let trb_virt_pointer = trb as *const Trb;
|
||||
let trbs_base_virt_pointer = self.trbs.as_ptr();
|
||||
|
||||
if (trb_virt_pointer as usize) < (trbs_base_virt_pointer as usize)
|
||||
|| (trb_virt_pointer as usize)
|
||||
> (trbs_base_virt_pointer as usize) + self.trbs.len() * mem::size_of::<Trb>()
|
||||
{
|
||||
panic!("Gave a TRB outside of the ring, when retrieving its physical address in that ring. TRB: {:?} (at address {:p})", trb, trb);
|
||||
}
|
||||
let trb_offset_from_base = trb_virt_pointer as u64 - trbs_base_virt_pointer as u64;
|
||||
|
||||
let trbs_base_phys_ptr = (self.trbs.physical() as u64)
|
||||
& if ac64 {
|
||||
0xFFFF_FFFF_FFFF_FFFF
|
||||
} else {
|
||||
0xFFFF_FFFF
|
||||
};
|
||||
let trb_phys_ptr = trbs_base_phys_ptr + trb_offset_from_base;
|
||||
trb_phys_ptr
|
||||
}
|
||||
/*
|
||||
/// Endless mutable iterator that iterates through the ring items, over and over again. The
|
||||
/// iterator doesn't enqueue or dequeue anything, but the trbs are mutably borrowed.
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Trb> + '_ {
|
||||
IterMut { ring: self, i: self.i }
|
||||
}*/
|
||||
}
|
||||
struct Iter<'ring> {
|
||||
ring: &'ring Ring,
|
||||
i: usize,
|
||||
}
|
||||
impl<'ring> Iterator for Iter<'ring> {
|
||||
type Item = &'ring Trb;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let i = self.i;
|
||||
self.i = (self.i + 1) % self.ring.trbs.len();
|
||||
Some(&self.ring.trbs[i])
|
||||
}
|
||||
}
|
||||
/*struct IterMut<'ring> {
|
||||
ring: &'ring mut Ring,
|
||||
i: usize,
|
||||
}
|
||||
impl<'ring> Iterator for IterMut<'ring> {
|
||||
type Item = &'ring mut Trb;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let i = self.i;
|
||||
self.i = (self.i + 1) % self.ring.trbs.len();
|
||||
Some(&mut self.ring.trbs[i])
|
||||
}
|
||||
}*/
|
||||
@@ -0,0 +1,20 @@
|
||||
use common::io::Mmio;
|
||||
|
||||
#[repr(C, packed)]
|
||||
pub struct Interrupter {
|
||||
pub iman: Mmio<u32>,
|
||||
pub imod: Mmio<u32>,
|
||||
pub erstsz: Mmio<u32>,
|
||||
_rsvd: Mmio<u32>,
|
||||
pub erstba_low: Mmio<u32>,
|
||||
pub erstba_high: Mmio<u32>,
|
||||
pub erdp_low: Mmio<u32>,
|
||||
pub erdp_high: Mmio<u32>,
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
pub struct RuntimeRegs {
|
||||
pub mfindex: Mmio<u32>,
|
||||
_rsvd: [Mmio<u32>; 7],
|
||||
pub ints: [Interrupter; 1024],
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,510 @@
|
||||
use super::context::StreamContextType;
|
||||
use crate::usb;
|
||||
use common::io::{Io, Mmio};
|
||||
use log::trace;
|
||||
use std::{fmt, mem};
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum TrbType {
|
||||
Reserved,
|
||||
/* Transfer */
|
||||
Normal,
|
||||
SetupStage,
|
||||
DataStage,
|
||||
StatusStage,
|
||||
Isoch,
|
||||
Link,
|
||||
EventData,
|
||||
NoOp,
|
||||
/* Command */
|
||||
EnableSlot,
|
||||
DisableSlot,
|
||||
AddressDevice,
|
||||
ConfigureEndpoint,
|
||||
EvaluateContext,
|
||||
ResetEndpoint,
|
||||
StopEndpoint,
|
||||
SetTrDequeuePointer,
|
||||
ResetDevice,
|
||||
ForceEvent,
|
||||
NegotiateBandwidth,
|
||||
SetLatencyToleranceValue,
|
||||
GetPortBandwidth,
|
||||
ForceHeader,
|
||||
NoOpCmd,
|
||||
/* Reserved */
|
||||
GetExtendedProperty,
|
||||
SetExtendedProperty,
|
||||
Rsv26,
|
||||
Rsv27,
|
||||
Rsv28,
|
||||
Rsv29,
|
||||
Rsv30,
|
||||
Rsv31,
|
||||
/* Events */
|
||||
Transfer,
|
||||
CommandCompletion,
|
||||
PortStatusChange,
|
||||
BandwidthRequest,
|
||||
Doorbell,
|
||||
HostController,
|
||||
DeviceNotification,
|
||||
MfindexWrap,
|
||||
/* Reserved from 40 to 47, vendor devined from 48 to 63 */
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum TrbCompletionCode {
|
||||
Invalid = 0x00,
|
||||
Success = 0x01,
|
||||
DataBuffer = 0x02,
|
||||
BabbleDetected = 0x03,
|
||||
UsbTransaction = 0x04,
|
||||
Trb = 0x05,
|
||||
Stall = 0x06,
|
||||
Resource = 0x07,
|
||||
Bandwidth = 0x08,
|
||||
NoSlotsAvailable = 0x09,
|
||||
InvalidStreamType = 0x0A,
|
||||
SlotNotEnabled = 0x0B,
|
||||
EndpointNotEnabled = 0x0C,
|
||||
ShortPacket = 0x0D,
|
||||
RingUnderrun = 0x0E,
|
||||
RingOverrun = 0x0F,
|
||||
VfEventRingFull = 0x10,
|
||||
Parameter = 0x11,
|
||||
BandwidthOverrun = 0x12,
|
||||
ContextState = 0x13,
|
||||
NoPingResponse = 0x14,
|
||||
EventRingFull = 0x15,
|
||||
IncompatibleDevice = 0x16,
|
||||
MissedService = 0x17,
|
||||
CommandRingStopped = 0x18,
|
||||
CommandAborted = 0x19,
|
||||
Stopped = 0x1A,
|
||||
StoppedLengthInvalid = 0x1B,
|
||||
StoppedShortPacket = 0x1C,
|
||||
MaxExitLatencyTooLarge = 0x1D,
|
||||
Rsv30 = 0x1E,
|
||||
IsochBuffer = 0x1F,
|
||||
EventLost = 0x20,
|
||||
Undefined = 0x21,
|
||||
InvalidStreamId = 0x22,
|
||||
SecondaryBandwidth = 0x23,
|
||||
SplitTransaction = 0x24,
|
||||
/* Values from 37 to 191 are reserved */
|
||||
/* 192 to 223 are vendor defined errors */
|
||||
/* 224 to 255 are vendor defined information */
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum TransferKind {
|
||||
NoData,
|
||||
Reserved,
|
||||
Out,
|
||||
In,
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
pub struct Trb {
|
||||
pub data_low: Mmio<u32>,
|
||||
pub data_high: Mmio<u32>,
|
||||
pub status: Mmio<u32>,
|
||||
pub control: Mmio<u32>,
|
||||
}
|
||||
impl Clone for Trb {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
data_low: Mmio::new(self.data_low.read()),
|
||||
data_high: Mmio::new(self.data_high.read()),
|
||||
status: Mmio::new(self.status.read()),
|
||||
control: Mmio::new(self.control.read()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const TRB_STATUS_COMPLETION_CODE_SHIFT: u8 = 24;
|
||||
pub const TRB_STATUS_COMPLETION_CODE_MASK: u32 = 0xFF00_0000;
|
||||
|
||||
pub const TRB_STATUS_COMPLETION_PARAM_SHIFT: u8 = 0;
|
||||
pub const TRB_STATUS_COMPLETION_PARAM_MASK: u32 = 0x00FF_FFFF;
|
||||
|
||||
pub const TRB_STATUS_TRANSFER_LENGTH_SHIFT: u8 = 0;
|
||||
pub const TRB_STATUS_TRANSFER_LENGTH_MASK: u32 = 0x00FF_FFFF;
|
||||
|
||||
pub const TRB_CONTROL_TRB_TYPE_SHIFT: u8 = 10;
|
||||
pub const TRB_CONTROL_TRB_TYPE_MASK: u32 = 0x0000_FC00;
|
||||
|
||||
pub const TRB_CONTROL_EVENT_DATA_SHIFT: u8 = 2;
|
||||
pub const TRB_CONTROL_EVENT_DATA_BIT: u32 = 1 << TRB_CONTROL_EVENT_DATA_SHIFT;
|
||||
|
||||
pub const TRB_CONTROL_ENDPOINT_ID_MASK: u32 = 0x001F_0000;
|
||||
pub const TRB_CONTROL_ENDPOINT_ID_SHIFT: u8 = 16;
|
||||
|
||||
impl Trb {
|
||||
pub fn set(&mut self, data: u64, status: u32, control: u32) {
|
||||
self.data_low.write(data as u32);
|
||||
self.data_high.write((data >> 32) as u32);
|
||||
self.status.write(status);
|
||||
self.control.write(control);
|
||||
}
|
||||
|
||||
pub fn reserved(&mut self, cycle: bool) {
|
||||
self.set(0, 0, ((TrbType::Reserved as u32) << 10) | (cycle as u32));
|
||||
}
|
||||
|
||||
pub fn read_data(&self) -> u64 {
|
||||
(self.data_low.read() as u64) | ((self.data_high.read() as u64) << 32)
|
||||
}
|
||||
|
||||
pub fn completion_code(&self) -> u8 {
|
||||
(self.status.read() >> TRB_STATUS_COMPLETION_CODE_SHIFT) as u8
|
||||
}
|
||||
pub fn completion_param(&self) -> u32 {
|
||||
self.status.read() & TRB_STATUS_COMPLETION_PARAM_MASK
|
||||
}
|
||||
fn has_completion_trb_pointer(&self) -> bool {
|
||||
if self.completion_code() == TrbCompletionCode::RingUnderrun as u8
|
||||
|| self.completion_code() == TrbCompletionCode::RingOverrun as u8
|
||||
{
|
||||
false
|
||||
} else if self.completion_code() == TrbCompletionCode::VfEventRingFull as u8 {
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
pub fn completion_trb_pointer(&self) -> Option<u64> {
|
||||
debug_assert_eq!(self.trb_type(), TrbType::CommandCompletion as u8);
|
||||
|
||||
if self.has_completion_trb_pointer() {
|
||||
Some(self.read_data())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
pub fn transfer_event_trb_pointer(&self) -> Option<u64> {
|
||||
debug_assert_eq!(self.trb_type(), TrbType::Transfer as u8);
|
||||
|
||||
if self.has_completion_trb_pointer() {
|
||||
Some(self.read_data())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn port_status_change_port_id(&self) -> Option<u8> {
|
||||
debug_assert_eq!(self.trb_type(), TrbType::PortStatusChange as u8);
|
||||
|
||||
if self.has_completion_trb_pointer() {
|
||||
let data = self.read_data();
|
||||
Some(((data >> 24) & 0xFF) as u8)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn event_slot(&self) -> u8 {
|
||||
(self.control.read() >> 24) as u8
|
||||
}
|
||||
/// Returns the number of bytes that should have been transmitten, but weren't.
|
||||
pub fn transfer_length(&self) -> u32 {
|
||||
self.status.read() & TRB_STATUS_TRANSFER_LENGTH_MASK
|
||||
}
|
||||
pub fn event_data_bit(&self) -> bool {
|
||||
self.control.readf(TRB_CONTROL_EVENT_DATA_BIT)
|
||||
}
|
||||
pub fn event_data(&self) -> Option<u64> {
|
||||
if self.event_data_bit() {
|
||||
Some(self.read_data())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
pub fn endpoint_id(&self) -> u8 {
|
||||
((self.control.read() & TRB_CONTROL_ENDPOINT_ID_MASK) >> TRB_CONTROL_ENDPOINT_ID_SHIFT)
|
||||
as u8
|
||||
}
|
||||
pub fn trb_type(&self) -> u8 {
|
||||
((self.control.read() & TRB_CONTROL_TRB_TYPE_MASK) >> TRB_CONTROL_TRB_TYPE_SHIFT) as u8
|
||||
}
|
||||
|
||||
pub fn link(&mut self, address: usize, toggle: bool, cycle: bool) {
|
||||
self.set(
|
||||
address as u64,
|
||||
0,
|
||||
((TrbType::Link as u32) << 10) | ((toggle as u32) << 1) | (cycle as u32),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn no_op_cmd(&mut self, cycle: bool) {
|
||||
self.set(0, 0, ((TrbType::NoOpCmd as u32) << 10) | (cycle as u32));
|
||||
}
|
||||
|
||||
pub fn enable_slot(&mut self, slot_type: u8, cycle: bool) {
|
||||
trace!("Enabling slot with type {}", slot_type);
|
||||
self.set(
|
||||
0,
|
||||
0,
|
||||
(((slot_type as u32) & 0x1F) << 16)
|
||||
| ((TrbType::EnableSlot as u32) << 10)
|
||||
| (cycle as u32),
|
||||
);
|
||||
}
|
||||
pub fn disable_slot(&mut self, slot: u8, cycle: bool) {
|
||||
self.set(
|
||||
0,
|
||||
0,
|
||||
(u32::from(slot) << 24) | ((TrbType::DisableSlot as u32) << 10) | u32::from(cycle),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn address_device(&mut self, slot_id: u8, input_ctx_ptr: usize, bsr: bool, cycle: bool) {
|
||||
assert_eq!(
|
||||
(input_ctx_ptr as u64) & 0xFFFF_FFFF_FFFF_FFF0,
|
||||
input_ctx_ptr as u64,
|
||||
"unaligned input context ptr"
|
||||
);
|
||||
self.set(
|
||||
input_ctx_ptr as u64,
|
||||
0,
|
||||
(u32::from(slot_id) << 24)
|
||||
| ((TrbType::AddressDevice as u32) << 10)
|
||||
| (u32::from(bsr) << 9)
|
||||
| u32::from(cycle),
|
||||
);
|
||||
}
|
||||
// Synchronizes the input context endpoints with the device context endpoints, I think.
|
||||
pub fn configure_endpoint(&mut self, slot_id: u8, input_ctx_ptr: usize, cycle: bool) {
|
||||
assert_eq!(
|
||||
(input_ctx_ptr as u64) & 0xFFFF_FFFF_FFFF_FFF0,
|
||||
input_ctx_ptr as u64,
|
||||
"unaligned input context ptr"
|
||||
);
|
||||
|
||||
self.set(
|
||||
input_ctx_ptr as u64,
|
||||
0,
|
||||
(u32::from(slot_id) << 24)
|
||||
| ((TrbType::ConfigureEndpoint as u32) << 10)
|
||||
| u32::from(cycle),
|
||||
);
|
||||
}
|
||||
pub fn evaluate_context(&mut self, slot_id: u8, input_ctx_ptr: usize, bsr: bool, cycle: bool) {
|
||||
assert_eq!(
|
||||
(input_ctx_ptr as u64) & 0xFFFF_FFFF_FFFF_FFF0,
|
||||
input_ctx_ptr as u64,
|
||||
"unaligned input context ptr"
|
||||
);
|
||||
self.set(
|
||||
input_ctx_ptr as u64,
|
||||
0,
|
||||
(u32::from(slot_id) << 24)
|
||||
| ((TrbType::EvaluateContext as u32) << 10)
|
||||
| (u32::from(bsr) << 9)
|
||||
| u32::from(cycle),
|
||||
);
|
||||
}
|
||||
pub fn reset_endpoint(&mut self, slot_id: u8, endp_num_xhc: u8, tsp: bool, cycle: bool) {
|
||||
assert_eq!(endp_num_xhc & 0x1F, endp_num_xhc);
|
||||
self.set(
|
||||
0,
|
||||
0,
|
||||
(u32::from(slot_id) << 24)
|
||||
| (u32::from(endp_num_xhc) << 16)
|
||||
| ((TrbType::ResetEndpoint as u32) << 10)
|
||||
| (u32::from(tsp) << 9)
|
||||
| u32::from(cycle),
|
||||
);
|
||||
}
|
||||
/// The deque_ptr has to contain the DCS bit (bit 0).
|
||||
pub fn set_tr_deque_ptr(
|
||||
&mut self,
|
||||
deque_ptr: u64,
|
||||
cycle: bool,
|
||||
sct: StreamContextType,
|
||||
stream_id: u16,
|
||||
endp_num_xhc: u8,
|
||||
slot: u8,
|
||||
) {
|
||||
assert_eq!(deque_ptr & 0xFFFF_FFFF_FFFF_FFF1, deque_ptr);
|
||||
assert_eq!(endp_num_xhc & 0x1F, endp_num_xhc);
|
||||
|
||||
self.set(
|
||||
deque_ptr | ((sct as u64) << 1),
|
||||
u32::from(stream_id) << 16,
|
||||
(u32::from(slot) << 24)
|
||||
| (u32::from(endp_num_xhc) << 16)
|
||||
| ((TrbType::SetTrDequeuePointer as u32) << 10)
|
||||
| u32::from(cycle),
|
||||
)
|
||||
}
|
||||
pub fn stop_endpoint(&mut self, slot_id: u8, endp_num_xhc: u8, suspend: bool, cycle: bool) {
|
||||
assert_eq!(endp_num_xhc & 0x1F, endp_num_xhc);
|
||||
self.set(
|
||||
0,
|
||||
0,
|
||||
(u32::from(slot_id) << 24)
|
||||
| (u32::from(suspend) << 23)
|
||||
| (u32::from(endp_num_xhc) << 16)
|
||||
| ((TrbType::StopEndpoint as u32) << 10)
|
||||
| u32::from(cycle),
|
||||
);
|
||||
}
|
||||
pub fn reset_device(&mut self, slot_id: u8, cycle: bool) {
|
||||
self.set(
|
||||
0,
|
||||
0,
|
||||
(u32::from(slot_id) << 24) | ((TrbType::ResetDevice as u32) << 10) | u32::from(cycle),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn transfer_no_op(&mut self, interrupter: u8, ent: bool, ch: bool, ioc: bool, cycle: bool) {
|
||||
self.set(
|
||||
0,
|
||||
u32::from(interrupter) << 22,
|
||||
((TrbType::NoOp as u32) << 10)
|
||||
| (u32::from(ioc) << 5)
|
||||
| (u32::from(ch) << 4)
|
||||
| (u32::from(ent) << 1)
|
||||
| u32::from(cycle),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn setup(&mut self, setup: usb::Setup, transfer: TransferKind, cycle: bool) {
|
||||
self.set(
|
||||
unsafe { mem::transmute(setup) },
|
||||
8,
|
||||
((transfer as u32) << 16)
|
||||
| ((TrbType::SetupStage as u32) << 10)
|
||||
| (1 << 6)
|
||||
| (cycle as u32),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn data(&mut self, buffer: usize, length: u16, input: bool, cycle: bool) {
|
||||
self.set(
|
||||
buffer as u64,
|
||||
length as u32,
|
||||
((input as u32) << 16) | ((TrbType::DataStage as u32) << 10) | (cycle as u32),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn cycle(&self) -> bool {
|
||||
self.control.readf(0x01)
|
||||
}
|
||||
|
||||
pub fn status(
|
||||
&mut self,
|
||||
interrupter: u16,
|
||||
input: bool,
|
||||
ioc: bool,
|
||||
ch: bool,
|
||||
ent: bool,
|
||||
cycle: bool,
|
||||
) {
|
||||
self.set(
|
||||
0,
|
||||
u32::from(interrupter) << 22,
|
||||
(u32::from(input) << 16)
|
||||
| ((TrbType::StatusStage as u32) << 10)
|
||||
| (u32::from(ioc) << 5)
|
||||
| (u32::from(ch) << 4)
|
||||
| (u32::from(ent) << 1)
|
||||
| (cycle as u32),
|
||||
);
|
||||
}
|
||||
pub fn normal(
|
||||
&mut self,
|
||||
buffer: u64,
|
||||
len: u32,
|
||||
cycle: bool,
|
||||
estimated_td_size: u8,
|
||||
interrupter: u8,
|
||||
ent: bool,
|
||||
isp: bool,
|
||||
chain: bool,
|
||||
ioc: bool,
|
||||
idt: bool,
|
||||
bei: bool,
|
||||
) {
|
||||
assert_eq!(estimated_td_size & 0x1F, estimated_td_size);
|
||||
// NOTE: The interrupter target and no snoop flags have been omitted.
|
||||
self.set(
|
||||
buffer,
|
||||
len | (u32::from(estimated_td_size) << 17) | (u32::from(interrupter) << 22),
|
||||
u32::from(cycle)
|
||||
| (u32::from(ent) << 1)
|
||||
| (u32::from(isp) << 2)
|
||||
| (u32::from(chain) << 4)
|
||||
| (u32::from(ioc) << 5)
|
||||
| (u32::from(idt) << 6)
|
||||
| (u32::from(bei) << 9)
|
||||
| ((TrbType::Normal as u32) << 10),
|
||||
)
|
||||
}
|
||||
pub fn is_command_trb(&self) -> bool {
|
||||
let valid_trb_types = [
|
||||
TrbType::NoOpCmd as u8,
|
||||
TrbType::EnableSlot as u8,
|
||||
TrbType::DisableSlot as u8,
|
||||
TrbType::AddressDevice as u8,
|
||||
TrbType::ConfigureEndpoint as u8,
|
||||
TrbType::EvaluateContext as u8,
|
||||
TrbType::ResetEndpoint as u8,
|
||||
TrbType::StopEndpoint as u8,
|
||||
TrbType::SetTrDequeuePointer as u8,
|
||||
TrbType::ResetDevice as u8,
|
||||
TrbType::ForceEvent as u8,
|
||||
TrbType::NegotiateBandwidth as u8,
|
||||
TrbType::SetLatencyToleranceValue as u8,
|
||||
TrbType::GetPortBandwidth as u8,
|
||||
TrbType::ForceHeader as u8,
|
||||
TrbType::GetExtendedProperty as u8,
|
||||
TrbType::SetExtendedProperty as u8,
|
||||
];
|
||||
valid_trb_types.contains(&self.trb_type())
|
||||
}
|
||||
pub fn is_transfer_trb(&self) -> bool {
|
||||
// XXX: Unfortunately, the only way to use match statements with integer constants, is to
|
||||
// precast them into valid enum values, which either requires a derive macro such as
|
||||
// num_traits's #[derive(FromPrimitive)], or manually writing the reverse match statement
|
||||
// first.
|
||||
let valid_trb_types = [
|
||||
TrbType::Normal as u8,
|
||||
TrbType::SetupStage as u8,
|
||||
TrbType::DataStage as u8,
|
||||
TrbType::StatusStage as u8,
|
||||
TrbType::Isoch as u8,
|
||||
TrbType::NoOp as u8,
|
||||
];
|
||||
valid_trb_types.contains(&self.trb_type())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Trb {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"Trb {{ data: {:>016X}, status: {:>08X}, control: {:>08X} }}",
|
||||
self.read_data(),
|
||||
self.status.read(),
|
||||
self.control.read()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Trb {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"({:>016X}, {:>08X}, {:>08X})",
|
||||
self.read_data(),
|
||||
self.status.read(),
|
||||
self.control.read()
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user