1 // Copyright 2022 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 // These tests are only implemented for kvm & gvm. Other hypervisors may be added in the future.
6 #![cfg(all(
7 any(feature = "gvm", unix),
8 any(target_arch = "x86", target_arch = "x86_64")
9 ))]
10
11 mod sys;
12
13 use std::collections::BTreeMap;
14 use std::ffi::CString;
15 use std::sync::Arc;
16 use std::thread;
17
18 use arch::LinuxArch;
19 use base::Event;
20 use base::Tube;
21 use devices::IrqChipX86_64;
22 use devices::PciConfigIo;
23 use hypervisor::CpuConfigX86_64;
24 use hypervisor::HypervisorX86_64;
25 use hypervisor::IoOperation;
26 use hypervisor::IoParams;
27 use hypervisor::ProtectionType;
28 use hypervisor::Regs;
29 use hypervisor::VcpuExit;
30 use hypervisor::VcpuX86_64;
31 use hypervisor::VmCap;
32 use hypervisor::VmX86_64;
33 use resources::AddressRange;
34 use resources::SystemAllocator;
35 use sync::Mutex;
36 use vm_memory::GuestAddress;
37 use vm_memory::GuestMemory;
38 use x86_64::acpi;
39 use x86_64::arch_memory_regions;
40 use x86_64::bootparam;
41 use x86_64::cpuid::setup_cpuid;
42 use x86_64::init_low_memory_layout;
43 use x86_64::interrupts::set_lint;
44 use x86_64::mptable;
45 use x86_64::read_pci_mmio_before_32bit;
46 use x86_64::read_pcie_cfg_mmio;
47 use x86_64::regs::configure_segments_and_sregs;
48 use x86_64::regs::long_mode_msrs;
49 use x86_64::regs::mtrr_msrs;
50 use x86_64::regs::setup_page_tables;
51 use x86_64::smbios;
52 use x86_64::X8664arch;
53 use x86_64::BOOT_STACK_POINTER;
54 use x86_64::KERNEL_64BIT_ENTRY_OFFSET;
55 use x86_64::KERNEL_START_OFFSET;
56 use x86_64::X86_64_SCI_IRQ;
57 use x86_64::ZERO_PAGE_OFFSET;
58
59 enum TaggedControlTube {
60 VmMemory(Tube),
61 VmIrq(Tube),
62 Vm(Tube),
63 }
64
65 /// Tests the integration of x86_64 with some hypervisor and devices setup. This test can help
66 /// narrow down whether boot issues are caused by the interaction between hypervisor and devices
67 /// and x86_64, or if they are caused by an invalid kernel or image. You can also swap in parts
68 /// of this function to load a real kernel and/or ramdisk.
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, usize, Tube) -> I,69 fn simple_vm_test<H, V, Vcpu, I, FV, FI>(create_vm: FV, create_irq_chip: FI)
70 where
71 H: HypervisorX86_64 + 'static,
72 V: VmX86_64 + 'static,
73 Vcpu: VcpuX86_64 + 'static,
74 I: IrqChipX86_64 + 'static,
75 FV: FnOnce(GuestMemory) -> (H, V),
76 FI: FnOnce(V, /* vcpu_count: */ usize, Tube) -> I,
77 {
78 /*
79 0x0000000000000000: 67 89 18 mov dword ptr [eax], ebx
80 0x0000000000000003: 89 D9 mov ecx, ebx
81 0x0000000000000005: 89 C8 mov eax, ecx
82 0x0000000000000007: E6 FF out 0xff, al
83 */
84 let code = [0x67, 0x89, 0x18, 0x89, 0xd9, 0x89, 0xc8, 0xe6, 0xff];
85
86 // 2GB memory
87 let memory_size = 0x80000000u64;
88 let start_addr = GuestAddress(KERNEL_START_OFFSET + KERNEL_64BIT_ENTRY_OFFSET);
89
90 // write to 4th page
91 let write_addr = GuestAddress(0x4000);
92
93 init_low_memory_layout(
94 Some(AddressRange {
95 start: 0xC000_0000,
96 end: 0xCFFF_FFFF,
97 }),
98 Some(0x8000_0000),
99 );
100 // guest mem is 400 pages
101 let arch_mem_regions = arch_memory_regions(memory_size, None);
102 let guest_mem = GuestMemory::new_with_options(&arch_mem_regions).unwrap();
103
104 let (hyp, mut vm) = create_vm(guest_mem.clone());
105 let mut resources =
106 SystemAllocator::new(X8664arch::get_system_allocator_config(&vm), None, &[])
107 .expect("failed to create system allocator");
108 let (irqchip_tube, device_tube) = Tube::pair().expect("failed to create irq tube");
109
110 let mut irq_chip = create_irq_chip(vm.try_clone().expect("failed to clone vm"), 1, device_tube);
111
112 let mmio_bus = Arc::new(devices::Bus::new());
113 let io_bus = Arc::new(devices::Bus::new());
114 let (exit_evt_wrtube, _) = Tube::directional_pair().unwrap();
115
116 let mut control_tubes = vec![TaggedControlTube::VmIrq(irqchip_tube)];
117 // Create one control socket per disk.
118 let mut disk_device_tubes = Vec::new();
119 let mut disk_host_tubes = Vec::new();
120 let disk_count = 0;
121 for _ in 0..disk_count {
122 let (disk_host_tube, disk_device_tube) = Tube::pair().unwrap();
123 disk_host_tubes.push(disk_host_tube);
124 disk_device_tubes.push(disk_device_tube);
125 }
126 let (gpu_host_tube, _gpu_device_tube) = Tube::pair().unwrap();
127
128 control_tubes.push(TaggedControlTube::VmMemory(gpu_host_tube));
129
130 let devices = vec![];
131
132 let (pci, pci_irqs, _pid_debug_label_map, _amls) = arch::generate_pci_root(
133 devices,
134 &mut irq_chip,
135 mmio_bus.clone(),
136 io_bus.clone(),
137 &mut resources,
138 &mut vm,
139 4,
140 None,
141 #[cfg(feature = "swap")]
142 None,
143 )
144 .unwrap();
145 let pci = Arc::new(Mutex::new(pci));
146 let (pcibus_exit_evt_wrtube, _) = Tube::directional_pair().unwrap();
147 let pci_bus = Arc::new(Mutex::new(PciConfigIo::new(
148 pci.clone(),
149 pcibus_exit_evt_wrtube,
150 )));
151 io_bus.insert(pci_bus, 0xcf8, 0x8).unwrap();
152
153 X8664arch::setup_legacy_i8042_device(
154 &io_bus,
155 irq_chip.pit_uses_speaker_port(),
156 exit_evt_wrtube.try_clone().unwrap(),
157 )
158 .unwrap();
159
160 let (host_cmos_tube, cmos_tube) = Tube::pair().unwrap();
161 X8664arch::setup_legacy_cmos_device(&io_bus, &mut irq_chip, cmos_tube, memory_size).unwrap();
162 control_tubes.push(TaggedControlTube::Vm(host_cmos_tube));
163
164 let mut serial_params = BTreeMap::new();
165
166 arch::set_default_serial_parameters(&mut serial_params, false);
167
168 X8664arch::setup_serial_devices(
169 ProtectionType::Unprotected,
170 &mut irq_chip,
171 &io_bus,
172 &serial_params,
173 None,
174 #[cfg(feature = "swap")]
175 None,
176 )
177 .unwrap();
178
179 let param_args = "nokaslr acpi=noirq";
180
181 let mut cmdline = X8664arch::get_base_linux_cmdline();
182
183 cmdline.insert_str(param_args).unwrap();
184
185 let params = bootparam::boot_params::default();
186 // write our custom kernel code to start_addr
187 guest_mem.write_at_addr(&code[..], start_addr).unwrap();
188 let kernel_end = KERNEL_START_OFFSET + code.len() as u64;
189 let initrd_image = None;
190
191 // alternatively, load a real initrd and kernel from disk
192 // let initrd_image = Some(File::open("/mnt/host/source/src/avd/ramdisk.img").expect("failed to open ramdisk"));
193 // let mut kernel_image = File::open("/mnt/host/source/src/avd/vmlinux.uncompressed").expect("failed to open kernel");
194 // let (params, kernel_end) = X8664arch::load_kernel(&guest_mem, &mut kernel_image).expect("failed to load kernel");
195
196 let max_bus = (read_pcie_cfg_mmio().len().unwrap() / 0x100000 - 1) as u8;
197 let suspend_evt = Event::new().unwrap();
198 let mut resume_notify_devices = Vec::new();
199 let acpi_dev_resource = X8664arch::setup_acpi_devices(
200 pci,
201 &guest_mem,
202 &io_bus,
203 &mut resources,
204 suspend_evt
205 .try_clone()
206 .expect("unable to clone suspend_evt"),
207 exit_evt_wrtube
208 .try_clone()
209 .expect("unable to clone exit_evt_wrtube"),
210 Default::default(),
211 #[cfg(feature = "direct")]
212 &[], // direct_gpe
213 #[cfg(feature = "direct")]
214 &[], // direct_fixed_evts
215 &mut irq_chip,
216 X86_64_SCI_IRQ,
217 (None, None),
218 &mmio_bus,
219 max_bus,
220 &mut resume_notify_devices,
221 #[cfg(feature = "swap")]
222 None,
223 #[cfg(unix)]
224 false,
225 )
226 .unwrap();
227
228 X8664arch::setup_system_memory(
229 &guest_mem,
230 &CString::new(cmdline).expect("failed to create cmdline"),
231 initrd_image,
232 None,
233 kernel_end,
234 params,
235 None,
236 )
237 .expect("failed to setup system_memory");
238
239 // Note that this puts the mptable at 0x9FC00 in guest physical memory.
240 mptable::setup_mptable(&guest_mem, 1, &pci_irqs).expect("failed to setup mptable");
241 smbios::setup_smbios(&guest_mem, None, &Vec::new()).expect("failed to setup smbios");
242
243 let mut apic_ids = Vec::new();
244 acpi::create_acpi_tables(
245 &guest_mem,
246 1,
247 X86_64_SCI_IRQ,
248 0xcf9,
249 6,
250 &acpi_dev_resource.0,
251 None,
252 &mut apic_ids,
253 &pci_irqs,
254 read_pcie_cfg_mmio().start,
255 max_bus,
256 false,
257 );
258
259 let guest_mem2 = guest_mem.clone();
260
261 let handle = thread::Builder::new()
262 .name("crosvm_simple_vm_vcpu".to_string())
263 .spawn(move || {
264 let mut vcpu = *vm
265 .create_vcpu(0)
266 .expect("failed to create vcpu")
267 .downcast::<Vcpu>()
268 .map_err(|_| ())
269 .expect("failed to downcast vcpu");
270
271 irq_chip
272 .add_vcpu(0, &vcpu)
273 .expect("failed to add vcpu to irqchip");
274
275 let cpu_config = CpuConfigX86_64::new(false, false, false, false, false, false, None);
276 if !vm.check_capability(VmCap::EarlyInitCpuid) {
277 setup_cpuid(&hyp, &irq_chip, &vcpu, 0, 1, cpu_config).unwrap();
278 }
279
280 let mut msrs = long_mode_msrs();
281 msrs.append(&mut mtrr_msrs(&vm, read_pci_mmio_before_32bit().start));
282 vcpu.set_msrs(&msrs).unwrap();
283
284 let mut vcpu_regs = Regs {
285 rip: start_addr.offset(),
286 rsp: BOOT_STACK_POINTER,
287 rsi: ZERO_PAGE_OFFSET,
288 ..Default::default()
289 };
290
291 // instruction is
292 // mov [eax],ebx
293 // so we're writing 0x12 (the contents of ebx) to the address
294 // in eax (write_addr).
295 vcpu_regs.rax = write_addr.offset() as u64;
296 vcpu_regs.rbx = 0x12;
297 // ecx will contain 0, but after the second instruction it will
298 // also contain 0x12
299 vcpu_regs.rcx = 0x0;
300 vcpu.set_regs(&vcpu_regs).expect("set regs failed");
301
302 let vcpu_fpu_regs = Default::default();
303 vcpu.set_fpu(&vcpu_fpu_regs).expect("set fpu regs failed");
304
305 let mut sregs = vcpu.get_sregs().expect("get sregs failed");
306 configure_segments_and_sregs(&guest_mem, &mut sregs).unwrap();
307 setup_page_tables(&guest_mem, &mut sregs).unwrap();
308 vcpu.set_sregs(&sregs).expect("set sregs failed");
309
310 set_lint(0, &mut irq_chip).unwrap();
311
312 let run_handle = vcpu.take_run_handle(None).unwrap();
313 loop {
314 match vcpu.run(&run_handle).expect("run failed") {
315 VcpuExit::Io => {
316 vcpu.handle_io(&mut |IoParams {
317 address,
318 size,
319 operation: direction,
320 }| {
321 match direction {
322 IoOperation::Write { data } => {
323 // We consider this test to be done when this particular
324 // one-byte port-io to port 0xff with the value of 0x12, which
325 // was in register eax
326 assert_eq!(address, 0xff);
327 assert_eq!(size, 1);
328 assert_eq!(data[0], 0x12);
329 }
330 _ => panic!("unexpected direction {:?}", direction),
331 }
332 None
333 })
334 .expect("vcpu.handle_io failed");
335 break;
336 }
337 r => {
338 panic!("unexpected exit {:?}", r);
339 }
340 }
341 }
342 let regs = vcpu.get_regs().unwrap();
343 // ecx and eax should now contain 0x12
344 assert_eq!(regs.rcx, 0x12);
345 assert_eq!(regs.rax, 0x12);
346 })
347 .unwrap();
348
349 if let Err(e) = handle.join() {
350 panic!("failed to join vcpu thread: {:?}", e);
351 }
352
353 assert_eq!(
354 guest_mem2.read_obj_from_addr::<u64>(write_addr).unwrap(),
355 0x12
356 );
357 }
358