Files
RedBear-OS/drivers/i2c/dw-acpi-i2cd/src/main.rs
T
Red Bear OS bd595851e2 base: apply Red Bear patches on latest upstream/main
251 files: init, acpid, ipcd, netcfg, ihdgd, virtio-gpud, scheme-utils,
inputd, block driver, ptyd, ramfs, randd, initfs bootstrap, path deps,
version +rb0.3.1, author attribution
2026-07-11 11:39:24 +03:00

362 lines
12 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, ExtendedIrqDescriptor, FixedMemory32Descriptor, I2cSerialBusDescriptor,
IrqDescriptor, Memory32RangeDescriptor, ResourceDescriptor,
};
use anyhow::{Context, Result};
use common::{MemoryType, PhysBorrowed, Prot};
use i2c_interface::{I2cAdapterInfo, I2cControlRequest, I2cControlResponse};
use serde::Deserialize;
const SUPPORTED_IDS: &[&str] = &["80860F41", "808622C1", "AMDI0010", "AMDI0019", "AMDI0510"];
const DW_IC_CON: usize = 0x00;
const DW_IC_TAR: usize = 0x04;
const DW_IC_SS_SCL_HCNT: usize = 0x14;
const DW_IC_SS_SCL_LCNT: usize = 0x18;
const DW_IC_DATA_CMD: usize = 0x10;
const DW_IC_INTR_MASK: usize = 0x30;
const DW_IC_CLR_INTR: usize = 0x40;
const DW_IC_ENABLE: usize = 0x6C;
const DW_IC_STATUS: usize = 0x70;
const DW_MMIO_WINDOW: usize = DW_IC_STATUS + core::mem::size_of::<u32>();
#[derive(Debug, Deserialize)]
struct AmlSymbol {
name: String,
value: AmlValue,
}
#[derive(Debug, Deserialize)]
enum AmlValue {
Integer(u64),
String(String),
}
#[derive(Clone, Debug)]
struct ControllerResources {
mmio_base: usize,
mmio_len: usize,
irq: Option<u32>,
serial_bus: Option<I2cSerialBusDescriptor>,
}
#[derive(Debug)]
struct ControllerDescriptor {
device: String,
hid: String,
resources: ControllerResources,
}
struct RegisteredController {
_mmio: Option<PhysBorrowed>,
_registration: File,
}
fn main() {
common::setup_logging(
"bus",
"i2c",
"dw-acpi-i2cd",
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!("dw-acpi-i2cd: {err:#}");
process::exit(1);
}
process::exit(0);
}
fn daemon_main(daemon: daemon::Daemon) -> Result<()> {
common::init();
let controllers = discover_controllers(SUPPORTED_IDS)
.context("failed to discover DesignWare ACPI I2C controllers")?;
if controllers.is_empty() {
log::info!("dw-acpi-i2cd: no supported ACPI controllers found");
}
let mut registered = Vec::new();
for controller in controllers {
match register_controller("dw-acpi", controller) {
Ok(controller) => registered.push(controller),
Err(err) => log::warn!("dw-acpi-i2cd: controller registration skipped: {err:#}"),
}
}
daemon.ready();
libredox::call::setrens(0, 0).context("failed to enter null namespace")?;
log::info!("dw-acpi-i2cd: registered {} controller(s)", registered.len());
loop {
std::thread::park();
}
}
fn discover_controllers(supported_ids: &[&str]) -> Result<Vec<ControllerDescriptor>> {
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!("dw-acpi-i2cd: 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 device = file_name
.strip_suffix("_HID")
.or_else(|| file_name.strip_suffix("_CID"))
.map(str::to_owned);
if let Some(device) = device {
matched.entry(device).or_insert(id);
}
}
let mut controllers = Vec::new();
for (device, hid) in matched {
let resources = read_controller_resources(&device)
.with_context(|| format!("failed to read resources for {device}"))?;
controllers.push(ControllerDescriptor {
device,
hid,
resources,
});
}
Ok(controllers)
}
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!(
"dw-acpi-i2cd: 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!("dw-acpi-i2cd: {} -> {id}", symbol.name);
Ok(Some(id))
}
fn read_controller_resources(device: &str) -> Result<ControllerResources> {
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 mmio = None;
let mut irq = None;
let mut serial_bus = None;
for resource in &resources {
match resource {
ResourceDescriptor::FixedMemory32(FixedMemory32Descriptor {
address,
address_length,
..
}) if mmio.is_none() => {
mmio = Some((*address as usize, (*address_length as usize).max(DW_MMIO_WINDOW)));
}
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).max(DW_MMIO_WINDOW)),
));
}
ResourceDescriptor::Address32(descriptor)
if mmio.is_none()
&& matches!(descriptor.resource_type, AddressResourceType::MemoryRange) =>
{
mmio = Some((
descriptor.minimum as usize,
(descriptor.address_length as usize).max(DW_MMIO_WINDOW),
));
}
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.max(DW_MMIO_WINDOW)));
}
ResourceDescriptor::Irq(IrqDescriptor { interrupts, .. }) if irq.is_none() => {
irq = interrupts.first().copied().map(u32::from);
}
ResourceDescriptor::ExtendedIrq(ExtendedIrqDescriptor { interrupts, .. })
if irq.is_none() =>
{
irq = interrupts.first().copied();
}
ResourceDescriptor::I2cSerialBus(descriptor) if serial_bus.is_none() => {
serial_bus = Some(descriptor.clone());
}
_ => {}
}
}
let (mmio_base, mmio_len) = mmio.context("no MMIO resource was found")?;
Ok(ControllerResources {
mmio_base,
mmio_len,
irq,
serial_bus,
})
}
fn register_controller(prefix: &str, controller: ControllerDescriptor) -> Result<RegisteredController> {
let ControllerDescriptor {
device,
hid,
resources,
} = controller;
let mmio = match PhysBorrowed::map(
resources.mmio_base,
resources.mmio_len,
Prot::RW,
MemoryType::Uncacheable,
) {
Ok(mapping) => Some(mapping),
Err(err) => {
log::warn!(
"dw-acpi-i2cd: failed to map MMIO for {device} ({:#x}, len {:#x}): {err}",
resources.mmio_base,
resources.mmio_len,
);
None
}
};
log::info!(
"dw-acpi-i2cd: discovered {device} hid={hid} mmio={:#x}+{:#x} irq={:?}",
resources.mmio_base,
resources.mmio_len,
resources.irq,
);
log::debug!(
"dw-acpi-i2cd: DesignWare regs con={DW_IC_CON:#x} tar={DW_IC_TAR:#x} data_cmd={DW_IC_DATA_CMD:#x} intr_mask={DW_IC_INTR_MASK:#x} clr_intr={DW_IC_CLR_INTR:#x} enable={DW_IC_ENABLE:#x} ss_hcnt={DW_IC_SS_SCL_HCNT:#x} ss_lcnt={DW_IC_SS_SCL_LCNT:#x}",
);
let info = I2cAdapterInfo {
id: 0,
name: format!("{prefix}:{device}"),
max_transaction_size: 0,
supports_10bit_addr: resources
.serial_bus
.as_ref()
.map(|bus| bus.access_mode_10bit)
.unwrap_or(false),
};
let mut registration = register_adapter(&info)
.with_context(|| format!("failed to register {device} with i2cd"))?;
let response = read_registration_response(&mut registration)
.with_context(|| format!("failed to read i2cd registration response for {device}"))?;
match response {
I2cControlResponse::AdapterRegistered { id } => {
log::info!("dw-acpi-i2cd: adapter {device} registered with i2cd as {id}");
}
other => {
anyhow::bail!("unexpected i2cd registration response for {device}: {other:?}");
}
}
Ok(RegisteredController {
_mmio: mmio,
_registration: registration,
})
}
fn register_adapter(info: &I2cAdapterInfo) -> Result<File> {
let mut file = OpenOptions::new()
.read(true)
.write(true)
.open("/scheme/i2c/register")
.context("failed to open /scheme/i2c/register")?;
let payload = ron::ser::to_string(&I2cControlRequest::RegisterAdapter { info: info.clone() })
.context("failed to encode I2C adapter registration")?;
file.write_all(payload.as_bytes())
.context("failed to send I2C adapter registration")?;
Ok(file)
}
fn read_registration_response(file: &mut File) -> Result<I2cControlResponse> {
let mut buffer = vec![0_u8; 4096];
let count = file
.read(&mut buffer)
.context("failed to read I2C registration response")?;
buffer.truncate(count);
let text = std::str::from_utf8(&buffer).context("I2C registration response was not UTF-8")?;
ron::from_str(text).context("failed to decode I2C registration 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}"
)
}