Add runtime tools and Red Bear service wiring
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
[source]
|
||||
path = "source"
|
||||
|
||||
[build]
|
||||
template = "cargo"
|
||||
|
||||
[package.files]
|
||||
"/usr/bin/lspci" = "lspci"
|
||||
"/usr/bin/lsusb" = "lsusb"
|
||||
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "redbear-hwutils"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[[bin]]
|
||||
name = "lspci"
|
||||
path = "src/bin/lspci.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "lsusb"
|
||||
path = "src/bin/lsusb.rs"
|
||||
|
||||
[dependencies]
|
||||
xhcid = { path = "../../../../../recipes/core/base/source/drivers/usb/xhcid" }
|
||||
@@ -0,0 +1,94 @@
|
||||
use std::fs;
|
||||
use std::process;
|
||||
|
||||
use redbear_hwutils::{parse_args, parse_pci_location, PciLocation};
|
||||
|
||||
const USAGE: &str = "Usage: lspci\nList PCI devices exposed by /scheme/pci.";
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||
struct PciDeviceSummary {
|
||||
location: PciLocation,
|
||||
vendor_id: u16,
|
||||
device_id: u16,
|
||||
class_code: u8,
|
||||
subclass: u8,
|
||||
prog_if: u8,
|
||||
revision: u8,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
match run() {
|
||||
Ok(()) => {}
|
||||
Err(err) if err.is_empty() => {}
|
||||
Err(err) => {
|
||||
eprintln!("lspci: {err}");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run() -> Result<(), String> {
|
||||
parse_args("lspci", USAGE, std::env::args())?;
|
||||
|
||||
let mut devices = collect_devices()?;
|
||||
devices.sort();
|
||||
|
||||
for device in devices {
|
||||
println!(
|
||||
"{} class {:02x}:{:02x}.{:02x} vendor {:04x} device {:04x} rev {:02x}",
|
||||
device.location,
|
||||
device.class_code,
|
||||
device.subclass,
|
||||
device.prog_if,
|
||||
device.vendor_id,
|
||||
device.device_id,
|
||||
device.revision,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn collect_devices() -> Result<Vec<PciDeviceSummary>, String> {
|
||||
let entries =
|
||||
fs::read_dir("/scheme/pci").map_err(|err| format!("failed to read /scheme/pci: {err}"))?;
|
||||
|
||||
let mut devices = Vec::new();
|
||||
|
||||
for entry in entries {
|
||||
let entry = match entry {
|
||||
Ok(entry) => entry,
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
let file_name = entry.file_name();
|
||||
let Some(file_name) = file_name.to_str() else {
|
||||
continue;
|
||||
};
|
||||
let Some(location) = parse_pci_location(file_name) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let config_path = format!("{}/config", location.scheme_path());
|
||||
let config = match fs::read(&config_path) {
|
||||
Ok(config) => config,
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
if config.len() < 16 {
|
||||
continue;
|
||||
}
|
||||
|
||||
devices.push(PciDeviceSummary {
|
||||
location,
|
||||
vendor_id: u16::from_le_bytes([config[0x00], config[0x01]]),
|
||||
device_id: u16::from_le_bytes([config[0x02], config[0x03]]),
|
||||
revision: config[0x08],
|
||||
prog_if: config[0x09],
|
||||
subclass: config[0x0A],
|
||||
class_code: config[0x0B],
|
||||
});
|
||||
}
|
||||
|
||||
Ok(devices)
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
use std::fs;
|
||||
use std::process;
|
||||
|
||||
use redbear_hwutils::{describe_usb_device, parse_args};
|
||||
use xhcid_interface::{PortId, PortState, XhciClientHandle};
|
||||
|
||||
const USAGE: &str = "Usage: lsusb\nList USB devices exposed by native usb.* schemes.";
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
struct UsbDeviceSummary {
|
||||
controller: String,
|
||||
port: PortId,
|
||||
vendor_id: u16,
|
||||
product_id: u16,
|
||||
class: u8,
|
||||
subclass: u8,
|
||||
protocol: u8,
|
||||
usb_major: u8,
|
||||
usb_minor: u8,
|
||||
description: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
struct UsbPortStateSummary {
|
||||
controller: String,
|
||||
port: PortId,
|
||||
state: PortState,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
match run() {
|
||||
Ok(()) => {}
|
||||
Err(err) if err.is_empty() => {}
|
||||
Err(err) => {
|
||||
eprintln!("lsusb: {err}");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run() -> Result<(), String> {
|
||||
parse_args("lsusb", USAGE, std::env::args())?;
|
||||
|
||||
let (mut devices, mut fallback_ports) = collect_usb_state()?;
|
||||
devices.sort_by(|left, right| {
|
||||
left.controller
|
||||
.cmp(&right.controller)
|
||||
.then(left.port.cmp(&right.port))
|
||||
});
|
||||
fallback_ports.sort_by(|left, right| {
|
||||
left.controller
|
||||
.cmp(&right.controller)
|
||||
.then(left.port.cmp(&right.port))
|
||||
});
|
||||
|
||||
for device in devices {
|
||||
println!(
|
||||
"{} {} ID {:04x}:{:04x} class {:02x}/{:02x}/{:02x} usb {}.{:02x} {}",
|
||||
device.controller,
|
||||
device.port,
|
||||
device.vendor_id,
|
||||
device.product_id,
|
||||
device.class,
|
||||
device.subclass,
|
||||
device.protocol,
|
||||
device.usb_major,
|
||||
device.usb_minor,
|
||||
device.description,
|
||||
);
|
||||
}
|
||||
|
||||
for fallback in fallback_ports {
|
||||
println!(
|
||||
"{} {} state {}",
|
||||
fallback.controller,
|
||||
fallback.port,
|
||||
fallback.state.as_str(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn collect_usb_state() -> Result<(Vec<UsbDeviceSummary>, Vec<UsbPortStateSummary>), String> {
|
||||
let entries =
|
||||
fs::read_dir("/scheme").map_err(|err| format!("failed to read /scheme: {err}"))?;
|
||||
|
||||
let mut devices = Vec::new();
|
||||
let mut fallback_ports = Vec::new();
|
||||
|
||||
for entry in entries {
|
||||
let entry = match entry {
|
||||
Ok(entry) => entry,
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
let file_name = entry.file_name();
|
||||
let Some(controller) = file_name.to_str() else {
|
||||
continue;
|
||||
};
|
||||
if !controller.starts_with("usb.") {
|
||||
continue;
|
||||
}
|
||||
|
||||
let controller_dir = format!("/scheme/{controller}");
|
||||
let ports = match fs::read_dir(&controller_dir) {
|
||||
Ok(ports) => ports,
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
for port_entry in ports {
|
||||
let port_entry = match port_entry {
|
||||
Ok(port_entry) => port_entry,
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
let port_name = port_entry.file_name();
|
||||
let Some(port_name) = port_name.to_str() else {
|
||||
continue;
|
||||
};
|
||||
let Some(raw_port_id) = port_name.strip_prefix("port") else {
|
||||
continue;
|
||||
};
|
||||
let Ok(port) = raw_port_id.parse::<PortId>() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let handle = match XhciClientHandle::new(controller.to_string(), port) {
|
||||
Ok(handle) => handle,
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
let state = handle.port_state().ok();
|
||||
|
||||
match handle.get_standard_descs() {
|
||||
Ok(descriptors) => {
|
||||
devices.push(UsbDeviceSummary {
|
||||
controller: controller.to_string(),
|
||||
port,
|
||||
vendor_id: descriptors.vendor,
|
||||
product_id: descriptors.product,
|
||||
class: descriptors.class,
|
||||
subclass: descriptors.sub_class,
|
||||
protocol: descriptors.protocol,
|
||||
usb_major: descriptors.major_version(),
|
||||
usb_minor: descriptors.minor_version(),
|
||||
description: describe_usb_device(
|
||||
descriptors.manufacturer_str.as_deref(),
|
||||
descriptors.product_str.as_deref(),
|
||||
),
|
||||
});
|
||||
}
|
||||
Err(_) => {
|
||||
if let Some(state) =
|
||||
state.filter(|state| *state != PortState::EnabledOrDisabled)
|
||||
{
|
||||
fallback_ports.push(UsbPortStateSummary {
|
||||
controller: controller.to_string(),
|
||||
port,
|
||||
state,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok((devices, fallback_ports))
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||
pub struct PciLocation {
|
||||
pub segment: u16,
|
||||
pub bus: u8,
|
||||
pub device: u8,
|
||||
pub function: u8,
|
||||
}
|
||||
|
||||
impl PciLocation {
|
||||
pub fn scheme_path(&self) -> String {
|
||||
format!(
|
||||
"/scheme/pci/{:04x}--{:02x}--{:02x}.{}",
|
||||
self.segment, self.bus, self.device, self.function
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for PciLocation {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{:04x}:{:02x}:{:02x}.{}",
|
||||
self.segment, self.bus, self.device, self.function
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_pci_location(name: &str) -> Option<PciLocation> {
|
||||
let (segment, rest) = name.split_once("--")?;
|
||||
let (bus, rest) = rest.split_once("--")?;
|
||||
let (device, function) = rest.split_once('.')?;
|
||||
|
||||
Some(PciLocation {
|
||||
segment: u16::from_str_radix(segment, 16).ok()?,
|
||||
bus: u8::from_str_radix(bus, 16).ok()?,
|
||||
device: u8::from_str_radix(device, 16).ok()?,
|
||||
function: function.parse().ok()?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_args(
|
||||
program: &str,
|
||||
usage: &str,
|
||||
args: impl IntoIterator<Item = String>,
|
||||
) -> Result<(), String> {
|
||||
let extras: Vec<String> = args.into_iter().skip(1).collect();
|
||||
|
||||
if extras.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if extras.len() == 1 && matches!(extras[0].as_str(), "-h" | "--help") {
|
||||
println!("{usage}");
|
||||
return Err(String::new());
|
||||
}
|
||||
|
||||
Err(format!(
|
||||
"{program}: unsupported arguments: {}",
|
||||
extras.join(" ")
|
||||
))
|
||||
}
|
||||
|
||||
pub fn describe_usb_device(manufacturer: Option<&str>, product: Option<&str>) -> String {
|
||||
let mut parts = Vec::new();
|
||||
|
||||
if let Some(manufacturer) = manufacturer.filter(|value| !value.is_empty()) {
|
||||
parts.push(manufacturer);
|
||||
}
|
||||
if let Some(product) = product.filter(|value| !value.is_empty()) {
|
||||
parts.push(product);
|
||||
}
|
||||
|
||||
if parts.is_empty() {
|
||||
"USB device".to_string()
|
||||
} else {
|
||||
parts.join(" ")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user