From 6166237cef1c87b1c1745ae2726505441f249d8d Mon Sep 17 00:00:00 2001 From: Vasilito Date: Tue, 14 Apr 2026 11:43:37 +0100 Subject: [PATCH] Decouple redbear-hwutils from xhcid source paths Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- .../system/redbear-hwutils/source/Cargo.toml | 3 +- .../redbear-hwutils/source/src/bin/lsusb.rs | 146 +++++++++++++++++- 2 files changed, 140 insertions(+), 9 deletions(-) diff --git a/local/recipes/system/redbear-hwutils/source/Cargo.toml b/local/recipes/system/redbear-hwutils/source/Cargo.toml index ec71962b..129b467c 100644 --- a/local/recipes/system/redbear-hwutils/source/Cargo.toml +++ b/local/recipes/system/redbear-hwutils/source/Cargo.toml @@ -12,4 +12,5 @@ name = "lsusb" path = "src/bin/lsusb.rs" [dependencies] -xhcid = { path = "../../../../../recipes/core/base/source/drivers/usb/xhcid" } +serde = { version = "1", features = ["derive"] } +serde_json = "1" diff --git a/local/recipes/system/redbear-hwutils/source/src/bin/lsusb.rs b/local/recipes/system/redbear-hwutils/source/src/bin/lsusb.rs index bbf9edba..9aed8ab3 100644 --- a/local/recipes/system/redbear-hwutils/source/src/bin/lsusb.rs +++ b/local/recipes/system/redbear-hwutils/source/src/bin/lsusb.rs @@ -1,8 +1,10 @@ +use std::fmt; use std::fs; use std::process; +use std::str::FromStr; use redbear_hwutils::{describe_usb_device, parse_args}; -use xhcid_interface::{PortId, PortState, XhciClientHandle}; +use serde::Deserialize; const USAGE: &str = "Usage: lsusb\nList USB devices exposed by native usb.* schemes."; @@ -27,6 +29,125 @@ struct UsbPortStateSummary { state: PortState, } +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +struct PortId { + root_hub_port_num: u8, + route_string: u32, +} + +impl fmt::Display for PortId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.root_hub_port_num)?; + let mut route_string = self.route_string; + while route_string != 0 { + write!(f, ".{}", route_string & 0xF)?; + route_string >>= 4; + } + Ok(()) + } +} + +impl FromStr for PortId { + type Err = String; + + fn from_str(s: &str) -> Result { + 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(|err| format!("failed to parse {:?}: {}", part, err))?; + + if value == 0 { + return Err("zero is not a valid port ID component".to_string()); + } + + if i == 0 { + root_hub_port_num = value; + continue; + } + + let depth = i - 1; + if depth >= 5 { + return Err("too many route string components".to_string()); + } + if value & 0xF0 != 0 { + return Err(format!( + "value {:?} is too large for route string component", + value + )); + } + route_string |= u32::from(value) << (depth * 4); + } + + if root_hub_port_num == 0 { + return Err("missing root hub port number".to_string()); + } + + Ok(Self { + root_hub_port_num, + route_string, + }) + } +} + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum PortState { + EnabledOrDisabled, + Default, + Addressed, + Configured, +} + +impl PortState { + fn as_str(&self) -> &'static str { + match self { + Self::EnabledOrDisabled => "enabled_or_disabled", + Self::Default => "default", + Self::Addressed => "addressed", + Self::Configured => "configured", + } + } +} + +impl FromStr for PortState { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.trim() { + "enabled_or_disabled" | "enabled/disabled" => Ok(Self::EnabledOrDisabled), + "default" => Ok(Self::Default), + "addressed" => Ok(Self::Addressed), + "configured" => Ok(Self::Configured), + _ => Err("read reserved port state".to_string()), + } + } +} + +#[derive(Clone, Debug, Deserialize)] +struct DevDesc { + usb: u16, + class: u8, + sub_class: u8, + protocol: u8, + vendor: u16, + product: u16, + manufacturer_str: Option, + product_str: Option, +} + +impl DevDesc { + fn major_version(&self) -> u8 { + ((self.usb & 0xFF00) >> 8) as u8 + } + + fn minor_version(&self) -> u8 { + self.usb as u8 + } +} + fn main() { match run() { Ok(()) => {} @@ -125,14 +246,9 @@ fn collect_usb_state() -> Result<(Vec, Vec handle, - Err(_) => continue, - }; + let state = read_port_state(controller, port).ok(); - let state = handle.port_state().ok(); - - match handle.get_standard_descs() { + match read_standard_descs(controller, port) { Ok(descriptors) => { devices.push(UsbDeviceSummary { controller: controller.to_string(), @@ -167,3 +283,17 @@ fn collect_usb_state() -> Result<(Vec, Vec Result { + let state_path = format!("/scheme/{controller}/port{port}/state"); + let raw = fs::read_to_string(&state_path) + .map_err(|err| format!("failed to read {state_path}: {err}"))?; + raw.parse() +} + +fn read_standard_descs(controller: &str, port: PortId) -> Result { + let descriptor_path = format!("/scheme/{controller}/port{port}/descriptors"); + let raw = fs::read(&descriptor_path) + .map_err(|err| format!("failed to read {descriptor_path}: {err}"))?; + serde_json::from_slice(&raw).map_err(|err| format!("failed to parse {descriptor_path}: {err}")) +}