6ac41ee37a
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.
455 lines
14 KiB
Rust
455 lines
14 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::{GpioDescriptor, I2cSerialBusDescriptor, ResourceDescriptor};
|
|
use anyhow::{Context, Result};
|
|
use i2c_interface::{
|
|
I2cAdapterInfo, I2cControlRequest, I2cControlResponse, I2cTransferRequest,
|
|
I2cTransferResponse, I2cTransferSegment,
|
|
};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct AmlSymbol {
|
|
name: String,
|
|
value: AmlValue,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
enum AmlValue {
|
|
Integer(u64),
|
|
String(String),
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
struct ExpanderResources {
|
|
i2c: I2cSerialBusDescriptor,
|
|
pin_count: usize,
|
|
gpio_int_count: usize,
|
|
gpio_io_count: usize,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct ExpanderDescriptor {
|
|
device: String,
|
|
hid: String,
|
|
resources: ExpanderResources,
|
|
}
|
|
|
|
struct RegisteredExpander {
|
|
_registration: File,
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
struct GpioControllerInfo {
|
|
id: u32,
|
|
name: String,
|
|
pin_count: usize,
|
|
supports_interrupt: bool,
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
enum GpioControlRequest {
|
|
RegisterController { info: GpioControllerInfo },
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
enum GpioControlResponse {
|
|
ControllerRegistered { id: u32 },
|
|
Error(String),
|
|
}
|
|
|
|
fn main() {
|
|
common::setup_logging(
|
|
"gpio",
|
|
"i2c-gpio-expander",
|
|
"i2c-gpio-expanderd",
|
|
common::output_level(),
|
|
common::file_level(),
|
|
);
|
|
|
|
daemon::Daemon::new(daemon_runner);
|
|
}
|
|
|
|
fn daemon_runner(daemon: daemon::Daemon) -> ! {
|
|
if let Err(err) = daemon_main(daemon) {
|
|
log::error!("i2c-gpio-expanderd: {err:#}");
|
|
process::exit(1);
|
|
}
|
|
|
|
process::exit(0);
|
|
}
|
|
|
|
fn daemon_main(daemon: daemon::Daemon) -> Result<()> {
|
|
let expanders = discover_expanders().context("failed to discover ACPI I2C GPIO expanders")?;
|
|
if expanders.is_empty() {
|
|
log::info!("i2c-gpio-expanderd: no probable ACPI I2C GPIO expanders found");
|
|
}
|
|
|
|
let adapters = list_i2c_adapters().unwrap_or_else(|err| {
|
|
log::warn!("i2c-gpio-expanderd: unable to query i2cd adapters: {err:#}");
|
|
Vec::new()
|
|
});
|
|
|
|
let mut registered = Vec::new();
|
|
for expander in expanders {
|
|
match register_expander(expander, &adapters) {
|
|
Ok(expander) => registered.push(expander),
|
|
Err(err) => log::warn!("i2c-gpio-expanderd: expander registration skipped: {err:#}"),
|
|
}
|
|
}
|
|
|
|
daemon.ready();
|
|
libredox::call::setrens(0, 0).context("failed to enter null namespace")?;
|
|
|
|
log::info!("i2c-gpio-expanderd: registered {} expander(s)", registered.len());
|
|
|
|
loop {
|
|
std::thread::park();
|
|
}
|
|
}
|
|
|
|
fn discover_expanders() -> Result<Vec<ExpanderDescriptor>> {
|
|
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!("i2c-gpio-expanderd: 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 is_excluded_device_id(&id) {
|
|
continue;
|
|
}
|
|
|
|
let Some(device) = file_name
|
|
.strip_suffix("_HID")
|
|
.or_else(|| file_name.strip_suffix("_CID"))
|
|
.map(str::to_owned)
|
|
else {
|
|
continue;
|
|
};
|
|
|
|
let resources = match read_expander_resources(&device) {
|
|
Ok(resources) => resources,
|
|
Err(err) => {
|
|
log::debug!("i2c-gpio-expanderd: skipping {device}: {err:#}");
|
|
continue;
|
|
}
|
|
};
|
|
if resources.gpio_int_count == 0 && resources.gpio_io_count == 0 {
|
|
continue;
|
|
}
|
|
|
|
matched.entry(device).or_insert((id, resources));
|
|
}
|
|
|
|
let mut expanders = Vec::new();
|
|
for (device, (hid, resources)) in matched {
|
|
expanders.push(ExpanderDescriptor {
|
|
device,
|
|
hid,
|
|
resources,
|
|
});
|
|
}
|
|
Ok(expanders)
|
|
}
|
|
|
|
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!(
|
|
"i2c-gpio-expanderd: 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!("i2c-gpio-expanderd: {} -> {id}", symbol.name);
|
|
Ok(Some(id))
|
|
}
|
|
|
|
fn read_expander_resources(device: &str) -> Result<ExpanderResources> {
|
|
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;
|
|
let mut pin_count = 0usize;
|
|
let mut gpio_int_count = 0usize;
|
|
let mut gpio_io_count = 0usize;
|
|
|
|
for resource in resources {
|
|
match resource {
|
|
ResourceDescriptor::I2cSerialBus(bus) if i2c.is_none() => i2c = Some(bus),
|
|
ResourceDescriptor::GpioInt(descriptor) => {
|
|
gpio_int_count += 1;
|
|
pin_count = pin_count.max(pin_count_from_descriptor(&descriptor));
|
|
}
|
|
ResourceDescriptor::GpioIo(descriptor) => {
|
|
gpio_io_count += 1;
|
|
pin_count = pin_count.max(pin_count_from_descriptor(&descriptor));
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
Ok(ExpanderResources {
|
|
i2c: i2c.context("no I2cSerialBus resource was found")?,
|
|
pin_count,
|
|
gpio_int_count,
|
|
gpio_io_count,
|
|
})
|
|
}
|
|
|
|
fn pin_count_from_descriptor(descriptor: &GpioDescriptor) -> usize {
|
|
descriptor
|
|
.pins
|
|
.iter()
|
|
.copied()
|
|
.max()
|
|
.map(|pin| usize::from(pin).saturating_add(1))
|
|
.unwrap_or(0)
|
|
}
|
|
|
|
fn is_excluded_device_id(id: &str) -> bool {
|
|
matches!(
|
|
id,
|
|
"PNP0C50"
|
|
| "ACPI0C50"
|
|
| "INT34C5"
|
|
| "INTC1055"
|
|
| "INT33C2"
|
|
| "INT33C3"
|
|
| "INT3432"
|
|
| "INT3433"
|
|
| "INTC10EF"
|
|
| "AMDI0010"
|
|
| "AMDI0019"
|
|
| "AMDI0510"
|
|
| "PNP0CA0"
|
|
| "AMDI0042"
|
|
) || id.starts_with("ELAN")
|
|
|| id.starts_with("CYAP")
|
|
|| id.starts_with("SYNA")
|
|
}
|
|
|
|
fn register_expander(expander: ExpanderDescriptor, adapters: &[I2cAdapterInfo]) -> Result<RegisteredExpander> {
|
|
let ExpanderDescriptor {
|
|
device,
|
|
hid,
|
|
resources,
|
|
} = expander;
|
|
|
|
let adapter_name = resources
|
|
.i2c
|
|
.resource_source
|
|
.as_ref()
|
|
.map(|source| source.source.clone())
|
|
.filter(|source| !source.is_empty())
|
|
.unwrap_or_else(|| String::from("ACPI-I2C"));
|
|
let adapter = match match_i2c_adapter(adapters, &adapter_name) {
|
|
Some(adapter) => Some(adapter.clone()),
|
|
None => {
|
|
log::warn!(
|
|
"i2c-gpio-expanderd: unable to resolve I2C adapter {} for {}",
|
|
adapter_name,
|
|
device,
|
|
);
|
|
None
|
|
}
|
|
};
|
|
|
|
if let Some(adapter) = adapter.as_ref() {
|
|
if let Err(err) = probe_expander(adapter, &adapter_name, resources.i2c.slave_address) {
|
|
log::warn!(
|
|
"i2c-gpio-expanderd: expander {} probe on {}@{:04x} failed: {err:#}",
|
|
device,
|
|
adapter_name,
|
|
resources.i2c.slave_address,
|
|
);
|
|
}
|
|
}
|
|
|
|
let info = GpioControllerInfo {
|
|
id: 0,
|
|
name: format!("i2c-gpio-expander:{device}"),
|
|
pin_count: resources.pin_count,
|
|
supports_interrupt: resources.gpio_int_count > 0,
|
|
};
|
|
let mut registration = register_with_gpiod(&info)
|
|
.with_context(|| format!("failed to register {device} with gpiod"))?;
|
|
let response = read_gpio_registration_response(&mut registration)
|
|
.with_context(|| format!("failed to read gpiod registration response for {device}"))?;
|
|
|
|
match response {
|
|
GpioControlResponse::ControllerRegistered { id } => {
|
|
log::info!(
|
|
"RB_I2C_GPIO_EXPANDERD_DEVICE device={} hid={} controller_id={} adapter={} addr={:04x} pin_count={} gpio_int={} gpio_io={}",
|
|
device,
|
|
hid,
|
|
id,
|
|
adapter_name,
|
|
resources.i2c.slave_address,
|
|
info.pin_count,
|
|
resources.gpio_int_count,
|
|
resources.gpio_io_count,
|
|
);
|
|
}
|
|
GpioControlResponse::Error(message) => {
|
|
anyhow::bail!("gpiod rejected expander {device}: {message}");
|
|
}
|
|
}
|
|
|
|
Ok(RegisteredExpander {
|
|
_registration: registration,
|
|
})
|
|
}
|
|
|
|
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) => anyhow::bail!("i2cd returned an error: {message}"),
|
|
other => anyhow::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 probe_expander(adapter: &I2cAdapterInfo, adapter_name: &str, address: u16) -> Result<I2cTransferResponse> {
|
|
let request = I2cTransferRequest {
|
|
adapter: adapter_name.to_string(),
|
|
segments: vec![I2cTransferSegment::read(address, 1)],
|
|
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 I2C expander probe request")?;
|
|
file.write_all(payload.as_bytes())
|
|
.context("failed to send I2C expander probe 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"));
|
|
anyhow::bail!("I2C probe failed: {detail}");
|
|
}
|
|
Ok(result)
|
|
}
|
|
I2cControlResponse::Error(message) => anyhow::bail!("i2cd returned an error: {message}"),
|
|
other => anyhow::bail!("unexpected I2C transfer response: {other:?}"),
|
|
}
|
|
}
|
|
|
|
fn register_with_gpiod(info: &GpioControllerInfo) -> Result<File> {
|
|
let mut file = OpenOptions::new()
|
|
.read(true)
|
|
.write(true)
|
|
.open("/scheme/gpio/register")
|
|
.context("failed to open /scheme/gpio/register")?;
|
|
let payload = ron::ser::to_string(&GpioControlRequest::RegisterController { info: info.clone() })
|
|
.context("failed to encode GPIO controller registration")?;
|
|
file.write_all(payload.as_bytes())
|
|
.context("failed to send GPIO controller registration")?;
|
|
Ok(file)
|
|
}
|
|
|
|
fn read_gpio_registration_response(file: &mut File) -> Result<GpioControlResponse> {
|
|
let mut buffer = vec![0_u8; 4096];
|
|
let count = file
|
|
.read(&mut buffer)
|
|
.context("failed to read GPIO registration response")?;
|
|
buffer.truncate(count);
|
|
let text = std::str::from_utf8(&buffer).context("GPIO registration response was not UTF-8")?;
|
|
ron::from_str(text).context("failed to decode GPIO registration response")
|
|
}
|
|
|
|
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 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}"
|
|
)
|
|
}
|