use anyhow::{Context, Result};
use vm_memory::{Address, Bytes, GuestAddress, GuestMemoryMmap};
use super::topology::apic_id;
use crate::vmm::topology::Topology;
const MPTABLE_START: u64 = 0x9fc00;
const SMP_MAGIC: [u8; 4] = *b"_MP_";
const MPC_MAGIC: [u8; 4] = *b"PCMP";
const MP_PROCESSOR: u8 = 0;
const MP_BUS: u8 = 1;
const MP_IOAPIC: u8 = 2;
const MP_INTSRC: u8 = 3;
const MP_LINTSRC: u8 = 4;
const CPU_ENABLED: u8 = 0x01;
const CPU_BSP: u8 = 0x02;
const APIC_VERSION: u8 = 0x14;
const IO_APIC_ID: u8 = 0xfe;
const IO_APIC_ADDR: u32 = 0xfec0_0000;
pub fn setup_mptable(mem: &GuestMemoryMmap, topo: &Topology) -> Result<()> {
let num_cpus = topo.total_cpus();
let mut addr = GuestAddress(MPTABLE_START);
let mpf_size = 16u64;
let mpc_start = addr.raw_value() + mpf_size;
let mut mpf = [0u8; 16];
mpf[0..4].copy_from_slice(&SMP_MAGIC);
mpf[4..8].copy_from_slice(&(mpc_start as u32).to_le_bytes());
mpf[8] = 1; mpf[9] = 4; mpf[12] = 0x80;
let cksum = mpf.iter().fold(0u8, |acc, &b| acc.wrapping_add(b));
mpf[10] = (!cksum).wrapping_add(1);
mem.write_slice(&mpf, addr).context("write mpf")?;
addr = addr.unchecked_add(mpf_size);
let header_addr = addr;
let header_size = 44u64;
addr = addr.unchecked_add(header_size);
let cpu_entry_size = 20u64;
for cpu_id in 0..num_cpus {
let apic_id = apic_id(topo, cpu_id) as u8;
let mut entry = [0u8; 20];
entry[0] = MP_PROCESSOR;
entry[1] = apic_id;
entry[2] = APIC_VERSION;
entry[3] = CPU_ENABLED | if cpu_id == 0 { CPU_BSP } else { 0 };
entry[4..8].copy_from_slice(&0x0600u32.to_le_bytes());
entry[8..12].copy_from_slice(&0x0201u32.to_le_bytes());
mem.write_slice(&entry, addr).context("write mpc_cpu")?;
addr = addr.unchecked_add(cpu_entry_size);
}
let bus_entry_size = 8u64;
let mut bus = [0u8; 8];
bus[0] = MP_BUS;
bus[1] = 0; bus[2..8].copy_from_slice(b"ISA ");
mem.write_slice(&bus, addr).context("write mpc_bus")?;
addr = addr.unchecked_add(bus_entry_size);
let ioapic_entry_size = 8u64;
let mut ioapic = [0u8; 8];
ioapic[0] = MP_IOAPIC;
ioapic[1] = IO_APIC_ID;
ioapic[2] = APIC_VERSION;
ioapic[3] = 0x01; ioapic[4..8].copy_from_slice(&IO_APIC_ADDR.to_le_bytes());
mem.write_slice(&ioapic, addr).context("write mpc_ioapic")?;
addr = addr.unchecked_add(ioapic_entry_size);
const NUM_IRQS: u32 = 24;
let intsrc_entry_size = 8u64;
for irq in 0u8..NUM_IRQS as u8 {
let mut intsrc = [0u8; 8];
intsrc[0] = MP_INTSRC;
intsrc[1] = 0; intsrc[2] = 0; intsrc[3] = 0;
intsrc[4] = 0; intsrc[5] = irq; intsrc[6] = IO_APIC_ID; intsrc[7] = irq; mem.write_slice(&intsrc, addr).context("write mpc_intsrc")?;
addr = addr.unchecked_add(intsrc_entry_size);
}
let lintsrc_entry_size = 8u64;
for lint in 0u8..2 {
let mut lintsrc = [0u8; 8];
lintsrc[0] = MP_LINTSRC;
lintsrc[1] = if lint == 0 { 3 } else { 1 }; lintsrc[6] = 0xff; lintsrc[7] = lint; mem.write_slice(&lintsrc, addr)
.context("write mpc_lintsrc")?;
addr = addr.unchecked_add(lintsrc_entry_size);
}
let table_end = addr;
let table_len = (table_end.raw_value() - header_addr.raw_value() - header_size) as u16;
let mut header = [0u8; 44];
header[0..4].copy_from_slice(&MPC_MAGIC);
let total_len = (header_size as u16) + table_len;
header[4..6].copy_from_slice(&total_len.to_le_bytes());
header[6] = 4; header[8..16].copy_from_slice(b"KTSTR "); header[16..28].copy_from_slice(b"000000000000"); let entry_count = num_cpus + 1 + 1 + NUM_IRQS + 2; header[34..36].copy_from_slice(&(entry_count as u16).to_le_bytes());
header[36..40].copy_from_slice(&0xfee0_0000u32.to_le_bytes());
let entries_start = header_addr.unchecked_add(header_size);
let entries_len = (table_end.raw_value() - entries_start.raw_value()) as usize;
let mut entry_bytes = vec![0u8; entries_len];
mem.read_slice(&mut entry_bytes, entries_start)
.context("read entries for checksum")?;
let mut cksum: u8 = 0;
for &b in &header {
cksum = cksum.wrapping_add(b);
}
for &b in &entry_bytes {
cksum = cksum.wrapping_add(b);
}
header[7] = (!cksum).wrapping_add(1);
mem.write_slice(&header, header_addr)
.context("write mpc_table header")?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn test_mem(mb: u32) -> GuestMemoryMmap {
GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), (mb as usize) << 20)]).unwrap()
}
#[test]
fn mptable_single_cpu() {
let mem = test_mem(16);
let topo = Topology {
llcs: 1,
cores_per_llc: 1,
threads_per_core: 1,
numa_nodes: 1,
nodes: None,
distances: None,
};
setup_mptable(&mem, &topo).unwrap();
let mut magic = [0u8; 4];
mem.read_slice(&mut magic, GuestAddress(MPTABLE_START))
.unwrap();
assert_eq!(&magic, b"_MP_");
}
#[test]
fn mptable_multi_llc() {
let mem = test_mem(16);
let topo = Topology {
llcs: 2,
cores_per_llc: 2,
threads_per_core: 2,
numa_nodes: 1,
nodes: None,
distances: None,
};
setup_mptable(&mem, &topo).unwrap();
let mut magic = [0u8; 4];
mem.read_slice(&mut magic, GuestAddress(MPTABLE_START))
.unwrap();
assert_eq!(&magic, b"_MP_");
let mut mpc_magic = [0u8; 4];
mem.read_slice(&mut mpc_magic, GuestAddress(MPTABLE_START + 16))
.unwrap();
assert_eq!(&mpc_magic, b"PCMP");
}
#[test]
fn mptable_mpf_checksum() {
let mem = test_mem(16);
let topo = Topology {
llcs: 1,
cores_per_llc: 2,
threads_per_core: 1,
numa_nodes: 1,
nodes: None,
distances: None,
};
setup_mptable(&mem, &topo).unwrap();
let mut mpf = [0u8; 16];
mem.read_slice(&mut mpf, GuestAddress(MPTABLE_START))
.unwrap();
let sum: u8 = mpf.iter().fold(0u8, |acc, &b| acc.wrapping_add(b));
assert_eq!(sum, 0, "MPF checksum must be zero");
}
#[test]
fn mptable_header_checksum() {
let mem = test_mem(16);
let topo = Topology {
llcs: 1,
cores_per_llc: 4,
threads_per_core: 1,
numa_nodes: 1,
nodes: None,
distances: None,
};
setup_mptable(&mem, &topo).unwrap();
let header_addr = GuestAddress(MPTABLE_START + 16);
let mut len_bytes = [0u8; 2];
mem.read_slice(&mut len_bytes, header_addr.unchecked_add(4))
.unwrap();
let table_len = u16::from_le_bytes(len_bytes) as usize;
let mut table = vec![0u8; table_len];
mem.read_slice(&mut table, header_addr).unwrap();
let sum: u8 = table.iter().fold(0u8, |acc, &b| acc.wrapping_add(b));
assert_eq!(sum, 0, "MPC table checksum must be zero");
}
#[test]
fn mptable_cpu_apic_ids_match_topology() {
let mem = test_mem(16);
let topo = Topology {
llcs: 2,
cores_per_llc: 2,
threads_per_core: 1,
numa_nodes: 1,
nodes: None,
distances: None,
};
setup_mptable(&mem, &topo).unwrap();
let cpu_start = GuestAddress(MPTABLE_START + 16 + 44);
for i in 0..topo.total_cpus() {
let entry_addr = cpu_start.unchecked_add(i as u64 * 20);
let mut entry = [0u8; 20];
mem.read_slice(&mut entry, entry_addr).unwrap();
assert_eq!(entry[0], MP_PROCESSOR, "entry type should be processor");
let id = entry[1];
let expected = apic_id(&topo, i) as u8;
assert_eq!(id, expected, "CPU {i}: APIC ID {id} != expected {expected}");
}
}
#[test]
fn mptable_bsp_flagged() {
let mem = test_mem(16);
let topo = Topology {
llcs: 2,
cores_per_llc: 2,
threads_per_core: 1,
numa_nodes: 1,
nodes: None,
distances: None,
};
setup_mptable(&mem, &topo).unwrap();
let cpu_start = GuestAddress(MPTABLE_START + 16 + 44);
let mut entry0 = [0u8; 20];
mem.read_slice(&mut entry0, cpu_start).unwrap();
assert_ne!(entry0[3] & CPU_BSP, 0, "CPU 0 should be BSP");
assert_ne!(entry0[3] & CPU_ENABLED, 0, "CPU 0 should be enabled");
let mut entry1 = [0u8; 20];
mem.read_slice(&mut entry1, cpu_start.unchecked_add(20))
.unwrap();
assert_eq!(entry1[3] & CPU_BSP, 0, "CPU 1 should not be BSP");
assert_ne!(entry1[3] & CPU_ENABLED, 0, "CPU 1 should be enabled");
}
#[test]
fn mptable_large_topology_240_cpus() {
let mem = test_mem(2048);
let topo = Topology {
llcs: 15,
cores_per_llc: 8,
threads_per_core: 2,
numa_nodes: 1,
nodes: None,
distances: None,
};
assert_eq!(topo.total_cpus(), 240);
assert!(setup_mptable(&mem, &topo).is_ok());
}
#[test]
fn mptable_large_topology() {
let mem = test_mem(4096);
let topo = Topology {
llcs: 14,
cores_per_llc: 9,
threads_per_core: 2,
numa_nodes: 1,
nodes: None,
distances: None,
};
assert_eq!(topo.total_cpus(), 252);
assert!(setup_mptable(&mem, &topo).is_ok());
}
}