xref: /aosp_15_r20/external/crosvm/hypervisor/tests/mmio_fetch_memory.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
1 // Copyright 2017 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #![cfg(target_arch = "x86_64")]
6 // Test applies to whpx only.
7 #![cfg(all(windows, feature = "whpx"))]
8 
9 use std::sync::atomic::AtomicU16;
10 use std::sync::atomic::Ordering;
11 
12 use hypervisor::*;
13 use vm_memory::GuestAddress;
14 use vm_memory::GuestMemory;
15 
16 // This test case is for the following scenario:
17 // Test sets up a guest memory instruction page, such that an instruction
18 // straddles across a page boundary. That is, the bytes for the instruction are
19 // split between end of first page and start of the next page. This triggers
20 // WHV_EMULATOR to perform an "MMIO" load which is just a memory read from the
21 // second page.
22 #[test]
test_whpx_mmio_fetch_memory()23 fn test_whpx_mmio_fetch_memory() {
24     use hypervisor::whpx::*;
25     /*
26     0x0000000000000000:  67 88 03    mov byte ptr [ebx], al
27     0x0000000000000003:  67 8A 01    mov al, byte ptr [ecx]
28     0x000000000000000a:  F4          hlt
29     */
30 
31     // Start executing the following instructions on the last byte of the page
32     // so that the instruction emulator needs to fetch it from the next page
33     // first.
34     // If `code` or `load_addr` is changed, the memory load on line 89 will need
35     // to be updated.
36     let code = [0x67, 0x88, 0x03, 0x67, 0x8a, 0x01, 0xf4];
37     let load_addr = GuestAddress(0x0fff);
38     let mem_size = 0x2000;
39 
40     let guest_mem =
41         GuestMemory::new(&[(GuestAddress(0), mem_size)]).expect("failed to create guest mem");
42     guest_mem
43         .write_at_addr(&code[..], load_addr)
44         .expect("failed to write to guest memory");
45 
46     if !Whpx::is_enabled() {
47         panic!("whpx not enabled!");
48     }
49 
50     let whpx = Whpx::new().expect("failed to create whpx");
51     let vm =
52         WhpxVm::new(&whpx, 1, guest_mem, CpuId::new(0), false, None).expect("failed to create vm");
53 
54     let mut vcpu = vm.create_vcpu(0).expect("new vcpu failed");
55     let mut vcpu_sregs = vcpu.get_sregs().expect("get sregs failed");
56     vcpu_sregs.cs.base = 0;
57     vcpu_sregs.cs.selector = 0;
58 
59     vcpu.set_sregs(&vcpu_sregs).expect("set sregs failed");
60 
61     let vcpu_regs = Regs {
62         rip: load_addr.offset() as u64,
63         rflags: 2,
64         rax: 0x33,
65         rbx: 0x3000,
66         rcx: 0x3010,
67         ..Default::default()
68     };
69     vcpu.set_regs(&vcpu_regs).expect("set regs failed");
70 
71     // Ensure we get exactly 2 exits for the mmio read and write.
72     let exits = AtomicU16::new(0);
73     // Also, ensure that we have 2 "mmio" reads, one for reading execution page
74     // and the second one for unmapped data page.
75     let memory_reads = AtomicU16::new(0);
76     // And ensure that 1 write is performed.
77     let memory_writes = AtomicU16::new(0);
78 
79     loop {
80         match vcpu.run().expect("run failed") {
81             VcpuExit::Mmio => {
82                 exits.fetch_add(1, Ordering::SeqCst);
83                 vcpu.handle_mmio(&mut |IoParams { address, operation }| {
84                     match operation {
85                         IoOperation::Read(data) => {
86                             memory_reads.fetch_add(1, Ordering::SeqCst);
87                             match (address, data.len()) {
88                                 // First MMIO read from the WHV_EMULATOR asks to
89                                 // load the first 8 bytes of a new execution
90                                 // page, when an instruction crosses page
91                                 // boundary.
92                                 // Return the rest of instructions that are
93                                 // supposed to be on the second page.
94                                 (0x1000, 8) => {
95                                     // Ensure this instruction is the first read
96                                     // in the sequence.
97                                     assert_eq!(memory_reads.load(Ordering::SeqCst), 1);
98                                     data.copy_from_slice(&[
99                                         0x88, 0x03, 0x67, 0x8a, 0x01, 0xf4, 0, 0,
100                                     ]);
101                                     Ok(())
102                                 }
103                                 // Second MMIO read is a regular read from an
104                                 // unmapped memory.
105                                 (0x3010, 1) => {
106                                     data.copy_from_slice(&[0x66]);
107                                     Ok(())
108                                 }
109                                 _ => {
110                                     panic!("invalid address({:#x})/size({})", address, data.len())
111                                 }
112                             }
113                         }
114                         IoOperation::Write(data) => {
115                             assert_eq!(address, 0x3000);
116                             assert_eq!(data[0], 0x33);
117                             assert_eq!(data.len(), 1);
118                             memory_writes.fetch_add(1, Ordering::SeqCst);
119                             Ok(())
120                         }
121                     }
122                 })
123                 .expect("failed to set the data");
124             }
125             VcpuExit::Hlt => {
126                 break;
127             }
128             // Continue on external interrupt or signal
129             VcpuExit::Intr => continue,
130             r => panic!("unexpected exit reason: {:?}", r),
131         }
132     }
133 
134     assert_eq!(exits.load(Ordering::SeqCst), 2);
135     assert_eq!(memory_reads.load(Ordering::SeqCst), 2);
136     assert_eq!(memory_writes.load(Ordering::SeqCst), 1);
137     let regs = vcpu.get_regs().expect("get_regs() failed");
138     assert_eq!(regs.rax, 0x66);
139 }
140