689 lines
20 KiB
Rust
689 lines
20 KiB
Rust
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(_)));
|
|
}
|
|
}
|