usb: add basic userspace lsusb + usb sysfs
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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) {}
|
||||
|
||||
Generated
+14
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
@@ -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"),
|
||||
|
||||
Reference in New Issue
Block a user