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
@@ -1,4 +1,4 @@
#[derive(Clone, Debug)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Subsystem {
Gpu,
Network,
@@ -9,8 +9,16 @@ pub enum Subsystem {
Unknown,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum InputKind {
Keyboard,
Mouse,
Generic,
}
#[derive(Clone, Debug)]
pub struct DeviceInfo {
pub is_pci: bool,
pub bus: u8,
pub dev: u8,
pub func: u8,
@@ -19,15 +27,89 @@ pub struct DeviceInfo {
pub class_code: u8,
pub subclass: u8,
pub subsystem: Subsystem,
pub input_kind: Option<InputKind>,
pub name: String,
pub path: String,
pub devpath: String,
pub devnode: String,
pub scheme_target: String,
pub symlinks: Vec<String>,
}
impl DeviceInfo {
pub fn new_platform_input(
name: &str,
devpath: &str,
input_kind: InputKind,
devnode: &str,
scheme_target: &str,
) -> Self {
Self {
is_pci: false,
bus: 0,
dev: 0,
func: 0,
vendor_id: 0,
device_id: 0,
class_code: 0,
subclass: 0,
subsystem: Subsystem::Input,
input_kind: Some(input_kind),
name: name.to_string(),
devpath: devpath.to_string(),
devnode: devnode.to_string(),
scheme_target: scheme_target.to_string(),
symlinks: Vec::new(),
}
}
pub fn set_node_metadata(
&mut self,
devnode: impl Into<String>,
scheme_target: impl Into<String>,
symlinks: Vec<String>,
) {
self.devnode = devnode.into();
self.scheme_target = scheme_target.into();
self.symlinks = symlinks;
}
pub fn subsystem_name(&self) -> &'static str {
match self.subsystem {
Subsystem::Gpu => "drm",
Subsystem::Network => "net",
Subsystem::Storage => "block",
Subsystem::Audio => "sound",
Subsystem::Usb => "usb",
Subsystem::Input => "input",
Subsystem::Unknown => "unknown",
}
}
pub fn id_path(&self) -> String {
if let Some(slot) = self.devpath.strip_prefix("/devices/pci/") {
return format!("pci-{slot}");
}
self.devpath
.trim_start_matches("/devices/")
.replace('/', "-")
}
pub fn is_input_keyboard(&self) -> bool {
self.input_kind == Some(InputKind::Keyboard)
}
pub fn is_input_mouse(&self) -> bool {
self.input_kind == Some(InputKind::Mouse)
}
}
pub fn classify_pci_device(bus: u8, dev: u8, func: u8) -> DeviceInfo {
let path = format!("/devices/pci/{:04x}:{:02x}:{:02x}.{}", bus, 0, dev, func);
let devpath = format!("/devices/pci/{:04x}:{:02x}:{:02x}.{}", bus, 0, dev, func);
let config_path = format!("/scheme/pci/{}.{}.{}", bus, dev, func);
let (vendor_id, device_id, class_code, subclass) = read_pci_config(&config_path);
let input_kind = detect_input_kind(class_code, subclass);
let subsystem = match class_code {
0x03 => Subsystem::Gpu,
@@ -39,9 +121,10 @@ pub fn classify_pci_device(bus: u8, dev: u8, func: u8) -> DeviceInfo {
_ => Subsystem::Unknown,
};
let name = format_device_name(vendor_id, device_id, class_code);
let name = format_device_name(vendor_id, device_id, class_code, subclass, input_kind);
DeviceInfo {
is_pci: true,
bus,
dev,
func,
@@ -50,8 +133,12 @@ pub fn classify_pci_device(bus: u8, dev: u8, func: u8) -> DeviceInfo {
class_code,
subclass,
subsystem,
input_kind,
name,
path,
devpath,
devnode: String::new(),
scheme_target: String::new(),
symlinks: Vec::new(),
}
}
@@ -68,7 +155,40 @@ fn read_pci_config(path: &str) -> (u16, u16, u8, u8) {
}
}
fn format_device_name(vendor_id: u16, device_id: u16, class_code: u8) -> String {
fn detect_input_kind(class_code: u8, subclass: u8) -> Option<InputKind> {
if class_code != 0x09 {
return None;
}
match subclass {
0x00 => Some(InputKind::Keyboard),
0x04 => Some(InputKind::Generic),
_ => Some(InputKind::Generic),
}
}
fn format_device_name(
vendor_id: u16,
device_id: u16,
class_code: u8,
subclass: u8,
input_kind: Option<InputKind>,
) -> String {
if class_code == 0x03 {
if let Some(name) = gpu_device_name(vendor_id, device_id) {
return format!("{name} [{vendor_id:04x}:{device_id:04x}]");
}
}
if class_code == 0x09 {
let name = match (subclass, input_kind) {
(0x00, Some(InputKind::Keyboard)) => "PS/2 Keyboard Controller",
(0x04, _) => "USB HID Controller",
_ => "Input Device",
};
return format!("{name} [{vendor_id:04x}:{device_id:04x}]");
}
let vendor_name = match vendor_id {
0x8086 => "Intel",
0x1002 => "AMD",
@@ -95,19 +215,93 @@ fn format_device_name(vendor_id: u16, device_id: u16, class_code: u8) -> String
)
}
pub fn format_device_info(dev: &DeviceInfo) -> String {
let subsystem = match dev.subsystem {
Subsystem::Gpu => "gpu",
Subsystem::Network => "net",
Subsystem::Storage => "block",
Subsystem::Audio => "sound",
Subsystem::Usb => "usb",
Subsystem::Input => "input",
Subsystem::Unknown => "unknown",
};
format!(
"P={}\nE=SUBSYSTEM={}\nE=PCI_VENDOR_ID={:#06x}\nE=PCI_DEVICE_ID={:#06x}\nE=PCI_CLASS={:#04x}{:02x}\nE=DEVNAME={}\n",
dev.path, subsystem, dev.vendor_id, dev.device_id, dev.class_code, dev.subclass, dev.name
)
fn gpu_device_name(vendor_id: u16, device_id: u16) -> Option<&'static str> {
match vendor_id {
0x1002 => match device_id {
0x73A3 => Some("AMD Radeon RX 6600 XT / 6650 XT (RDNA2)"),
0x73BF => Some("AMD Radeon RX 6800 XT / 6900 XT (RDNA2)"),
0x73DF => Some("AMD Radeon RX 6700 XT / 6750 XT (RDNA2)"),
0x73EF => Some("AMD Radeon RX 6800 / 6850M XT (RDNA2)"),
0x7422 => Some("AMD Radeon 780M (RDNA3)"),
0x7448 => Some("AMD Radeon RX 7900 XT (RDNA3)"),
0x744C => Some("AMD Radeon RX 7900 XTX (RDNA3)"),
0x7480 => Some("AMD Radeon RX 7800 XT / 7700 XT (RDNA3)"),
_ => Some("AMD Radeon GPU"),
},
0x8086 => match device_id {
0x3E92 => Some("Intel UHD Graphics 630"),
0x5912 => Some("Intel HD Graphics 630"),
0x9A49 => Some("Intel Iris Xe Graphics (Tiger Lake)"),
0x46A6 => Some("Intel Iris Xe Graphics (Alder Lake-P)"),
0x56A0 => Some("Intel Arc Graphics (DG2)"),
0x56A1 => Some("Intel Arc A380 (DG2)"),
_ => Some("Intel Graphics"),
},
_ => None,
}
}
pub fn device_properties(dev: &DeviceInfo) -> Vec<(String, String)> {
let mut props = Vec::new();
props.push(("DEVPATH".to_string(), dev.devpath.clone()));
props.push(("SUBSYSTEM".to_string(), dev.subsystem_name().to_string()));
props.push(("ID_MODEL_FROM_DATABASE".to_string(), dev.name.clone()));
if !dev.devnode.is_empty() {
props.push(("DEVNAME".to_string(), dev.devnode.clone()));
}
let id_path = dev.id_path();
if !id_path.is_empty() {
props.push(("ID_PATH".to_string(), id_path));
}
if dev.is_pci {
props.push((
"PCI_VENDOR_ID".to_string(),
format!("0x{:04x}", dev.vendor_id),
));
props.push((
"PCI_DEVICE_ID".to_string(),
format!("0x{:04x}", dev.device_id),
));
props.push((
"PCI_CLASS".to_string(),
format!("0x{:02x}{:02x}", dev.class_code, dev.subclass),
));
}
if dev.subsystem == Subsystem::Input {
props.push(("ID_INPUT".to_string(), "1".to_string()));
match dev.input_kind {
Some(InputKind::Keyboard) => {
props.push(("ID_INPUT_KEYBOARD".to_string(), "1".to_string()));
}
Some(InputKind::Mouse) => {
props.push(("ID_INPUT_MOUSE".to_string(), "1".to_string()));
}
_ => {}
}
}
props
}
pub fn format_device_info(dev: &DeviceInfo) -> String {
let mut info = format!("P={}\n", dev.devpath);
for (key, value) in device_properties(dev) {
info.push_str(&format!("E={key}={value}\n"));
}
for link in &dev.symlinks {
info.push_str(&format!("S={}\n", link.trim_start_matches('/')));
}
info
}
pub fn format_uevent_info(dev: &DeviceInfo) -> String {
let mut info = String::from("ACTION=add\n");
for (key, value) in device_properties(dev) {
info.push_str(&format!("{key}={value}\n"));
}
info
}