Files
RedBear-OS/local/patches/base/P2-acpi-i2c-resources.patch
T
vasilito 89247e0d77 Regenerate P2 patches against current upstream (463f76b9 + redox.patch)
Both P2 patches were stale — generated against an older upstream HEAD whose
context lines shifted after redox.patch modified the same files. Regenerated
from scratch against the current upstream commit so they apply cleanly.

P2-boot-runtime-fixes: hwd I2C candidate logging, pcid-spawner initfs detach,
pcid sendfd PCI fd handoff (319 lines)
P2-acpi-i2c-resources: new acpi-resource shared decoder crate (688 lines),
acpid /scheme/acpi/resources/ endpoint, resources.rs re-export shim,
sleep.rs restore (1265 lines)
2026-04-22 23:09:41 +01:00

1266 lines
43 KiB
Diff

diff --git a/Cargo.toml b/Cargo.toml
index 9e776232..d4d108ee 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -20,6 +20,7 @@ members = [
"drivers/common",
"drivers/executor",
+ "drivers/acpi-resource",
"drivers/acpid",
"drivers/hwd",
"drivers/pcid",
diff --git a/drivers/acpi-resource/Cargo.toml b/drivers/acpi-resource/Cargo.toml
new file mode 100644
index 00000000..f30c6d02
--- /dev/null
+++ b/drivers/acpi-resource/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "acpi-resource"
+description = "Shared ACPI resource template decoder"
+version = "0.0.1"
+authors = ["Red Bear OS"]
+repository = "https://gitlab.redox-os.org/redox-os/drivers"
+categories = ["hardware-support"]
+license = "MIT/Apache-2.0"
+edition = "2021"
+
+[dependencies]
+serde.workspace = true
+thiserror.workspace = true
diff --git a/drivers/acpi-resource/src/lib.rs b/drivers/acpi-resource/src/lib.rs
new file mode 100644
index 00000000..57ae4b4b
--- /dev/null
+++ b/drivers/acpi-resource/src/lib.rs
@@ -0,0 +1,688 @@
+use serde::{Deserialize, Serialize};
+use thiserror::Error;
+
+const SMALL_IRQ: u8 = 0x20;
+const SMALL_END_TAG: u8 = 0x78;
+
+const LARGE_MEMORY32: u8 = 0x85;
+const LARGE_FIXED_MEMORY32: u8 = 0x86;
+const LARGE_ADDRESS32: u8 = 0x87;
+const LARGE_EXTENDED_IRQ: u8 = 0x89;
+const LARGE_ADDRESS64: u8 = 0x8A;
+const LARGE_GPIO: u8 = 0x8C;
+const LARGE_SERIAL_BUS: u8 = 0x8E;
+
+const SERIAL_BUS_I2C: u8 = 1;
+const I2C_TYPE_DATA_LEN: usize = 6;
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub enum InterruptTrigger {
+ Edge,
+ Level,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub enum InterruptPolarity {
+ ActiveHigh,
+ ActiveLow,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub enum AddressResourceType {
+ MemoryRange,
+ IoRange,
+ BusNumberRange,
+ Unknown(u8),
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct ResourceSource {
+ pub index: u8,
+ pub source: String,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct IrqDescriptor {
+ pub interrupts: Vec<u8>,
+ pub triggering: InterruptTrigger,
+ pub polarity: InterruptPolarity,
+ pub shareable: bool,
+ pub wake_capable: bool,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct ExtendedIrqDescriptor {
+ pub producer_consumer: bool,
+ pub interrupts: Vec<u32>,
+ pub triggering: InterruptTrigger,
+ pub polarity: InterruptPolarity,
+ pub shareable: bool,
+ pub wake_capable: bool,
+ pub resource_source: Option<ResourceSource>,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct GpioDescriptor {
+ pub revision_id: u8,
+ pub producer_consumer: bool,
+ pub pin_config: u8,
+ pub shareable: bool,
+ pub wake_capable: bool,
+ pub io_restriction: u8,
+ pub triggering: InterruptTrigger,
+ pub polarity: InterruptPolarity,
+ pub drive_strength: u16,
+ pub debounce_timeout: u16,
+ pub pins: Vec<u16>,
+ pub resource_source: Option<ResourceSource>,
+ pub vendor_data: Vec<u8>,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct I2cSerialBusDescriptor {
+ pub revision_id: u8,
+ pub producer_consumer: bool,
+ pub slave_mode: bool,
+ pub connection_sharing: bool,
+ pub type_revision_id: u8,
+ pub access_mode_10bit: bool,
+ pub slave_address: u16,
+ pub connection_speed: u32,
+ pub resource_source: Option<ResourceSource>,
+ pub vendor_data: Vec<u8>,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct Memory32RangeDescriptor {
+ pub write_protect: bool,
+ pub minimum: u32,
+ pub maximum: u32,
+ pub alignment: u32,
+ pub address_length: u32,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct FixedMemory32Descriptor {
+ pub write_protect: bool,
+ pub address: u32,
+ pub address_length: u32,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct Address32Descriptor {
+ pub resource_type: AddressResourceType,
+ pub producer_consumer: bool,
+ pub decode: bool,
+ pub min_address_fixed: bool,
+ pub max_address_fixed: bool,
+ pub specific_flags: u8,
+ pub granularity: u32,
+ pub minimum: u32,
+ pub maximum: u32,
+ pub translation_offset: u32,
+ pub address_length: u32,
+ pub resource_source: Option<ResourceSource>,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct Address64Descriptor {
+ pub resource_type: AddressResourceType,
+ pub producer_consumer: bool,
+ pub decode: bool,
+ pub min_address_fixed: bool,
+ pub max_address_fixed: bool,
+ pub specific_flags: u8,
+ pub granularity: u64,
+ pub minimum: u64,
+ pub maximum: u64,
+ pub translation_offset: u64,
+ pub address_length: u64,
+ pub resource_source: Option<ResourceSource>,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub enum ResourceDescriptor {
+ Irq(IrqDescriptor),
+ ExtendedIrq(ExtendedIrqDescriptor),
+ GpioInt(GpioDescriptor),
+ GpioIo(GpioDescriptor),
+ I2cSerialBus(I2cSerialBusDescriptor),
+ Memory32Range(Memory32RangeDescriptor),
+ FixedMemory32(FixedMemory32Descriptor),
+ Address32(Address32Descriptor),
+ Address64(Address64Descriptor),
+}
+
+#[derive(Debug, Error, PartialEq, Eq)]
+pub enum ResourceDecodeError {
+ #[error("descriptor at offset {offset} overruns the resource template")]
+ TruncatedDescriptor { offset: usize },
+
+ #[error("unsupported small descriptor length {length} for tag {tag:#04x} at offset {offset}")]
+ InvalidSmallLength {
+ offset: usize,
+ tag: u8,
+ length: usize,
+ },
+
+ #[error("descriptor {descriptor} at offset {offset} is shorter than {minimum} bytes")]
+ InvalidLargeLength {
+ offset: usize,
+ descriptor: &'static str,
+ minimum: usize,
+ },
+
+ #[error("descriptor {descriptor} at offset {offset} has an invalid internal offset")]
+ InvalidInternalOffset {
+ offset: usize,
+ descriptor: &'static str,
+ },
+}
+
+pub fn decode_resource_template(
+ bytes: &[u8],
+) -> Result<Vec<ResourceDescriptor>, ResourceDecodeError> {
+ let mut resources = Vec::new();
+ let mut offset = 0usize;
+
+ while offset < bytes.len() {
+ let descriptor = *bytes
+ .get(offset)
+ .ok_or(ResourceDecodeError::TruncatedDescriptor { offset })?;
+
+ if descriptor & 0x80 == 0 {
+ let length = usize::from(descriptor & 0x07);
+ let end = offset + 1 + length;
+ let desc = bytes
+ .get(offset..end)
+ .ok_or(ResourceDecodeError::TruncatedDescriptor { offset })?;
+ let body = &desc[1..];
+
+ match descriptor & 0x78 {
+ SMALL_IRQ => resources.push(ResourceDescriptor::Irq(parse_irq(body, offset)?)),
+ SMALL_END_TAG => break,
+ _ => {}
+ }
+
+ offset = end;
+ continue;
+ }
+
+ let length = usize::from(read_u16(bytes, offset + 1)?);
+ let end = offset + 3 + length;
+ let desc = bytes
+ .get(offset..end)
+ .ok_or(ResourceDecodeError::TruncatedDescriptor { offset })?;
+ let body = &desc[3..];
+
+ match descriptor {
+ LARGE_MEMORY32 => resources.push(ResourceDescriptor::Memory32Range(parse_memory32(
+ body, offset,
+ )?)),
+ LARGE_FIXED_MEMORY32 => resources.push(ResourceDescriptor::FixedMemory32(
+ parse_fixed_memory32(body, offset)?,
+ )),
+ LARGE_ADDRESS32 => {
+ resources.push(ResourceDescriptor::Address32(parse_address32(
+ desc, body, offset,
+ )?));
+ }
+ LARGE_ADDRESS64 => {
+ resources.push(ResourceDescriptor::Address64(parse_address64(
+ desc, body, offset,
+ )?));
+ }
+ LARGE_EXTENDED_IRQ => resources.push(ResourceDescriptor::ExtendedIrq(
+ parse_extended_irq(desc, body, offset)?,
+ )),
+ LARGE_GPIO => {
+ let (is_interrupt, descriptor) = parse_gpio(desc, body, offset)?;
+ resources.push(if is_interrupt {
+ ResourceDescriptor::GpioInt(descriptor)
+ } else {
+ ResourceDescriptor::GpioIo(descriptor)
+ });
+ }
+ LARGE_SERIAL_BUS => {
+ if let Some(descriptor) = parse_i2c_serial_bus(desc, body, offset)? {
+ resources.push(ResourceDescriptor::I2cSerialBus(descriptor));
+ }
+ }
+ _ => {}
+ }
+
+ offset = end;
+ }
+
+ Ok(resources)
+}
+
+fn parse_irq(body: &[u8], offset: usize) -> Result<IrqDescriptor, ResourceDecodeError> {
+ if body.len() != 2 && body.len() != 3 {
+ return Err(ResourceDecodeError::InvalidSmallLength {
+ offset,
+ tag: SMALL_IRQ,
+ length: body.len(),
+ });
+ }
+
+ let mask = u16::from_le_bytes([body[0], body[1]]);
+ let flags = body.get(2).copied().unwrap_or(0);
+ let interrupts = (0..16)
+ .filter(|irq| mask & (1 << irq) != 0)
+ .map(|irq| irq as u8)
+ .collect();
+
+ Ok(IrqDescriptor {
+ interrupts,
+ triggering: if flags & 0x01 != 0 {
+ InterruptTrigger::Level
+ } else {
+ InterruptTrigger::Edge
+ },
+ polarity: if flags & 0x08 != 0 {
+ InterruptPolarity::ActiveLow
+ } else {
+ InterruptPolarity::ActiveHigh
+ },
+ shareable: flags & 0x10 != 0,
+ wake_capable: flags & 0x20 != 0,
+ })
+}
+
+fn parse_extended_irq(
+ desc: &[u8],
+ body: &[u8],
+ offset: usize,
+) -> Result<ExtendedIrqDescriptor, ResourceDecodeError> {
+ ensure_length(body, 2, offset, "ExtendedIrq")?;
+
+ let flags = body[0];
+ let count = usize::from(body[1]);
+ let ints_len = count * 4;
+ ensure_length(body, 2 + ints_len, offset, "ExtendedIrq")?;
+
+ let interrupts = (0..count)
+ .map(|index| read_u32(body, 2 + index * 4))
+ .collect::<Result<Vec<_>, _>>()?;
+ let resource_source = if body.len() > 2 + ints_len {
+ Some(parse_source_inline(&body[2 + ints_len..]))
+ } else {
+ None
+ };
+
+ let _ = desc;
+
+ Ok(ExtendedIrqDescriptor {
+ producer_consumer: flags & 0x01 != 0,
+ triggering: if flags & 0x02 != 0 {
+ InterruptTrigger::Level
+ } else {
+ InterruptTrigger::Edge
+ },
+ polarity: if flags & 0x04 != 0 {
+ InterruptPolarity::ActiveLow
+ } else {
+ InterruptPolarity::ActiveHigh
+ },
+ shareable: flags & 0x08 != 0,
+ wake_capable: flags & 0x10 != 0,
+ interrupts,
+ resource_source,
+ })
+}
+
+fn parse_gpio(
+ desc: &[u8],
+ body: &[u8],
+ offset: usize,
+) -> Result<(bool, GpioDescriptor), ResourceDecodeError> {
+ ensure_length(body, 20, offset, "Gpio")?;
+
+ let connection_type = body[1];
+ let flags = read_u16(body, 2)?;
+ let int_flags = read_u16(body, 4)?;
+ let pin_table_offset = usize::from(read_u16(body, 11)?);
+ let resource_source_index = body[13];
+ let resource_source_offset = usize::from(read_u16(body, 14)?);
+ let vendor_offset = usize::from(read_u16(body, 16)?);
+ let vendor_length = usize::from(read_u16(body, 18)?);
+
+ let pins_end = min_nonzero([resource_source_offset, vendor_offset, desc.len()]);
+ let pins = parse_u16_list(desc, pin_table_offset, pins_end, offset, "Gpio")?;
+ let resource_source = parse_source_absolute(
+ desc,
+ resource_source_offset,
+ min_nonzero([vendor_offset, desc.len()]),
+ resource_source_index,
+ offset,
+ "Gpio",
+ )?;
+ let vendor_data = parse_blob_absolute(desc, vendor_offset, vendor_length, offset, "Gpio")?;
+
+ Ok((
+ connection_type == 0,
+ GpioDescriptor {
+ revision_id: body[0],
+ producer_consumer: flags & 0x0001 != 0,
+ pin_config: body[6],
+ shareable: int_flags & 0x0008 != 0,
+ wake_capable: int_flags & 0x0010 != 0,
+ io_restriction: (int_flags & 0x0003) as u8,
+ triggering: if int_flags & 0x0001 != 0 {
+ InterruptTrigger::Level
+ } else {
+ InterruptTrigger::Edge
+ },
+ polarity: if int_flags & 0x0002 != 0 {
+ InterruptPolarity::ActiveLow
+ } else {
+ InterruptPolarity::ActiveHigh
+ },
+ drive_strength: read_u16(body, 7)?,
+ debounce_timeout: read_u16(body, 9)?,
+ pins,
+ resource_source,
+ vendor_data,
+ },
+ ))
+}
+
+fn parse_i2c_serial_bus(
+ desc: &[u8],
+ body: &[u8],
+ offset: usize,
+) -> Result<Option<I2cSerialBusDescriptor>, ResourceDecodeError> {
+ ensure_length(body, 15, offset, "SerialBus")?;
+ if body[2] != SERIAL_BUS_I2C {
+ return Ok(None);
+ }
+
+ let type_data_length = usize::from(read_u16(body, 7)?);
+ if type_data_length < I2C_TYPE_DATA_LEN {
+ return Err(ResourceDecodeError::InvalidLargeLength {
+ offset,
+ descriptor: "I2cSerialBus",
+ minimum: 15,
+ });
+ }
+
+ let vendor_length = type_data_length - I2C_TYPE_DATA_LEN;
+ let vendor_data = parse_blob_absolute(desc, 18, vendor_length, offset, "I2cSerialBus")?;
+ let resource_source = parse_source_absolute(
+ desc,
+ 12 + type_data_length,
+ desc.len(),
+ body[1],
+ offset,
+ "I2cSerialBus",
+ )?;
+
+ Ok(Some(I2cSerialBusDescriptor {
+ revision_id: body[0],
+ producer_consumer: body[3] & 0x02 != 0,
+ slave_mode: body[3] & 0x01 != 0,
+ connection_sharing: body[3] & 0x04 != 0,
+ type_revision_id: body[6],
+ access_mode_10bit: read_u16(body, 4)? & 0x0001 != 0,
+ connection_speed: read_u32(body, 9)?,
+ slave_address: read_u16(body, 13)?,
+ resource_source,
+ vendor_data,
+ }))
+}
+
+fn parse_memory32(
+ body: &[u8],
+ offset: usize,
+) -> Result<Memory32RangeDescriptor, ResourceDecodeError> {
+ ensure_length(body, 17, offset, "Memory32Range")?;
+ Ok(Memory32RangeDescriptor {
+ write_protect: body[0] & 0x01 != 0,
+ minimum: read_u32(body, 1)?,
+ maximum: read_u32(body, 5)?,
+ alignment: read_u32(body, 9)?,
+ address_length: read_u32(body, 13)?,
+ })
+}
+
+fn parse_fixed_memory32(
+ body: &[u8],
+ offset: usize,
+) -> Result<FixedMemory32Descriptor, ResourceDecodeError> {
+ ensure_length(body, 9, offset, "FixedMemory32")?;
+ Ok(FixedMemory32Descriptor {
+ write_protect: body[0] & 0x01 != 0,
+ address: read_u32(body, 1)?,
+ address_length: read_u32(body, 5)?,
+ })
+}
+
+fn parse_address32(
+ desc: &[u8],
+ body: &[u8],
+ offset: usize,
+) -> Result<Address32Descriptor, ResourceDecodeError> {
+ ensure_length(body, 23, offset, "Address32")?;
+ Ok(Address32Descriptor {
+ resource_type: parse_address_type(body[0]),
+ producer_consumer: body[1] & 0x01 != 0,
+ decode: body[1] & 0x02 != 0,
+ min_address_fixed: body[1] & 0x04 != 0,
+ max_address_fixed: body[1] & 0x08 != 0,
+ specific_flags: body[2],
+ granularity: read_u32(body, 3)?,
+ minimum: read_u32(body, 7)?,
+ maximum: read_u32(body, 11)?,
+ translation_offset: read_u32(body, 15)?,
+ address_length: read_u32(body, 19)?,
+ resource_source: if desc.len() > 26 {
+ parse_source_absolute(desc, 26, desc.len(), desc[26], offset, "Address32")?
+ } else {
+ None
+ },
+ })
+}
+
+fn parse_address64(
+ desc: &[u8],
+ body: &[u8],
+ offset: usize,
+) -> Result<Address64Descriptor, ResourceDecodeError> {
+ ensure_length(body, 43, offset, "Address64")?;
+ Ok(Address64Descriptor {
+ resource_type: parse_address_type(body[0]),
+ producer_consumer: body[1] & 0x01 != 0,
+ decode: body[1] & 0x02 != 0,
+ min_address_fixed: body[1] & 0x04 != 0,
+ max_address_fixed: body[1] & 0x08 != 0,
+ specific_flags: body[2],
+ granularity: read_u64(body, 3)?,
+ minimum: read_u64(body, 11)?,
+ maximum: read_u64(body, 19)?,
+ translation_offset: read_u64(body, 27)?,
+ address_length: read_u64(body, 35)?,
+ resource_source: if desc.len() > 46 {
+ parse_source_absolute(desc, 46, desc.len(), desc[46], offset, "Address64")?
+ } else {
+ None
+ },
+ })
+}
+
+fn ensure_length(
+ body: &[u8],
+ minimum: usize,
+ offset: usize,
+ descriptor: &'static str,
+) -> Result<(), ResourceDecodeError> {
+ if body.len() < minimum {
+ return Err(ResourceDecodeError::InvalidLargeLength {
+ offset,
+ descriptor,
+ minimum,
+ });
+ }
+ Ok(())
+}
+
+fn parse_source_inline(bytes: &[u8]) -> ResourceSource {
+ let index = bytes.first().copied().unwrap_or(0);
+ let source = bytes.get(1..).map(parse_nul_string).unwrap_or_default();
+ ResourceSource { index, source }
+}
+
+fn parse_source_absolute(
+ desc: &[u8],
+ start: usize,
+ end: usize,
+ index: u8,
+ offset: usize,
+ descriptor: &'static str,
+) -> Result<Option<ResourceSource>, ResourceDecodeError> {
+ if start == 0 || start >= end || start > desc.len() {
+ return Ok(None);
+ }
+ let slice = desc
+ .get(start..end)
+ .ok_or(ResourceDecodeError::InvalidInternalOffset { offset, descriptor })?;
+ Ok(Some(ResourceSource {
+ index,
+ source: parse_nul_string(slice),
+ }))
+}
+
+fn parse_blob_absolute(
+ desc: &[u8],
+ start: usize,
+ length: usize,
+ offset: usize,
+ descriptor: &'static str,
+) -> Result<Vec<u8>, ResourceDecodeError> {
+ if start == 0 || length == 0 {
+ return Ok(Vec::new());
+ }
+ let end = start + length;
+ Ok(desc
+ .get(start..end)
+ .ok_or(ResourceDecodeError::InvalidInternalOffset { offset, descriptor })?
+ .to_vec())
+}
+
+fn parse_u16_list(
+ desc: &[u8],
+ start: usize,
+ end: usize,
+ offset: usize,
+ descriptor: &'static str,
+) -> Result<Vec<u16>, ResourceDecodeError> {
+ if start == 0 || start >= end || start > desc.len() {
+ return Ok(Vec::new());
+ }
+ let slice = desc
+ .get(start..end)
+ .ok_or(ResourceDecodeError::InvalidInternalOffset { offset, descriptor })?;
+ if slice.len() % 2 != 0 {
+ return Err(ResourceDecodeError::InvalidInternalOffset { offset, descriptor });
+ }
+ slice
+ .chunks_exact(2)
+ .map(|chunk| Ok(u16::from_le_bytes([chunk[0], chunk[1]])))
+ .collect()
+}
+
+fn parse_nul_string(bytes: &[u8]) -> String {
+ let end = bytes
+ .iter()
+ .position(|byte| *byte == 0)
+ .unwrap_or(bytes.len());
+ String::from_utf8_lossy(&bytes[..end]).to_string()
+}
+
+fn parse_address_type(value: u8) -> AddressResourceType {
+ match value {
+ 0 => AddressResourceType::MemoryRange,
+ 1 => AddressResourceType::IoRange,
+ 2 => AddressResourceType::BusNumberRange,
+ other => AddressResourceType::Unknown(other),
+ }
+}
+
+fn read_u16(bytes: &[u8], offset: usize) -> Result<u16, ResourceDecodeError> {
+ let slice = bytes
+ .get(offset..offset + 2)
+ .ok_or(ResourceDecodeError::TruncatedDescriptor { offset })?;
+ Ok(u16::from_le_bytes([slice[0], slice[1]]))
+}
+
+fn read_u32(bytes: &[u8], offset: usize) -> Result<u32, ResourceDecodeError> {
+ let slice = bytes
+ .get(offset..offset + 4)
+ .ok_or(ResourceDecodeError::TruncatedDescriptor { offset })?;
+ Ok(u32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]]))
+}
+
+fn read_u64(bytes: &[u8], offset: usize) -> Result<u64, ResourceDecodeError> {
+ let slice = bytes
+ .get(offset..offset + 8)
+ .ok_or(ResourceDecodeError::TruncatedDescriptor { offset })?;
+ Ok(u64::from_le_bytes([
+ slice[0], slice[1], slice[2], slice[3], slice[4], slice[5], slice[6], slice[7],
+ ]))
+}
+
+fn min_nonzero<const N: usize>(values: [usize; N]) -> usize {
+ values
+ .into_iter()
+ .filter(|value| *value != 0)
+ .min()
+ .unwrap_or(0)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{decode_resource_template, ResourceDescriptor};
+
+ #[test]
+ fn decodes_small_irq_descriptor() {
+ let resources = decode_resource_template(&[0x23, 0x0A, 0x00, 0x19, 0x79, 0x00]).unwrap();
+
+ assert!(matches!(
+ &resources[0],
+ ResourceDescriptor::Irq(descriptor)
+ if descriptor.interrupts == vec![1, 3]
+ && descriptor.shareable
+ && descriptor.wake_capable == false
+ ));
+ }
+
+ #[test]
+ fn decodes_i2c_serial_bus_descriptor() {
+ let template = [
+ 0x8E, 0x14, 0x00, 0x01, 0x02, 0x01, 0x02, 0x00, 0x00, 0x01, 0x06, 0x00, 0x80, 0x1A,
+ 0x06, 0x00, 0x15, 0x00, b'I', b'2', b'C', b'0', 0x00, 0x79, 0x00,
+ ];
+ let resources = decode_resource_template(&template).unwrap();
+
+ assert!(matches!(
+ &resources[0],
+ ResourceDescriptor::I2cSerialBus(descriptor)
+ if descriptor.connection_speed == 400_000
+ && descriptor.slave_address == 0x15
+ && descriptor.resource_source.as_ref().map(|source| source.source.as_str())
+ == Some("I2C0")
+ ));
+ }
+
+ #[test]
+ fn decodes_gpio_interrupt_descriptor() {
+ let template = [
+ 0x8C, 0x1B, 0x00, 0x01, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17,
+ 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, b'\\', b'_', b'S', b'B',
+ 0x00, 0x79, 0x00,
+ ];
+ let resources = decode_resource_template(&template).unwrap();
+
+ assert!(matches!(&resources[0], ResourceDescriptor::GpioInt(_)));
+ }
+}
diff --git a/drivers/acpid/Cargo.toml b/drivers/acpid/Cargo.toml
index f03a4ccb..712b6d6e 100644
--- a/drivers/acpid/Cargo.toml
+++ b/drivers/acpid/Cargo.toml
@@ -8,6 +8,7 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
+acpi-resource = { path = "../acpi-resource" }
acpi = { git = "https://github.com/jackpot51/acpi.git" }
arrayvec = "0.7.6"
log.workspace = true
diff --git a/drivers/acpid/src/main.rs b/drivers/acpid/src/main.rs
index e388cd46..3b0deeab 100644
--- a/drivers/acpid/src/main.rs
+++ b/drivers/acpid/src/main.rs
@@ -15,8 +15,9 @@ mod aml_physmem;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
mod ec;
-mod sleep;
+mod resources;
mod scheme;
+mod sleep;
#[derive(Debug, Error)]
enum StartupError {
@@ -179,15 +180,18 @@ fn run_acpid(daemon: daemon::Daemon) -> Result<(), StartupError> {
while mounted {
let Some(event) = event_queue.next().transpose().map_err(|error| {
StartupError::runtime("failed to read event file", error.to_string())
- })? else {
+ })?
+ else {
break;
};
if event.fd == socket.inner().raw() {
loop {
- match handler.process_requests_nonblocking(&mut scheme).map_err(|error| {
- StartupError::runtime("failed to process requests", error.to_string())
- })? {
+ match handler
+ .process_requests_nonblocking(&mut scheme)
+ .map_err(|error| {
+ StartupError::runtime("failed to process requests", error.to_string())
+ })? {
ControlFlow::Continue(()) => {}
ControlFlow::Break(()) => break,
}
@@ -204,7 +208,10 @@ fn run_acpid(daemon: daemon::Daemon) -> Result<(), StartupError> {
drop(event_queue);
acpi_context.set_global_s_state(5).map_err(|error| {
- StartupError::runtime("failed to shut down after kernel request", error.to_string())
+ StartupError::runtime(
+ "failed to shut down after kernel request",
+ error.to_string(),
+ )
})
}
@@ -289,8 +296,8 @@ mod tests {
#[test]
fn xsdt_physaddrs_parse_without_panic() {
let payload = [
- 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11,
- 0x00, 0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0x99,
+ 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00, 0xFF, 0xEE, 0xDD, 0xCC, 0xBB,
+ 0xAA, 0x99,
];
let sdt = parse_root_sdt(make_sdt(*b"XSDT", &payload))
.unwrap()
diff --git a/drivers/acpid/src/resources.rs b/drivers/acpid/src/resources.rs
new file mode 100644
index 00000000..86bcfd1e
--- /dev/null
+++ b/drivers/acpid/src/resources.rs
@@ -0,0 +1 @@
+pub use acpi_resource::{decode_resource_template, ResourceDescriptor};
diff --git a/drivers/acpid/src/scheme.rs b/drivers/acpid/src/scheme.rs
index 7070e8b9..4fe3b8d8 100644
--- a/drivers/acpid/src/scheme.rs
+++ b/drivers/acpid/src/scheme.rs
@@ -15,7 +15,9 @@ use syscall::FobtainFdFlags;
use syscall::data::Stat;
use syscall::error::{Error, Result};
-use syscall::error::{EACCES, EAGAIN, EBADF, EBADFD, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR, EOPNOTSUPP};
+use syscall::error::{
+ EACCES, EAGAIN, EBADF, EBADFD, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR, EOPNOTSUPP,
+};
use syscall::flag::{MODE_DIR, MODE_FILE};
use syscall::flag::{O_ACCMODE, O_DIRECTORY, O_RDONLY, O_STAT, O_SYMLINK};
use syscall::{EOVERFLOW, EPERM};
@@ -24,6 +26,7 @@ use crate::acpi::{
AcpiBattery, AcpiContext, AcpiPowerAdapter, AcpiPowerSnapshot, AmlSymbols, DmiInfo,
SdtSignature,
};
+use crate::resources::{decode_resource_template, ResourceDescriptor};
pub struct AcpiScheme<'acpi, 'sock> {
ctx: &'acpi AcpiContext,
@@ -42,6 +45,8 @@ enum HandleKind<'a> {
Table(SdtSignature),
Symbols(RwLockReadGuard<'a, AmlSymbols>),
Symbol { name: String, description: String },
+ ResourcesDir,
+ Resources(String),
Reboot,
DmiDir,
Dmi(String),
@@ -197,6 +202,7 @@ fn top_level_entries(power_available: bool) -> Vec<(&'static str, DirentKind)> {
let mut entries = vec![
("tables", DirentKind::Directory),
("symbols", DirentKind::Directory),
+ ("resources", DirentKind::Directory),
("dmi", DirentKind::Directory),
("reboot", DirentKind::Regular),
];
@@ -206,6 +212,37 @@ fn top_level_entries(power_available: bool) -> Vec<(&'static str, DirentKind)> {
entries
}
+fn resource_symbol_path(path: &str) -> Option<String> {
+ let normalized = path.trim_matches('/').trim_start_matches('\\');
+ if normalized.is_empty() {
+ return None;
+ }
+
+ let normalized = normalized.replace('/', ".");
+ if normalized.is_empty() {
+ None
+ } else {
+ Some(format!("{normalized}._CRS"))
+ }
+}
+
+fn resource_entry_name(symbol: &str) -> Option<String> {
+ symbol
+ .strip_suffix("._CRS")
+ .map(str::to_string)
+ .filter(|path| !path.is_empty())
+}
+
+fn resource_dir_entries<'a>(symbols: impl IntoIterator<Item = &'a str>) -> Vec<String> {
+ let mut entries = symbols
+ .into_iter()
+ .filter_map(resource_entry_name)
+ .collect::<Vec<_>>();
+ entries.sort_unstable();
+ entries.dedup();
+ entries
+}
+
impl HandleKind<'_> {
fn is_dir(&self) -> bool {
match self {
@@ -214,6 +251,8 @@ impl HandleKind<'_> {
Self::Table(_) => false,
Self::Symbols(_) => true,
Self::Symbol { .. } => false,
+ Self::ResourcesDir => true,
+ Self::Resources(_) => false,
Self::Reboot => false,
Self::DmiDir => true,
Self::Dmi(_) => false,
@@ -235,12 +274,14 @@ impl HandleKind<'_> {
.ok_or(Error::new(EBADFD))?
.length(),
Self::Symbol { description, .. } => description.len(),
+ Self::Resources(contents) => contents.len(),
Self::Reboot => 0,
Self::Dmi(contents) => contents.len(),
Self::PowerFile(contents) => contents.len(),
// Directories
Self::TopLevel
| Self::Symbols(_)
+ | Self::ResourcesDir
| Self::Tables
| Self::DmiDir
| Self::PowerDir
@@ -280,6 +321,58 @@ impl<'acpi, 'sock> AcpiScheme<'acpi, 'sock> {
matches!(self.ctx.power_snapshot(), Ok(_))
}
+ fn resources_handle(&self, path: &str) -> Result<HandleKind<'acpi>> {
+ if !self.ctx.pci_ready() {
+ let display_path = if path.is_empty() { "resources" } else { path };
+ log::warn!(
+ "Deferring ACPI resource lookup for {display_path} until PCI registration is ready"
+ );
+ return Err(Error::new(EAGAIN));
+ }
+
+ let normalized = path.trim_matches('/');
+ if normalized.is_empty() {
+ return Ok(HandleKind::ResourcesDir);
+ }
+
+ let symbol_path = resource_symbol_path(normalized).ok_or(Error::new(ENOENT))?;
+ if self.ctx.aml_lookup(&symbol_path).is_none() {
+ return Err(Error::new(ENOENT));
+ }
+
+ let aml_name =
+ AmlName::from_str(&format!("\\{symbol_path}")).map_err(|_| Error::new(ENOENT))?;
+ let buffer = match self.ctx.aml_eval(aml_name, Vec::new()) {
+ Ok(AmlSerdeValue::Buffer(bytes)) => bytes,
+ Ok(other) => {
+ log::debug!(
+ "Skipping ACPI resources for {normalized} due to unexpected _CRS value: {:?}",
+ other
+ );
+ return Err(Error::new(ENOENT));
+ }
+ Err(error) => {
+ log::debug!(
+ "Failed to evaluate ACPI resources for {symbol_path}: {:?}",
+ error
+ );
+ return Err(Error::new(ENOENT));
+ }
+ };
+
+ let descriptors: Vec<ResourceDescriptor> =
+ decode_resource_template(&buffer).map_err(|error| {
+ log::warn!("Failed to decode ACPI _CRS for {symbol_path}: {error}");
+ Error::new(EIO)
+ })?;
+ let serialized = ron::ser::to_string(&descriptors).map_err(|error| {
+ log::warn!("Failed to serialize decoded ACPI resources for {symbol_path}: {error}");
+ Error::new(EIO)
+ })?;
+
+ Ok(HandleKind::Resources(serialized))
+ }
+
fn power_handle(&self, path: &str) -> Result<HandleKind<'acpi>> {
let normalized = path.trim_matches('/');
self.power_snapshot()?;
@@ -464,76 +557,85 @@ impl SchemeSync for AcpiScheme<'_, '_> {
let kind = match handle.kind {
HandleKind::SchemeRoot => {
- // TODO: arrayvec
- let components = {
- let mut v = arrayvec::ArrayVec::<&str, 4>::new();
- let it = path.split('/');
- for component in it.take(4) {
- v.push(component);
- }
-
- v
- };
-
- match &*components {
- [""] => HandleKind::TopLevel,
- ["reboot"] => HandleKind::Reboot,
- ["dmi"] => {
- if flag_dir || flag_stat || path.ends_with('/') {
- HandleKind::DmiDir
- } else {
- HandleKind::Dmi(
- dmi_contents(self.ctx.dmi_info(), "match_all")
- .expect("match_all should always resolve"),
- )
+ if path == "resources" || path == "resources/" {
+ self.resources_handle("")?
+ } else if let Some(rest) = path.strip_prefix("resources/") {
+ self.resources_handle(rest)?
+ } else {
+ // TODO: arrayvec
+ let components = {
+ let mut v = arrayvec::ArrayVec::<&str, 4>::new();
+ let it = path.split('/');
+ for component in it.take(4) {
+ v.push(component);
}
- }
- ["dmi", ""] => HandleKind::DmiDir,
- ["dmi", field] => HandleKind::Dmi(
- dmi_contents(self.ctx.dmi_info(), field).ok_or(Error::new(ENOENT))?,
- ),
- ["power"] => self.power_handle("")?,
- ["power", tail] => self.power_handle(tail)?,
- ["power", a, b] => self.power_handle(&format!("{a}/{b}"))?,
- ["power", a, b, c] => self.power_handle(&format!("{a}/{b}/{c}"))?,
- ["register_pci"] => HandleKind::RegisterPci,
- ["tables"] => HandleKind::Tables,
-
- ["tables", table] => {
- let signature = parse_table(table.as_bytes()).ok_or(Error::new(ENOENT))?;
- HandleKind::Table(signature)
- }
- ["symbols"] => {
- if !self.ctx.pci_ready() {
- log::warn!("Deferring AML symbol scan until PCI registration is ready");
- return Err(Error::new(EAGAIN));
+ v
+ };
+
+ match &*components {
+ [""] => HandleKind::TopLevel,
+ ["reboot"] => HandleKind::Reboot,
+ ["dmi"] => {
+ if flag_dir || flag_stat || path.ends_with('/') {
+ HandleKind::DmiDir
+ } else {
+ HandleKind::Dmi(
+ dmi_contents(self.ctx.dmi_info(), "match_all")
+ .expect("match_all should always resolve"),
+ )
+ }
}
- if let Ok(aml_symbols) = self.ctx.aml_symbols() {
- HandleKind::Symbols(aml_symbols)
- } else {
- return Err(Error::new(EIO));
+ ["dmi", ""] => HandleKind::DmiDir,
+ ["dmi", field] => HandleKind::Dmi(
+ dmi_contents(self.ctx.dmi_info(), field).ok_or(Error::new(ENOENT))?,
+ ),
+ ["power"] => self.power_handle("")?,
+ ["power", tail] => self.power_handle(tail)?,
+ ["power", a, b] => self.power_handle(&format!("{a}/{b}"))?,
+ ["power", a, b, c] => self.power_handle(&format!("{a}/{b}/{c}"))?,
+ ["register_pci"] => HandleKind::RegisterPci,
+ ["tables"] => HandleKind::Tables,
+
+ ["tables", table] => {
+ let signature =
+ parse_table(table.as_bytes()).ok_or(Error::new(ENOENT))?;
+ HandleKind::Table(signature)
}
- }
- ["symbols", symbol] => {
- if !self.ctx.pci_ready() {
- log::warn!(
- "Deferring AML symbol lookup for {symbol} until PCI registration is ready"
- );
- return Err(Error::new(EAGAIN));
+ ["symbols"] => {
+ if !self.ctx.pci_ready() {
+ log::warn!(
+ "Deferring AML symbol scan until PCI registration is ready"
+ );
+ return Err(Error::new(EAGAIN));
+ }
+ if let Ok(aml_symbols) = self.ctx.aml_symbols() {
+ HandleKind::Symbols(aml_symbols)
+ } else {
+ return Err(Error::new(EIO));
+ }
}
- if let Some(description) = self.ctx.aml_lookup(symbol) {
- HandleKind::Symbol {
- name: (*symbol).to_owned(),
- description,
+
+ ["symbols", symbol] => {
+ if !self.ctx.pci_ready() {
+ log::warn!(
+ "Deferring AML symbol lookup for {symbol} until PCI registration is ready"
+ );
+ return Err(Error::new(EAGAIN));
+ }
+ if let Some(description) = self.ctx.aml_lookup(symbol) {
+ HandleKind::Symbol {
+ name: (*symbol).to_owned(),
+ description,
+ }
+ } else {
+ return Err(Error::new(ENOENT));
}
- } else {
- return Err(Error::new(ENOENT));
}
- }
- _ => return Err(Error::new(ENOENT)),
+ _ => return Err(Error::new(ENOENT)),
+ }
}
}
HandleKind::DmiDir => {
@@ -545,6 +647,7 @@ impl SchemeSync for AcpiScheme<'_, '_> {
)
}
}
+ HandleKind::ResourcesDir => self.resources_handle(path)?,
HandleKind::Symbols(ref aml_symbols) => {
if let Some(description) = aml_symbols.lookup(path) {
HandleKind::Symbol {
@@ -646,6 +749,7 @@ impl SchemeSync for AcpiScheme<'_, '_> {
.ok_or(Error::new(EBADFD))?
.as_slice(),
HandleKind::Symbol { description, .. } => description.as_bytes(),
+ HandleKind::Resources(contents) => contents.as_bytes(),
HandleKind::Dmi(contents) => contents.as_bytes(),
HandleKind::PowerFile(contents) => contents.as_bytes(),
_ => return Err(Error::new(EINVAL)),
@@ -698,6 +802,19 @@ impl SchemeSync for AcpiScheme<'_, '_> {
})?;
}
}
+ HandleKind::ResourcesDir => {
+ let aml_symbols = self.ctx.aml_symbols().map_err(|_| Error::new(EIO))?;
+ let entries =
+ resource_dir_entries(aml_symbols.symbols_cache().keys().map(String::as_str));
+ for (idx, name) in entries.iter().enumerate().skip(opaque_offset as usize) {
+ buf.entry(DirEntry {
+ inode: 0,
+ next_opaque_id: idx as u64 + 1,
+ name,
+ kind: DirentKind::Regular,
+ })?;
+ }
+ }
HandleKind::PowerDir => {
const POWER_ROOT_ENTRIES: &[(&str, DirentKind)] = &[
("on_battery", DirentKind::Regular),
@@ -958,7 +1075,7 @@ impl SchemeSync for AcpiScheme<'_, '_> {
#[cfg(test)]
mod tests {
- use super::{dmi_contents, top_level_entries};
+ use super::{dmi_contents, resource_dir_entries, resource_symbol_path, top_level_entries};
use crate::acpi::DmiInfo;
use syscall::dirent::DirentKind;
@@ -994,9 +1111,9 @@ mod tests {
#[test]
fn top_level_entries_always_include_reboot() {
let entries = top_level_entries(false);
- assert!(entries.iter().any(|(name, kind)| {
- *name == "reboot" && matches!(kind, DirentKind::Regular)
- }));
+ assert!(entries
+ .iter()
+ .any(|(name, kind)| { *name == "reboot" && matches!(kind, DirentKind::Regular) }));
}
#[test]
@@ -1005,8 +1122,40 @@ mod tests {
let visible = top_level_entries(true);
assert!(!hidden.iter().any(|(name, _)| *name == "power"));
- assert!(visible.iter().any(|(name, kind)| {
- *name == "power" && matches!(kind, DirentKind::Directory)
- }));
+ assert!(visible
+ .iter()
+ .any(|(name, kind)| { *name == "power" && matches!(kind, DirentKind::Directory) }));
+ }
+
+ #[test]
+ fn top_level_entries_always_include_resources() {
+ let entries = top_level_entries(false);
+ assert!(entries
+ .iter()
+ .any(|(name, kind)| { *name == "resources" && matches!(kind, DirentKind::Directory) }));
+ }
+
+ #[test]
+ fn resource_symbol_path_accepts_dotted_and_slash_paths() {
+ assert_eq!(
+ resource_symbol_path("_SB.PCI0.I2C0.TPD0").as_deref(),
+ Some("_SB.PCI0.I2C0.TPD0._CRS")
+ );
+ assert_eq!(
+ resource_symbol_path("\\_SB/PCI0/I2C0/TPD0").as_deref(),
+ Some("_SB.PCI0.I2C0.TPD0._CRS")
+ );
+ }
+
+ #[test]
+ fn resource_dir_entries_list_devices_with_crs_suffix() {
+ assert_eq!(
+ resource_dir_entries([
+ "_SB.PCI0.I2C0.TPD0._CRS",
+ "_SB.PCI0.I2C1.TPD1._HID",
+ "_SB.PCI0.I2C0.TPD0._CRS",
+ ]),
+ vec!["_SB.PCI0.I2C0.TPD0".to_string()]
+ );
}
}
diff --git a/drivers/acpid/src/sleep.rs b/drivers/acpid/src/sleep.rs
new file mode 100644
index 00000000..e5d6ab22
--- /dev/null
+++ b/drivers/acpid/src/sleep.rs
@@ -0,0 +1,44 @@
+use std::convert::TryFrom;
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum SleepTarget {
+ S0,
+ S1,
+ S2,
+ S3,
+ S4,
+ S5,
+}
+
+impl SleepTarget {
+ pub fn aml_method_name(self) -> &'static str {
+ match self {
+ Self::S0 => "_S0",
+ Self::S1 => "_S1",
+ Self::S2 => "_S2",
+ Self::S3 => "_S3",
+ Self::S4 => "_S4",
+ Self::S5 => "_S5",
+ }
+ }
+
+ pub fn is_soft_off(self) -> bool {
+ matches!(self, Self::S5)
+ }
+}
+
+impl TryFrom<u8> for SleepTarget {
+ type Error = ();
+
+ fn try_from(value: u8) -> Result<Self, Self::Error> {
+ match value {
+ 0 => Ok(Self::S0),
+ 1 => Ok(Self::S1),
+ 2 => Ok(Self::S2),
+ 3 => Ok(Self::S3),
+ 4 => Ok(Self::S4),
+ 5 => Ok(Self::S5),
+ _ => Err(()),
+ }
+ }
+}