pub const MP_FPS_ADDR: u64 = 0x9_fc00;
const MP_CONFIG_ADDR: u64 = MP_FPS_ADDR + 16;
const APIC_BASE: u32 = 0xfee0_0000;
const IOAPIC_BASE: u32 = 0xfec0_0000;
const ENTRY_PROCESSOR: u8 = 0;
const ENTRY_BUS: u8 = 1;
const ENTRY_IOAPIC: u8 = 2;
const ENTRY_IOINTERRUPT: u8 = 3;
const LEN_PROCESSOR: usize = 20;
const LEN_BUS: usize = 8;
const LEN_IOAPIC: usize = 8;
const LEN_IOINTERRUPT: usize = 8;
const CPU_ENABLED: u8 = 0x1;
const CPU_BSP: u8 = 0x2;
const NUM_ISA_IRQS: u8 = 16;
#[derive(Debug, PartialEq, Eq)]
pub struct MpTableTooBig {
pub need: usize,
pub avail: usize,
}
impl std::fmt::Display for MpTableTooBig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"MP table too big: need {} bytes, {} available in the reserved window",
self.need, self.avail
)
}
}
impl std::error::Error for MpTableTooBig {}
fn checksum(bytes: &[u8]) -> u8 {
let sum = bytes.iter().fold(0u8, |a, &b| a.wrapping_add(b));
(!sum).wrapping_add(1) }
fn put_u16(buf: &mut [u8], off: usize, v: u16) {
buf[off..off + 2].copy_from_slice(&v.to_le_bytes());
}
fn put_u32(buf: &mut [u8], off: usize, v: u32) {
buf[off..off + 4].copy_from_slice(&v.to_le_bytes());
}
pub fn write_mptable(mem: &mut [u8], num_cpus: u8) -> Result<(), MpTableTooBig> {
assert!(num_cpus >= 1, "num_cpus must be >= 1");
let header_len = 44usize;
let entries_len = num_cpus as usize * LEN_PROCESSOR
+ LEN_BUS
+ LEN_IOAPIC
+ NUM_ISA_IRQS as usize * LEN_IOINTERRUPT;
let table_len = header_len + entries_len;
let window = 0xa_0000usize - MP_FPS_ADDR as usize;
let total = 16 + table_len;
if total > window || MP_CONFIG_ADDR as usize + table_len > mem.len() {
return Err(MpTableTooBig {
need: total,
avail: window,
});
}
let mut table = vec![0u8; table_len];
table[0..4].copy_from_slice(b"PCMP");
put_u16(&mut table, 4, table_len as u16); table[6] = 4; table[8..16].copy_from_slice(b"SUPRMCHN"); table[16..28].copy_from_slice(b"x86-64 KVM "); let entry_count = num_cpus as u16 + 1 + 1 + NUM_ISA_IRQS as u16;
put_u16(&mut table, 34, entry_count);
put_u32(&mut table, 36, APIC_BASE);
let mut off = header_len;
for apic_id in 0..num_cpus {
table[off] = ENTRY_PROCESSOR;
table[off + 1] = apic_id; table[off + 2] = 0x14; table[off + 3] = CPU_ENABLED | if apic_id == 0 { CPU_BSP } else { 0 };
put_u32(&mut table, off + 4, 0x600); put_u32(&mut table, off + 8, 0x201); off += LEN_PROCESSOR;
}
table[off] = ENTRY_BUS;
table[off + 1] = 0; table[off + 2..off + 8].copy_from_slice(b"ISA ");
off += LEN_BUS;
let ioapic_id = num_cpus;
table[off] = ENTRY_IOAPIC;
table[off + 1] = ioapic_id;
table[off + 2] = 0x11; table[off + 3] = 0x1; put_u32(&mut table, off + 4, IOAPIC_BASE);
off += LEN_IOAPIC;
for irq in 0..NUM_ISA_IRQS {
table[off] = ENTRY_IOINTERRUPT;
table[off + 1] = 0; put_u16(&mut table, off + 2, 0); table[off + 4] = 0; table[off + 5] = irq; table[off + 6] = ioapic_id; table[off + 7] = irq; off += LEN_IOINTERRUPT;
}
debug_assert_eq!(off, table_len);
table[7] = checksum(&table);
let mut fps = [0u8; 16];
fps[0..4].copy_from_slice(b"_MP_");
put_u32(&mut fps, 4, MP_CONFIG_ADDR as u32); fps[8] = 1; fps[9] = 4; fps[10] = checksum(&fps);
let fps_at = MP_FPS_ADDR as usize;
mem[fps_at..fps_at + 16].copy_from_slice(&fps);
let table_at = MP_CONFIG_ADDR as usize;
mem[table_at..table_at + table_len].copy_from_slice(&table);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn read_u16(mem: &[u8], gpa: u64) -> u16 {
let s = gpa as usize;
u16::from_le_bytes(mem[s..s + 2].try_into().unwrap())
}
fn read_u32(mem: &[u8], gpa: u64) -> u32 {
let s = gpa as usize;
u32::from_le_bytes(mem[s..s + 4].try_into().unwrap())
}
#[test]
fn checksum_makes_region_sum_to_zero() {
let data = [1u8, 2, 3, 4, 250];
let mut buf = data.to_vec();
buf.push(checksum(&data));
let sum = buf.iter().fold(0u8, |a, &b| a.wrapping_add(b));
assert_eq!(sum, 0);
}
#[test]
fn writes_valid_floating_pointer() {
let mut mem = vec![0u8; 1024 * 1024];
write_mptable(&mut mem, 4).unwrap();
let fps = MP_FPS_ADDR as usize;
assert_eq!(&mem[fps..fps + 4], b"_MP_");
assert_eq!(read_u32(&mem, MP_FPS_ADDR + 4), MP_CONFIG_ADDR as u32);
assert_eq!(mem[fps + 8], 1, "length in 16-byte units");
assert_eq!(mem[fps + 9], 4, "spec rev");
let sum = mem[fps..fps + 16]
.iter()
.fold(0u8, |a, &b| a.wrapping_add(b));
assert_eq!(sum, 0, "FPS checksum");
}
#[test]
fn writes_valid_config_table_for_n_cpus() {
let n = 4u8;
let mut mem = vec![0u8; 1024 * 1024];
write_mptable(&mut mem, n).unwrap();
let cfg = MP_CONFIG_ADDR as usize;
assert_eq!(&mem[cfg..cfg + 4], b"PCMP");
let len = read_u16(&mem, MP_CONFIG_ADDR + 4) as usize;
assert_eq!(read_u32(&mem, MP_CONFIG_ADDR + 36), APIC_BASE, "lapic addr");
assert_eq!(
read_u16(&mem, MP_CONFIG_ADDR + 34),
n as u16 + 1 + 1 + NUM_ISA_IRQS as u16
);
let sum = mem[cfg..cfg + len]
.iter()
.fold(0u8, |a, &b| a.wrapping_add(b));
assert_eq!(sum, 0, "config table checksum");
}
#[test]
fn processor_entries_flag_bsp_and_enable_all() {
let n = 3u8;
let mut mem = vec![0u8; 1024 * 1024];
write_mptable(&mut mem, n).unwrap();
let mut off = MP_CONFIG_ADDR as usize + 44; for apic_id in 0..n {
assert_eq!(mem[off], ENTRY_PROCESSOR);
assert_eq!(mem[off + 1], apic_id, "apic id");
let flags = mem[off + 3];
assert_eq!(flags & CPU_ENABLED, CPU_ENABLED, "cpu enabled");
assert_eq!(flags & CPU_BSP != 0, apic_id == 0, "only cpu 0 is the BSP");
off += LEN_PROCESSOR;
}
}
#[test]
fn ioapic_entry_follows_the_processors() {
let n = 2u8;
let mut mem = vec![0u8; 1024 * 1024];
write_mptable(&mut mem, n).unwrap();
let ioapic = MP_CONFIG_ADDR as usize + 44 + n as usize * LEN_PROCESSOR + LEN_BUS;
assert_eq!(mem[ioapic], ENTRY_IOAPIC);
assert_eq!(mem[ioapic + 1], n, "ioapic id = num_cpus");
assert_eq!(read_u32(&mem, ioapic as u64 + 4), IOAPIC_BASE);
}
#[test]
fn rejects_when_table_would_overflow_the_window() {
let mut mem = vec![0u8; 1024 * 1024];
let err = write_mptable(&mut mem, 200).unwrap_err();
assert!(err.need > err.avail);
}
}