blob: ad3ec46efcb2915f61ed73f54c4cc4166d4bd229 [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.
extern crate byteorder;
extern crate data_model;
extern crate kvm;
extern crate kvm_sys;
extern crate libc;
extern crate sys_util;
#[allow(dead_code)]
#[allow(non_upper_case_globals)]
#[allow(non_camel_case_types)]
#[allow(non_snake_case)]
mod bootparam;
// Bindgen didn't implement copy for boot_params because edid_info contains an array with len > 32.
impl Copy for bootparam::edid_info {}
impl Clone for bootparam::edid_info {
fn clone(&self) -> Self {
*self
}
}
impl Copy for bootparam::boot_params {}
impl Clone for bootparam::boot_params {
fn clone(&self) -> Self {
*self
}
}
// boot_params is just a series of ints, it is safe to initialize it.
unsafe impl data_model::DataInit for bootparam::boot_params {}
#[allow(dead_code)]
#[allow(non_upper_case_globals)]
mod msr_index;
#[allow(dead_code)]
#[allow(non_upper_case_globals)]
#[allow(non_camel_case_types)]
mod mpspec;
// These mpspec types are only data, reading them from data is a safe initialization.
unsafe impl data_model::DataInit for mpspec::mpc_bus {}
unsafe impl data_model::DataInit for mpspec::mpc_cpu {}
unsafe impl data_model::DataInit for mpspec::mpc_intsrc {}
unsafe impl data_model::DataInit for mpspec::mpc_ioapic {}
unsafe impl data_model::DataInit for mpspec::mpc_table {}
unsafe impl data_model::DataInit for mpspec::mpc_lintsrc {}
unsafe impl data_model::DataInit for mpspec::mpf_intel {}
mod cpuid;
mod gdt;
mod interrupts;
mod mptable;
mod regs;
use std::mem;
use std::result;
use bootparam::boot_params;
use bootparam::E820_RAM;
use sys_util::{GuestAddress, GuestMemory};
pub use regs::Error as RegError;
pub use interrupts::Error as IntError;
pub use mptable::Error as MpTableError;
#[derive(Debug)]
pub enum Error {
/// Error configuring the VCPU.
CpuSetup(cpuid::Error),
/// The kernel extends past the end of RAM
KernelOffsetPastEnd,
/// Error configuring the VCPU registers.
RegisterConfiguration(RegError),
/// Error configuring the VCPU floating point registers.
FpuRegisterConfiguration(RegError),
/// Error configuring the VCPU segment registers.
SegmentRegisterConfiguration(RegError),
/// Error configuring the VCPU local interrupt.
LocalIntConfiguration(IntError),
/// Error writing MP table to memory.
MpTableSetup(MpTableError),
/// Error writing the zero page of guest memory.
ZeroPageSetup,
/// The zero page extends past the end of guest_mem.
ZeroPagePastRamEnd,
/// Invalid e820 setup params.
E820Configuration,
}
pub type Result<T> = result::Result<T, Error>;
const BOOT_STACK_POINTER: usize = 0x8000;
const MEM_32BIT_GAP_SIZE: usize = (768 << 20);
const FIRST_ADDR_PAST_32BITS: usize = (1 << 32);
const KERNEL_64BIT_ENTRY_OFFSET: usize = 0x200;
const ZERO_PAGE_OFFSET: usize = 0x7000;
/// Returns a Vec of the valid memory addresses.
/// These should be used to configure the GuestMemory structure for the platfrom.
/// For x86_64 all addresses are valid from the start of the kenel except a
/// carve out at the end of 32bit address space.
pub fn arch_memory_regions(size: usize) -> Vec<(GuestAddress, usize)> {
let mem_end = GuestAddress(size);
let first_addr_past_32bits = GuestAddress(FIRST_ADDR_PAST_32BITS);
let end_32bit_gap_start = GuestAddress(FIRST_ADDR_PAST_32BITS - MEM_32BIT_GAP_SIZE);
let mut regions = Vec::new();
if mem_end < end_32bit_gap_start {
regions.push((GuestAddress(0), size));
} else {
regions.push((GuestAddress(0), end_32bit_gap_start.offset()));
if mem_end > first_addr_past_32bits {
regions.push((first_addr_past_32bits, mem_end.offset_from(first_addr_past_32bits)));
}
}
regions
}
/// Configures the vcpu and should be called once per vcpu from the vcpu's thread.
///
/// # Arguments
///
/// * `guest_mem` - The memory to be used by the guest.
/// * `kernel_load_offset` - Offset from `guest_mem` at which the kernel starts.
/// * `kvm` - The /dev/kvm object that created vcpu.
/// * `vcpu` - The VCPU object to configure.
/// * `cpu_id` - The id of the given `vcpu`.
/// * `num_cpus` - Number of virtual CPUs the guest will have.
pub fn configure_vcpu(guest_mem: &GuestMemory,
kernel_load_addr: GuestAddress,
kvm: &kvm::Kvm,
vcpu: &kvm::Vcpu,
cpu_id: u64,
num_cpus: u64)
-> Result<()> {
cpuid::setup_cpuid(kvm, vcpu, cpu_id, num_cpus).map_err(Error::CpuSetup)?;
regs::setup_msrs(vcpu).map_err(Error::RegisterConfiguration)?;
let kernel_end = guest_mem.checked_offset(kernel_load_addr, KERNEL_64BIT_ENTRY_OFFSET)
.ok_or(Error::KernelOffsetPastEnd)?;
regs::setup_regs(vcpu,
(kernel_end).offset() as u64,
BOOT_STACK_POINTER as u64,
ZERO_PAGE_OFFSET as u64).map_err(Error::RegisterConfiguration)?;
regs::setup_fpu(vcpu).map_err(Error::FpuRegisterConfiguration)?;
regs::setup_sregs(guest_mem, vcpu).map_err(Error::SegmentRegisterConfiguration)?;
interrupts::set_lint(vcpu).map_err(Error::LocalIntConfiguration)?;
Ok(())
}
/// Configures the system and should be called once per vm before starting vcpu threads.
///
/// # Arguments
///
/// * `guest_mem` - The memory to be used by the guest.
/// * `kernel_addr` - Address in `guest_mem` where the kernel was loaded.
/// * `cmdline_addr` - Address in `guest_mem` where the kernel command line was loaded.
/// * `cmdline_size` - Size of the kernel command line in bytes including the null terminator.
/// * `num_cpus` - Number of virtual CPUs the guest will have.
pub fn configure_system(guest_mem: &GuestMemory,
kernel_addr: GuestAddress,
cmdline_addr: GuestAddress,
cmdline_size: usize,
num_cpus: u8)
-> Result<()> {
const EBDA_START: u64 = 0x0009fc00;
const KERNEL_BOOT_FLAG_MAGIC: u16 = 0xaa55;
const KERNEL_HDR_MAGIC: u32 = 0x53726448;
const KERNEL_LOADER_OTHER: u8 = 0xff;
const KERNEL_MIN_ALIGNMENT_BYTES: u32 = 0x1000000; // Must be non-zero.
let first_addr_past_32bits = GuestAddress(FIRST_ADDR_PAST_32BITS);
let end_32bit_gap_start = GuestAddress(FIRST_ADDR_PAST_32BITS - MEM_32BIT_GAP_SIZE);
// Note that this puts the mptable at 0x0 in guest physical memory.
mptable::setup_mptable(guest_mem, num_cpus).map_err(Error::MpTableSetup)?;
let mut params: boot_params = Default::default();
params.hdr.type_of_loader = KERNEL_LOADER_OTHER;
params.hdr.boot_flag = KERNEL_BOOT_FLAG_MAGIC;
params.hdr.header = KERNEL_HDR_MAGIC;
params.hdr.cmd_line_ptr = cmdline_addr.offset() as u32;
params.hdr.cmdline_size = cmdline_size as u32;
params.hdr.kernel_alignment = KERNEL_MIN_ALIGNMENT_BYTES;
add_e820_entry(&mut params, 0, EBDA_START, E820_RAM)?;
let mem_end = guest_mem.end_addr();
if mem_end < end_32bit_gap_start {
add_e820_entry(&mut params,
kernel_addr.offset() as u64,
mem_end.offset_from(kernel_addr) as u64,
E820_RAM)?;
} else {
add_e820_entry(&mut params,
kernel_addr.offset() as u64,
end_32bit_gap_start.offset_from(kernel_addr) as u64,
E820_RAM)?;
if mem_end > first_addr_past_32bits {
add_e820_entry(&mut params,
first_addr_past_32bits.offset() as u64,
mem_end.offset_from(first_addr_past_32bits) as u64,
E820_RAM)?;
}
}
let zero_page_addr = GuestAddress(ZERO_PAGE_OFFSET);
guest_mem.checked_offset(zero_page_addr, mem::size_of::<boot_params>())
.ok_or(Error::ZeroPagePastRamEnd)?;
guest_mem.write_obj_at_addr(params, zero_page_addr)
.map_err(|_| Error::ZeroPageSetup)?;
Ok(())
}
/// Add an e820 region to the e820 map.
/// Returns Ok(()) if successful, or an error if there is no space left in the map.
fn add_e820_entry(params: &mut boot_params, addr: u64, size: u64, mem_type: u32) -> Result<()> {
if params.e820_entries >= params.e820_map.len() as u8 {
return Err(Error::E820Configuration);
}
params.e820_map[params.e820_entries as usize].addr = addr;
params.e820_map[params.e820_entries as usize].size = size;
params.e820_map[params.e820_entries as usize].type_ = mem_type;
params.e820_entries += 1;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn regions_lt_4gb() {
let regions = arch_memory_regions(1usize << 29);
assert_eq!(1, regions.len());
assert_eq!(GuestAddress(0), regions[0].0);
assert_eq!(1usize << 29, regions[0].1);
}
#[test]
fn regions_gt_4gb() {
let regions = arch_memory_regions((1usize << 32) + 0x8000);
assert_eq!(2, regions.len());
assert_eq!(GuestAddress(0), regions[0].0);
assert_eq!(GuestAddress(1usize << 32), regions[1].0);
}
}