blob: 7d6701ae24dc2129adef93cb49ee48c235369d2e [file] [log] [blame]
// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::convert::TryFrom;
use std::fmt::{self, Display};
use std::mem;
use std::result;
use std::slice;
use libc::c_char;
use devices::{PciAddress, PciInterruptPin};
use vm_memory::{GuestAddress, GuestMemory};
use crate::mpspec::*;
#[derive(Debug)]
pub enum Error {
/// There was too little guest memory to store the entire MP table.
NotEnoughMemory,
/// The MP table has too little address space to be stored.
AddressOverflow,
/// Failure while zeroing out the memory for the MP table.
Clear,
/// Failure to write the MP floating pointer.
WriteMpfIntel,
/// Failure to write MP CPU entry.
WriteMpcCpu,
/// Failure to write MP ioapic entry.
WriteMpcIoapic,
/// Failure to write MP bus entry.
WriteMpcBus,
/// Failure to write MP interrupt source entry.
WriteMpcIntsrc,
/// Failure to write MP local interrupt source entry.
WriteMpcLintsrc,
/// Failure to write MP table header.
WriteMpcTable,
}
impl std::error::Error for Error {}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::Error::*;
let description = match self {
NotEnoughMemory => "There was too little guest memory to store the MP table",
AddressOverflow => "The MP table has too little address space to be stored",
Clear => "Failure while zeroing out the memory for the MP table",
WriteMpfIntel => "Failure to write the MP floating pointer",
WriteMpcCpu => "Failure to write MP CPU entry",
WriteMpcIoapic => "Failure to write MP ioapic entry",
WriteMpcBus => "Failure to write MP bus entry",
WriteMpcIntsrc => "Failure to write MP interrupt source entry",
WriteMpcLintsrc => "Failure to write MP local interrupt source entry",
WriteMpcTable => "Failure to write MP table header",
};
write!(f, "MPTable error: {}", description)
}
}
pub type Result<T> = result::Result<T, Error>;
// Convenience macro for making arrays of diverse character types.
macro_rules! char_array {
($t:ty; $( $c:expr ),*) => ( [ $( $c as $t ),* ] )
}
// Most of these variables are sourced from the Intel MP Spec 1.4.
const SMP_MAGIC_IDENT: [c_char; 4] = char_array!(c_char; '_', 'M', 'P', '_');
const MPC_SIGNATURE: [c_char; 4] = char_array!(c_char; 'P', 'C', 'M', 'P');
const MPC_SPEC: i8 = 4;
const MPC_OEM: [c_char; 8] = char_array!(c_char; 'C', 'R', 'O', 'S', 'V', 'M', ' ', ' ');
const MPC_PRODUCT_ID: [c_char; 12] = ['0' as c_char; 12];
const BUS_TYPE_ISA: [u8; 6] = char_array!(u8; 'I', 'S', 'A', ' ', ' ', ' ');
const BUS_TYPE_PCI: [u8; 6] = char_array!(u8; 'P', 'C', 'I', ' ', ' ', ' ');
// source: linux/arch/x86/include/asm/apicdef.h
pub const IO_APIC_DEFAULT_PHYS_BASE: u32 = 0xfec00000;
// source: linux/arch/x86/include/asm/apicdef.h
pub const APIC_DEFAULT_PHYS_BASE: u32 = 0xfee00000;
const APIC_VERSION: u8 = 0x14;
const CPU_STEPPING: u32 = 0x600;
const CPU_FEATURE_APIC: u32 = 0x200;
const CPU_FEATURE_FPU: u32 = 0x001;
const MPTABLE_START: u64 = 0x400 * 639; // Last 1k of Linux's 640k base RAM.
fn compute_checksum<T: Copy>(v: &T) -> u8 {
// Safe because we are only reading the bytes within the size of the `T` reference `v`.
let v_slice = unsafe { slice::from_raw_parts(v as *const T as *const u8, mem::size_of::<T>()) };
let mut checksum: u8 = 0;
for i in v_slice {
checksum = checksum.wrapping_add(*i);
}
checksum
}
fn mpf_intel_compute_checksum(v: &mpf_intel) -> u8 {
let checksum = compute_checksum(v).wrapping_sub(v.checksum);
(!checksum).wrapping_add(1)
}
fn compute_mp_size(num_cpus: u8) -> usize {
mem::size_of::<mpf_intel>()
+ mem::size_of::<mpc_table>()
+ mem::size_of::<mpc_cpu>() * (num_cpus as usize)
+ mem::size_of::<mpc_ioapic>()
+ mem::size_of::<mpc_bus>() * 2
+ mem::size_of::<mpc_intsrc>()
+ mem::size_of::<mpc_intsrc>() * 16
+ mem::size_of::<mpc_lintsrc>() * 2
}
/// Performs setup of the MP table for the given `num_cpus`.
pub fn setup_mptable(
mem: &GuestMemory,
num_cpus: u8,
pci_irqs: Vec<(PciAddress, u32, PciInterruptPin)>,
) -> Result<()> {
// Used to keep track of the next base pointer into the MP table.
let mut base_mp = GuestAddress(MPTABLE_START);
// Calculate ISA bus number in the system, report at least one PCI bus.
let isa_bus_id = match pci_irqs.iter().max_by_key(|v| v.0.bus) {
Some(pci_irq) => pci_irq.0.bus + 1,
_ => 1,
};
let mp_size = compute_mp_size(num_cpus);
// The checked_add here ensures the all of the following base_mp.unchecked_add's will be without
// overflow.
if let Some(end_mp) = base_mp.checked_add(mp_size as u64 - 1) {
if !mem.address_in_range(end_mp) {
return Err(Error::NotEnoughMemory);
}
} else {
return Err(Error::AddressOverflow);
}
mem.get_slice_at_addr(base_mp, mp_size)
.map_err(|_| Error::Clear)?
.write_bytes(0);
{
let size = mem::size_of::<mpf_intel>();
let mut mpf_intel = mpf_intel::default();
mpf_intel.signature = SMP_MAGIC_IDENT;
mpf_intel.length = 1;
mpf_intel.specification = 4;
mpf_intel.physptr = (base_mp.offset() + mem::size_of::<mpf_intel>() as u64) as u32;
mpf_intel.checksum = mpf_intel_compute_checksum(&mpf_intel);
mem.write_obj_at_addr(mpf_intel, base_mp)
.map_err(|_| Error::WriteMpfIntel)?;
base_mp = base_mp.unchecked_add(size as u64);
}
// We set the location of the mpc_table here but we can't fill it out until we have the length
// of the entire table later.
let table_base = base_mp;
base_mp = base_mp.unchecked_add(mem::size_of::<mpc_table>() as u64);
let mut checksum: u8 = 0;
let ioapicid: u8 = num_cpus + 1;
for cpu_id in 0..num_cpus {
let size = mem::size_of::<mpc_cpu>();
let mpc_cpu = mpc_cpu {
type_: MP_PROCESSOR as u8,
apicid: cpu_id,
apicver: APIC_VERSION,
cpuflag: CPU_ENABLED as u8
| if cpu_id == 0 {
CPU_BOOTPROCESSOR as u8
} else {
0
},
cpufeature: CPU_STEPPING,
featureflag: CPU_FEATURE_APIC | CPU_FEATURE_FPU,
..Default::default()
};
mem.write_obj_at_addr(mpc_cpu, base_mp)
.map_err(|_| Error::WriteMpcCpu)?;
base_mp = base_mp.unchecked_add(size as u64);
checksum = checksum.wrapping_add(compute_checksum(&mpc_cpu));
}
{
let size = mem::size_of::<mpc_ioapic>();
let mpc_ioapic = mpc_ioapic {
type_: MP_IOAPIC as u8,
apicid: ioapicid,
apicver: APIC_VERSION,
flags: MPC_APIC_USABLE as u8,
apicaddr: IO_APIC_DEFAULT_PHYS_BASE,
};
mem.write_obj_at_addr(mpc_ioapic, base_mp)
.map_err(|_| Error::WriteMpcIoapic)?;
base_mp = base_mp.unchecked_add(size as u64);
checksum = checksum.wrapping_add(compute_checksum(&mpc_ioapic));
}
for pci_bus_id in 0..isa_bus_id {
let size = mem::size_of::<mpc_bus>();
let mpc_bus = mpc_bus {
type_: MP_BUS as u8,
busid: pci_bus_id,
bustype: BUS_TYPE_PCI,
};
mem.write_obj_at_addr(mpc_bus, base_mp)
.map_err(|_| Error::WriteMpcBus)?;
base_mp = base_mp.unchecked_add(size as u64);
checksum = checksum.wrapping_add(compute_checksum(&mpc_bus));
}
{
let size = mem::size_of::<mpc_bus>();
let mpc_bus = mpc_bus {
type_: MP_BUS as u8,
busid: isa_bus_id,
bustype: BUS_TYPE_ISA,
};
mem.write_obj_at_addr(mpc_bus, base_mp)
.map_err(|_| Error::WriteMpcBus)?;
base_mp = base_mp.unchecked_add(size as u64);
checksum = checksum.wrapping_add(compute_checksum(&mpc_bus));
}
{
let size = mem::size_of::<mpc_intsrc>();
let mpc_intsrc = mpc_intsrc {
type_: MP_INTSRC as u8,
irqtype: mp_irq_source_types_mp_INT as u8,
irqflag: MP_IRQDIR_DEFAULT as u16,
srcbus: isa_bus_id,
srcbusirq: 0,
dstapic: 0,
dstirq: 0,
};
mem.write_obj_at_addr(mpc_intsrc, base_mp)
.map_err(|_| Error::WriteMpcIntsrc)?;
base_mp = base_mp.unchecked_add(size as u64);
checksum = checksum.wrapping_add(compute_checksum(&mpc_intsrc));
}
let sci_irq = super::X86_64_SCI_IRQ as u8;
// Per kvm_setup_default_irq_routing() in kernel
for i in 0..sci_irq {
let size = mem::size_of::<mpc_intsrc>();
let mpc_intsrc = mpc_intsrc {
type_: MP_INTSRC as u8,
irqtype: mp_irq_source_types_mp_INT as u8,
irqflag: MP_IRQDIR_DEFAULT as u16,
srcbus: isa_bus_id,
srcbusirq: i,
dstapic: ioapicid,
dstirq: i,
};
mem.write_obj_at_addr(mpc_intsrc, base_mp)
.map_err(|_| Error::WriteMpcIntsrc)?;
base_mp = base_mp.unchecked_add(size as u64);
checksum = checksum.wrapping_add(compute_checksum(&mpc_intsrc));
}
// Insert SCI interrupt before PCI interrupts. Set the SCI interrupt
// to be the default trigger/polarity of PCI bus, which is level/low.
// This setting can be changed in future if necessary.
{
let size = mem::size_of::<mpc_intsrc>();
let mpc_intsrc = mpc_intsrc {
type_: MP_INTSRC as u8,
irqtype: mp_irq_source_types_mp_INT as u8,
irqflag: (MP_IRQDIR_HIGH | MP_LEVEL_TRIGGER) as u16,
srcbus: isa_bus_id,
srcbusirq: sci_irq,
dstapic: ioapicid,
dstirq: sci_irq,
};
mem.write_obj_at_addr(mpc_intsrc, base_mp)
.map_err(|_| Error::WriteMpcIntsrc)?;
base_mp = base_mp.unchecked_add(size as u64);
checksum = checksum.wrapping_add(compute_checksum(&mpc_intsrc));
}
// Insert PCI interrupts after platform IRQs.
for (address, irq_num, irq_pin) in pci_irqs.iter() {
let size = mem::size_of::<mpc_intsrc>();
let mpc_intsrc = mpc_intsrc {
type_: MP_INTSRC as u8,
irqtype: mp_irq_source_types_mp_INT as u8,
irqflag: MP_IRQDIR_DEFAULT as u16,
srcbus: address.bus,
srcbusirq: address.dev << 2 | irq_pin.to_mask() as u8,
dstapic: ioapicid,
dstirq: u8::try_from(*irq_num).map_err(|_| Error::WriteMpcIntsrc)?,
};
mem.write_obj_at_addr(mpc_intsrc, base_mp)
.map_err(|_| Error::WriteMpcIntsrc)?;
base_mp = base_mp.unchecked_add(size as u64);
checksum = checksum.wrapping_add(compute_checksum(&mpc_intsrc));
}
let starting_isa_irq_num = pci_irqs
.into_iter()
.map(|(_, irq_num, _)| irq_num + 1)
.fold(super::X86_64_IRQ_BASE, u32::max) as u8;
// Finally insert ISA interrupts.
for i in starting_isa_irq_num..16 {
let size = mem::size_of::<mpc_intsrc>();
let mpc_intsrc = mpc_intsrc {
type_: MP_INTSRC as u8,
irqtype: mp_irq_source_types_mp_INT as u8,
irqflag: MP_IRQDIR_DEFAULT as u16,
srcbus: isa_bus_id,
srcbusirq: i as u8,
dstapic: ioapicid,
dstirq: i as u8,
};
mem.write_obj_at_addr(mpc_intsrc, base_mp)
.map_err(|_| Error::WriteMpcIntsrc)?;
base_mp = base_mp.unchecked_add(size as u64);
checksum = checksum.wrapping_add(compute_checksum(&mpc_intsrc));
}
{
let size = mem::size_of::<mpc_lintsrc>();
let mpc_lintsrc = mpc_lintsrc {
type_: MP_LINTSRC as u8,
irqtype: mp_irq_source_types_mp_ExtINT as u8,
irqflag: MP_IRQDIR_DEFAULT as u16,
srcbusid: isa_bus_id,
srcbusirq: 0,
destapic: 0,
destapiclint: 0,
};
mem.write_obj_at_addr(mpc_lintsrc, base_mp)
.map_err(|_| Error::WriteMpcLintsrc)?;
base_mp = base_mp.unchecked_add(size as u64);
checksum = checksum.wrapping_add(compute_checksum(&mpc_lintsrc));
}
{
let size = mem::size_of::<mpc_lintsrc>();
let mpc_lintsrc = mpc_lintsrc {
type_: MP_LINTSRC as u8,
irqtype: mp_irq_source_types_mp_NMI as u8,
irqflag: MP_IRQDIR_DEFAULT as u16,
srcbusid: isa_bus_id,
srcbusirq: 0,
destapic: 0xFF, // Per SeaBIOS
destapiclint: 1,
};
mem.write_obj_at_addr(mpc_lintsrc, base_mp)
.map_err(|_| Error::WriteMpcLintsrc)?;
base_mp = base_mp.unchecked_add(size as u64);
checksum = checksum.wrapping_add(compute_checksum(&mpc_lintsrc));
}
// At this point we know the size of the mp_table.
let table_end = base_mp;
{
let mut mpc_table = mpc_table {
signature: MPC_SIGNATURE,
length: table_end.offset_from(table_base) as u16,
spec: MPC_SPEC,
oem: MPC_OEM,
productid: MPC_PRODUCT_ID,
lapic: APIC_DEFAULT_PHYS_BASE,
..Default::default()
};
checksum = checksum.wrapping_add(compute_checksum(&mpc_table));
mpc_table.checksum = (!checksum).wrapping_add(1) as i8;
mem.write_obj_at_addr(mpc_table, table_base)
.map_err(|_| Error::WriteMpcTable)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use base::pagesize;
fn compute_page_aligned_mp_size(num_cpus: u8) -> u64 {
let mp_size = compute_mp_size(num_cpus);
let pg_size = pagesize();
(mp_size + pg_size - (mp_size % pg_size)) as u64
}
fn table_entry_size(type_: u8) -> usize {
match type_ as u32 {
MP_PROCESSOR => mem::size_of::<mpc_cpu>(),
MP_BUS => mem::size_of::<mpc_bus>(),
MP_IOAPIC => mem::size_of::<mpc_ioapic>(),
MP_INTSRC => mem::size_of::<mpc_intsrc>(),
MP_LINTSRC => mem::size_of::<mpc_lintsrc>(),
_ => panic!("unrecognized mpc table entry type: {}", type_),
}
}
#[test]
fn bounds_check() {
let num_cpus = 4;
let mem = GuestMemory::new(&[(
GuestAddress(MPTABLE_START),
compute_page_aligned_mp_size(num_cpus),
)])
.unwrap();
setup_mptable(&mem, num_cpus, Vec::new()).unwrap();
}
#[test]
fn bounds_check_fails() {
let num_cpus = 255;
let mem = GuestMemory::new(&[(GuestAddress(MPTABLE_START), 0x1000)]).unwrap();
assert!(setup_mptable(&mem, num_cpus, Vec::new()).is_err());
}
#[test]
fn mpf_intel_checksum() {
let num_cpus = 1;
let mem = GuestMemory::new(&[(
GuestAddress(MPTABLE_START),
compute_page_aligned_mp_size(num_cpus),
)])
.unwrap();
setup_mptable(&mem, num_cpus, Vec::new()).unwrap();
let mpf_intel = mem.read_obj_from_addr(GuestAddress(MPTABLE_START)).unwrap();
assert_eq!(mpf_intel_compute_checksum(&mpf_intel), mpf_intel.checksum);
}
#[test]
fn mpc_table_checksum() {
let num_cpus = 4;
let mem = GuestMemory::new(&[(
GuestAddress(MPTABLE_START),
compute_page_aligned_mp_size(num_cpus),
)])
.unwrap();
setup_mptable(&mem, num_cpus, Vec::new()).unwrap();
let mpf_intel: mpf_intel = mem.read_obj_from_addr(GuestAddress(MPTABLE_START)).unwrap();
let mpc_offset = GuestAddress(mpf_intel.physptr as u64);
let mpc_table: mpc_table = mem.read_obj_from_addr(mpc_offset).unwrap();
let mut buf = vec![0; mpc_table.length as usize];
mem.read_at_addr(&mut buf[..], mpc_offset).unwrap();
let mut sum: u8 = 0;
for &v in &buf {
sum = sum.wrapping_add(v);
}
assert_eq!(sum, 0);
}
#[test]
fn cpu_entry_count() {
const MAX_CPUS: u8 = 0xff;
let mem = GuestMemory::new(&[(
GuestAddress(MPTABLE_START),
compute_page_aligned_mp_size(MAX_CPUS),
)])
.unwrap();
for i in 0..MAX_CPUS {
setup_mptable(&mem, i, Vec::new()).unwrap();
let mpf_intel: mpf_intel = mem.read_obj_from_addr(GuestAddress(MPTABLE_START)).unwrap();
let mpc_offset = GuestAddress(mpf_intel.physptr as u64);
let mpc_table: mpc_table = mem.read_obj_from_addr(mpc_offset).unwrap();
let mpc_end = mpc_offset.checked_add(mpc_table.length as u64).unwrap();
let mut entry_offset = mpc_offset
.checked_add(mem::size_of::<mpc_table>() as u64)
.unwrap();
let mut cpu_count = 0;
while entry_offset < mpc_end {
let entry_type: u8 = mem.read_obj_from_addr(entry_offset).unwrap();
entry_offset = entry_offset
.checked_add(table_entry_size(entry_type) as u64)
.unwrap();
assert!(entry_offset <= mpc_end);
if entry_type as u32 == MP_PROCESSOR {
cpu_count += 1;
}
}
assert_eq!(cpu_count, i);
}
}
}