use alloc::{vec, vec::Vec};
use super::linux::X86LinuxRange;
pub const MP_TABLE_GPA: usize = 0x9f800;
pub const MP_TABLE_SIZE: usize = 0x800;
const MP_CONFIG_GPA: usize = MP_TABLE_GPA;
const MP_FLOATING_POINTER_GPA: usize = 0x9fc00;
const MP_FLOATING_POINTER_OFFSET: usize = MP_FLOATING_POINTER_GPA - MP_TABLE_GPA;
const LOCAL_APIC_ADDR: u32 = 0xfee0_0000;
const IO_APIC_ADDR: u32 = 0xfec0_0000;
const BSP_APIC_ID: u8 = 0;
const IO_APIC_ID: u8 = 1;
const APIC_VERSION: u8 = 0x14;
const IO_APIC_VERSION: u8 = 0x11;
const BUS_ID_PCI: u8 = 0;
const BUS_ID_ISA: u8 = 1;
pub const fn reserved_range() -> X86LinuxRange {
X86LinuxRange::new(MP_TABLE_GPA, MP_TABLE_SIZE)
}
pub fn build() -> [u8; MP_TABLE_SIZE] {
let mut image = [0u8; MP_TABLE_SIZE];
let config = build_config_table();
image[..config.len()].copy_from_slice(&config);
let floating = build_floating_pointer();
image[MP_FLOATING_POINTER_OFFSET..MP_FLOATING_POINTER_OFFSET + floating.len()]
.copy_from_slice(&floating);
image
}
fn build_floating_pointer() -> [u8; 16] {
let mut data = [0u8; 16];
data[0..4].copy_from_slice(b"_MP_");
data[4..8].copy_from_slice(&(MP_CONFIG_GPA as u32).to_le_bytes());
data[8] = 1; data[9] = 4; data[10] = checksum(&data);
data
}
fn build_config_table() -> Vec<u8> {
let entries = config_entries();
let entries_len: usize = entries.iter().map(Vec::len).sum();
let mut table = Vec::with_capacity(44 + entries_len);
table.extend_from_slice(b"PCMP");
table.extend_from_slice(&0u16.to_le_bytes());
table.push(4); table.push(0); table.extend_from_slice(b"AXVISOR ");
table.extend_from_slice(b"X86LINUX ");
table.extend_from_slice(&0u32.to_le_bytes());
table.extend_from_slice(&0u16.to_le_bytes());
table.extend_from_slice(&(entries.len() as u16).to_le_bytes());
table.extend_from_slice(&LOCAL_APIC_ADDR.to_le_bytes());
table.extend_from_slice(&0u16.to_le_bytes());
table.push(0);
table.push(0);
for entry in &entries {
table.extend_from_slice(entry);
}
let len = table.len() as u16;
table[4..6].copy_from_slice(&len.to_le_bytes());
table[7] = checksum(&table);
table
}
fn config_entries() -> Vec<Vec<u8>> {
let mut entries = vec![
processor_entry(),
bus_entry(BUS_ID_PCI, b"PCI "),
bus_entry(BUS_ID_ISA, b"ISA "),
io_apic_entry(),
];
push_isa_interrupt_entries(&mut entries);
push_pci_interrupt_entries(&mut entries);
entries
}
fn processor_entry() -> Vec<u8> {
let mut entry = Vec::with_capacity(20);
entry.push(0);
entry.push(BSP_APIC_ID);
entry.push(APIC_VERSION);
entry.push(0x03); entry.extend_from_slice(&0x0000_0600u32.to_le_bytes());
entry.extend_from_slice(&0x0000_0201u32.to_le_bytes());
entry.extend_from_slice(&[0; 8]);
entry
}
fn bus_entry(bus_id: u8, bus_type: &[u8; 6]) -> Vec<u8> {
let mut entry = Vec::with_capacity(8);
entry.push(1);
entry.push(bus_id);
entry.extend_from_slice(bus_type);
entry
}
fn io_apic_entry() -> Vec<u8> {
let mut entry = Vec::with_capacity(8);
entry.push(2);
entry.push(IO_APIC_ID);
entry.push(IO_APIC_VERSION);
entry.push(0x01); entry.extend_from_slice(&IO_APIC_ADDR.to_le_bytes());
entry
}
fn push_isa_interrupt_entries(entries: &mut Vec<Vec<u8>>) {
for irq in 0u8..16 {
entries.push(interrupt_entry(0, BUS_ID_ISA, irq, irq));
}
}
fn push_pci_interrupt_entries(entries: &mut Vec<Vec<u8>>) {
for dev in 0u8..4 {
for pin in 0u8..4 {
let source_irq = (dev << 2) | pin;
let intin = pci_intx_gsi(dev, pin);
entries.push(interrupt_entry(0, BUS_ID_PCI, source_irq, intin));
}
}
}
const fn pci_intx_gsi(dev: u8, pin: u8) -> u8 {
16 + ((dev + pin) & 3)
}
fn interrupt_entry(
interrupt_type: u8,
source_bus_id: u8,
source_bus_irq: u8,
dest_io_apic_intin: u8,
) -> Vec<u8> {
let mut entry = Vec::with_capacity(8);
entry.push(3);
entry.push(interrupt_type);
entry.extend_from_slice(&0u16.to_le_bytes());
entry.push(source_bus_id);
entry.push(source_bus_irq);
entry.push(IO_APIC_ID);
entry.push(dest_io_apic_intin);
entry
}
fn checksum(bytes: &[u8]) -> u8 {
0u8.wrapping_sub(bytes.iter().fold(0u8, |sum, byte| sum.wrapping_add(*byte)))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builds_valid_mp_table_checksums() {
let image = build();
let config_len = u16::from_le_bytes([image[4], image[5]]) as usize;
assert_eq!(&image[..4], b"PCMP");
assert_eq!(
image[..config_len]
.iter()
.fold(0u8, |sum, byte| sum.wrapping_add(*byte)),
0
);
let fp = &image[MP_FLOATING_POINTER_OFFSET..MP_FLOATING_POINTER_OFFSET + 16];
assert_eq!(&fp[..4], b"_MP_");
assert_eq!(fp.iter().fold(0u8, |sum, byte| sum.wrapping_add(*byte)), 0);
assert_eq!(
u32::from_le_bytes([fp[4], fp[5], fp[6], fp[7]]) as usize,
MP_CONFIG_GPA
);
}
#[test]
fn q35_dev3_inta_uses_swizzled_gsi19() {
assert_eq!(pci_intx_gsi(3, 0), 19);
}
}