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:
2026-04-14 10:50:42 +01:00
parent fd60edc823
commit 51f3c21121
62 changed files with 9613 additions and 881 deletions
@@ -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(" ")
}
}