Files
RedBear-OS/drivers/usb/ucsid/src/main.rs
T
Red Bear OS 6ac41ee37a daemon: tolerate BrokenPipe on ready(); i2cd: handle empty RON response
daemon/src/lib.rs: Daemon::ready() previously called .unwrap() on the
init pipe write, causing a panic with BrokenPipe when init had already
closed its read end during the startup phase. Daemons like i2c-gpio-expanderd,
intel-gpiod, dw-acpi-i2cd, and i2c-hidd hit this in redbear-mini boots.
Now BrokenPipe is silently ignored — the daemon is operational regardless
of init's readiness tracking state.

drivers/usb/ucsid/src/main.rs and drivers/gpio/i2c-gpio-expanderd/src/main.rs:
read_i2c_control_response() returned an empty buffer (no I2C adapters
registered) and then tried ron::from_str('') which failed at 1:1 with
'Unexpected end of RON'. This produced false-positive warnings on every
boot where no I2C hardware is present. Now an empty/whitespace response
returns AdapterList(Vec::new()) gracefully.
2026-06-28 04:00:50 +03:00

840 lines
26 KiB
Rust

use std::collections::BTreeMap;
use std::fs::{self, File, OpenOptions};
use std::io::{Read, Write};
use std::path::Path;
use std::process;
use acpi_resource::{
AddressResourceType, FixedMemory32Descriptor, I2cSerialBusDescriptor, Memory32RangeDescriptor,
ResourceDescriptor,
};
use anyhow::{bail, Context, Result};
use i2c_interface::{
I2cAdapterInfo, I2cControlRequest, I2cControlResponse, I2cTransferRequest,
I2cTransferSegment,
};
use libredox::flag::{O_CLOEXEC, O_RDWR};
use redox_scheme::scheme::SchemeSync;
use redox_scheme::{CallerCtx, OpenResult, Socket};
use scheme_utils::{Blocking, HandleMap};
use serde::{Deserialize, Serialize};
use syscall::schemev2::NewFdFlags;
use syscall::{Error as SysError, EACCES, EBADF, EINVAL, ENOENT};
const SUPPORTED_IDS: &[&str] = &["PNP0CA0", "AMDI0042"];
const GET_CAPABILITY: u8 = 0x01;
const GET_CONNECTOR_STATUS: u8 = 0x10;
const UCSI_RESPONSE_HEADER_LEN: usize = 4;
const UCSI_CAPABILITY_READ_LEN: usize = 20;
const UCSI_CONNECTOR_STATUS_READ_LEN: usize = 20;
const MAX_CONNECTOR_PROBE: u8 = 8;
#[derive(Debug, Deserialize)]
struct AmlSymbol {
name: String,
value: AmlValue,
}
#[derive(Debug, Deserialize)]
enum AmlValue {
Integer(u64),
String(String),
}
#[derive(Clone, Copy, Debug)]
struct UcsiCommand {
command: u8,
data_length: u8,
specific_data: [u8; 6],
}
impl UcsiCommand {
fn new(command: u8, data_length: u8, specific_data: [u8; 6]) -> Self {
Self {
command,
data_length,
specific_data,
}
}
fn as_bytes(self) -> [u8; 8] {
let mut bytes = [0_u8; 8];
bytes[0] = self.command;
bytes[1] = self.data_length;
bytes[2..].copy_from_slice(&self.specific_data);
bytes
}
}
#[derive(Clone, Copy, Debug)]
struct UcsiResponseHeader {
_status: u16,
data_length: u16,
}
impl UcsiResponseHeader {
fn parse(bytes: &[u8]) -> Option<Self> {
let header = bytes.get(..UCSI_RESPONSE_HEADER_LEN)?;
Some(Self {
_status: u16::from_le_bytes([header[0], header[1]]),
data_length: u16::from_le_bytes([header[2], header[3]]),
})
}
}
#[derive(Clone, Debug)]
struct DiscoveredUcsiDevice {
name: String,
hid: String,
transport: UcsiTransport,
dsm_probe: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
enum UcsiTransport {
I2c {
adapter: String,
address: u16,
ten_bit_address: bool,
},
Mmio {
base: usize,
len: usize,
},
Unknown,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct UcsiCapability {
connector_count: u8,
supports_usb_pd: bool,
supports_alt_modes: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct UcsiConnectorSummary {
device: String,
connector_number: u8,
connected: bool,
data_role: String,
power_direction: String,
input_critical: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct UcsiDeviceSummary {
name: String,
hid: String,
transport: UcsiTransport,
capability: Option<UcsiCapability>,
connectors: Vec<UcsiConnectorSummary>,
dsm_probe: bool,
issues: Vec<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct UcsiSummary {
schema_version: u32,
device_count: usize,
connector_count: usize,
input_critical_ports: usize,
devices: Vec<UcsiDeviceSummary>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct UcsiHealth {
healthy: bool,
scanned_devices: usize,
responsive_devices: usize,
issues: Vec<String>,
}
struct UcsiState {
summary: UcsiSummary,
connectors: Vec<UcsiConnectorSummary>,
health: UcsiHealth,
}
enum Handle {
SchemeRoot,
Summary { pending: Vec<u8> },
Connectors { pending: Vec<u8> },
Health { pending: Vec<u8> },
}
struct UcsiScheme {
handles: HandleMap<Handle>,
state: UcsiState,
}
impl UcsiScheme {
fn new(state: UcsiState) -> Self {
Self {
handles: HandleMap::new(),
state,
}
}
fn serialize_payload<T: Serialize>(value: &T) -> syscall::Result<Vec<u8>> {
ron::ser::to_string(value)
.map(|text| text.into_bytes())
.map_err(|err| {
log::error!("ucsid: failed to serialize scheme payload: {err}");
SysError::new(EINVAL)
})
}
fn set_pending(handle: &mut Handle, pending: Vec<u8>) -> syscall::Result<()> {
match handle {
Handle::Summary { pending: slot }
| Handle::Connectors { pending: slot }
| Handle::Health { pending: slot } => {
*slot = pending;
Ok(())
}
Handle::SchemeRoot => Err(SysError::new(EBADF)),
}
}
fn copy_pending(handle: &mut Handle, buf: &mut [u8], offset: u64) -> syscall::Result<usize> {
let pending = match handle {
Handle::Summary { pending }
| Handle::Connectors { pending }
| Handle::Health { pending } => pending,
Handle::SchemeRoot => return Err(SysError::new(EBADF)),
};
let offset = usize::try_from(offset).map_err(|_| SysError::new(EINVAL))?;
if offset >= pending.len() {
return Ok(0);
}
let copy_len = buf.len().min(pending.len() - offset);
buf[..copy_len].copy_from_slice(&pending[offset..offset + copy_len]);
Ok(copy_len)
}
}
impl SchemeSync for UcsiScheme {
fn scheme_root(&mut self) -> syscall::Result<usize> {
Ok(self.handles.insert(Handle::SchemeRoot))
}
fn openat(
&mut self,
dirfd: usize,
path: &str,
_flags: usize,
_fcntl_flags: u32,
_ctx: &CallerCtx,
) -> syscall::Result<OpenResult> {
if !matches!(self.handles.get(dirfd)?, Handle::SchemeRoot) {
return Err(SysError::new(EACCES));
}
let handle = match path.trim_matches('/') {
"summary" => Handle::Summary {
pending: Vec::new(),
},
"connectors" => Handle::Connectors {
pending: Vec::new(),
},
"health" => Handle::Health {
pending: Vec::new(),
},
"" => return Err(SysError::new(EINVAL)),
_ => return Err(SysError::new(ENOENT)),
};
let fd = self.handles.insert(handle);
Ok(OpenResult::ThisScheme {
number: fd,
flags: NewFdFlags::empty(),
})
}
fn read(
&mut self,
id: usize,
buf: &mut [u8],
offset: u64,
_fcntl_flags: u32,
_ctx: &CallerCtx,
) -> syscall::Result<usize> {
let payload = match self.handles.get(id)? {
Handle::Summary { pending } if pending.is_empty() => {
Some(Self::serialize_payload(&self.state.summary)?)
}
Handle::Connectors { pending } if pending.is_empty() => {
Some(Self::serialize_payload(&self.state.connectors)?)
}
Handle::Health { pending } if pending.is_empty() => {
log::info!(
"RB_UCSID_HEALTH healthy={} scanned_devices={} responsive_devices={} issues={}",
self.state.health.healthy,
self.state.health.scanned_devices,
self.state.health.responsive_devices,
self.state.health.issues.len(),
);
Some(Self::serialize_payload(&self.state.health)?)
}
_ => None,
};
let handle = self.handles.get_mut(id)?;
if let Some(payload) = payload {
Self::set_pending(handle, payload)?;
}
Self::copy_pending(handle, buf, offset)
}
}
fn main() {
common::setup_logging(
"usb",
"ucsi",
"ucsid",
common::output_level(),
common::file_level(),
);
daemon::SchemeDaemon::new(daemon_runner);
}
fn daemon_runner(daemon: daemon::SchemeDaemon) -> ! {
if let Err(err) = run_daemon(daemon) {
log::error!("ucsid: {err:#}");
process::exit(1);
}
process::exit(0);
}
fn run_daemon(daemon: daemon::SchemeDaemon) -> Result<()> {
log::info!("RB_UCSID_SCHEMA version=1");
let state = build_state().context("failed to build UCSI device snapshot")?;
let socket = Socket::create().context("failed to create ucsi scheme socket")?;
let mut scheme = UcsiScheme::new(state);
let handler = Blocking::new(&socket, 16);
daemon
.ready_sync_scheme(&socket, &mut scheme)
.context("failed to publish ucsi scheme root")?;
libredox::call::setrens(0, 0).context("failed to enter null namespace")?;
handler
.process_requests_blocking(scheme)
.context("failed to process ucsid requests")?;
}
fn build_state() -> Result<UcsiState> {
let adapters = list_i2c_adapters().unwrap_or_else(|err| {
log::warn!("ucsid: failed to query i2cd adapters: {err:#}");
Vec::new()
});
let devices = discover_ucsi_devices().context("failed to discover ACPI UCSI devices")?;
let mut summaries = Vec::new();
let mut connectors = Vec::new();
let mut issues = Vec::new();
let mut responsive_devices = 0usize;
for device in devices {
log::info!(
"RB_UCSID_DEVICE name={} hid={} transport={:?} dsm_probe={}",
device.name,
device.hid,
device.transport,
device.dsm_probe,
);
let summary = summarize_device(device, &adapters)
.context("failed to summarize discovered UCSI device")?;
if summary.capability.is_some() {
responsive_devices += 1;
}
issues.extend(summary.issues.iter().cloned());
connectors.extend(summary.connectors.iter().cloned());
summaries.push(summary);
}
let summary = UcsiSummary {
schema_version: 1,
device_count: summaries.len(),
connector_count: connectors.len(),
input_critical_ports: connectors.iter().filter(|connector| connector.input_critical).count(),
devices: summaries,
};
let health = UcsiHealth {
healthy: issues.is_empty(),
scanned_devices: summary.device_count,
responsive_devices,
issues,
};
log::info!(
"RB_UCSID_SUMMARY devices={} connectors={} input_critical_ports={} healthy={}",
summary.device_count,
summary.connector_count,
summary.input_critical_ports,
health.healthy,
);
Ok(UcsiState {
summary,
connectors,
health,
})
}
fn discover_ucsi_devices() -> Result<Vec<DiscoveredUcsiDevice>> {
let mut matched = BTreeMap::new();
let entries = match fs::read_dir("/scheme/acpi/symbols") {
Ok(entries) => entries,
Err(err) if err.kind() == std::io::ErrorKind::WouldBlock || err.raw_os_error() == Some(11) => {
log::debug!("ucsid: ACPI symbols are not ready yet");
return Ok(Vec::new());
}
Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"),
};
for entry in entries {
let entry = entry.context("failed to read ACPI symbol directory entry")?;
let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else {
continue;
};
if !file_name.ends_with("_HID") && !file_name.ends_with("_CID") {
continue;
}
let Some(id) = read_symbol_id(&entry.path())? else {
continue;
};
if !SUPPORTED_IDS.iter().any(|candidate| *candidate == id) {
continue;
}
let Some(device) = file_name
.strip_suffix("_HID")
.or_else(|| file_name.strip_suffix("_CID"))
.map(str::to_owned)
else {
continue;
};
matched.entry(device).or_insert(id);
}
let mut devices = Vec::new();
for (device, hid) in matched {
let transport = read_ucsi_transport(&device)
.with_context(|| format!("failed to decode transport resources for {device}"))?;
let dsm_probe = bounded_dsm_probe(&device).unwrap_or_else(|err| {
log::debug!("ucsid: bounded _DSM probe failed for {device}: {err:#}");
false
});
devices.push(DiscoveredUcsiDevice {
name: device,
hid,
transport,
dsm_probe,
});
}
Ok(devices)
}
fn summarize_device(device: DiscoveredUcsiDevice, adapters: &[I2cAdapterInfo]) -> Result<UcsiDeviceSummary> {
let mut issues = Vec::new();
let capability = match &device.transport {
UcsiTransport::I2c {
adapter,
address,
ten_bit_address,
} => match match_i2c_adapter(adapters, adapter) {
Some(adapter_info) => match execute_ucsi_i2c_command(
adapter_info,
adapter,
*address,
*ten_bit_address,
UcsiCommand::new(GET_CAPABILITY, 0, [0; 6]),
UCSI_CAPABILITY_READ_LEN,
) {
Ok(bytes) => parse_ucsi_payload(&bytes)
.and_then(|(_header, payload)| parse_capability(payload))
.or_else(|| {
issues.push(format!(
"{}: GET_CAPABILITY returned an unexpected payload",
device.name
));
None
}),
Err(err) => {
issues.push(format!("{}: GET_CAPABILITY failed: {err:#}", device.name));
None
}
},
None => {
issues.push(format!(
"{}: no i2cd adapter matched ACPI source {}",
device.name, adapter
));
None
}
},
UcsiTransport::Mmio { base, len } => {
issues.push(format!(
"{}: MMIO UCSI transport discovered at {base:#x}+{len:#x} but command execution is not implemented yet",
device.name,
));
None
}
UcsiTransport::Unknown => {
issues.push(format!(
"{}: no supported UCSI transport was decoded from ACPI resources",
device.name,
));
None
}
};
let connector_count = capability
.as_ref()
.map(|capability| capability.connector_count.min(MAX_CONNECTOR_PROBE))
.unwrap_or(0);
let mut connectors = Vec::new();
for connector in 1..=connector_count {
match query_connector_status(&device, adapters, connector) {
Ok(connector_summary) => connectors.push(connector_summary),
Err(err) => issues.push(format!(
"{}: GET_CONNECTOR_STATUS({connector}) failed: {err:#}",
device.name,
)),
}
}
Ok(UcsiDeviceSummary {
name: device.name,
hid: device.hid,
transport: device.transport,
capability,
connectors,
dsm_probe: device.dsm_probe,
issues,
})
}
fn read_ucsi_transport(device: &str) -> Result<UcsiTransport> {
let contents = fs::read_to_string(format!("/scheme/acpi/resources/{device}"))
.with_context(|| format!("failed to read /scheme/acpi/resources/{device}"))?;
let resources = ron::from_str::<Vec<ResourceDescriptor>>(&contents)
.with_context(|| format!("failed to decode RON resources for {device}"))?;
let mut i2c = None::<I2cSerialBusDescriptor>;
let mut mmio = None::<(usize, usize)>;
for resource in resources {
match resource {
ResourceDescriptor::I2cSerialBus(bus) if i2c.is_none() => i2c = Some(bus),
ResourceDescriptor::FixedMemory32(FixedMemory32Descriptor {
address,
address_length,
..
}) if mmio.is_none() => {
mmio = Some((address as usize, address_length as usize));
}
ResourceDescriptor::Memory32Range(Memory32RangeDescriptor {
minimum,
maximum,
address_length,
..
}) if mmio.is_none() && maximum >= minimum => {
let span = maximum.saturating_sub(minimum).saturating_add(1) as usize;
mmio = Some((minimum as usize, span.max(address_length as usize)));
}
ResourceDescriptor::Address32(descriptor)
if mmio.is_none()
&& matches!(descriptor.resource_type, AddressResourceType::MemoryRange) =>
{
mmio = Some((descriptor.minimum as usize, descriptor.address_length as usize));
}
ResourceDescriptor::Address64(descriptor)
if mmio.is_none()
&& matches!(descriptor.resource_type, AddressResourceType::MemoryRange) =>
{
let base = usize::try_from(descriptor.minimum)
.context("64-bit MMIO base does not fit in usize")?;
let len = usize::try_from(descriptor.address_length)
.context("64-bit MMIO length does not fit in usize")?;
mmio = Some((base, len));
}
_ => {}
}
}
if let Some(bus) = i2c {
let adapter = bus
.resource_source
.as_ref()
.map(|source| source.source.clone())
.filter(|source| !source.is_empty())
.unwrap_or_else(|| String::from("ACPI-I2C"));
return Ok(UcsiTransport::I2c {
adapter,
address: bus.slave_address,
ten_bit_address: bus.access_mode_10bit,
});
}
if let Some((base, len)) = mmio {
return Ok(UcsiTransport::Mmio { base, len });
}
Ok(UcsiTransport::Unknown)
}
fn bounded_dsm_probe(device: &str) -> Result<bool> {
let symbol_name = format!("{}.{}", normalize_device_path(device), "_DSM");
let symbol_path = format!("/scheme/acpi/symbols/{symbol_name}");
let fd = match libredox::Fd::open(&symbol_path, O_RDWR | O_CLOEXEC, 0) {
Ok(fd) => fd,
Err(err) => {
log::debug!("ucsid: {} has no callable _DSM: {err}", device);
return Ok(false);
}
};
let mut payload = ron::to_string(&Vec::<u8>::new())
.context("failed to serialize bounded _DSM probe arguments")?
.into_bytes();
payload.resize(payload.len() + 1024, 0);
match libredox::call::call_ro(fd.raw(), &mut payload, syscall::CallFlags::empty(), &[]) {
Ok(_) => Ok(true),
Err(err) => {
log::debug!("ucsid: bounded _DSM probe for {} failed: {err}", device);
Ok(false)
}
}
}
fn parse_capability(payload: &[u8]) -> Option<UcsiCapability> {
let connector_count = *payload.first()?;
let flags = payload.get(1).copied().unwrap_or(0);
Some(UcsiCapability {
connector_count,
supports_usb_pd: flags & 0x01 != 0,
supports_alt_modes: flags & 0x02 != 0,
})
}
fn query_connector_status(
device: &DiscoveredUcsiDevice,
adapters: &[I2cAdapterInfo],
connector: u8,
) -> Result<UcsiConnectorSummary> {
match &device.transport {
UcsiTransport::I2c {
adapter,
address,
ten_bit_address,
} => {
let adapter_info = match_i2c_adapter(adapters, adapter).with_context(|| {
format!("no i2cd adapter matched ACPI source {} for {}", adapter, device.name)
})?;
let bytes = execute_ucsi_i2c_command(
adapter_info,
adapter,
*address,
*ten_bit_address,
UcsiCommand::new(GET_CONNECTOR_STATUS, 1, [connector, 0, 0, 0, 0, 0]),
UCSI_CONNECTOR_STATUS_READ_LEN,
)?;
let (_header, payload) = parse_ucsi_payload(&bytes)
.with_context(|| format!("{}: malformed connector-status response", device.name))?;
Ok(parse_connector_summary(&device.name, connector, payload))
}
UcsiTransport::Mmio { base, len } => bail!(
"MMIO connector-status transport is not implemented yet for {:#x}+{:#x}",
base,
len,
),
UcsiTransport::Unknown => bail!("unknown UCSI transport"),
}
}
fn parse_connector_summary(device_name: &str, connector: u8, payload: &[u8]) -> UcsiConnectorSummary {
let state = payload.first().copied().unwrap_or(0);
let connected = state & 0x01 != 0;
let power_direction = if state & 0x02 != 0 { "source" } else { "sink" };
let data_role = if state & 0x04 != 0 { "dfp" } else { "ufp" };
UcsiConnectorSummary {
device: device_name.to_string(),
connector_number: connector,
connected,
data_role: data_role.to_string(),
power_direction: power_direction.to_string(),
input_critical: classify_input_critical(device_name),
}
}
fn classify_input_critical(device_name: &str) -> bool {
let normalized = device_name.to_ascii_lowercase();
normalized.contains("kbd")
|| normalized.contains("key")
|| normalized.contains("touch")
|| normalized.contains("thc")
}
fn parse_ucsi_payload(bytes: &[u8]) -> Option<(UcsiResponseHeader, &[u8])> {
let header = UcsiResponseHeader::parse(bytes)?;
let body = bytes.get(UCSI_RESPONSE_HEADER_LEN..)?;
let body_len = usize::from(header.data_length).min(body.len());
Some((header, &body[..body_len]))
}
fn execute_ucsi_i2c_command(
adapter: &I2cAdapterInfo,
adapter_name: &str,
address: u16,
ten_bit_address: bool,
command: UcsiCommand,
read_len: usize,
) -> Result<Vec<u8>> {
let request = I2cTransferRequest {
adapter: adapter_name.to_string(),
segments: vec![
I2cTransferSegment {
address,
ten_bit_address,
op: i2c_interface::I2cTransferOp::Write(command.as_bytes().to_vec()),
},
I2cTransferSegment {
address,
ten_bit_address,
op: i2c_interface::I2cTransferOp::Read(read_len),
},
],
stop: true,
};
let mut file = OpenOptions::new()
.read(true)
.write(true)
.open("/scheme/i2c/transfer")
.context("failed to open /scheme/i2c/transfer")?;
let payload = ron::ser::to_string(&I2cControlRequest::Transfer {
adapter_id: adapter.id,
request,
})
.context("failed to encode UCSI I2C transfer request")?;
file.write_all(payload.as_bytes())
.context("failed to send UCSI I2C transfer request")?;
let response = read_i2c_control_response(&mut file)?;
match response {
I2cControlResponse::TransferResult(result) => {
if !result.ok {
let detail = result
.error
.clone()
.unwrap_or_else(|| String::from("unknown I2C transfer failure"));
bail!("UCSI I2C transfer failed: {detail}");
}
result
.read_data
.into_iter()
.next()
.context("UCSI I2C transfer returned no response payload")
}
I2cControlResponse::Error(message) => bail!("i2cd returned an error: {message}"),
other => bail!("unexpected i2cd transfer response: {other:?}"),
}
}
fn list_i2c_adapters() -> Result<Vec<I2cAdapterInfo>> {
let mut file = OpenOptions::new()
.read(true)
.write(true)
.open("/scheme/i2c/adapters")
.context("failed to open /scheme/i2c/adapters")?;
let payload = ron::ser::to_string(&I2cControlRequest::ListAdapters)
.context("failed to encode I2C list-adapters request")?;
file.write_all(payload.as_bytes())
.context("failed to request I2C adapter list")?;
let response = read_i2c_control_response(&mut file)?;
match response {
I2cControlResponse::AdapterList(adapters) => Ok(adapters),
I2cControlResponse::Error(message) => bail!("i2cd returned an error: {message}"),
other => bail!("unexpected i2cd list-adapters response: {other:?}"),
}
}
fn match_i2c_adapter<'a>(adapters: &'a [I2cAdapterInfo], wanted: &str) -> Option<&'a I2cAdapterInfo> {
adapters
.iter()
.find(|adapter| adapter.name == wanted)
.or_else(|| adapters.iter().find(|adapter| adapter.name.ends_with(wanted)))
.or_else(|| adapters.iter().find(|adapter| wanted.ends_with(&adapter.name)))
}
fn read_i2c_control_response(file: &mut File) -> Result<I2cControlResponse> {
let mut buffer = vec![0_u8; 4096];
let count = file
.read(&mut buffer)
.context("failed to read I2C control response")?;
buffer.truncate(count);
let text = std::str::from_utf8(&buffer).context("I2C control response was not UTF-8")?;
let trimmed = text.trim();
if trimmed.is_empty() {
return Ok(I2cControlResponse::AdapterList(Vec::new()));
}
ron::from_str(trimmed).context("failed to decode I2C control response")
}
fn read_symbol_id(path: &Path) -> Result<Option<String>> {
let contents = fs::read_to_string(path)
.with_context(|| format!("failed to read ACPI symbol {}", path.display()))?;
let symbol = match ron::from_str::<AmlSymbol>(&contents) {
Ok(symbol) => symbol,
Err(err) => {
log::debug!(
"ucsid: skipping {} because the symbol payload was not a scalar ID: {err}",
path.display(),
);
return Ok(None);
}
};
let id = match symbol.value {
AmlValue::Integer(integer) => eisa_id_from_integer(integer),
AmlValue::String(string) => string,
};
log::debug!("ucsid: {} -> {id}", symbol.name);
Ok(Some(id))
}
fn normalize_device_path(path: &str) -> String {
path.trim_start_matches('\\')
.trim_matches('/')
.replace('/', ".")
}
fn eisa_id_from_integer(integer: u64) -> String {
let vendor = integer & 0xFFFF;
let device = (integer >> 16) & 0xFFFF;
let vendor_rev = ((vendor & 0xFF) << 8) | (vendor >> 8);
let vendor_1 = (((vendor_rev >> 10) & 0x1F) as u8 + 64) as char;
let vendor_2 = (((vendor_rev >> 5) & 0x1F) as u8 + 64) as char;
let vendor_3 = (((vendor_rev >> 0) & 0x1F) as u8 + 64) as char;
let device_1 = (device >> 4) & 0xF;
let device_2 = (device >> 0) & 0xF;
let device_3 = (device >> 12) & 0xF;
let device_4 = (device >> 8) & 0xF;
format!(
"{vendor_1}{vendor_2}{vendor_3}{device_1:01X}{device_2:01X}{device_3:01X}{device_4:01X}"
)
}