fn nameseg(s: &str) -> [u8; 4] {
let mut b = [b'_'; 4];
let bytes = s.as_bytes();
b[..bytes.len()].copy_from_slice(bytes);
b
}
fn pkg_length(content_len: usize) -> Vec<u8> {
if content_len + 1 < 0x40 {
vec![(content_len + 1) as u8]
} else if content_len + 2 < 0x1000 {
let total = content_len + 2;
vec![0x40 | (total & 0x0F) as u8, (total >> 4) as u8]
} else {
let total = content_len + 3;
vec![
0x80 | (total & 0x0F) as u8,
(total >> 4) as u8,
(total >> 12) as u8,
]
}
}
fn int_obj(v: u64) -> Vec<u8> {
match v {
0 => vec![0x00], 1 => vec![0x01], x if x <= 0xFF => vec![0x0A, x as u8],
x if x <= 0xFFFF => {
let mut o = vec![0x0B];
o.extend_from_slice(&(x as u16).to_le_bytes());
o
}
x if x <= 0xFFFF_FFFF => {
let mut o = vec![0x0C];
o.extend_from_slice(&(x as u32).to_le_bytes());
o
}
x => {
let mut o = vec![0x0E];
o.extend_from_slice(&x.to_le_bytes());
o
}
}
}
fn name_int(name: &str, v: u64) -> Vec<u8> {
let mut o = vec![0x08];
o.extend_from_slice(&nameseg(name));
o.extend(int_obj(v));
o
}
fn name_data(name: &str, data: &[u8]) -> Vec<u8> {
let mut o = vec![0x08];
o.extend_from_slice(&nameseg(name));
o.extend_from_slice(data);
o
}
fn package(elements: &[Vec<u8>]) -> Vec<u8> {
let mut inner = vec![elements.len() as u8]; for e in elements {
inner.extend_from_slice(e);
}
let mut o = vec![0x12]; o.extend(pkg_length(inner.len()));
o.extend(inner);
o
}
fn eisaid(id: &str) -> Vec<u8> {
let b = id.as_bytes();
assert!(b.len() == 7, "EISA id must be exactly 7 chars, got {id:?}");
let hex = |c: u8| {
if c.is_ascii_digit() {
c - b'0'
} else {
c - b'A' + 10
}
};
let packed: u32 = ((b[0] - 0x40) as u32) << 26
| ((b[1] - 0x40) as u32) << 21
| ((b[2] - 0x40) as u32) << 16
| (hex(b[3]) as u32) << 12
| (hex(b[4]) as u32) << 8
| (hex(b[5]) as u32) << 4
| (hex(b[6]) as u32);
let mut o = vec![0x0C]; o.extend_from_slice(&packed.to_be_bytes());
o
}
fn device(name: &str, body: &[u8]) -> Vec<u8> {
let mut inner = Vec::with_capacity(4 + body.len());
inner.extend_from_slice(&nameseg(name));
inner.extend_from_slice(body);
let mut o = vec![0x5B, 0x82];
o.extend(pkg_length(inner.len()));
o.extend(inner);
o
}
fn root_scope(name: &str, body: &[u8]) -> Vec<u8> {
let mut inner = vec![0x5C]; inner.extend_from_slice(&nameseg(name));
inner.extend_from_slice(body);
let mut o = vec![0x10];
o.extend(pkg_length(inner.len()));
o.extend(inner);
o
}
fn resource_template(descriptors: &[u8]) -> Vec<u8> {
let mut buf = Vec::with_capacity(descriptors.len() + 2);
buf.extend_from_slice(descriptors);
buf.push(0x79); buf.push(0x00); let mut inner = int_obj(buf.len() as u64);
inner.extend_from_slice(&buf);
let mut o = vec![0x11];
o.extend(pkg_length(inner.len()));
o.extend(inner);
o
}
fn word_bus_number(min: u16, max: u16) -> Vec<u8> {
debug_assert!(max >= min, "bus range max {max} < min {min}");
let length = max as u32 - min as u32 + 1;
debug_assert!(length <= u16::MAX as u32, "bus count {length} exceeds u16");
let mut o = vec![0x88]; o.extend_from_slice(&0x000Du16.to_le_bytes()); o.push(0x02); o.push(0x0C); o.push(0x00); o.extend_from_slice(&0u16.to_le_bytes()); o.extend_from_slice(&min.to_le_bytes()); o.extend_from_slice(&max.to_le_bytes()); o.extend_from_slice(&0u16.to_le_bytes()); o.extend_from_slice(&(length as u16).to_le_bytes()); o
}
fn dword_memory(min: u32, max: u32) -> Vec<u8> {
debug_assert!(max >= min, "memory window max {max:#x} < min {min:#x}");
let length = max as u64 - min as u64 + 1;
debug_assert!(
length <= u32::MAX as u64,
"DWordMemory window exceeds 32-bit length"
);
let mut o = vec![0x87]; o.extend_from_slice(&0x0017u16.to_le_bytes()); o.push(0x00); o.push(0x0C); o.push(0x01); o.extend_from_slice(&0u32.to_le_bytes()); o.extend_from_slice(&min.to_le_bytes()); o.extend_from_slice(&max.to_le_bytes()); o.extend_from_slice(&0u32.to_le_bytes()); o.extend_from_slice(&(length as u32).to_le_bytes()); o
}
fn memory32_fixed(base: u32, len: u32) -> Vec<u8> {
let mut o = vec![0x86]; o.extend_from_slice(&0x0009u16.to_le_bytes()); o.push(0x01); o.extend_from_slice(&base.to_le_bytes());
o.extend_from_slice(&len.to_le_bytes());
o
}
pub(super) fn pci_host_bridge_dsdt_body(
ecam_base: u32,
ecam_size: u32,
bar_base: u32,
bar_last: u32,
irq_routes: &[(u32, u32)],
) -> Vec<u8> {
let mut pci0 = Vec::new();
pci0.extend(name_data("_HID", &eisaid("PNP0A08"))); pci0.extend(name_data("_CID", &eisaid("PNP0A03"))); pci0.extend(name_int("_SEG", 0));
pci0.extend(name_int("_UID", 0));
pci0.extend(name_int("_CCA", 1)); pci0.extend(name_int("_BBN", 0)); let mut crs = Vec::new();
crs.extend(word_bus_number(0, 0)); crs.extend(dword_memory(bar_base, bar_last)); pci0.extend(name_data("_CRS", &resource_template(&crs)));
if !irq_routes.is_empty() {
let prt_entries: Vec<Vec<u8>> = irq_routes
.iter()
.map(|&(slot, gsi)| {
package(&[
int_obj(u64::from((slot << 16) | 0xFFFF)),
int_obj(0), int_obj(0), int_obj(u64::from(gsi)),
])
})
.collect();
pci0.extend(name_data("_PRT", &package(&prt_entries)));
}
let pci0_dev = device("PCI0", &pci0);
let mut pcir = Vec::new();
pcir.extend(name_data("_HID", &eisaid("PNP0C02")));
pcir.extend(name_data(
"_CRS",
&resource_template(&memory32_fixed(ecam_base, ecam_size)),
));
let pcir_dev = device("PCIR", &pcir);
let mut sb = pci0_dev;
sb.extend(pcir_dev);
root_scope("_SB", &sb)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn eisaid_canonical_bytes() {
assert_eq!(eisaid("PNP0A08"), [0x0C, 0x41, 0xD0, 0x0A, 0x08]);
assert_eq!(eisaid("PNP0A03"), [0x0C, 0x41, 0xD0, 0x0A, 0x03]);
assert_eq!(eisaid("PNP0C02"), [0x0C, 0x41, 0xD0, 0x0C, 0x02]);
}
fn decode_eisaid(dword_bytes: [u8; 4]) -> String {
let swapped = u32::from_le_bytes(dword_bytes).swap_bytes();
let letter = |shift: u32| (b'@' + ((swapped >> shift) & 0x1F) as u8) as char;
let nibble = |shift: u32| {
char::from_digit((swapped >> shift) & 0xF, 16)
.unwrap()
.to_ascii_uppercase()
};
format!(
"{}{}{}{}{}{}{}",
letter(26),
letter(21),
letter(16),
nibble(12),
nibble(8),
nibble(4),
nibble(0),
)
}
#[test]
fn eisaid_decodes_to_pnp_id() {
for id in ["PNP0A08", "PNP0A03", "PNP0C02"] {
let aml = eisaid(id);
let bytes: [u8; 4] = aml[1..5].try_into().unwrap();
assert_eq!(decode_eisaid(bytes), id, "eisaid({id}) must round-trip");
}
}
#[test]
fn pkg_length_widths() {
assert_eq!(pkg_length(10), vec![11]);
assert_eq!(pkg_length(0x3E), vec![0x3F]); assert_eq!(pkg_length(0x3F), vec![0x40 | (0x41 & 0x0F), 0x41 >> 4]);
let pl = pkg_length(200);
let total = 202usize; assert_eq!(pl, vec![0x40 | (total & 0x0F) as u8, (total >> 4) as u8]);
}
#[test]
fn int_obj_forms() {
assert_eq!(int_obj(0), vec![0x00]);
assert_eq!(int_obj(1), vec![0x01]);
assert_eq!(int_obj(0x42), vec![0x0A, 0x42]);
assert_eq!(int_obj(0x1234), vec![0x0B, 0x34, 0x12]);
assert_eq!(int_obj(0xDEAD_BEEF), vec![0x0C, 0xEF, 0xBE, 0xAD, 0xDE]);
}
#[test]
fn word_bus_number_single_bus() {
let d = word_bus_number(0, 0);
assert_eq!(d.len(), 16); assert_eq!(d[0], 0x88);
assert_eq!(&d[1..3], &0x000Du16.to_le_bytes());
assert_eq!(d[3], 0x02); assert_eq!(&d[14..16], &1u16.to_le_bytes());
}
#[test]
fn dword_memory_window_length_consistent() {
let d = dword_memory(0xE010_0000, 0xFEBF_FFFF);
assert_eq!(d.len(), 26); assert_eq!(d[0], 0x87);
let len_field = u32::from_le_bytes([d[22], d[23], d[24], d[25]]);
assert_eq!(len_field, 0xFEBF_FFFF - 0xE010_0000 + 1);
}
#[test]
fn resource_template_has_endtag_and_checksum() {
let rt = resource_template(&memory32_fixed(0xE000_0000, 0x10_0000));
assert_eq!(rt[0], 0x11); assert_eq!(rt[rt.len() - 2], 0x79);
assert_eq!(rt[rt.len() - 1], 0x00);
}
#[test]
fn dsdt_body_structure() {
let body =
pci_host_bridge_dsdt_body(0xE000_0000, 0x10_0000, 0xE010_0000, 0xFEBF_FFFF, &[(1, 7)]);
assert_eq!(body[0], 0x10); let contains = |needle: &[u8]| body.windows(needle.len()).any(|w| w == needle);
assert!(contains(&[0x0C, 0x41, 0xD0, 0x0A, 0x08]), "PNP0A08 _HID");
assert!(contains(&[0x0C, 0x41, 0xD0, 0x0A, 0x03]), "PNP0A03 _CID");
assert!(
contains(&[0x0C, 0x41, 0xD0, 0x0C, 0x02]),
"PNP0C02 reservation"
);
assert!(contains(&[0x88, 0x0D, 0x00]), "WordBusNumber descriptor");
assert!(contains(&[0x87, 0x17, 0x00]), "DWordMemory descriptor");
assert!(contains(&[0x86, 0x09, 0x00]), "Memory32Fixed descriptor");
assert!(
contains(&[0x79, 0x00]),
"ResourceTemplate EndTag + checksum"
);
assert!(contains(&[0x5B, 0x82]), "Device opcode");
assert!(contains(b"_PRT"), "_PRT name");
}
#[test]
fn prt_entry_encoding() {
let body =
pci_host_bridge_dsdt_body(0xE000_0000, 0x10_0000, 0xE010_0000, 0xFEBF_FFFF, &[(1, 7)]);
let contains = |needle: &[u8]| body.windows(needle.len()).any(|w| w == needle);
let entry = [
0x12, 0x0B, 0x04, 0x0C, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x0A, 0x07, ];
assert!(contains(&entry), "_PRT routing entry bytes");
let empty =
pci_host_bridge_dsdt_body(0xE000_0000, 0x10_0000, 0xE010_0000, 0xFEBF_FFFF, &[]);
assert!(
!empty.windows(4).any(|w| w == b"_PRT"),
"empty irq_routes emits no _PRT"
);
}
#[test]
fn prt_emits_one_entry_per_route() {
let routes = [(1u32, 7u32), (2, 8), (3, 10)];
let body =
pci_host_bridge_dsdt_body(0xE000_0000, 0x10_0000, 0xE010_0000, 0xFEBF_FFFF, &routes);
let contains = |needle: &[u8]| body.windows(needle.len()).any(|w| w == needle);
for &(slot, gsi) in &routes {
let entry = [
0x12, 0x0B, 0x04, 0x0C, 0xFF, 0xFF, slot as u8, 0x00, 0x00, 0x00, 0x0A, gsi as u8, ];
assert!(contains(&entry), "_PRT entry for slot {slot} GSI {gsi}");
}
let prt_pos = body
.windows(4)
.position(|w| w == b"_PRT")
.expect("_PRT present");
assert_eq!(
body[prt_pos + 4],
0x12,
"_PRT value is a Package (PackageOp follows the NameSeg)"
);
assert_eq!(
body[prt_pos + 6],
0x03,
"_PRT package holds one entry per NIC (NumElements = 3)"
);
}
#[test]
fn package_num_elements_and_pkglen() {
let p = package(&[int_obj(1), int_obj(1)]);
assert_eq!(p, vec![0x12, 0x04, 0x02, 0x01, 0x01]); }
}