Support addressing of hub devices

This commit is contained in:
Jeremy Soller
2025-03-21 10:15:36 -06:00
parent 2c349f7eb0
commit 58bd24da8c
6 changed files with 244 additions and 40 deletions
+1
View File
@@ -240,6 +240,7 @@ fn main() {
config_desc: conf_desc.configuration_value,
interface_desc: Some(interface_num),
alternate_setting: Some(if_desc.alternate_setting),
hub_ports: None,
})
.expect("Failed to configure endpoints");
+1
View File
@@ -74,6 +74,7 @@ fn daemon(daemon: redox_daemon::Daemon, scheme: String, port: PortId, protocol:
config_desc: configuration_value,
interface_desc: Some(interface_num),
alternate_setting: Some(alternate_setting),
hub_ports: None,
})
.expect("Failed to configure endpoints");
+71 -22
View File
@@ -19,7 +19,7 @@ fn main() {
const USAGE: &'static str = "usbhubd <scheme> <port> <interface>";
let scheme = args.next().expect(USAGE);
let port = args
let port_id = args
.next()
.expect(USAGE)
.parse::<PortId>()
@@ -33,11 +33,11 @@ fn main() {
log::info!(
"USB HUB driver spawned with scheme `{}`, port {}, interface {}",
scheme,
port,
port_id,
interface_num
);
let handle = XhciClientHandle::new(scheme, port);
let handle = XhciClientHandle::new(scheme.clone(), port_id);
let desc: DevDesc = handle
.get_standard_descs()
.expect("Failed to get standard descriptors");
@@ -57,14 +57,6 @@ fn main() {
})
.expect("Failed to find suitable configuration");
handle
.configure_endpoints(&ConfigureEndpointsReq {
config_desc: conf_desc.configuration_value,
interface_desc: Some(interface_num),
alternate_setting: Some(if_desc.alternate_setting),
})
.expect("Failed to configure endpoints");
let mut hub_desc = usb::HubDescriptor::default();
handle
.device_request(
@@ -78,10 +70,69 @@ fn main() {
)
.expect("Failed to retrieve hub descriptor");
handle
.configure_endpoints(&ConfigureEndpointsReq {
config_desc: conf_desc.configuration_value,
interface_desc: Some(interface_num),
alternate_setting: Some(if_desc.alternate_setting),
hub_ports: Some(hub_desc.ports),
})
.expect("Failed to configure endpoints");
/*TODO: only set hub depth on USB 3+ hubs
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..=hub_desc.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: usb::HubPortStatus::default(),
handle: XhciClientHandle::new(scheme.clone(), child_port_id),
attached: false,
});
}
//TODO: use change flags?
let mut last_port_statuses = vec![usb::HubPortStatus::default(); hub_desc.ports.into()];
loop {
for port in 1..=hub_desc.ports {
let port_idx: usize = port.checked_sub(1).unwrap().into();
let mut state = states.get_mut(port_idx).unwrap();
let mut port_sts = usb::HubPortStatus::default();
handle
.device_request(
@@ -93,14 +144,9 @@ fn main() {
DeviceReqData::In(unsafe { plain::as_mut_bytes(&mut port_sts) }),
)
.expect("Failed to retrieve port status");
{
let port_idx: usize = port.checked_sub(1).unwrap().into();
let last_port_sts = last_port_statuses.get_mut(port_idx).unwrap();
if *last_port_sts != port_sts {
*last_port_sts = port_sts;
log::info!("port {} status {:X?}", port, 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
@@ -116,17 +162,19 @@ fn main() {
DeviceReqData::NoData,
)
.expect("Failed to set port power");
state.ensure_attached(false);
continue;
}
// Ignore disconnected port
//TODO: turn off disconnected ports?
if !port_sts.contains(usb::HubPortStatus::CONNECTION) {
state.ensure_attached(false);
continue;
}
// Ignore port in reset
if port_sts.contains(usb::HubPortStatus::RESET) {
state.ensure_attached(false);
continue;
}
@@ -143,10 +191,11 @@ fn main() {
DeviceReqData::NoData,
)
.expect("Failed to set port enable");
state.ensure_attached(false);
continue;
}
//TODO: address device
state.ensure_attached(true);
}
//TODO: use interrupts or poll faster?
+50 -10
View File
@@ -20,6 +20,7 @@ pub struct ConfigureEndpointsReq {
pub config_desc: u8,
pub interface_desc: Option<u8>,
pub alternate_setting: Option<u8>,
pub hub_ports: Option<u8>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
@@ -290,6 +291,33 @@ 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),
})
}
}
impl fmt::Display for PortId {
@@ -299,8 +327,7 @@ impl fmt::Display for PortId {
// 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.
// The lowest 4 bits are ignored.
let mut route_string = self.route_string >> 4;
let mut route_string = self.route_string;
while route_string > 0 {
write!(f, ".{}", route_string & 0xF)?;
route_string >>= 4;
@@ -320,6 +347,12 @@ impl str::FromStr for PortId {
.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;
@@ -327,7 +360,8 @@ impl str::FromStr for PortId {
}
// Parse route string component
if i > 5 {
let depth = i - 1;
if depth >= 5 {
return Err(format!("too many route string components"));
}
if value & 0xF0 != 0 {
@@ -336,13 +370,7 @@ impl str::FromStr for PortId {
value
));
}
route_string |= (value as u32) << (i * 4);
}
if root_hub_port_num == 0 {
return Err(format!(
"invalid root hub port number {:?}",
root_hub_port_num
));
route_string |= u32::from(value) << (depth * 4);
}
Ok(Self {
root_hub_port_num,
@@ -485,6 +513,18 @@ impl XhciClientHandle {
Self { scheme, port }
}
pub fn attach(&self) -> result::Result<(), XhciClientHandleError> {
let path = format!("/scheme/{}/port{}/attach", self.scheme, self.port);
let mut file = OpenOptions::new().read(false).write(true).open(path)?;
let _bytes_written = file.write(&[])?;
Ok(())
}
pub fn detach(&self) -> result::Result<(), XhciClientHandleError> {
let path = format!("/scheme/{}/port{}/detach", self.scheme, self.port);
let mut file = OpenOptions::new().read(false).write(true).open(path)?;
let _bytes_written = file.write(&[])?;
Ok(())
}
pub fn get_standard_descs(&self) -> result::Result<DevDesc, XhciClientHandleError> {
let path = format!("/scheme/{}/port{}/descriptors", self.scheme, self.port);
let json = std::fs::read(path)?;
+1
View File
@@ -739,6 +739,7 @@ impl Xhci {
pub async fn attach_device(&self, port_id: PortId) -> syscall::Result<()> {
if self.port_states.contains_key(&port_id) {
println!("Already contains port {}", port_id);
return Err(syscall::Error::new(EAGAIN));
}
+120 -8
View File
@@ -52,6 +52,10 @@ use regex::Regex;
lazy_static! {
static ref REGEX_PORT_CONFIGURE: Regex = Regex::new(r"^port([\d\.]+)/configure$")
.expect("Failed to create the regex for the port<n>/configure scheme.");
static ref REGEX_PORT_ATTACH: Regex = Regex::new(r"^port([\d\.]+)/attach$")
.expect("Failed to create the regex for the port<n>/attach scheme.");
static ref REGEX_PORT_DETACH: Regex = Regex::new(r"^port([\d\.]+)/detach$")
.expect("Failed to create the regex for the port<n>/detach scheme.");
static ref REGEX_PORT_DESCRIPTORS: Regex = Regex::new(r"^port([\d\.]+)/descriptors$")
.expect("Failed to create the regex for the port<n>/descriptors");
static ref REGEX_PORT_STATE: Regex = Regex::new(r"^port([\d\.]+)/state$")
@@ -131,6 +135,8 @@ pub enum Handle {
Endpoints(PortId, Vec<u8>), // port, contents
Endpoint(PortId, u8, EndpointHandleTy), // port, endpoint, state
ConfigureEndpoints(PortId), // port
AttachDevice(PortId), // port
DetachDevice(PortId), // port
}
/// The type of handle.
@@ -170,6 +176,10 @@ enum SchemeParameters {
Endpoint(PortId, u8, String), // port number, endpoint number, handle type
/// /port<n>/configure
ConfigureEndpoints(PortId), // port number
/// /port<n>/attach
AttachDevice(PortId), // port number
/// /port<n>/detach
DetachDevice(PortId), // port number
}
impl Handle {
@@ -212,6 +222,12 @@ impl Handle {
Handle::ConfigureEndpoints(port_num) => {
format!("port{}/configure", port_num)
}
Handle::AttachDevice(port_num) => {
format!("port{}/attach", port_num)
}
Handle::DetachDevice(port_num) => {
format!("port{}/detach", port_num)
}
}
}
@@ -236,6 +252,8 @@ impl Handle {
&Handle::PortState(_) => HandleType::Character,
&Handle::PortReq(_, _) => HandleType::Character,
&Handle::ConfigureEndpoints(_) => HandleType::Character,
&Handle::AttachDevice(_) => HandleType::Character,
&Handle::DetachDevice(_) => HandleType::Character,
&Handle::Endpoint(_, _, ref st) => match st {
EndpointHandleTy::Data => HandleType::Character,
EndpointHandleTy::Ctl => HandleType::Character,
@@ -264,6 +282,8 @@ impl Handle {
&Handle::PortState(_) => None,
&Handle::PortReq(_, _) => None,
&Handle::ConfigureEndpoints(_) => None,
&Handle::AttachDevice(_) => None,
&Handle::DetachDevice(_) => None,
&Handle::Endpoint(_, _, ref st) => match st {
EndpointHandleTy::Data => None,
EndpointHandleTy::Ctl => None,
@@ -345,6 +365,14 @@ impl SchemeParameters {
let port_num = get_port_id_from_regex(&REGEX_PORT_CONFIGURE, scheme, 0)?;
Ok(Self::ConfigureEndpoints(port_num))
} else if REGEX_PORT_ATTACH.is_match(scheme) {
let port_num = get_port_id_from_regex(&REGEX_PORT_ATTACH, scheme, 0)?;
Ok(Self::AttachDevice(port_num))
} else if REGEX_PORT_DETACH.is_match(scheme) {
let port_num = get_port_id_from_regex(&REGEX_PORT_DETACH, scheme, 0)?;
Ok(Self::DetachDevice(port_num))
} else if REGEX_PORT_DESCRIPTORS.is_match(scheme) {
let port_num = get_port_id_from_regex(&REGEX_PORT_DESCRIPTORS, scheme, 0)?;
@@ -971,13 +999,28 @@ impl Xhci {
const CONTEXT_ENTRIES_MASK: u32 = 0xF800_0000;
const CONTEXT_ENTRIES_SHIFT: u8 = 27;
let current_slot_a = input_context.device.slot.a.read();
const HUB_PORTS_MASK: u32 = 0xFF00_0000;
const HUB_PORTS_SHIFT: u8 = 24;
let mut current_slot_a = input_context.device.slot.a.read();
let mut current_slot_b = input_context.device.slot.b.read();
// Set context entries
current_slot_a &= !CONTEXT_ENTRIES_MASK;
current_slot_a |=
(u32::from(new_context_entries) << CONTEXT_ENTRIES_SHIFT) & CONTEXT_ENTRIES_MASK;
// Set hub data
current_slot_a &= !(1 << 26);
current_slot_b &= !HUB_PORTS_MASK;
if let Some(hub_ports) = req.hub_ports {
current_slot_a |= 1 << 26;
current_slot_b |= (u32::from(hub_ports) << HUB_PORTS_SHIFT) & HUB_PORTS_MASK;
}
input_context.device.slot.a.write(current_slot_a);
input_context.device.slot.b.write(current_slot_b);
input_context.device.slot.a.write(
(current_slot_a & !CONTEXT_ENTRIES_MASK)
| ((u32::from(new_context_entries) << CONTEXT_ENTRIES_SHIFT)
& CONTEXT_ENTRIES_MASK),
);
let control = if self.op.lock().unwrap().cie() {
(u32::from(req.alternate_setting.unwrap_or(0)) << 16)
| (u32::from(req.interface_desc.unwrap_or(0)) << 8)
@@ -1966,6 +2009,54 @@ impl Xhci {
Ok(Handle::ConfigureEndpoints(port_num))
}
/// implements open() for /port<n>/attach
///
/// # Arguments
/// - 'port_num: [PortId]' - The port number specified in the scheme path
/// - 'flags: [usize]' - The flags parameter passed to open()
///
/// # Returns
/// This function returns a [Result] containing either:
///
/// - [Handle::Port] - The handle was opened successfully
/// - [EISDIR] - open() was called on this scheme endpoint, but no directory-specific flags were passed to open
/// - [ENOENT] - The scheme is valid, but there is no port associated with the given port_num, or no endpoint with the given endpoint_num
fn open_handle_attach_device(&self, port_num: PortId, flags: usize) -> Result<Handle> {
if flags & O_DIRECTORY != 0 && flags & O_STAT == 0 {
return Err(Error::new(ENOTDIR));
}
if flags & O_RDWR != O_WRONLY && flags & O_STAT == 0 {
return Err(Error::new(EACCES));
}
Ok(Handle::AttachDevice(port_num))
}
/// implements open() for /port<n>/detach
///
/// # Arguments
/// - 'port_num: [PortId]' - The port number specified in the scheme path
/// - 'flags: [usize]' - The flags parameter passed to open()
///
/// # Returns
/// This function returns a [Result] containing either:
///
/// - [Handle::Port] - The handle was opened successfully
/// - [EISDIR] - open() was called on this scheme endpoint, but no directory-specific flags were passed to open
/// - [ENOENT] - The scheme is valid, but there is no port associated with the given port_num, or no endpoint with the given endpoint_num
fn open_handle_detach_device(&self, port_num: PortId, flags: usize) -> Result<Handle> {
if flags & O_DIRECTORY != 0 && flags & O_STAT == 0 {
return Err(Error::new(ENOTDIR));
}
if flags & O_RDWR != O_WRONLY && flags & O_STAT == 0 {
return Err(Error::new(EACCES));
}
Ok(Handle::DetachDevice(port_num))
}
/// implements open() for /port<n>/request
///
/// # Arguments
@@ -2020,6 +2111,12 @@ impl Scheme for &Xhci {
SchemeParameters::ConfigureEndpoints(port_number) => {
self.open_handle_configure_endpoints(port_number, flags)?
}
SchemeParameters::AttachDevice(port_number) => {
self.open_handle_attach_device(port_number, flags)?
}
SchemeParameters::DetachDevice(port_number) => {
self.open_handle_detach_device(port_number, flags)?
}
};
let fd = self.next_handle.fetch_add(1, atomic::Ordering::Relaxed);
@@ -2049,8 +2146,11 @@ impl Scheme for &Xhci {
};
//If we have a handle to the configure scheme, we need to mark it as write only.
if let &Handle::ConfigureEndpoints(_) = &*guard {
stat.st_mode = stat.st_mode | 0o200;
match &*guard {
Handle::ConfigureEndpoints(_) | Handle::AttachDevice(_) | Handle::DetachDevice(_) => {
stat.st_mode = stat.st_mode | 0o200;
}
_ => {}
}
Ok(0)
@@ -2098,6 +2198,8 @@ impl Scheme for &Xhci {
Ok(bytes_to_read)
}
Handle::ConfigureEndpoints(_) => Err(Error::new(EBADF)),
Handle::AttachDevice(_) => Err(Error::new(EBADF)),
Handle::DetachDevice(_) => Err(Error::new(EBADF)),
&mut Handle::Endpoint(port_num, endp_num, ref mut st) => match st {
EndpointHandleTy::Ctl => self.on_read_endp_ctl(port_num, endp_num, buf),
@@ -2150,6 +2252,16 @@ impl Scheme for &Xhci {
block_on(self.configure_endpoints(port_num, buf))?;
Ok(buf.len())
}
&mut Handle::AttachDevice(port_num) => {
//TODO: accept some arguments in buffer?
block_on(self.attach_device(port_num))?;
Ok(buf.len())
}
&mut Handle::DetachDevice(port_num) => {
//TODO: accept some arguments in buffer?
block_on(self.detach_device(port_num))?;
Ok(buf.len())
}
&mut Handle::Endpoint(port_num, endp_num, ref ep_file_ty) => match ep_file_ty {
EndpointHandleTy::Ctl => block_on(self.on_write_endp_ctl(port_num, endp_num, buf)),
EndpointHandleTy::Data => {