usb: add basic userspace lsusb + usb sysfs

This commit is contained in:
2025-07-11 20:40:24 +03:00
parent 2964b668df
commit 552de70191
9 changed files with 308 additions and 20 deletions
+50 -7
View File
@@ -1,34 +1,77 @@
use core::sync::atomic::{AtomicU16, Ordering};
use alloc::{collections::BTreeMap, sync::Arc};
use libk_util::{queue::UnboundedMpmcQueue, sync::spin_rwlock::IrqSafeRwLock};
use alloc::{collections::BTreeMap, format, sync::Arc};
use libk::fs::sysfs::{self, object::KObject};
use libk_util::{queue::UnboundedMpmcQueue, sync::spin_rwlock::IrqSafeRwLock, OneTimeInit};
use crate::{
class_driver,
device::{UsbBusAddress, UsbDeviceAccess},
device::{UsbBusAddress, UsbDevice, UsbDeviceAccess},
UsbHostController,
};
pub type UsbBusKObject = Arc<KObject<Arc<UsbBusWrapper>>>;
pub type UsbDeviceKObject = Arc<KObject<Arc<UsbDeviceAccess>>>;
pub struct UsbBusWrapper {
hc: Arc<dyn UsbHostController>,
index: u16,
kobject: OneTimeInit<UsbBusKObject>,
}
pub struct UsbBusManager {
busses: IrqSafeRwLock<BTreeMap<u16, Arc<dyn UsbHostController>>>,
busses: IrqSafeRwLock<BTreeMap<u16, Arc<UsbBusWrapper>>>,
devices: IrqSafeRwLock<BTreeMap<UsbBusAddress, Arc<UsbDeviceAccess>>>,
last_bus_address: AtomicU16,
}
impl UsbBusWrapper {
pub fn register_sysfs_object(self: &Arc<Self>) {
let root = sysfs_usb_root();
let bus_kobject = self.kobject.init(KObject::new(self.clone()));
self.hc.register_sysfs_properties(bus_kobject);
root.add_object(format!("bus{}", self.index), bus_kobject.clone())
.ok();
}
pub fn kobject(&self) -> &UsbBusKObject {
self.kobject.get()
}
}
fn sysfs_usb_root() -> &'static Arc<KObject<()>> {
static USB_ROOT: OneTimeInit<Arc<KObject<()>>> = OneTimeInit::new();
USB_ROOT.or_init_with(|| {
let bus_object = sysfs::bus().expect("bus object");
let usb_object = KObject::new(());
bus_object.add_object("usb", usb_object.clone()).ok();
usb_object
})
}
impl UsbBusManager {
pub fn register_bus(hc: Arc<dyn UsbHostController>) -> u16 {
pub fn register_bus(hc: Arc<dyn UsbHostController>) -> (u16, Arc<UsbBusWrapper>) {
let i = BUS_MANAGER.last_bus_address.fetch_add(1, Ordering::AcqRel);
BUS_MANAGER.busses.write().insert(i, hc);
i
let wrapper = Arc::new(UsbBusWrapper {
hc,
index: i,
kobject: OneTimeInit::new(),
});
BUS_MANAGER.busses.write().insert(i, wrapper.clone());
wrapper.register_sysfs_object();
(i, wrapper)
}
pub fn register_device(device: Arc<UsbDeviceAccess>) {
log::info!("usb: register device {}", device.bus_address());
BUS_MANAGER
.devices
.write()
.insert(device.bus_address(), device.clone());
device.register_sysfs_object();
QUEUE.push_back(device);
}
+99 -6
View File
@@ -1,14 +1,31 @@
use core::{fmt, ops::Deref};
use alloc::{boxed::Box, sync::Arc, vec::Vec};
use alloc::{
boxed::Box,
format,
string::{String, ToString},
sync::Arc,
vec::Vec,
};
use async_trait::async_trait;
use libk_util::sync::spin_rwlock::{IrqSafeRwLock, IrqSafeRwLockReadGuard};
use libk::{
error::Error,
fs::sysfs::{
attribute::{StringAttribute, StringAttributeOps},
object::KObject,
},
};
use libk_util::{
sync::spin_rwlock::{IrqSafeRwLock, IrqSafeRwLockReadGuard},
OneTimeInit,
};
use crate::{
bus::{UsbBusWrapper, UsbDeviceKObject},
error::UsbError,
info::{
UsbConfigurationInfo, UsbDeviceInfo, UsbEndpointInfo, UsbEndpointType, UsbInterfaceInfo,
UsbVersion,
UsbConfigurationInfo, UsbDeviceClass, UsbDeviceInfo, UsbEndpointInfo, UsbEndpointType,
UsbInterfaceInfo, UsbVersion,
},
pipe::{
control::{ConfigurationDescriptorEntry, UsbControlPipeAccess},
@@ -30,8 +47,11 @@ pub struct UsbBusAddress {
pub struct UsbDeviceAccess {
pub device: Arc<dyn UsbDevice>,
pub bus: Arc<UsbBusWrapper>,
pub info: UsbDeviceInfo,
pub current_configuration: IrqSafeRwLock<Option<UsbConfigurationInfo>>,
kobject: OneTimeInit<UsbDeviceKObject>,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
@@ -69,7 +89,7 @@ pub trait UsbDevice: Send + Sync {
fn port_number(&self) -> u8;
fn bus_address(&self) -> UsbBusAddress;
fn speed(&self) -> UsbSpeed;
fn controller_ref(&self) -> &dyn UsbHostController;
fn host_controller(&self) -> Arc<dyn UsbHostController>;
fn set_detach_handler(&self, handler: Arc<dyn UsbDeviceDetachHandler>);
fn handle_detach(&self);
@@ -78,13 +98,83 @@ pub trait UsbDevice: Send + Sync {
}
impl UsbDeviceAccess {
pub(crate) fn register_sysfs_object(self: &Arc<Self>) {
struct Class;
struct Version;
struct IdVendor;
struct IdProduct;
impl StringAttributeOps for Class {
type Data = Arc<UsbDeviceAccess>;
const NAME: &'static str = "class";
fn read(state: &Self::Data) -> Result<String, Error> {
let s = match state.info.device_class {
UsbDeviceClass::Hid => "hid",
UsbDeviceClass::MassStorage => "mass-storage",
UsbDeviceClass::FromInterface => "-",
UsbDeviceClass::Unknown => "unknown",
};
Ok(s.into())
}
}
impl StringAttributeOps for Version {
type Data = Arc<UsbDeviceAccess>;
const NAME: &'static str = "version";
fn read(state: &Self::Data) -> Result<String, Error> {
Ok(state.info.usb_version.to_string())
}
}
impl StringAttributeOps for IdProduct {
type Data = Arc<UsbDeviceAccess>;
const NAME: &'static str = "product";
fn read(state: &Self::Data) -> Result<String, Error> {
Ok(format!("{:x}", state.info.id_product))
}
}
impl StringAttributeOps for IdVendor {
type Data = Arc<UsbDeviceAccess>;
const NAME: &'static str = "vendor";
fn read(state: &Self::Data) -> Result<String, Error> {
Ok(format!("{:x}", state.info.id_vendor))
}
}
let bus_kobject = self.bus.kobject();
let device_kobject = self.kobject.init(KObject::new(self.clone()));
device_kobject
.add_attribute(StringAttribute::from(Class))
.ok();
device_kobject
.add_attribute(StringAttribute::from(Version))
.ok();
device_kobject
.add_attribute(StringAttribute::from(IdVendor))
.ok();
device_kobject
.add_attribute(StringAttribute::from(IdProduct))
.ok();
let address = self.bus_address();
bus_kobject
.add_object(format!("{}", address.device), device_kobject.clone())
.ok();
}
/// Expected device state:
///
/// * Link-layer stuff has been reset and established properly by the HCD
/// * Device is not yet configured
/// * Control pipe for the device has been properly set up
/// * Device has been assigned a bus address
pub async fn setup(raw: Arc<dyn UsbDevice>) -> Result<Self, UsbError> {
pub async fn setup(bus: Arc<UsbBusWrapper>, raw: Arc<dyn UsbDevice>) -> Result<Self, UsbError> {
let control = raw.control_pipe();
let device_desc = control.query_device_descriptor().await?;
@@ -123,8 +213,11 @@ impl UsbDeviceAccess {
Ok(Self {
device: raw,
bus,
info,
current_configuration: IrqSafeRwLock::new(None),
kobject: OneTimeInit::new(),
})
}
+7 -1
View File
@@ -8,6 +8,8 @@
maybe_uninit_fill
)]
use crate::bus::UsbBusKObject;
extern crate alloc;
pub mod bus;
@@ -25,4 +27,8 @@ pub mod class_driver;
pub trait UsbEndpoint: Sync {}
pub trait UsbHostController: Sync + Send {}
pub trait UsbHostController: Sync + Send {
fn register_sysfs_properties(&self, kobject: &UsbBusKObject) {
let _ = kobject;
}
}
+8 -4
View File
@@ -25,7 +25,7 @@ use tock_registers::{
};
use ygg_driver_pci::{device::PciDeviceInfo, PciConfigurationSpace};
use ygg_driver_usb::{
bus::UsbBusManager,
bus::{UsbBusManager, UsbBusWrapper},
device::{UsbBusAddress, UsbDeviceAccess, UsbSpeed},
error::UsbError,
info::UsbVersion,
@@ -90,6 +90,7 @@ pub struct Xhci {
pub(crate) slots: Vec<IrqSafeRwLock<Option<Arc<XhciBusDevice>>>>,
pub(crate) port_slot_map: Vec<AtomicU8>,
bus_index: OneTimeInit<u16>,
bus: OneTimeInit<Arc<UsbBusWrapper>>,
port_event_map: EventBitmap,
}
@@ -195,6 +196,7 @@ impl Xhci {
root_hub_ports,
bus_index: OneTimeInit::new(),
bus: OneTimeInit::new(),
endpoints: IrqSafeRwLock::new(BTreeMap::new()),
slots,
port_slot_map,
@@ -341,7 +343,8 @@ impl Xhci {
device: bus_address,
});
let device = UsbDeviceAccess::setup(slot).await?;
let bus = self.bus.get();
let device = UsbDeviceAccess::setup(bus.clone(), slot).await?;
UsbBusManager::register_device(device.into());
Ok(())
@@ -523,8 +526,9 @@ impl Device for Xhci {
op.wait_usbsts_bit(USBSTS::CNR::CLEAR, 100000000)?;
let bus = UsbBusManager::register_bus(self.clone());
self.bus_index.init(bus);
let (bus_index, bus) = UsbBusManager::register_bus(self.clone());
self.bus_index.init(bus_index);
self.bus.init(bus);
runtime::spawn(self.clone().port_handler_task()).ok();
+2 -2
View File
@@ -63,8 +63,8 @@ impl UsbDevice for XhciBusDevice {
*self.detach_handler.lock() = Some(handler);
}
fn controller_ref(&self) -> &dyn UsbHostController {
self.xhci.as_ref()
fn host_controller(&self) -> Arc<dyn UsbHostController> {
self.xhci.clone()
}
fn debug(&self) {}
+14
View File
@@ -2883,6 +2883,7 @@ dependencies = [
"sha2",
"thiserror",
"tui",
"usb-ids",
"yggdrasil-abi",
"yggdrasil-rt",
]
@@ -3166,6 +3167,19 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "usb-ids"
version = "1.2025.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f464d03993287ba27fae1c81bfa368df4493983de7e340429fc10e470043383"
dependencies = [
"nom",
"phf",
"phf_codegen",
"proc-macro2",
"quote",
]
[[package]]
name = "utf8_iter"
version = "1.0.4"
+5
View File
@@ -25,6 +25,7 @@ tui.workspace = true
# Own regex implementation?
regex = "1.11.1"
pci-ids = { version = "0.2.5" }
usb-ids = { version = "1.2025.2" }
cryptic.workspace = true
rustls.workspace = true
@@ -145,6 +146,10 @@ path = "src/sleep.rs"
name = "lspci"
path = "src/lspci.rs"
[[bin]]
name = "lsusb"
path = "src/lsusb.rs"
[[bin]]
name = "ps"
path = "src/ps.rs"
+122
View File
@@ -0,0 +1,122 @@
use std::{
fs::{self, ReadDir},
io,
path::PathBuf,
process::ExitCode,
};
struct BusAddress {
bus: u16,
device: u8,
}
struct Device {
path: PathBuf,
address: BusAddress,
id_vendor: Option<u16>,
id_product: Option<u16>,
}
impl Device {
pub fn read(address: BusAddress) -> Self {
let path = PathBuf::from("/sys/bus/usb")
.join(format!("bus{}", address.bus))
.join(address.device.to_string());
let id_vendor = fs::read_to_string(path.join("vendor"))
.inspect_err(|e| eprintln!("{path:?}/vendor: {e}"))
.ok()
.and_then(|v| u16::from_str_radix(v.trim(), 16).ok());
let id_product = fs::read_to_string(path.join("product"))
.inspect_err(|e| eprintln!("{path:?}/product: {e}"))
.ok()
.and_then(|v| u16::from_str_radix(v.trim(), 16).ok());
Self {
path,
address,
id_vendor,
id_product,
}
}
pub fn format_short(&self) -> String {
let product = if let (Some(vendor), Some(product)) = (self.id_vendor, self.id_product) {
let s = usb_ids::Device::from_vid_pid(vendor, product)
.map(|v| v.name())
.unwrap_or("unknown device");
format!("{vendor:04x}:{product:04x} {s}")
} else {
"????:???? Unknown device".into()
};
format!(
"Bus {:03} Device {:03}: {product}",
self.address.bus, self.address.device
)
}
}
fn list_busses() -> io::Result<Vec<u16>> {
let mut res = vec![];
let dir = fs::read_dir("/sys/bus/usb")?;
for item in dir {
let Ok(item) = item else {
continue;
};
let Some(index) = item
.file_name()
.to_str()
.and_then(|s| s.strip_prefix("bus"))
.and_then(|s| s.parse().ok())
else {
continue;
};
res.push(index);
}
Ok(res)
}
fn list_bus(bus: u16, devices: &mut Vec<Device>) -> io::Result<()> {
let path = PathBuf::from("/sys/bus/usb").join(format!("bus{bus}"));
let dir = fs::read_dir(path)?;
for item in dir {
let Ok(item) = item else {
continue;
};
let Some(name) = item.file_name().to_str().and_then(|s| s.parse().ok()) else {
continue;
};
let address = BusAddress { bus, device: name };
devices.push(Device::read(address));
}
Ok(())
}
fn list_devices(busses: &[u16]) -> Vec<Device> {
let mut devices = vec![];
for &bus in busses {
if let Err(error) = list_bus(bus, &mut devices) {
eprintln!("bus {bus}: {error}");
}
}
devices
}
fn list_all() -> Vec<Device> {
let Ok(busses) = list_busses() else {
return vec![];
};
list_devices(&busses)
}
fn main() {
let devices = list_all();
for device in devices {
let text = device.format_short();
println!("{text}");
}
}
+1
View File
@@ -53,6 +53,7 @@ const PROGRAMS: &[(&str, &str)] = &[
("sync", "bin/sync"),
("sleep", "bin/sleep"),
("lspci", "bin/lspci"),
("lsusb", "bin/lsusb"),
("ps", "bin/ps"),
("top", "bin/top"),
("tst", "bin/tst"),