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 // TODO(b/237714823): Currently, only kvm is enabled for this test once LUCI can run windows.
6 #![cfg(any(target_os = "android", target_os = "linux"))]
7 #![cfg(target_arch = "x86_64")]
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 #[test]
17 #[cfg(any(target_os = "android", target_os = "linux"))]
test_kvm_mmio_and_pio()18 fn test_kvm_mmio_and_pio() {
19 use hypervisor::kvm::*;
20 test_mmio_and_pio(|guest_mem| {
21 let kvm = Kvm::new().expect("failed to create kvm");
22 let vm = KvmVm::new(&kvm, guest_mem, Default::default()).expect("failed to create vm");
23 (kvm, vm)
24 });
25 }
26
27 #[test]
28 #[cfg(all(windows, feature = "haxm"))]
test_haxm_mmio_and_pio()29 fn test_haxm_mmio_and_pio() {
30 use hypervisor::haxm::*;
31 test_mmio_and_pio(|guest_mem| {
32 let haxm = Haxm::new().expect("failed to create haxm");
33 let vm = HaxmVm::new(&haxm, guest_mem).expect("failed to create vm");
34 (haxm, vm)
35 });
36 }
37
38 #[test]
39 #[cfg(all(windows, feature = "whpx"))]
test_whpx_mmio_and_pio()40 fn test_whpx_mmio_and_pio() {
41 use hypervisor::whpx::*;
42 if !Whpx::is_enabled() {
43 return;
44 }
45 test_mmio_and_pio(|guest_mem| {
46 let whpx = Whpx::new().expect("failed to create whpx");
47 let vm =
48 WhpxVm::new(&whpx, 1, guest_mem, CpuId::new(0), false).expect("failed to create vm");
49 (whpx, vm)
50 });
51 }
52
53 #[test]
54 #[cfg(feature = "gvm")]
test_gvm_mmio_and_pio()55 fn test_gvm_mmio_and_pio() {
56 use hypervisor::gvm::*;
57 test_mmio_and_pio(|guest_mem| {
58 let gvm = Gvm::new().expect("failed to create gvm");
59 let vm = GvmVm::new(&gvm, guest_mem).expect("failed to create vm");
60 (gvm, vm)
61 });
62 }
63
test_mmio_and_pio<CreateVm, HypervisorT, VmT>(create_vm: CreateVm) where CreateVm: FnOnce(GuestMemory) -> (HypervisorT, VmT), HypervisorT: Hypervisor, VmT: VmX86_64,64 fn test_mmio_and_pio<CreateVm, HypervisorT, VmT>(create_vm: CreateVm)
65 where
66 CreateVm: FnOnce(GuestMemory) -> (HypervisorT, VmT),
67 HypervisorT: Hypervisor,
68 VmT: VmX86_64,
69 {
70 /*
71 0x0000000000000000: 67 88 03 mov byte ptr [ebx], al
72 0x0000000000000003: 67 8A 01 mov al, byte ptr [ecx]
73 0x0000000000000006: E6 19 out 0x19, al
74 0x0000000000000008: E4 20 in al, 0x20
75 0x000000000000000a: F4 hlt
76 */
77
78 let code: [u8; 11] = [
79 0x67, 0x88, 0x03, 0x67, 0x8a, 0x01, 0xe6, 0x19, 0xe4, 0x20, 0xf4,
80 ];
81 let mem_size = 0x2000;
82 let load_addr = GuestAddress(0x1000);
83
84 let guest_mem =
85 GuestMemory::new(&[(GuestAddress(0), mem_size)]).expect("failed to create guest mem");
86 guest_mem
87 .write_at_addr(&code[..], load_addr)
88 .expect("failed to write to guest memory");
89
90 let (_, vm) = create_vm(guest_mem);
91 let mut vcpu = vm.create_vcpu(0).expect("new vcpu failed");
92 let mut vcpu_sregs = vcpu.get_sregs().expect("get sregs failed");
93 vcpu_sregs.cs.base = 0;
94 vcpu_sregs.cs.selector = 0;
95
96 vcpu.set_sregs(&vcpu_sregs).expect("set sregs failed");
97
98 let vcpu_regs = Regs {
99 rip: load_addr.offset(),
100 rflags: 2,
101 rax: 0x33,
102 rbx: 0x3000,
103 rcx: 0x3010,
104 ..Default::default()
105 };
106 vcpu.set_regs(&vcpu_regs).expect("set regs failed");
107
108 // Ensure we get exactly 2 exits for the mmio and the pio.
109 let exits = AtomicU16::new(0);
110
111 loop {
112 match vcpu.run().expect("run failed") {
113 VcpuExit::Mmio => {
114 vcpu.handle_mmio(&mut |IoParams { address, operation }| {
115 match operation {
116 IoOperation::Read(data) => {
117 assert_eq!(address, 0x3010);
118 assert_eq!(data.len(), 1);
119 exits.fetch_add(1, Ordering::SeqCst);
120 // this number will be read into al register
121 data.copy_from_slice(&[0x66]);
122 Ok(())
123 }
124 IoOperation::Write(data) => {
125 assert_eq!(address, 0x3000);
126 assert_eq!(data[0], 0x33);
127 assert_eq!(data.len(), 1);
128 exits.fetch_add(1, Ordering::SeqCst);
129 Ok(())
130 }
131 }
132 })
133 .expect("failed to set the data");
134 }
135 VcpuExit::Io => {
136 vcpu.handle_io(&mut |IoParams { address, operation }| {
137 match operation {
138 IoOperation::Read(data) => {
139 assert_eq!(address, 0x20);
140 assert_eq!(data.len(), 1);
141 exits.fetch_add(1, Ordering::SeqCst);
142 // this number will be read into the al register
143 data.copy_from_slice(&[0x77]);
144 }
145 IoOperation::Write(data) => {
146 assert_eq!(address, 0x19);
147 assert_eq!(data.len(), 1);
148 assert_eq!(data[0], 0x66);
149 exits.fetch_add(1, Ordering::SeqCst);
150 }
151 }
152 })
153 .expect("failed to set the data");
154 }
155 VcpuExit::Hlt => {
156 break;
157 }
158 // Continue on external interrupt or signal
159 VcpuExit::Intr => continue,
160 r => panic!("unexpected exit reason: {:?}", r),
161 }
162 }
163
164 assert_eq!(exits.load(Ordering::SeqCst), 4);
165 let regs: Regs = vcpu.get_regs().expect("failed to get regs");
166 assert_eq!(regs.rax, 0x77);
167 }
168
169 #[test]
170 #[cfg(any(target_os = "android", target_os = "linux"))]
test_kvm_pio_out()171 fn test_kvm_pio_out() {
172 use hypervisor::kvm::*;
173 test_pio_out(|guest_mem| {
174 let kvm = Kvm::new().expect("failed to create kvm");
175 let vm = KvmVm::new(&kvm, guest_mem, Default::default()).expect("failed to create vm");
176 (kvm, vm)
177 });
178 }
179
180 #[test]
181 #[cfg(all(windows, feature = "haxm"))]
test_haxm_pio_out()182 fn test_haxm_pio_out() {
183 use hypervisor::haxm::*;
184 test_pio_out(|guest_mem| {
185 let haxm = Haxm::new().expect("failed to create haxm");
186 let vm = HaxmVm::new(&haxm, guest_mem).expect("failed to create vm");
187 (haxm, vm)
188 });
189 }
190
191 #[test]
192 #[cfg(all(windows, feature = "whpx"))]
test_whpx_pio_out()193 fn test_whpx_pio_out() {
194 use hypervisor::whpx::*;
195 if !Whpx::is_enabled() {
196 return;
197 }
198 test_pio_out(|guest_mem| {
199 let whpx = Whpx::new().expect("failed to create whpx");
200 let vm =
201 WhpxVm::new(&whpx, 1, guest_mem, CpuId::new(0), false).expect("failed to create vm");
202 (whpx, vm)
203 });
204 }
205
206 #[test]
207 #[cfg(feature = "gvm")]
test_gvm_pio_out()208 fn test_gvm_pio_out() {
209 use hypervisor::gvm::*;
210 test_pio_out(|guest_mem| {
211 let gvm = Gvm::new().expect("failed to create gvm");
212 let vm = GvmVm::new(&gvm, guest_mem).expect("failed to create vm");
213 (gvm, vm)
214 });
215 }
216
test_pio_out<CreateVm, HypervisorT, VmT>(create_vm: CreateVm) where CreateVm: FnOnce(GuestMemory) -> (HypervisorT, VmT), HypervisorT: Hypervisor, VmT: VmX86_64,217 fn test_pio_out<CreateVm, HypervisorT, VmT>(create_vm: CreateVm)
218 where
219 CreateVm: FnOnce(GuestMemory) -> (HypervisorT, VmT),
220 HypervisorT: Hypervisor,
221 VmT: VmX86_64,
222 {
223 /*
224 0x00: 31 c0 xor ax, ax (ax = 0)
225 0x02: 40 inc ax (ax = 1)
226 0x03: e7 01 out 0x1, ax (OUT 0x0001 to port 1)
227 0x05: 40 inc ax (ax = 2)
228 0x06: e6 02 out 0x2, al (OUT 0x02 to port 2)
229 0x08: 40 inc ax (ax = 3)
230 0x09: 89 c2 mov dx, ax (dx = 3)
231 0x0b: ef out dx, ax (OUT 0x0003 to port 3)
232 0x0c: 40 inc ax (ax = 4)
233 0x0d: 42 inc dx (dx = 4)
234 0x0e: ee out dx, al (OUT 0x04 to port 4)
235 0x0f: f4 hlt
236 */
237
238 let code: [u8; 16] = [
239 0x31, 0xc0, 0x40, 0xe7, 0x01, 0x40, 0xe6, 0x02, 0x40, 0x89, 0xc2, 0xef, 0x40, 0x42, 0xee,
240 0xf4,
241 ];
242 let mem_size = 0x2000;
243 let load_addr = GuestAddress(0x1000);
244
245 let guest_mem =
246 GuestMemory::new(&[(GuestAddress(0), mem_size)]).expect("failed to create guest mem");
247 guest_mem
248 .write_at_addr(&code[..], load_addr)
249 .expect("failed to write to guest memory");
250
251 let (_, vm) = create_vm(guest_mem);
252 let mut vcpu = vm.create_vcpu(0).expect("new vcpu failed");
253 let mut vcpu_sregs = vcpu.get_sregs().expect("get sregs failed");
254 vcpu_sregs.cs.base = 0;
255 vcpu_sregs.cs.selector = 0;
256
257 vcpu.set_sregs(&vcpu_sregs).expect("set sregs failed");
258
259 let vcpu_regs = Regs {
260 rip: load_addr.offset(),
261 rflags: 2,
262 ..Default::default()
263 };
264 vcpu.set_regs(&vcpu_regs).expect("set regs failed");
265
266 // Ensure we get the expected PIO exits
267 let exit_count = AtomicU16::new(0);
268 let exit_bits = AtomicU16::new(0);
269
270 loop {
271 match vcpu.run().expect("run failed") {
272 VcpuExit::Io => {
273 vcpu.handle_io(&mut |IoParams { address, operation }| match operation {
274 IoOperation::Read(_) => panic!("unexpected PIO read"),
275 IoOperation::Write(data) => {
276 assert!((1..=4).contains(&address));
277 if address % 2 == 0 {
278 assert_eq!(data.len(), 1);
279 assert_eq!(data[0], address as u8);
280 } else {
281 assert_eq!(data.len(), 2);
282 assert_eq!(data[0], address as u8);
283 assert_eq!(data[1], 0);
284 }
285 exit_bits.fetch_or(1 << (address - 1), Ordering::SeqCst);
286 exit_count.fetch_add(1, Ordering::SeqCst);
287 }
288 })
289 .expect("failed to set the data");
290 }
291 VcpuExit::Hlt => {
292 break;
293 }
294 // Continue on external interrupt or signal
295 VcpuExit::Intr => continue,
296 r => panic!("unexpected exit reason: {:?}", r),
297 }
298 }
299
300 // bits 0 through 3 have been set
301 assert_eq!(exit_bits.load(Ordering::SeqCst), 0xf);
302 assert_eq!(exit_count.load(Ordering::SeqCst), 4);
303 }
304
305 #[test]
306 #[cfg(any(target_os = "android", target_os = "linux"))]
test_kvm_pio_in()307 fn test_kvm_pio_in() {
308 use hypervisor::kvm::*;
309 test_pio_in(|guest_mem| {
310 let kvm = Kvm::new().expect("failed to create kvm");
311 let vm = KvmVm::new(&kvm, guest_mem, Default::default()).expect("failed to create vm");
312 (kvm, vm)
313 });
314 }
315
316 #[test]
317 #[cfg(all(windows, feature = "haxm"))]
test_haxm_pio_in()318 fn test_haxm_pio_in() {
319 use hypervisor::haxm::*;
320 test_pio_in(|guest_mem| {
321 let haxm = Haxm::new().expect("failed to create haxm");
322 let vm = HaxmVm::new(&haxm, guest_mem).expect("failed to create vm");
323 (haxm, vm)
324 });
325 }
326
327 #[test]
328 #[cfg(all(windows, feature = "whpx"))]
test_whpx_pio_in()329 fn test_whpx_pio_in() {
330 use hypervisor::whpx::*;
331 if !Whpx::is_enabled() {
332 return;
333 }
334 test_pio_in(|guest_mem| {
335 let whpx = Whpx::new().expect("failed to create whpx");
336 let vm =
337 WhpxVm::new(&whpx, 1, guest_mem, CpuId::new(0), false).expect("failed to create vm");
338 (whpx, vm)
339 });
340 }
341
342 #[test]
343 #[cfg(feature = "gvm")]
test_gvm_pio_in()344 fn test_gvm_pio_in() {
345 use hypervisor::gvm::*;
346 test_pio_in(|guest_mem| {
347 let gvm = Gvm::new().expect("failed to create gvm");
348 let vm = GvmVm::new(&gvm, guest_mem).expect("failed to create vm");
349 (gvm, vm)
350 });
351 }
352
test_pio_in<CreateVm, HypervisorT, VmT>(create_vm: CreateVm) where CreateVm: FnOnce(GuestMemory) -> (HypervisorT, VmT), HypervisorT: Hypervisor, VmT: VmX86_64,353 fn test_pio_in<CreateVm, HypervisorT, VmT>(create_vm: CreateVm)
354 where
355 CreateVm: FnOnce(GuestMemory) -> (HypervisorT, VmT),
356 HypervisorT: Hypervisor,
357 VmT: VmX86_64,
358 {
359 /*
360 0x00: e5 01 in ax, 0x1 (IN 16 bits from port 1)
361 0x02: 89 c6 mov si, ax (si = value from port 1)
362 0x04: 31 c0 xor ax, ax (clear ax, since IN will only write the lower byte)
363 0x06: e4 02 in al, 0x02 (IN 8 bits from port 2)
364 0x08: 89 c3 mov bx, ax (bx = value from port 2)
365 0x0a: ba 03 00 mov dx, 0x03 (dx = 3)
366 0x0d: ed in ax, dx (IN 16 bits from port 3)
367 0x0e: 89 c1 mov cx, ax (cx = value from port 3)
368 0x10: 42 inc dx (dx = 4)
369 0x11: 31 c0 xor ax, ax (clear ax, since IN will only write the lower byte)
370 0x13: ec in al, dx (IN 8 bits from port 4)
371 0x14: 89 c2 mov dx, ax (dx = value from port 4)
372 0x16: 89 f0 mov ax, si (ax = value from port 1)
373 0x18: f4 hlt
374 */
375
376 let code: [u8; 25] = [
377 0xe5, 0x01, 0x89, 0xc6, 0x31, 0xc0, 0xe4, 0x02, 0x89, 0xc3, 0xba, 0x03, 0x00, 0xed, 0x89,
378 0xc1, 0x42, 0x31, 0xc0, 0xec, 0x89, 0xc2, 0x89, 0xf0, 0xf4,
379 ];
380 let mem_size = 0x2000;
381 let load_addr = GuestAddress(0x1000);
382
383 let guest_mem =
384 GuestMemory::new(&[(GuestAddress(0), mem_size)]).expect("failed to create guest mem");
385 guest_mem
386 .write_at_addr(&code[..], load_addr)
387 .expect("failed to write to guest memory");
388
389 let (_, vm) = create_vm(guest_mem);
390 let mut vcpu = vm.create_vcpu(0).expect("new vcpu failed");
391 let mut vcpu_sregs = vcpu.get_sregs().expect("get sregs failed");
392 vcpu_sregs.cs.base = 0;
393 vcpu_sregs.cs.selector = 0;
394
395 vcpu.set_sregs(&vcpu_sregs).expect("set sregs failed");
396
397 let vcpu_regs = Regs {
398 rip: load_addr.offset(),
399 rflags: 2,
400 ..Default::default()
401 };
402 vcpu.set_regs(&vcpu_regs).expect("set regs failed");
403
404 // Ensure we get the expected PIO exits
405 let exit_count = AtomicU16::new(0);
406 let exit_bits = AtomicU16::new(0);
407
408 loop {
409 match vcpu.run().expect("run failed") {
410 VcpuExit::Io => {
411 vcpu.handle_io(&mut |IoParams { address, operation }| match operation {
412 IoOperation::Read(data) => {
413 assert!((1..=4).contains(&address));
414
415 if address % 2 == 0 {
416 assert_eq!(data.len(), 1);
417 data[0] = address as u8;
418 } else {
419 assert_eq!(data.len(), 2);
420 data[0] = address as u8;
421 data[1] = address as u8;
422 }
423
424 exit_bits.fetch_or(1 << (address - 1), Ordering::SeqCst);
425 exit_count.fetch_add(1, Ordering::SeqCst);
426 }
427 IoOperation::Write(_) => panic!("unexpected PIO write"),
428 })
429 .expect("failed to set the data");
430 }
431 VcpuExit::Hlt => {
432 break;
433 }
434 // Continue on external interrupt or signal
435 VcpuExit::Intr => continue,
436 r => panic!("unexpected exit reason: {:?}", r),
437 }
438 }
439
440 // bits 0 through 3 have been set
441 assert_eq!(exit_bits.load(Ordering::SeqCst), 0xf);
442 assert_eq!(exit_count.load(Ordering::SeqCst), 4);
443 let regs: Regs = vcpu.get_regs().expect("failed to get regs");
444 assert_eq!(regs.rax, 0x0101);
445 assert_eq!(regs.rbx, 0x02);
446 assert_eq!(regs.rcx, 0x0303);
447 assert_eq!(regs.rdx, 0x04);
448 }
449