mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2025-02-05 10:10:41 +00:00
x86_64: add integration test case
This test was useful for debugging issues with the hypervisor abstraction layer while it was in development. It's similar to some of the kvm integration tests, but runs the x86_64 setup functions. It has some commented out lines for having this test load a real kernel and/or ramdisk, which can also be useful for debugging boot problems. RESTRICT_AUTOMERGE BUG=b:175025264 TEST=cargo test -p x86_64 Change-Id: If5b89fe48d34db50fb962382032881e4e588db6e Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2579896 Tested-by: kokoro <noreply+kokoro@google.com> Commit-Queue: Colin Downs-Razouk <colindr@google.com> Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
This commit is contained in:
parent
6ea1f74a70
commit
2bf3202d63
3 changed files with 298 additions and 0 deletions
|
@ -26,3 +26,6 @@ base = { path = "../base" }
|
|||
acpi_tables = {path = "../acpi_tables" }
|
||||
vm_control = { path = "../vm_control" }
|
||||
vm_memory = { path = "../vm_memory" }
|
||||
|
||||
[dev-dependencies]
|
||||
msg_socket = { path = "../msg_socket"}
|
|
@ -1142,6 +1142,9 @@ impl X8664arch {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_integration;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
292
x86_64/src/test_integration.rs
Normal file
292
x86_64/src/test_integration.rs
Normal file
|
@ -0,0 +1,292 @@
|
|||
// Copyright 2020 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.
|
||||
|
||||
#![cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
|
||||
use devices::IrqChipX86_64;
|
||||
use hypervisor::{HypervisorX86_64, VcpuExit, VcpuX86_64, VmX86_64};
|
||||
use msg_socket;
|
||||
use vm_memory::{GuestAddress, GuestMemory};
|
||||
|
||||
use super::cpuid::setup_cpuid;
|
||||
use super::interrupts::set_lint;
|
||||
use super::regs::{setup_fpu, setup_msrs, setup_regs, setup_sregs};
|
||||
use super::X8664arch;
|
||||
use super::{acpi, bootparam, mptable, smbios};
|
||||
use super::{
|
||||
BOOT_STACK_POINTER, END_ADDR_BEFORE_32BITS, KERNEL_64BIT_ENTRY_OFFSET, KERNEL_START_OFFSET,
|
||||
X86_64_SCI_IRQ, ZERO_PAGE_OFFSET,
|
||||
};
|
||||
|
||||
use base::Event;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::ffi::CString;
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use sync::Mutex;
|
||||
|
||||
use devices::PciConfigIo;
|
||||
|
||||
use vm_control::{
|
||||
DiskControlCommand, DiskControlResult, VmIrqRequest, VmIrqRequestSocket, VmIrqResponse,
|
||||
VmIrqResponseSocket, VmMemoryControlResponseSocket, VmMemoryRequest, VmMemoryResponse,
|
||||
};
|
||||
|
||||
enum TaggedControlSocket {
|
||||
VmMemory(VmMemoryControlResponseSocket),
|
||||
VmIrq(VmIrqResponseSocket),
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_kvm_kernel_irqchip_test() {
|
||||
use devices::KvmKernelIrqChip;
|
||||
use hypervisor::kvm::*;
|
||||
simple_vm_test::<_, _, KvmVcpu, _, _, _>(
|
||||
|guest_mem| {
|
||||
let kvm = Kvm::new().expect("failed to create kvm");
|
||||
let vm = KvmVm::new(&kvm, guest_mem).expect("failed to create kvm vm");
|
||||
(kvm, vm)
|
||||
},
|
||||
|vm, vcpu_count, _| {
|
||||
KvmKernelIrqChip::new(vm, vcpu_count).expect("failed to create KvmKernelIrqChip")
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_kvm_split_irqchip_test() {
|
||||
use devices::KvmSplitIrqChip;
|
||||
use hypervisor::kvm::*;
|
||||
simple_vm_test::<_, _, KvmVcpu, _, _, _>(
|
||||
|guest_mem| {
|
||||
let kvm = Kvm::new().expect("failed to create kvm");
|
||||
let vm = KvmVm::new(&kvm, guest_mem).expect("failed to create kvm vm");
|
||||
(kvm, vm)
|
||||
},
|
||||
|vm, vcpu_count, device_socket| {
|
||||
KvmSplitIrqChip::new(vm, vcpu_count, device_socket)
|
||||
.expect("failed to create KvmSplitIrqChip")
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Tests the integration of x86_64 with some hypervisor and devices setup. This test can help
|
||||
/// narrow down whether boot issues are caused by the interaction between hypervisor and devices
|
||||
/// and x86_64, or if they are caused by an invalid kernel or image. You can also swap in parts
|
||||
/// of this function to load a real kernel and/or ramdisk.
|
||||
fn simple_vm_test<H, V, Vcpu, I, FV, FI>(create_vm: FV, create_irq_chip: FI)
|
||||
where
|
||||
H: HypervisorX86_64 + 'static,
|
||||
V: VmX86_64 + 'static,
|
||||
Vcpu: VcpuX86_64 + 'static,
|
||||
I: IrqChipX86_64 + 'static,
|
||||
FV: FnOnce(GuestMemory) -> (H, V),
|
||||
FI: FnOnce(V, /* vcpu_count: */ usize, VmIrqRequestSocket) -> I,
|
||||
{
|
||||
/*
|
||||
0x0000000000000000: 67 89 18 mov dword ptr [eax], ebx
|
||||
0x0000000000000003: 89 D9 mov ecx, ebx
|
||||
0x0000000000000005: 89 C8 mov eax, ecx
|
||||
0x0000000000000007: E6 FF out 0xff, al
|
||||
*/
|
||||
let code = [0x67, 0x89, 0x18, 0x89, 0xd9, 0x89, 0xc8, 0xe6, 0xff];
|
||||
|
||||
// 2GB memory
|
||||
let memory_size = 0x80000000u64;
|
||||
let start_addr = GuestAddress(KERNEL_START_OFFSET + KERNEL_64BIT_ENTRY_OFFSET);
|
||||
|
||||
// write to 4th page
|
||||
let write_addr = GuestAddress(0x4000);
|
||||
|
||||
// guest mem is 400 pages
|
||||
let guest_mem = X8664arch::setup_memory(memory_size, false).unwrap();
|
||||
// let guest_mem = GuestMemory::new(&[(GuestAddress(0), memory_size)]).unwrap();
|
||||
let mut resources = X8664arch::get_resource_allocator(&guest_mem, false);
|
||||
|
||||
let (hyp, mut vm) = create_vm(guest_mem.clone());
|
||||
let (irqchip_socket, device_socket) =
|
||||
msg_socket::pair::<VmIrqResponse, VmIrqRequest>().expect("failed to create irq socket");
|
||||
|
||||
let mut irq_chip = create_irq_chip(
|
||||
vm.try_clone().expect("failed to clone vm"),
|
||||
1,
|
||||
device_socket,
|
||||
);
|
||||
|
||||
let mut mmio_bus = devices::Bus::new();
|
||||
let exit_evt = Event::new().unwrap();
|
||||
|
||||
let mut control_sockets = vec![TaggedControlSocket::VmIrq(irqchip_socket)];
|
||||
// Create one control socket per disk.
|
||||
let mut disk_device_sockets = Vec::new();
|
||||
let mut disk_host_sockets = Vec::new();
|
||||
let disk_count = 0;
|
||||
for _ in 0..disk_count {
|
||||
let (disk_host_socket, disk_device_socket) =
|
||||
msg_socket::pair::<DiskControlCommand, DiskControlResult>().unwrap();
|
||||
disk_host_sockets.push(disk_host_socket);
|
||||
disk_device_sockets.push(disk_device_socket);
|
||||
}
|
||||
let (gpu_host_socket, _gpu_device_socket) =
|
||||
msg_socket::pair::<VmMemoryResponse, VmMemoryRequest>().unwrap();
|
||||
|
||||
control_sockets.push(TaggedControlSocket::VmMemory(gpu_host_socket));
|
||||
|
||||
let devices = vec![];
|
||||
|
||||
let (pci, pci_irqs, _pid_debug_label_map) = arch::generate_pci_root(
|
||||
devices,
|
||||
&mut irq_chip,
|
||||
&mut mmio_bus,
|
||||
&mut resources,
|
||||
&mut vm,
|
||||
4,
|
||||
)
|
||||
.unwrap();
|
||||
let pci_bus = Arc::new(Mutex::new(PciConfigIo::new(pci)));
|
||||
|
||||
let mut io_bus = X8664arch::setup_io_bus(
|
||||
irq_chip.pit_uses_speaker_port(),
|
||||
exit_evt.try_clone().unwrap(),
|
||||
Some(pci_bus),
|
||||
memory_size,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut serial_params = BTreeMap::new();
|
||||
|
||||
arch::set_default_serial_parameters(&mut serial_params);
|
||||
|
||||
X8664arch::setup_serial_devices(false, &mut irq_chip, &mut io_bus, &serial_params, None)
|
||||
.unwrap();
|
||||
|
||||
let param_args = "nokaslr";
|
||||
|
||||
let mut cmdline = X8664arch::get_base_linux_cmdline();
|
||||
|
||||
cmdline.insert_str(¶m_args).unwrap();
|
||||
|
||||
let params = bootparam::boot_params::default();
|
||||
// write our custom kernel code to start_addr
|
||||
guest_mem.write_at_addr(&code[..], start_addr).unwrap();
|
||||
let kernel_end = KERNEL_START_OFFSET + code.len() as u64;
|
||||
let initrd_image = None;
|
||||
|
||||
// alternatively, load a real initrd and kernel from disk
|
||||
// let initrd_image = Some(File::open("/mnt/host/source/src/avd/ramdisk.img").expect("failed to open ramdisk"));
|
||||
// let mut kernel_image = File::open("/mnt/host/source/src/avd/vmlinux.uncompressed").expect("failed to open kernel");
|
||||
// let (params, kernel_end) = X8664arch::load_kernel(&guest_mem, &mut kernel_image).expect("failed to load kernel");
|
||||
|
||||
let suspend_evt = Event::new().unwrap();
|
||||
let acpi_dev_resource = X8664arch::setup_acpi_devices(
|
||||
&mut io_bus,
|
||||
&mut resources,
|
||||
suspend_evt
|
||||
.try_clone()
|
||||
.expect("unable to clone suspend_evt"),
|
||||
exit_evt.try_clone().expect("unable to clone exit_evt"),
|
||||
Default::default(),
|
||||
&mut irq_chip,
|
||||
(&None, None),
|
||||
&mut mmio_bus,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
X8664arch::setup_system_memory(
|
||||
&guest_mem,
|
||||
memory_size,
|
||||
&CString::new(cmdline).expect("failed to create cmdline"),
|
||||
initrd_image,
|
||||
None,
|
||||
kernel_end,
|
||||
params,
|
||||
)
|
||||
.expect("failed to setup system_memory");
|
||||
|
||||
// Note that this puts the mptable at 0x9FC00 in guest physical memory.
|
||||
mptable::setup_mptable(&guest_mem, 1, pci_irqs).expect("failed to setup mptable");
|
||||
smbios::setup_smbios(&guest_mem).expect("failed to setup smbios");
|
||||
|
||||
acpi::create_acpi_tables(&guest_mem, 1, X86_64_SCI_IRQ, acpi_dev_resource.0);
|
||||
|
||||
let guest_mem2 = guest_mem.clone();
|
||||
|
||||
let handle = thread::Builder::new()
|
||||
.name("crosvm_simple_vm_vcpu".to_string())
|
||||
.spawn(move || {
|
||||
let vcpu = *vm
|
||||
.create_vcpu(0)
|
||||
.expect("failed to create vcpu")
|
||||
.downcast::<Vcpu>()
|
||||
.map_err(|_| ())
|
||||
.expect("failed to downcast vcpu");
|
||||
|
||||
irq_chip
|
||||
.add_vcpu(0, &vcpu)
|
||||
.expect("failed to add vcpu to irqchip");
|
||||
|
||||
setup_cpuid(&hyp, &irq_chip, &vcpu, 0, 1, false).unwrap();
|
||||
setup_msrs(&vcpu, END_ADDR_BEFORE_32BITS).unwrap();
|
||||
|
||||
setup_regs(
|
||||
&vcpu,
|
||||
start_addr.offset() as u64,
|
||||
BOOT_STACK_POINTER as u64,
|
||||
ZERO_PAGE_OFFSET as u64,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut vcpu_regs = vcpu.get_regs().unwrap();
|
||||
// instruction is
|
||||
// mov [eax],ebx
|
||||
// so we're writing 0x12 (the contents of ebx) to the address
|
||||
// in eax (write_addr).
|
||||
vcpu_regs.rax = write_addr.offset() as u64;
|
||||
vcpu_regs.rbx = 0x12;
|
||||
// ecx will contain 0, but after the second instruction it will
|
||||
// also contain 0x12
|
||||
vcpu_regs.rcx = 0x0;
|
||||
vcpu.set_regs(&vcpu_regs).expect("set regs failed");
|
||||
|
||||
setup_fpu(&vcpu).unwrap();
|
||||
setup_sregs(&guest_mem, &vcpu).unwrap();
|
||||
set_lint(0, &mut irq_chip).unwrap();
|
||||
|
||||
let run_handle = vcpu.take_run_handle(None).unwrap();
|
||||
loop {
|
||||
match vcpu.run(&run_handle).expect("run failed") {
|
||||
VcpuExit::IoOut {
|
||||
port: 0xff,
|
||||
size,
|
||||
data,
|
||||
} => {
|
||||
// We consider this test to be done when this particular
|
||||
// one-byte port-io to port 0xff with the value of 0x12, which was in
|
||||
// register eax
|
||||
assert_eq!(size, 1);
|
||||
assert_eq!(data[0], 0x12);
|
||||
break;
|
||||
}
|
||||
r => {
|
||||
panic!("unexpected exit {:?}", r);
|
||||
}
|
||||
}
|
||||
}
|
||||
let regs = vcpu.get_regs().unwrap();
|
||||
// ecx and eax should now contain 0x12
|
||||
assert_eq!(regs.rcx, 0x12);
|
||||
assert_eq!(regs.rax, 0x12);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
if let Err(e) = handle.join() {
|
||||
panic!("failed to join vcpu thread: {:?}", e);
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
guest_mem2.read_obj_from_addr::<u64>(write_addr).unwrap(),
|
||||
0x12
|
||||
);
|
||||
}
|
Loading…
Reference in a new issue