89247e0d77
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)
1266 lines
43 KiB
Diff
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(()),
|
|
+ }
|
|
+ }
|
|
+}
|