use std::mem;
use kvm_bindings::{kvm_fpu, kvm_msr_entry, kvm_regs, kvm_sregs, Msrs};
use kvm_ioctls::VcpuFd;
use vm_memory::{Address, Bytes, GuestAddress, GuestMemory};
use super::gdt::kvm_segment_from_gdt;
use super::msr;
pub const EFER_NX: u64 = 0x800;
pub const EFER_LMA: u64 = 0x400;
pub const EFER_LME: u64 = 0x100;
pub const X86_CR0_PE: u64 = 0x1;
pub const X86_CR0_PG: u64 = 0x8000_0000;
pub const X86_CR4_PAE: u64 = 0x20;
#[derive(Debug)]
pub enum Error {
GetStatusRegisters(kvm_ioctls::Error),
SetBaseRegisters(kvm_ioctls::Error),
SetFPURegisters(kvm_ioctls::Error),
SetModelSpecificRegisters(kvm_ioctls::Error),
SetModelSpecificRegistersCount,
SetStatusRegisters(kvm_ioctls::Error),
WriteGDT,
WriteIDT,
}
type Result<T> = std::result::Result<T, Error>;
pub fn setup_fpu(vcpu: &VcpuFd) -> Result<()> {
let fpu: kvm_fpu = kvm_fpu {
fcw: 0x37f,
mxcsr: 0x1f80,
..Default::default()
};
vcpu.set_fpu(&fpu).map_err(Error::SetFPURegisters)
}
pub fn setup_msrs(vcpu: &VcpuFd) -> Result<()> {
let entry_vec = create_msr_entries();
let kvm_msrs =
Msrs::from_entries(&entry_vec).map_err(|_| Error::SetModelSpecificRegistersCount)?;
vcpu.set_msrs(&kvm_msrs)
.map_err(Error::SetModelSpecificRegisters)
.and_then(|msrs_written| {
if msrs_written as u32 != kvm_msrs.as_fam_struct_ref().nmsrs {
Err(Error::SetModelSpecificRegistersCount)
} else {
Ok(msrs_written)
}
})?;
Ok(())
}
pub fn setup_regs(vcpu: &VcpuFd, boot_ip: u64, rsp: u64, rbp: u64, rsi: u64) -> Result<()> {
let regs: kvm_regs = kvm_regs {
rflags: 0x0000_0000_0000_0002u64,
rip: boot_ip,
rsp,
rbp,
rsi,
..Default::default()
};
vcpu.set_regs(®s).map_err(Error::SetBaseRegisters)
}
pub fn setup_sregs<M: GuestMemory>(
mem: &M,
vcpu: &VcpuFd,
pgtable_addr: GuestAddress,
gdt_table: &[u64],
gdt_addr: u64,
idt_addr: u64,
) -> Result<()> {
let mut sregs: kvm_sregs = vcpu.get_sregs().map_err(Error::GetStatusRegisters)?;
configure_segments_and_sregs(mem, &mut sregs, pgtable_addr, gdt_table, gdt_addr, idt_addr)?;
vcpu.set_sregs(&sregs).map_err(Error::SetStatusRegisters)
}
fn configure_segments_and_sregs<M: GuestMemory>(
mem: &M,
sregs: &mut kvm_sregs,
pgtable_addr: GuestAddress,
gdt_table: &[u64],
gdt_addr: u64,
idt_addr: u64,
) -> Result<()> {
assert!(gdt_table.len() >= 4);
let code_seg = kvm_segment_from_gdt(gdt_table[1], 1);
let data_seg = kvm_segment_from_gdt(gdt_table[2], 2);
let tss_seg = kvm_segment_from_gdt(gdt_table[3], 3);
write_gdt_table(gdt_table, gdt_addr, mem)?;
sregs.gdt.base = gdt_addr;
sregs.gdt.limit = (mem::size_of::<u64>() * gdt_table.len()) as u16 - 1;
write_idt_value(0, idt_addr, mem)?;
sregs.idt.base = idt_addr;
sregs.idt.limit = mem::size_of::<u64>() as u16 - 1;
sregs.cs = code_seg;
sregs.ds = data_seg;
sregs.es = data_seg;
sregs.fs = data_seg;
sregs.gs = data_seg;
sregs.ss = data_seg;
sregs.tr = tss_seg;
sregs.cr0 |= X86_CR0_PE;
sregs.cr3 = pgtable_addr.raw_value() as u64;
sregs.cr4 |= X86_CR4_PAE;
sregs.cr0 |= X86_CR0_PG;
sregs.efer |= EFER_LME | EFER_LMA;
Ok(())
}
fn write_gdt_table<M: GuestMemory>(gdt_table: &[u64], gdt_addr: u64, guest_mem: &M) -> Result<()> {
let boot_gdt_addr = GuestAddress(gdt_addr);
for (index, entry) in gdt_table.iter().enumerate() {
let addr = guest_mem
.checked_offset(boot_gdt_addr, index * mem::size_of::<u64>())
.ok_or(Error::WriteGDT)?;
guest_mem
.write_obj(*entry, addr)
.map_err(|_| Error::WriteGDT)?;
}
Ok(())
}
fn write_idt_value<M: GuestMemory>(idt_table: u64, idt_addr: u64, guest_mem: &M) -> Result<()> {
let boot_idt_addr = GuestAddress(idt_addr);
guest_mem
.write_obj(idt_table, boot_idt_addr)
.map_err(|_| Error::WriteIDT)
}
#[allow(clippy::vec_init_then_push)]
fn create_msr_entries() -> Vec<kvm_msr_entry> {
let mut entries = Vec::<kvm_msr_entry>::new();
entries.push(kvm_msr_entry {
index: msr::MSR_IA32_SYSENTER_CS,
data: 0x0,
..Default::default()
});
entries.push(kvm_msr_entry {
index: msr::MSR_IA32_SYSENTER_ESP,
data: 0x0,
..Default::default()
});
entries.push(kvm_msr_entry {
index: msr::MSR_IA32_SYSENTER_EIP,
data: 0x0,
..Default::default()
});
entries.push(kvm_msr_entry {
index: msr::MSR_STAR,
data: 0x0,
..Default::default()
});
entries.push(kvm_msr_entry {
index: msr::MSR_CSTAR,
data: 0x0,
..Default::default()
});
entries.push(kvm_msr_entry {
index: msr::MSR_KERNEL_GS_BASE,
data: 0x0,
..Default::default()
});
entries.push(kvm_msr_entry {
index: msr::MSR_SYSCALL_MASK,
data: 0x0,
..Default::default()
});
entries.push(kvm_msr_entry {
index: msr::MSR_LSTAR,
data: 0x0,
..Default::default()
});
entries.push(kvm_msr_entry {
index: msr::MSR_IA32_TSC,
data: 0x0,
..Default::default()
});
entries.push(kvm_msr_entry {
index: msr::MSR_IA32_MISC_ENABLE,
data: u64::from(msr::MSR_IA32_MISC_ENABLE_FAST_STRING),
..Default::default()
});
entries
}
#[cfg(test)]
mod tests {
use super::*;
use crate::x86_64::gdt::gdt_entry;
use kvm_ioctls::Kvm;
use vm_memory::{Bytes, GuestAddress, GuestMemoryMmap};
const BOOT_GDT_OFFSET: u64 = 0x500;
const BOOT_IDT_OFFSET: u64 = 0x520;
const BOOT_STACK_POINTER: u64 = 0x100_0000;
const ZERO_PAGE_START: u64 = 0x7_C000;
const BOOT_GDT_MAX: usize = 4;
const PML4_START: u64 = 0x9000;
fn create_guest_mem() -> GuestMemoryMmap {
GuestMemoryMmap::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap()
}
fn read_u64(gm: &GuestMemoryMmap, offset: u64) -> u64 {
let read_addr = GuestAddress(offset as u64);
gm.read_obj(read_addr).unwrap()
}
fn validate_segments_and_sregs(gm: &GuestMemoryMmap, sregs: &kvm_sregs) {
assert_eq!(0x0, read_u64(gm, BOOT_GDT_OFFSET));
assert_eq!(0xaf_9b00_0000_ffff, read_u64(gm, BOOT_GDT_OFFSET + 8));
assert_eq!(0xcf_9300_0000_ffff, read_u64(gm, BOOT_GDT_OFFSET + 16));
assert_eq!(0x8f_8b00_0000_ffff, read_u64(gm, BOOT_GDT_OFFSET + 24));
assert_eq!(0x0, read_u64(gm, BOOT_IDT_OFFSET));
assert_eq!(0, sregs.cs.base);
assert_eq!(0xfffff, sregs.ds.limit);
assert_eq!(0x10, sregs.es.selector);
assert_eq!(1, sregs.fs.present);
assert_eq!(1, sregs.gs.g);
assert_eq!(0, sregs.ss.avl);
assert_eq!(0, sregs.tr.base);
assert_eq!(0xfffff, sregs.tr.limit);
assert_eq!(0, sregs.tr.avl);
assert!(sregs.cr0 & X86_CR0_PE != 0);
assert!(sregs.efer & EFER_LME != 0 && sregs.efer & EFER_LMA != 0);
}
#[test]
fn test_configure_segments_and_sregs() {
let mut sregs: kvm_sregs = Default::default();
let gm = create_guest_mem();
let gdt_table: [u64; BOOT_GDT_MAX as usize] = [
gdt_entry(0, 0, 0), gdt_entry(0xa09b, 0, 0xfffff), gdt_entry(0xc093, 0, 0xfffff), gdt_entry(0x808b, 0, 0xfffff), ];
configure_segments_and_sregs(
&gm,
&mut sregs,
GuestAddress(PML4_START),
&gdt_table,
BOOT_GDT_OFFSET,
BOOT_IDT_OFFSET,
)
.unwrap();
validate_segments_and_sregs(&gm, &sregs);
}
#[test]
fn test_setup_fpu() {
let kvm = Kvm::new().unwrap();
let vm = kvm.create_vm().unwrap();
let vcpu = vm.create_vcpu(0).unwrap();
setup_fpu(&vcpu).unwrap();
let expected_fpu: kvm_fpu = kvm_fpu {
fcw: 0x37f,
mxcsr: 0x1f80,
..Default::default()
};
let actual_fpu: kvm_fpu = vcpu.get_fpu().unwrap();
assert_eq!(expected_fpu.fcw, actual_fpu.fcw);
}
#[test]
#[allow(clippy::cast_ptr_alignment)]
fn test_setup_msrs() {
let kvm = Kvm::new().unwrap();
let vm = kvm.create_vm().unwrap();
let vcpu = vm.create_vcpu(0).unwrap();
setup_msrs(&vcpu).unwrap();
let test_kvm_msrs_entry = [kvm_msr_entry {
index: msr::MSR_IA32_MISC_ENABLE,
..Default::default()
}];
let mut kvm_msrs = Msrs::from_entries(&test_kvm_msrs_entry).unwrap();
let read_nmsrs = vcpu.get_msrs(&mut kvm_msrs).unwrap();
assert_eq!(read_nmsrs, 1);
let entry_vec = create_msr_entries();
assert_eq!(entry_vec[9], kvm_msrs.as_slice()[0]);
}
#[test]
fn test_setup_regs() {
let kvm = Kvm::new().unwrap();
let vm = kvm.create_vm().unwrap();
let vcpu = vm.create_vcpu(0).unwrap();
let expected_regs: kvm_regs = kvm_regs {
rflags: 0x0000_0000_0000_0002u64,
rip: 1,
rsp: BOOT_STACK_POINTER as u64,
rbp: BOOT_STACK_POINTER as u64,
rsi: ZERO_PAGE_START as u64,
..Default::default()
};
setup_regs(
&vcpu,
expected_regs.rip,
BOOT_STACK_POINTER,
BOOT_STACK_POINTER,
ZERO_PAGE_START,
)
.unwrap();
let actual_regs: kvm_regs = vcpu.get_regs().unwrap();
assert_eq!(actual_regs, expected_regs);
}
}