crosvm/hypervisor/tests/mmio_fetch_memory.rs
Dennis Kempin 4fea399df9 Reformat imports
crosvm is switching the import style to use one import per line.
While more verbose, this will greatly reduce the occurence of merge
conflicts going forward.

Note: This is using a nightly feature of rustfmt. So it's a one-off
re-format only. We are considering adding a nightly toolchain to
enable the feature permanently.

BUG=b:239937122
TEST=CQ

Change-Id: Id2dd4dbdc0adfc4f8f3dd1d09da1daafa2a39992
Reviewed-on: https://chromium-review.googlesource.com/c/crosvm/crosvm/+/3784345
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
Tested-by: Dennis Kempin <denniskempin@google.com>
Commit-Queue: Dennis Kempin <denniskempin@google.com>
2022-07-28 00:15:50 +00:00

135 lines
5.5 KiB
Rust

// 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.
#![cfg(any(target_arch = "x86", target_arch = "x86_64"))]
// Test applies to whpx only.
#![cfg(feature = "whpx")]
use std::sync::atomic::AtomicU16;
use std::sync::atomic::Ordering;
use hypervisor::*;
use vm_memory::GuestAddress;
use vm_memory::GuestMemory;
// This test case is for the following scenario:
// Test sets up a guest memory instruction page, such that an instruction
// straddles across a page boundary. That is, the bytes for the instruction are
// split between end of first page and start of the next page. This triggers
// WHV_EMULATOR to perform an "MMIO" load which is just a memory read from the
// second page.
#[test]
fn test_whpx_mmio_fetch_memory() {
use hypervisor::whpx::*;
/*
0x0000000000000000: 67 88 03 mov byte ptr [ebx], al
0x0000000000000003: 67 8A 01 mov al, byte ptr [ecx]
0x000000000000000a: F4 hlt
*/
// Start executing the following instructions on the last byte of the page
// so that the instruction emulator needs to fetch it from the next page
// first.
// If `code` or `load_addr` is changed, the memory load on line 89 will need
// to be updated.
let code = [0x67, 0x88, 0x03, 0x67, 0x8a, 0x01, 0xf4];
let load_addr = GuestAddress(0x0fff);
let mem_size = 0x2000;
let guest_mem =
GuestMemory::new(&[(GuestAddress(0), mem_size)]).expect("failed to create guest mem");
guest_mem
.write_at_addr(&code[..], load_addr)
.expect("failed to write to guest memory");
if !Whpx::is_enabled() {
panic!("whpx not enabled!");
}
let whpx = Whpx::new().expect("failed to create whpx");
let vm = WhpxVm::new(&whpx, 1, guest_mem, CpuId::new(0), false).expect("failed to create vm");
let mut vcpu = vm.create_vcpu(0).expect("new vcpu failed");
let mut vcpu_sregs = vcpu.get_sregs().expect("get sregs failed");
vcpu_sregs.cs.base = 0;
vcpu_sregs.cs.selector = 0;
vcpu.set_sregs(&vcpu_sregs).expect("set sregs failed");
let mut vcpu_regs = Regs::default();
vcpu_regs.rip = load_addr.offset() as u64;
vcpu_regs.rflags = 2;
vcpu_regs.rax = 0x33;
vcpu_regs.rbx = 0x3000;
vcpu_regs.rcx = 0x3010;
vcpu.set_regs(&vcpu_regs).expect("set regs failed");
// Ensure we get exactly 2 exits for the mmio read and write.
let exits = AtomicU16::new(0);
// Also, ensure that we have 2 "mmio" reads, one for reading execution page
// and the second one for unmapped data page.
let memory_reads = AtomicU16::new(0);
// And ensure that 1 write is performed.
let memory_writes = AtomicU16::new(0);
let run_handle = vcpu.take_run_handle(None).unwrap();
loop {
match vcpu.run(&run_handle).expect("run failed") {
VcpuExit::Mmio => {
exits.fetch_add(1, Ordering::SeqCst);
vcpu.handle_mmio(&mut |IoParams {
address,
size,
operation,
}| {
match operation {
IoOperation::Read => {
memory_reads.fetch_add(1, Ordering::SeqCst);
match (address, size) {
// First MMIO read from the WHV_EMULATOR asks to
// load the first 8 bytes of a new execution
// page, when an instruction crosses page
// boundary.
// Return the rest of instructions that are
// supposed to be on the second page.
(0x1000, 8) => {
// Ensure this instruction is the first read
// in the sequence.
assert_eq!(memory_reads.load(Ordering::SeqCst), 1);
Some([0x88, 0x03, 0x67, 0x8a, 0x01, 0xf4, 0, 0])
}
// Second MMIO read is a regular read from an
// unmapped memory.
(0x3010, 1) => Some([0x66, 0, 0, 0, 0, 0, 0, 0]),
_ => {
panic!("invalid address({:#x})/size({})", address, size)
}
}
}
IoOperation::Write { data } => {
assert_eq!(address, 0x3000);
assert_eq!(data[0], 0x33);
assert_eq!(size, 1);
memory_writes.fetch_add(1, Ordering::SeqCst);
None
}
}
})
.expect("failed to set the data");
}
VcpuExit::Hlt => {
break;
}
// Continue on external interrupt or signal
VcpuExit::Intr => continue,
r => panic!("unexpected exit reason: {:?}", r),
}
}
assert_eq!(exits.load(Ordering::SeqCst), 2);
assert_eq!(memory_reads.load(Ordering::SeqCst), 2);
assert_eq!(memory_writes.load(Ordering::SeqCst), 1);
let regs = vcpu.get_regs().expect("get_regs() failed");
assert_eq!(regs.rax, 0x66);
}