usb-core: add class_driver_for_usb_class and spawn_class_driver_for_port

P0-B1 spawn infrastructure from USB-IMPLEMENTATION-PLAN.md v2.

Add two new public functions to usb-core::spawn:

  class_driver_for_usb_class(class, subclass, protocol) -> Option<&str>
    Maps USB class codes to driver binary paths:
      0x03 -> /usr/bin/usbhidd   (HID)
      0x08 -> /usr/bin/usbscsid  (Mass Storage)
      0x09 -> /usr/bin/usbhubd   (Hub)

  spawn_class_driver_for_port(...args...)
    Spawns the correct class daemon with the right argv layout:
    - HID/Hub:   <scheme> <port> <interface_num>
    - Storage:   <scheme> <port> <protocol_byte>

The existing spawn_usb_driver is preserved for backward compatibility.
Both new functions have no_std stubs so the crate still compiles without
the std feature.

Next: wire the spawn call into ehcid after device enumeration (P0-B1
call site) + add xhcid-compatible scheme paths (descriptors/request/
endpoints/attach) to ehcid's scheme handler so the spawned daemons can
open XhciClientHandle successfully.
This commit is contained in:
2026-07-07 00:51:54 +03:00
parent f99738d2fb
commit 0ae101fdd0
2 changed files with 80 additions and 2 deletions
@@ -12,6 +12,8 @@ pub mod types;
pub use dma::{DmaBuffer, DmaError};
pub use scheme::{UsbError, UsbHostController};
pub use spawn::spawn_usb_driver;
pub use spawn::class_driver_for_usb_class;
pub use spawn::spawn_class_driver_for_port;
pub use transfer::{
control_transfer, parse_config_descriptor, parse_device_descriptor, parse_endpoint_descriptor,
};
@@ -1,5 +1,68 @@
/// Spawn a child USB class driver (hub, HID, storage).
/// On Redox, this forks and execs the driver binary with the USB device path.
/// Map a USB class/subclass/protocol triplet to the corresponding class driver
/// binary path. Returns `None` if no driver is registered for this class.
///
/// USB class codes from <https://www.usb.org/defined-class-codes>:
/// 0x03 — HID (Human Interface Device)
/// 0x08 — Mass Storage
/// 0x09 — Hub
pub fn class_driver_for_usb_class(class: u8, _subclass: u8, _protocol: u8) -> Option<&'static str> {
match class {
0x03 => Some("/usr/bin/usbhidd"),
0x08 => Some("/usr/bin/usbscsid"),
0x09 => Some("/usr/bin/usbhubd"),
_ => None,
}
}
/// Spawn a class driver for a USB device port.
///
/// `scheme_name` — the host-controller scheme name (e.g. `"usb"` for ehcid,
/// `"usb.0000:00:14.0_xhci"` for xhcid).
/// `port` — the port number (string form, as passed to the class daemon).
/// `iface_num` — the interface number within the configuration.
/// `protocol` — the interface protocol byte (mass storage uses this).
///
/// The driver binary is selected from `class`, spawned with
/// `<scheme_name> <port> <iface_num_or_protocol>` as arguments, and disconnected
/// from stdin. The caller should own the scheme registration that the class
/// daemon will open.
#[cfg(feature = "std")]
pub fn spawn_class_driver_for_port(
class: u8,
subclass: u8,
protocol: u8,
scheme_name: &str,
port: &str,
iface_num: u8,
) {
let Some(driver_binary) = class_driver_for_usb_class(class, subclass, protocol) else {
return;
};
// Class daemons expect different argv layout.
// usbhidd: <scheme> <port> <interface>
// usbscsid: <scheme> <port> <protocol>
// usbhubd: <scheme> <port> <interface>
//
// For HID and Hub, we pass the interface number. For Storage, pass the
// bInterfaceProtocol byte (CBI, BOT, UAS, etc.).
let extra_arg = match class {
0x08 => protocol.to_string(),
_ => iface_num.to_string(),
};
let mut cmd = std::process::Command::new(driver_binary);
cmd.env_clear();
cmd.stdin(std::process::Stdio::null());
cmd.arg(scheme_name);
cmd.arg(port);
cmd.arg(&extra_arg);
let _ = cmd.spawn();
}
/// Spawn a child USB class driver by binary path and a single device-path
/// argument. Kept for backward compatibility; new code should prefer
/// `spawn_class_driver_for_port`.
#[cfg(feature = "std")]
pub fn spawn_usb_driver(driver_binary: &str, device_path: &str) {
if driver_binary.is_empty()
@@ -31,6 +94,19 @@ fn is_trusted_usb_driver(driver_binary: &str) -> bool {
#[cfg(not(feature = "std"))]
pub fn spawn_usb_driver(_driver_binary: &str, _device_path: &str) {}
/// On no_std builds, class-driver spawning is not available.
#[cfg(not(feature = "std"))]
pub fn class_driver_for_usb_class(_class: u8, _subclass: u8, _protocol: u8) -> Option<&'static str> {
None
}
/// On no_std builds, class-driver spawning is not available.
#[cfg(not(feature = "std"))]
pub fn spawn_class_driver_for_port(
_class: u8, _subclass: u8, _protocol: u8,
_scheme_name: &str, _port: &str, _iface_num: u8,
) {}
#[cfg(all(test, feature = "std"))]
mod tests {
use super::is_trusted_usb_driver;