| // 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 arch::LinuxArch; |
| use devices::IrqChipX86_64; |
| use hypervisor::{ |
| HypervisorX86_64, IoOperation, IoParams, ProtectionType, VcpuExit, VcpuX86_64, VmX86_64, |
| }; |
| use resources::{MemRegion, SystemAllocator}; |
| 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, arch_memory_regions, bootparam, init_low_memory_layout, mptable, |
| read_pci_start_before_32bit, read_pcie_cfg_mmio_size, read_pcie_cfg_mmio_start, smbios, |
| }; |
| use super::{ |
| BOOT_STACK_POINTER, KERNEL_64BIT_ENTRY_OFFSET, KERNEL_START_OFFSET, X86_64_SCI_IRQ, |
| ZERO_PAGE_OFFSET, |
| }; |
| |
| use base::{Event, Tube}; |
| |
| use std::collections::BTreeMap; |
| use std::ffi::CString; |
| use std::sync::Arc; |
| use std::thread; |
| use sync::Mutex; |
| |
| use devices::PciConfigIo; |
| |
| enum TaggedControlTube { |
| VmMemory(Tube), |
| VmIrq(Tube), |
| } |
| |
| #[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, ProtectionType::Unprotected) |
| .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, ProtectionType::Unprotected) |
| .expect("failed to create kvm vm"); |
| (kvm, vm) |
| }, |
| |vm, vcpu_count, device_tube| { |
| KvmSplitIrqChip::new(vm, vcpu_count, device_tube, None) |
| .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, Tube) -> 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); |
| |
| init_low_memory_layout( |
| Some(MemRegion { |
| base: 0xC000_0000, |
| size: 0x1000_0000, |
| }), |
| Some(0x8000_0000), |
| ); |
| // guest mem is 400 pages |
| let arch_mem_regions = arch_memory_regions(memory_size, None); |
| let guest_mem = GuestMemory::new(&arch_mem_regions).unwrap(); |
| |
| let (hyp, mut vm) = create_vm(guest_mem.clone()); |
| let mut resources = |
| SystemAllocator::new(X8664arch::get_system_allocator_config(&vm), None, &[]) |
| .expect("failed to create system allocator"); |
| let (irqchip_tube, device_tube) = Tube::pair().expect("failed to create irq tube"); |
| |
| let mut irq_chip = create_irq_chip(vm.try_clone().expect("failed to clone vm"), 1, device_tube); |
| |
| let mmio_bus = Arc::new(devices::Bus::new()); |
| let io_bus = Arc::new(devices::Bus::new()); |
| let (exit_evt_wrtube, _) = Tube::directional_pair().unwrap(); |
| |
| let mut control_tubes = vec![TaggedControlTube::VmIrq(irqchip_tube)]; |
| // Create one control socket per disk. |
| let mut disk_device_tubes = Vec::new(); |
| let mut disk_host_tubes = Vec::new(); |
| let disk_count = 0; |
| for _ in 0..disk_count { |
| let (disk_host_tube, disk_device_tube) = Tube::pair().unwrap(); |
| disk_host_tubes.push(disk_host_tube); |
| disk_device_tubes.push(disk_device_tube); |
| } |
| let (gpu_host_tube, _gpu_device_tube) = Tube::pair().unwrap(); |
| |
| control_tubes.push(TaggedControlTube::VmMemory(gpu_host_tube)); |
| |
| let devices = vec![]; |
| |
| let (pci, pci_irqs, _pid_debug_label_map) = arch::generate_pci_root( |
| devices, |
| &mut irq_chip, |
| mmio_bus.clone(), |
| io_bus.clone(), |
| &mut resources, |
| &mut vm, |
| 4, |
| ) |
| .unwrap(); |
| let pci = Arc::new(Mutex::new(pci)); |
| let (pcibus_exit_evt_wrtube, _) = Tube::directional_pair().unwrap(); |
| let pci_bus = Arc::new(Mutex::new(PciConfigIo::new(pci, pcibus_exit_evt_wrtube))); |
| io_bus.insert(pci_bus, 0xcf8, 0x8).unwrap(); |
| |
| X8664arch::setup_legacy_devices( |
| &io_bus, |
| irq_chip.pit_uses_speaker_port(), |
| exit_evt_wrtube.try_clone().unwrap(), |
| memory_size, |
| ) |
| .unwrap(); |
| |
| let mut serial_params = BTreeMap::new(); |
| |
| arch::set_default_serial_parameters(&mut serial_params, false); |
| |
| X8664arch::setup_serial_devices( |
| ProtectionType::Unprotected, |
| &mut irq_chip, |
| &io_bus, |
| &serial_params, |
| None, |
| ) |
| .unwrap(); |
| |
| let param_args = "nokaslr acpi=noirq"; |
| |
| 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 max_bus = (read_pcie_cfg_mmio_size() / 0x100000 - 1) as u8; |
| let suspend_evt = Event::new().unwrap(); |
| let mut resume_notify_devices = Vec::new(); |
| let acpi_dev_resource = X8664arch::setup_acpi_devices( |
| &guest_mem, |
| &io_bus, |
| &mut resources, |
| suspend_evt |
| .try_clone() |
| .expect("unable to clone suspend_evt"), |
| exit_evt_wrtube |
| .try_clone() |
| .expect("unable to clone exit_evt_wrtube"), |
| Default::default(), |
| &mut irq_chip, |
| X86_64_SCI_IRQ, |
| (&None, None), |
| &mmio_bus, |
| max_bus, |
| &mut resume_notify_devices, |
| ) |
| .unwrap(); |
| |
| X8664arch::setup_system_memory( |
| &guest_mem, |
| &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, None).expect("failed to setup smbios"); |
| |
| let mut apic_ids = Vec::new(); |
| acpi::create_acpi_tables( |
| &guest_mem, |
| 1, |
| X86_64_SCI_IRQ, |
| 0xcf9, |
| 6, |
| &acpi_dev_resource.0, |
| None, |
| &mut apic_ids, |
| &pci_irqs, |
| read_pcie_cfg_mmio_start(), |
| max_bus, |
| false, |
| ); |
| |
| let guest_mem2 = guest_mem.clone(); |
| |
| let handle = thread::Builder::new() |
| .name("crosvm_simple_vm_vcpu".to_string()) |
| .spawn(move || { |
| let mut 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, false, false).unwrap(); |
| setup_msrs(&vm, &vcpu, read_pci_start_before_32bit()).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::Io => { |
| vcpu.handle_io(&mut |IoParams { |
| address, |
| size, |
| operation: direction, |
| }| { |
| match direction { |
| IoOperation::Write { 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!(address, 0xff); |
| assert_eq!(size, 1); |
| assert_eq!(data[0], 0x12); |
| } |
| _ => panic!("unexpected direction {:?}", direction), |
| } |
| None |
| }) |
| .expect("vcpu.handle_io failed"); |
| 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 |
| ); |
| } |