From 58bd24da8c6c187dd685da6d877d775d2213409d Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 21 Mar 2025 10:15:36 -0600 Subject: [PATCH] Support addressing of hub devices --- input/usbhidd/src/main.rs | 1 + storage/usbscsid/src/main.rs | 1 + usbhubd/src/main.rs | 93 ++++++++++++++++++------ xhcid/src/driver_interface.rs | 60 +++++++++++++--- xhcid/src/xhci/mod.rs | 1 + xhcid/src/xhci/scheme.rs | 128 +++++++++++++++++++++++++++++++--- 6 files changed, 244 insertions(+), 40 deletions(-) diff --git a/input/usbhidd/src/main.rs b/input/usbhidd/src/main.rs index da6af6a82e..eae573bae4 100644 --- a/input/usbhidd/src/main.rs +++ b/input/usbhidd/src/main.rs @@ -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"); diff --git a/storage/usbscsid/src/main.rs b/storage/usbscsid/src/main.rs index ff87d2109e..807ab11c38 100644 --- a/storage/usbscsid/src/main.rs +++ b/storage/usbscsid/src/main.rs @@ -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"); diff --git a/usbhubd/src/main.rs b/usbhubd/src/main.rs index 198641cc1a..16da57caf5 100644 --- a/usbhubd/src/main.rs +++ b/usbhubd/src/main.rs @@ -19,7 +19,7 @@ fn main() { const USAGE: &'static str = "usbhubd "; let scheme = args.next().expect(USAGE); - let port = args + let port_id = args .next() .expect(USAGE) .parse::() @@ -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? diff --git a/xhcid/src/driver_interface.rs b/xhcid/src/driver_interface.rs index 2d4604d0a3..bcb9dddad3 100644 --- a/xhcid/src/driver_interface.rs +++ b/xhcid/src/driver_interface.rs @@ -20,6 +20,7 @@ pub struct ConfigureEndpointsReq { pub config_desc: u8, pub interface_desc: Option, pub alternate_setting: Option, + pub hub_ports: Option, } #[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 { + 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 { let path = format!("/scheme/{}/port{}/descriptors", self.scheme, self.port); let json = std::fs::read(path)?; diff --git a/xhcid/src/xhci/mod.rs b/xhcid/src/xhci/mod.rs index 34b910e4af..cad83c23fa 100644 --- a/xhcid/src/xhci/mod.rs +++ b/xhcid/src/xhci/mod.rs @@ -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)); } diff --git a/xhcid/src/xhci/scheme.rs b/xhcid/src/xhci/scheme.rs index 924ed612e6..9c53a323dc 100644 --- a/xhcid/src/xhci/scheme.rs +++ b/xhcid/src/xhci/scheme.rs @@ -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/configure scheme."); + static ref REGEX_PORT_ATTACH: Regex = Regex::new(r"^port([\d\.]+)/attach$") + .expect("Failed to create the regex for the port/attach scheme."); + static ref REGEX_PORT_DETACH: Regex = Regex::new(r"^port([\d\.]+)/detach$") + .expect("Failed to create the regex for the port/detach scheme."); static ref REGEX_PORT_DESCRIPTORS: Regex = Regex::new(r"^port([\d\.]+)/descriptors$") .expect("Failed to create the regex for the port/descriptors"); static ref REGEX_PORT_STATE: Regex = Regex::new(r"^port([\d\.]+)/state$") @@ -131,6 +135,8 @@ pub enum Handle { Endpoints(PortId, Vec), // 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/configure ConfigureEndpoints(PortId), // port number + /// /port/attach + AttachDevice(PortId), // port number + /// /port/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(®EX_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(®EX_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(®EX_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(®EX_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/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 { + 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/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 { + 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/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 => {