use anyhow::{Context, Result};
use vm_memory::{Bytes, GuestAddress, GuestMemoryMmap};
use zerocopy::IntoBytes;
use super::topology::apic_id;
use crate::vmm::topology::Topology;
const RSDP_ADDR: u64 = 0x000E_0000;
const RSDP_SIZE: u64 = 36;
#[derive(Debug, Clone, Copy)]
#[allow(dead_code)]
pub struct AcpiLayout {
pub dsdt_addr: u64,
pub dsdt_size: u64,
pub madt_addr: u64,
pub madt_size: u64,
pub fadt_addr: u64,
pub fadt_size: u64,
pub srat_addr: u64,
pub srat_size: u64,
pub slit_addr: u64,
pub slit_size: u64,
pub rsdt_addr: u64,
pub rsdt_size: u64,
pub xsdt_addr: u64,
pub xsdt_size: u64,
pub rsdp_addr: u64,
pub rsdp_size: u64,
}
const FADT_F_PWR_BUTTON: u32 = 1 << 4;
const FADT_F_SLP_BUTTON: u32 = 1 << 5;
const IOAPIC_ADDR: u32 = 0xFEC0_0000;
const IOAPIC_ID: u8 = 0;
const LAPIC_ADDR: u32 = 0xFEE0_0000;
#[repr(C, packed)]
#[derive(Clone, Copy, Default, IntoBytes, zerocopy::Immutable, zerocopy::KnownLayout)]
struct SdtHeader {
signature: [u8; 4],
length: u32,
revision: u8,
checksum: u8,
oem_id: [u8; 6],
oem_table_id: [u8; 8],
oem_revision: u32,
creator_id: [u8; 4],
creator_revision: u32,
}
impl SdtHeader {
fn new(sig: &[u8; 4], length: u32, revision: u8) -> Self {
Self {
signature: *sig,
length,
revision,
oem_id: *b"KTSTR\0",
oem_table_id: {
let mut id = [0u8; 8];
let prefix = b"KTSR";
id[..prefix.len()].copy_from_slice(prefix);
id[prefix.len()..prefix.len() + sig.len()].copy_from_slice(sig);
id
},
oem_revision: 1,
creator_id: *b"KTSR",
creator_revision: 1,
..Default::default()
}
}
}
#[repr(C, packed)]
#[derive(Clone, Copy, Default, IntoBytes, zerocopy::Immutable, zerocopy::KnownLayout)]
struct Rsdp {
signature: [u8; 8],
checksum: u8,
oem_id: [u8; 6],
revision: u8,
rsdt_address: u32,
length: u32,
xsdt_address: u64,
extended_checksum: u8,
_reserved: [u8; 3],
}
#[repr(C, packed)]
#[derive(Clone, Copy, Default, IntoBytes, zerocopy::Immutable, zerocopy::KnownLayout)]
struct MadtHeader {
sdt: SdtHeader,
local_apic_address: u32,
flags: u32,
}
#[repr(C, packed)]
#[derive(Clone, Copy, Default, IntoBytes, zerocopy::Immutable, zerocopy::KnownLayout)]
struct MadtLocalApic {
entry_type: u8,
length: u8,
processor_id: u8,
apic_id: u8,
flags: u32,
}
#[repr(C, packed)]
#[derive(Clone, Copy, Default, IntoBytes, zerocopy::Immutable, zerocopy::KnownLayout)]
struct MadtX2Apic {
entry_type: u8,
length: u8,
_reserved: u16,
x2apic_id: u32,
flags: u32,
processor_uid: u32,
}
#[repr(C, packed)]
#[derive(Clone, Copy, Default, IntoBytes, zerocopy::Immutable, zerocopy::KnownLayout)]
struct MadtIoApic {
entry_type: u8,
length: u8,
io_apic_id: u8,
_reserved: u8,
io_apic_address: u32,
gsi_base: u32,
}
#[repr(C, packed)]
#[derive(Clone, Copy, Default, IntoBytes, zerocopy::Immutable, zerocopy::KnownLayout)]
struct MadtIso {
entry_type: u8,
length: u8,
bus: u8,
source: u8,
gsi: u32,
flags: u16,
}
#[repr(C, packed)]
#[derive(Clone, Copy, Default, IntoBytes, zerocopy::Immutable, zerocopy::KnownLayout)]
struct MadtLapicNmi {
entry_type: u8,
length: u8,
processor_id: u8,
flags: u16,
lint: u8,
}
#[repr(C, packed)]
#[derive(Clone, Copy, Default, IntoBytes, zerocopy::Immutable, zerocopy::KnownLayout)]
struct MadtX2ApicNmi {
entry_type: u8,
length: u8,
flags: u16,
processor_uid: u32,
lint: u8,
_reserved: [u8; 3],
}
#[repr(C, packed)]
#[derive(Clone, Copy, Default, IntoBytes, zerocopy::Immutable, zerocopy::KnownLayout)]
struct SratCpuAffinity {
entry_type: u8,
length: u8,
_reserved0: u16,
proximity_domain: u32,
x2apic_id: u32,
flags: u32,
clock_domain: u32,
_reserved1: u32,
}
#[repr(C, packed)]
#[derive(Clone, Copy, Default, IntoBytes, zerocopy::Immutable, zerocopy::KnownLayout)]
struct SratMemAffinity {
entry_type: u8,
length: u8,
proximity_domain_lo: u16, proximity_domain_hi: u16, _reserved0: u16,
base_address: u64,
address_length: u64,
_reserved1: u32,
flags: u32,
_reserved2: u64,
}
fn acpi_checksum(data: &[u8]) -> u8 {
let sum: u8 = data.iter().fold(0u8, |acc, &b| acc.wrapping_add(b));
(!sum).wrapping_add(1)
}
fn set_sdt_checksum(buf: &mut [u8]) {
buf[9] = 0;
buf[9] = acpi_checksum(buf);
}
pub fn setup_acpi(
mem: &GuestMemoryMmap,
topo: &Topology,
memory_mb: u32,
shm_size: u64,
) -> Result<AcpiLayout> {
let num_cpus = topo.total_cpus();
let num_sockets = topo.sockets;
let dsdt_size: u64 = 36;
let madt_size = compute_madt_size(topo) as u64;
let fadt_size: u64 = 276;
let srat_size: u64 =
(48 + std::mem::size_of::<SratCpuAffinity>() as u32 * num_cpus
+ std::mem::size_of::<SratMemAffinity>() as u32 * num_sockets) as u64;
let n = num_sockets as u64;
let slit_size: u64 = 36 + 8 + n * n;
let rsdt_size: u64 = 36 + 16;
let xsdt_size: u64 = 36 + 32;
let mut cursor = RSDP_ADDR + RSDP_SIZE;
let dsdt_addr = cursor;
cursor += dsdt_size;
let madt_addr = cursor;
cursor += madt_size;
let fadt_addr = cursor;
cursor += fadt_size;
let srat_addr = cursor;
cursor += srat_size;
let slit_addr = cursor;
cursor += slit_size;
let rsdt_addr = cursor;
cursor += rsdt_size;
let xsdt_addr = cursor;
let layout = AcpiLayout {
dsdt_addr,
dsdt_size,
madt_addr,
madt_size,
fadt_addr,
fadt_size,
srat_addr,
srat_size,
slit_addr,
slit_size,
rsdt_addr,
rsdt_size,
xsdt_addr,
xsdt_size,
rsdp_addr: RSDP_ADDR,
rsdp_size: RSDP_SIZE,
};
write_dsdt(mem, dsdt_addr)?;
write_madt(mem, topo, madt_addr)?;
write_fadt(mem, &layout)?;
write_srat(mem, topo, memory_mb, shm_size, srat_addr)?;
write_slit(mem, topo, slit_addr)?;
write_rsdt(mem, &layout)?;
write_xsdt(mem, &layout)?;
write_rsdp(mem, &layout)?;
Ok(layout)
}
fn write_rsdp(mem: &GuestMemoryMmap, layout: &AcpiLayout) -> Result<()> {
let mut rsdp = Rsdp {
signature: *b"RSD PTR ",
oem_id: *b"KTSTR\0",
revision: 2,
rsdt_address: layout.rsdt_addr as u32,
length: 36,
xsdt_address: layout.xsdt_addr,
..Default::default()
};
rsdp.checksum = acpi_checksum(&rsdp.as_bytes()[..20]);
rsdp.extended_checksum = acpi_checksum(rsdp.as_bytes());
mem.write_slice(rsdp.as_bytes(), GuestAddress(RSDP_ADDR))
.context("write RSDP")?;
Ok(())
}
fn write_rsdt(mem: &GuestMemoryMmap, layout: &AcpiLayout) -> Result<()> {
let len = 36 + 16;
let mut buf = vec![0u8; len];
let hdr = SdtHeader::new(b"RSDT", len as u32, 1);
buf[..36].copy_from_slice(hdr.as_bytes());
let entries: [u32; 4] = [
layout.fadt_addr as u32,
layout.madt_addr as u32,
layout.srat_addr as u32,
layout.slit_addr as u32,
];
buf[36..52].copy_from_slice(entries.as_bytes());
set_sdt_checksum(&mut buf);
mem.write_slice(&buf, GuestAddress(layout.rsdt_addr))
.context("write RSDT")?;
Ok(())
}
fn write_xsdt(mem: &GuestMemoryMmap, layout: &AcpiLayout) -> Result<()> {
let len = 36 + 32;
let mut buf = vec![0u8; len];
let hdr = SdtHeader::new(b"XSDT", len as u32, 1);
buf[..36].copy_from_slice(hdr.as_bytes());
let entries: [u64; 4] = [
layout.fadt_addr,
layout.madt_addr,
layout.srat_addr,
layout.slit_addr,
];
buf[36..68].copy_from_slice(entries.as_bytes());
set_sdt_checksum(&mut buf);
mem.write_slice(&buf, GuestAddress(layout.xsdt_addr))
.context("write XSDT")?;
Ok(())
}
fn write_dsdt(mem: &GuestMemoryMmap, addr: u64) -> Result<()> {
let mut buf = vec![0u8; 36];
let hdr = SdtHeader::new(b"DSDT", 36, 2);
buf[..36].copy_from_slice(hdr.as_bytes());
set_sdt_checksum(&mut buf);
mem.write_slice(&buf, GuestAddress(addr))
.context("write DSDT")?;
Ok(())
}
fn write_fadt(mem: &GuestMemoryMmap, layout: &AcpiLayout) -> Result<()> {
let mut buf = vec![0u8; 276];
let hdr = SdtHeader::new(b"FACP", 276, 6);
buf[..36].copy_from_slice(hdr.as_bytes());
buf[40..44].copy_from_slice(&(layout.dsdt_addr as u32).to_le_bytes());
buf[140..148].copy_from_slice(&layout.dsdt_addr.to_le_bytes());
let flags = FADT_F_PWR_BUTTON | FADT_F_SLP_BUTTON;
buf[112..116].copy_from_slice(&flags.to_le_bytes());
buf[131] = 5;
set_sdt_checksum(&mut buf);
mem.write_slice(&buf, GuestAddress(layout.fadt_addr))
.context("write FADT")?;
Ok(())
}
fn write_srat(
mem: &GuestMemoryMmap,
topo: &Topology,
memory_mb: u32,
shm_size: u64,
addr: u64,
) -> Result<()> {
let num_cpus = topo.total_cpus();
let num_sockets = topo.sockets;
let len = 48
+ std::mem::size_of::<SratCpuAffinity>() as u32 * num_cpus
+ std::mem::size_of::<SratMemAffinity>() as u32 * num_sockets;
let mut buf = vec![0u8; len as usize];
let hdr = SdtHeader::new(b"SRAT", len, 3);
buf[..36].copy_from_slice(hdr.as_bytes());
buf[36..40].copy_from_slice(&1u32.to_le_bytes());
let mut offset = 48;
for cpu_id in 0..num_cpus {
let (socket_id, _, _) = topo.decompose(cpu_id);
let entry = SratCpuAffinity {
entry_type: 2,
length: std::mem::size_of::<SratCpuAffinity>() as u8,
proximity_domain: socket_id,
x2apic_id: apic_id(topo, cpu_id),
flags: 1,
..Default::default()
};
let bytes = entry.as_bytes();
buf[offset..offset + bytes.len()].copy_from_slice(bytes);
offset += bytes.len();
}
let mem_bytes = (memory_mb as u64) << 20;
let usable_bytes = mem_bytes - shm_size;
let per_socket = usable_bytes / num_sockets as u64;
for socket in 0..num_sockets {
let base = socket as u64 * per_socket;
let length = if socket == num_sockets - 1 {
usable_bytes - base
} else {
per_socket
};
let entry = SratMemAffinity {
entry_type: 1,
length: std::mem::size_of::<SratMemAffinity>() as u8,
proximity_domain_lo: socket as u16,
proximity_domain_hi: (socket >> 16) as u16,
base_address: base,
address_length: length,
flags: 1,
..Default::default()
};
let bytes = entry.as_bytes();
buf[offset..offset + bytes.len()].copy_from_slice(bytes);
offset += bytes.len();
}
set_sdt_checksum(&mut buf);
mem.write_slice(&buf, GuestAddress(addr))
.context("write SRAT")?;
Ok(())
}
fn write_slit(mem: &GuestMemoryMmap, topo: &Topology, addr: u64) -> Result<()> {
let n = topo.sockets as u64;
let len = 36 + 8 + n * n;
let mut buf = vec![0u8; len as usize];
let hdr = SdtHeader::new(b"SLIT", len as u32, 1);
buf[..36].copy_from_slice(hdr.as_bytes());
buf[36..44].copy_from_slice(&n.to_le_bytes());
let matrix_start = 44;
for i in 0..n {
for j in 0..n {
buf[matrix_start + (i * n + j) as usize] = if i == j { 10 } else { 20 };
}
}
set_sdt_checksum(&mut buf);
mem.write_slice(&buf, GuestAddress(addr))
.context("write SLIT")?;
Ok(())
}
fn use_x2apic_entry(apic_id: u32) -> bool {
apic_id >= 255
}
fn compute_madt_size(topo: &Topology) -> u32 {
let num_cpus = topo.total_cpus();
let mut cpu_entries_size: u32 = 0;
let mut has_x2apic = false;
let mut has_lapic = false;
for cpu_id in 0..num_cpus {
if use_x2apic_entry(apic_id(topo, cpu_id)) {
cpu_entries_size += std::mem::size_of::<MadtX2Apic>() as u32;
has_x2apic = true;
} else {
cpu_entries_size += std::mem::size_of::<MadtLocalApic>() as u32;
has_lapic = true;
}
}
let nmi_size: u32 = if has_lapic {
std::mem::size_of::<MadtLapicNmi>() as u32
} else {
0
} + if has_x2apic {
std::mem::size_of::<MadtX2ApicNmi>() as u32
} else {
0
};
std::mem::size_of::<MadtHeader>() as u32
+ cpu_entries_size
+ std::mem::size_of::<MadtIoApic>() as u32
+ std::mem::size_of::<MadtIso>() as u32
+ nmi_size
}
fn write_madt(mem: &GuestMemoryMmap, topo: &Topology, addr: u64) -> Result<()> {
let num_cpus = topo.total_cpus();
let mut has_x2apic = false;
let mut has_lapic = false;
for cpu_id in 0..num_cpus {
if use_x2apic_entry(apic_id(topo, cpu_id)) {
has_x2apic = true;
} else {
has_lapic = true;
}
}
let len = compute_madt_size(topo);
let mut buf = vec![0u8; len as usize];
let hdr = MadtHeader {
sdt: SdtHeader::new(b"APIC", len, 3),
local_apic_address: LAPIC_ADDR,
flags: 1, };
buf[..std::mem::size_of::<MadtHeader>()].copy_from_slice(hdr.as_bytes());
let mut offset = std::mem::size_of::<MadtHeader>();
for cpu_id in 0..num_cpus {
let id = apic_id(topo, cpu_id);
if use_x2apic_entry(id) {
let entry = MadtX2Apic {
entry_type: 9,
length: std::mem::size_of::<MadtX2Apic>() as u8,
x2apic_id: id,
flags: 1,
processor_uid: cpu_id,
..Default::default()
};
let bytes = entry.as_bytes();
buf[offset..offset + bytes.len()].copy_from_slice(bytes);
offset += bytes.len();
} else {
let entry = MadtLocalApic {
entry_type: 0,
length: std::mem::size_of::<MadtLocalApic>() as u8,
processor_id: cpu_id as u8,
apic_id: id as u8,
flags: 1,
};
let bytes = entry.as_bytes();
buf[offset..offset + bytes.len()].copy_from_slice(bytes);
offset += bytes.len();
}
}
let ioapic = MadtIoApic {
entry_type: 1,
length: std::mem::size_of::<MadtIoApic>() as u8,
io_apic_id: IOAPIC_ID,
io_apic_address: IOAPIC_ADDR,
gsi_base: 0,
..Default::default()
};
let bytes = ioapic.as_bytes();
buf[offset..offset + bytes.len()].copy_from_slice(bytes);
offset += bytes.len();
let iso = MadtIso {
entry_type: 2,
length: std::mem::size_of::<MadtIso>() as u8,
bus: 0,
source: 0,
gsi: 2,
flags: 0,
};
let bytes = iso.as_bytes();
buf[offset..offset + bytes.len()].copy_from_slice(bytes);
offset += bytes.len();
if has_lapic {
let nmi = MadtLapicNmi {
entry_type: 4,
length: std::mem::size_of::<MadtLapicNmi>() as u8,
processor_id: 0xFF,
flags: 0,
lint: 1,
};
let bytes = nmi.as_bytes();
buf[offset..offset + bytes.len()].copy_from_slice(bytes);
offset += bytes.len();
}
if has_x2apic {
let nmi = MadtX2ApicNmi {
entry_type: 0x0A,
length: std::mem::size_of::<MadtX2ApicNmi>() as u8,
flags: 0,
processor_uid: 0xFFFF_FFFF,
lint: 1,
_reserved: [0; 3],
};
let bytes = nmi.as_bytes();
buf[offset..offset + bytes.len()].copy_from_slice(bytes);
}
set_sdt_checksum(&mut buf);
mem.write_slice(&buf, GuestAddress(addr))
.context("write MADT")?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn test_mem(mb: u32) -> GuestMemoryMmap {
GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), (mb as usize) << 20)]).unwrap()
}
fn read_table(mem: &GuestMemoryMmap, addr: u64) -> Vec<u8> {
let mut len_bytes = [0u8; 4];
mem.read_slice(&mut len_bytes, GuestAddress(addr + 4))
.unwrap();
let len = u32::from_le_bytes(len_bytes) as usize;
let mut buf = vec![0u8; len];
mem.read_slice(&mut buf, GuestAddress(addr)).unwrap();
buf
}
fn read_madt(mem: &GuestMemoryMmap, layout: &AcpiLayout) -> Vec<u8> {
read_table(mem, layout.madt_addr)
}
fn walk_madt_entries(madt: &[u8]) -> Vec<(u8, u8, &[u8])> {
let hdr_size = std::mem::size_of::<MadtHeader>();
let mut entries = Vec::new();
let mut offset = hdr_size;
while offset < madt.len() {
let entry_type = madt[offset];
let entry_len = madt[offset + 1];
entries.push((
entry_type,
entry_len,
&madt[offset..offset + entry_len as usize],
));
offset += entry_len as usize;
}
entries
}
const _: () = assert!(std::mem::size_of::<SdtHeader>() == 36);
const _: () = assert!(std::mem::size_of::<Rsdp>() == 36);
const _: () = assert!(std::mem::size_of::<MadtHeader>() == 44);
const _: () = assert!(std::mem::size_of::<MadtLocalApic>() == 8);
const _: () = assert!(std::mem::size_of::<MadtX2Apic>() == 16);
const _: () = assert!(std::mem::size_of::<MadtIoApic>() == 12);
const _: () = assert!(std::mem::size_of::<MadtIso>() == 10);
const _: () = assert!(std::mem::size_of::<MadtLapicNmi>() == 6);
const _: () = assert!(std::mem::size_of::<MadtX2ApicNmi>() == 12);
const _: () = assert!(std::mem::size_of::<SratCpuAffinity>() == 24);
const _: () = assert!(std::mem::size_of::<SratMemAffinity>() == 40);
#[test]
fn rsdp_signature_and_checksum() {
let mem = test_mem(16);
let topo = Topology {
sockets: 1,
cores_per_socket: 1,
threads_per_core: 1,
};
let l = setup_acpi(&mem, &topo, 256, 0).unwrap();
let mut rsdp = [0u8; 20];
mem.read_slice(&mut rsdp, GuestAddress(l.rsdp_addr))
.unwrap();
assert_eq!(&rsdp[..8], b"RSD PTR ");
let sum: u8 = rsdp.iter().fold(0u8, |acc, &b| acc.wrapping_add(b));
assert_eq!(sum, 0, "RSDP checksum must be zero");
}
#[test]
fn rsdt_signature_and_checksum() {
let mem = test_mem(16);
let topo = Topology {
sockets: 1,
cores_per_socket: 1,
threads_per_core: 1,
};
let l = setup_acpi(&mem, &topo, 256, 0).unwrap();
let rsdt = read_table(&mem, l.rsdt_addr);
assert_eq!(&rsdt[..4], b"RSDT");
let sum: u8 = rsdt.iter().fold(0u8, |acc, &b| acc.wrapping_add(b));
assert_eq!(sum, 0, "RSDT checksum must be zero");
}
#[test]
fn madt_signature_and_checksum() {
let mem = test_mem(16);
let topo = Topology {
sockets: 2,
cores_per_socket: 2,
threads_per_core: 1,
};
let l = setup_acpi(&mem, &topo, 256, 0).unwrap();
let madt = read_madt(&mem, &l);
assert_eq!(&madt[..4], b"APIC");
let sum: u8 = madt.iter().fold(0u8, |acc, &b| acc.wrapping_add(b));
assert_eq!(sum, 0, "MADT checksum must be zero");
}
#[test]
fn madt_has_correct_cpu_count() {
let mem = test_mem(16);
let topo = Topology {
sockets: 2,
cores_per_socket: 4,
threads_per_core: 2,
};
let l = setup_acpi(&mem, &topo, 256, 0).unwrap();
let madt = read_madt(&mem, &l);
let entries = walk_madt_entries(&madt);
let cpu_count = entries
.iter()
.filter(|(t, _, _)| *t == 0 || *t == 9)
.count();
assert_eq!(cpu_count, 16);
}
#[test]
fn madt_apic_ids_match_topology() {
let mem = test_mem(16);
let topo = Topology {
sockets: 2,
cores_per_socket: 2,
threads_per_core: 1,
};
let l = setup_acpi(&mem, &topo, 256, 0).unwrap();
let madt = read_madt(&mem, &l);
let entries = walk_madt_entries(&madt);
let mut cpu_idx = 0u32;
for (entry_type, _, data) in &entries {
match *entry_type {
0 => {
assert_eq!(data[3] as u32, apic_id(&topo, cpu_idx));
cpu_idx += 1;
}
9 => {
assert_eq!(
u32::from_le_bytes(data[4..8].try_into().unwrap()),
apic_id(&topo, cpu_idx)
);
cpu_idx += 1;
}
_ => {}
}
}
}
#[test]
fn madt_has_ioapic() {
let mem = test_mem(16);
let topo = Topology {
sockets: 1,
cores_per_socket: 1,
threads_per_core: 1,
};
let l = setup_acpi(&mem, &topo, 256, 0).unwrap();
let madt = read_madt(&mem, &l);
let entries = walk_madt_entries(&madt);
let ioapic = entries.iter().find(|(t, _, _)| *t == 1);
assert!(ioapic.is_some());
let (_, _, data) = ioapic.unwrap();
assert_eq!(
u32::from_le_bytes(data[4..8].try_into().unwrap()),
IOAPIC_ADDR
);
}
#[test]
fn rsdp_points_to_rsdt() {
let mem = test_mem(16);
let topo = Topology {
sockets: 1,
cores_per_socket: 1,
threads_per_core: 1,
};
let l = setup_acpi(&mem, &topo, 256, 0).unwrap();
let mut rsdp = [0u8; 20];
mem.read_slice(&mut rsdp, GuestAddress(l.rsdp_addr))
.unwrap();
assert_eq!(
u32::from_le_bytes(rsdp[16..20].try_into().unwrap()),
l.rsdt_addr as u32
);
}
#[test]
fn rsdt_points_to_fadt_and_madt() {
let mem = test_mem(16);
let topo = Topology {
sockets: 1,
cores_per_socket: 1,
threads_per_core: 1,
};
let l = setup_acpi(&mem, &topo, 256, 0).unwrap();
let mut entry = [0u8; 4];
mem.read_slice(&mut entry, GuestAddress(l.rsdt_addr + 36))
.unwrap();
assert_eq!(u32::from_le_bytes(entry), l.fadt_addr as u32);
mem.read_slice(&mut entry, GuestAddress(l.rsdt_addr + 40))
.unwrap();
assert_eq!(u32::from_le_bytes(entry), l.madt_addr as u32);
}
#[test]
fn madt_has_iso_irq0_gsi2() {
let mem = test_mem(16);
let topo = Topology {
sockets: 1,
cores_per_socket: 1,
threads_per_core: 1,
};
let l = setup_acpi(&mem, &topo, 256, 0).unwrap();
let madt = read_madt(&mem, &l);
let entries = walk_madt_entries(&madt);
let iso = entries.iter().find(|(t, _, _)| *t == 2).unwrap();
assert_eq!(iso.2[3], 0);
assert_eq!(u32::from_le_bytes(iso.2[4..8].try_into().unwrap()), 2);
}
#[test]
fn madt_has_nmi() {
let mem = test_mem(16);
let topo = Topology {
sockets: 1,
cores_per_socket: 1,
threads_per_core: 1,
};
let l = setup_acpi(&mem, &topo, 256, 0).unwrap();
let madt = read_madt(&mem, &l);
let entries = walk_madt_entries(&madt);
assert!(entries.iter().any(|(t, _, _)| *t == 4 || *t == 0x0A));
}
#[test]
fn small_topology_uses_lapic_entries() {
let mem = test_mem(16);
let topo = Topology {
sockets: 2,
cores_per_socket: 4,
threads_per_core: 2,
};
let l = setup_acpi(&mem, &topo, 256, 0).unwrap();
let madt = read_madt(&mem, &l);
let entries = walk_madt_entries(&madt);
assert_eq!(entries.iter().filter(|(t, _, _)| *t == 9).count(), 0);
assert_eq!(entries.iter().filter(|(t, _, _)| *t == 0).count(), 16);
assert!(entries.iter().any(|(t, _, _)| *t == 4));
assert!(!entries.iter().any(|(t, _, _)| *t == 0x0A));
}
#[test]
fn large_topology_uses_mixed_entries() {
let mem = test_mem(16);
let topo = Topology {
sockets: 14,
cores_per_socket: 9,
threads_per_core: 2,
};
let mut has_low = false;
let mut has_high = false;
for cpu_id in 0..topo.total_cpus() {
let id = apic_id(&topo, cpu_id);
if id < 255 {
has_low = true;
} else {
has_high = true;
}
}
assert!(has_low && has_high);
let l = setup_acpi(&mem, &topo, 256, 0).unwrap();
let madt = read_madt(&mem, &l);
let entries = walk_madt_entries(&madt);
let lapic_count = entries.iter().filter(|(t, _, _)| *t == 0).count();
let x2apic_count = entries.iter().filter(|(t, _, _)| *t == 9).count();
assert!(lapic_count > 0);
assert!(x2apic_count > 0);
assert_eq!(lapic_count + x2apic_count, topo.total_cpus() as usize);
assert!(entries.iter().any(|(t, _, _)| *t == 4));
assert!(entries.iter().any(|(t, _, _)| *t == 0x0A));
}
#[test]
fn x2apic_nmi_fields_correct() {
let mem = test_mem(16);
let topo = Topology {
sockets: 14,
cores_per_socket: 9,
threads_per_core: 2,
};
let l = setup_acpi(&mem, &topo, 256, 0).unwrap();
let madt = read_madt(&mem, &l);
let entries = walk_madt_entries(&madt);
let (_, len, data) = entries.iter().find(|(t, _, _)| *t == 0x0A).unwrap();
assert_eq!(*len, 12);
assert_eq!(u16::from_le_bytes(data[2..4].try_into().unwrap()), 0);
assert_eq!(
u32::from_le_bytes(data[4..8].try_into().unwrap()),
0xFFFF_FFFF
);
assert_eq!(data[8], 1);
}
#[test]
fn lapic_nmi_fields_correct() {
let mem = test_mem(16);
let topo = Topology {
sockets: 1,
cores_per_socket: 1,
threads_per_core: 1,
};
let l = setup_acpi(&mem, &topo, 256, 0).unwrap();
let madt = read_madt(&mem, &l);
let entries = walk_madt_entries(&madt);
let (_, len, data) = entries.iter().find(|(t, _, _)| *t == 4).unwrap();
assert_eq!(*len, 6);
assert_eq!(data[2], 0xFF);
assert_eq!(u16::from_le_bytes(data[3..5].try_into().unwrap()), 0);
assert_eq!(data[5], 1);
}
#[test]
fn madt_checksum_representative_topologies() {
let topos = [
(1, 1, 1), (2, 1, 1), (3, 3, 1), (1, 1, 2), (2, 4, 2), (7, 5, 3), (15, 16, 1), (14, 9, 2), (2, 128, 1), (14, 18, 1), ];
for (sockets, cores, threads) in topos {
let mem = test_mem(16);
let topo = Topology {
sockets,
cores_per_socket: cores,
threads_per_core: threads,
};
let l = setup_acpi(&mem, &topo, 256, 0).unwrap();
let madt = read_madt(&mem, &l);
let sum: u8 = madt.iter().fold(0u8, |acc, &b| acc.wrapping_add(b));
assert_eq!(
sum, 0,
"MADT checksum failed for {sockets}s/{cores}c/{threads}t"
);
let entries = walk_madt_entries(&madt);
let cpu_count = entries
.iter()
.filter(|(t, _, _)| *t == 0 || *t == 9)
.count();
assert_eq!(cpu_count, topo.total_cpus() as usize);
assert!(entries.iter().any(|(t, _, _)| *t == 4 || *t == 0x0A));
}
}
#[test]
fn cpu_entry_type_matches_apic_id() {
for (sockets, cores, threads) in [(1, 4, 1), (2, 2, 2), (15, 8, 2), (14, 9, 2)] {
let mem = test_mem(16);
let topo = Topology {
sockets,
cores_per_socket: cores,
threads_per_core: threads,
};
let l = setup_acpi(&mem, &topo, 256, 0).unwrap();
let madt = read_madt(&mem, &l);
let entries = walk_madt_entries(&madt);
let mut cpu_idx = 0u32;
for (entry_type, _, data) in &entries {
match *entry_type {
0 => {
let id = data[3] as u32;
assert!(id < 255);
assert_eq!(id, apic_id(&topo, cpu_idx));
cpu_idx += 1;
}
9 => {
let id = u32::from_le_bytes(data[4..8].try_into().unwrap());
assert!(id >= 255);
assert_eq!(id, apic_id(&topo, cpu_idx));
cpu_idx += 1;
}
_ => {}
}
}
}
}
#[test]
fn madt_entry_lengths_valid() {
let mem = test_mem(16);
let topo = Topology {
sockets: 14,
cores_per_socket: 9,
threads_per_core: 2,
};
let l = setup_acpi(&mem, &topo, 256, 0).unwrap();
let madt = read_madt(&mem, &l);
let entries = walk_madt_entries(&madt);
for (entry_type, entry_len, _) in &entries {
let expected = match *entry_type {
0 => 8,
1 => 12,
2 => 10,
4 => 6,
9 => 16,
0x0A => 12,
t => panic!("unexpected MADT entry type {t}"),
};
assert_eq!(*entry_len, expected);
}
}
#[test]
fn madt_total_length_matches_entries() {
let mem = test_mem(16);
let topo = Topology {
sockets: 14,
cores_per_socket: 9,
threads_per_core: 2,
};
let l = setup_acpi(&mem, &topo, 256, 0).unwrap();
let madt = read_madt(&mem, &l);
let declared_len = u32::from_le_bytes(madt[4..8].try_into().unwrap()) as usize;
assert_eq!(declared_len, madt.len());
let entries = walk_madt_entries(&madt);
let entries_size: usize = entries.iter().map(|(_, l, _)| *l as usize).sum();
assert_eq!(
std::mem::size_of::<MadtHeader>() + entries_size,
declared_len
);
}
#[test]
fn cpu_flags_enabled() {
let mem = test_mem(16);
let topo = Topology {
sockets: 2,
cores_per_socket: 2,
threads_per_core: 2,
};
let l = setup_acpi(&mem, &topo, 256, 0).unwrap();
let madt = read_madt(&mem, &l);
let entries = walk_madt_entries(&madt);
for (entry_type, _, data) in &entries {
match *entry_type {
0 => assert_eq!(u32::from_le_bytes(data[4..8].try_into().unwrap()) & 1, 1),
9 => assert_eq!(u32::from_le_bytes(data[8..12].try_into().unwrap()) & 1, 1),
_ => {}
}
}
}
#[test]
fn rsdp_rev2_structure() {
let mem = test_mem(16);
let topo = Topology {
sockets: 1,
cores_per_socket: 1,
threads_per_core: 1,
};
let l = setup_acpi(&mem, &topo, 256, 0).unwrap();
let mut rsdp = [0u8; 36];
mem.read_slice(&mut rsdp, GuestAddress(l.rsdp_addr))
.unwrap();
assert_eq!(&rsdp[..8], b"RSD PTR ");
assert_eq!(rsdp[15], 2);
assert_eq!(
u32::from_le_bytes(rsdp[16..20].try_into().unwrap()),
l.rsdt_addr as u32
);
assert_eq!(u32::from_le_bytes(rsdp[20..24].try_into().unwrap()), 36);
assert_eq!(
u64::from_le_bytes(rsdp[24..32].try_into().unwrap()),
l.xsdt_addr
);
let sum20: u8 = rsdp[..20].iter().fold(0u8, |acc, &b| acc.wrapping_add(b));
assert_eq!(sum20, 0);
let sum36: u8 = rsdp.iter().fold(0u8, |acc, &b| acc.wrapping_add(b));
assert_eq!(sum36, 0);
}
#[test]
fn xsdt_signature_and_checksum() {
let mem = test_mem(16);
let topo = Topology {
sockets: 1,
cores_per_socket: 1,
threads_per_core: 1,
};
let l = setup_acpi(&mem, &topo, 256, 0).unwrap();
let xsdt = read_table(&mem, l.xsdt_addr);
assert_eq!(&xsdt[..4], b"XSDT");
assert_eq!(xsdt.len(), 68);
let sum: u8 = xsdt.iter().fold(0u8, |acc, &b| acc.wrapping_add(b));
assert_eq!(sum, 0);
}
#[test]
fn xsdt_points_to_fadt_and_madt() {
let mem = test_mem(16);
let topo = Topology {
sockets: 1,
cores_per_socket: 1,
threads_per_core: 1,
};
let l = setup_acpi(&mem, &topo, 256, 0).unwrap();
let mut entry = [0u8; 8];
mem.read_slice(&mut entry, GuestAddress(l.xsdt_addr + 36))
.unwrap();
assert_eq!(u64::from_le_bytes(entry), l.fadt_addr);
mem.read_slice(&mut entry, GuestAddress(l.xsdt_addr + 44))
.unwrap();
assert_eq!(u64::from_le_bytes(entry), l.madt_addr);
}
#[test]
fn fadt_signature_and_checksum() {
let mem = test_mem(16);
let topo = Topology {
sockets: 1,
cores_per_socket: 1,
threads_per_core: 1,
};
let l = setup_acpi(&mem, &topo, 256, 0).unwrap();
let mut fadt = [0u8; 276];
mem.read_slice(&mut fadt, GuestAddress(l.fadt_addr))
.unwrap();
assert_eq!(&fadt[..4], b"FACP");
assert_eq!(u32::from_le_bytes(fadt[4..8].try_into().unwrap()), 276);
assert_eq!(fadt[8], 6);
let sum: u8 = fadt.iter().fold(0u8, |acc, &b| acc.wrapping_add(b));
assert_eq!(sum, 0);
}
#[test]
fn fadt_hw_reduced_flags() {
let mem = test_mem(16);
let topo = Topology {
sockets: 1,
cores_per_socket: 1,
threads_per_core: 1,
};
let l = setup_acpi(&mem, &topo, 256, 0).unwrap();
let mut fadt = [0u8; 276];
mem.read_slice(&mut fadt, GuestAddress(l.fadt_addr))
.unwrap();
let flags = u32::from_le_bytes(fadt[112..116].try_into().unwrap());
assert_eq!(flags & (1 << 20), 0, "HW_REDUCED_ACPI must not be set");
assert_ne!(flags & FADT_F_PWR_BUTTON, 0);
assert_ne!(flags & FADT_F_SLP_BUTTON, 0);
}
#[test]
fn fadt_minor_version() {
let mem = test_mem(16);
let topo = Topology {
sockets: 1,
cores_per_socket: 1,
threads_per_core: 1,
};
let l = setup_acpi(&mem, &topo, 256, 0).unwrap();
let mut fadt = [0u8; 276];
mem.read_slice(&mut fadt, GuestAddress(l.fadt_addr))
.unwrap();
assert_eq!(fadt[131], 5);
}
#[test]
fn fadt_dsdt_pointers() {
let mem = test_mem(16);
let topo = Topology {
sockets: 1,
cores_per_socket: 1,
threads_per_core: 1,
};
let l = setup_acpi(&mem, &topo, 256, 0).unwrap();
let mut fadt = [0u8; 276];
mem.read_slice(&mut fadt, GuestAddress(l.fadt_addr))
.unwrap();
assert_eq!(
u32::from_le_bytes(fadt[40..44].try_into().unwrap()),
l.dsdt_addr as u32
);
assert_eq!(
u64::from_le_bytes(fadt[140..148].try_into().unwrap()),
l.dsdt_addr
);
}
#[test]
fn dsdt_signature_and_checksum() {
let mem = test_mem(16);
let topo = Topology {
sockets: 1,
cores_per_socket: 1,
threads_per_core: 1,
};
let l = setup_acpi(&mem, &topo, 256, 0).unwrap();
let mut dsdt = [0u8; 36];
mem.read_slice(&mut dsdt, GuestAddress(l.dsdt_addr))
.unwrap();
assert_eq!(&dsdt[..4], b"DSDT");
assert_eq!(u32::from_le_bytes(dsdt[4..8].try_into().unwrap()), 36);
let sum: u8 = dsdt.iter().fold(0u8, |acc, &b| acc.wrapping_add(b));
assert_eq!(sum, 0, "DSDT checksum must be zero");
}
#[test]
fn rsdp_points_to_xsdt() {
let mem = test_mem(16);
let topo = Topology {
sockets: 1,
cores_per_socket: 1,
threads_per_core: 1,
};
let l = setup_acpi(&mem, &topo, 256, 0).unwrap();
let mut rsdp = [0u8; 36];
mem.read_slice(&mut rsdp, GuestAddress(l.rsdp_addr))
.unwrap();
assert_eq!(
u64::from_le_bytes(rsdp[24..32].try_into().unwrap()),
l.xsdt_addr
);
}
#[test]
fn srat_shm_reduces_last_socket_memory() {
let mem = test_mem(16);
let topo = Topology {
sockets: 2,
cores_per_socket: 2,
threads_per_core: 1,
};
let shm_size: u64 = 64 * 1024;
let l = setup_acpi(&mem, &topo, 256, shm_size).unwrap();
let srat = read_table(&mem, l.srat_addr);
let mut offset = 48;
let mut total_mem: u64 = 0;
while offset < srat.len() {
let entry_type = srat[offset];
let entry_len = srat[offset + 1] as usize;
if entry_type == 1 {
let addr_len =
u64::from_le_bytes(srat[offset + 16..offset + 24].try_into().unwrap());
total_mem += addr_len;
}
offset += entry_len;
}
let expected = (256u64 << 20) - shm_size;
assert_eq!(total_mem, expected);
}
}