bd595851e2
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
362 lines
12 KiB
Rust
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}"
|
|
)
|
|
}
|