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(any(feature = "gvm", unix), target_arch = "x86_64"))]
7
8 mod sys;
9
10 use std::collections::BTreeMap;
11 use std::ffi::CString;
12 use std::sync::Arc;
13 use std::thread;
14
15 use arch::LinuxArch;
16 use arch::SmbiosOptions;
17 use base::Event;
18 use base::Tube;
19 use devices::Bus;
20 use devices::BusType;
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::set_long_mode_msrs;
49 use x86_64::regs::set_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(Bus::new(BusType::Mmio));
113 let io_bus = Arc::new(Bus::new(BusType::Io));
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, _gpe_scope_amls) = arch::generate_pci_root(
133 devices,
134 &mut irq_chip,
135 mmio_bus.clone(),
136 GuestAddress(0),
137 12,
138 io_bus.clone(),
139 &mut resources,
140 &mut vm,
141 4,
142 None,
143 #[cfg(feature = "swap")]
144 &mut None,
145 )
146 .unwrap();
147 let pci = Arc::new(Mutex::new(pci));
148 let (pcibus_exit_evt_wrtube, _) = Tube::directional_pair().unwrap();
149 let pci_bus = Arc::new(Mutex::new(PciConfigIo::new(
150 pci.clone(),
151 false,
152 pcibus_exit_evt_wrtube,
153 )));
154 io_bus.insert(pci_bus, 0xcf8, 0x8).unwrap();
155
156 X8664arch::setup_legacy_i8042_device(
157 &io_bus,
158 irq_chip.pit_uses_speaker_port(),
159 exit_evt_wrtube.try_clone().unwrap(),
160 )
161 .unwrap();
162
163 let (host_cmos_tube, cmos_tube) = Tube::pair().unwrap();
164 X8664arch::setup_legacy_cmos_device(&io_bus, &mut irq_chip, cmos_tube, memory_size).unwrap();
165 control_tubes.push(TaggedControlTube::Vm(host_cmos_tube));
166
167 let mut serial_params = BTreeMap::new();
168
169 arch::set_default_serial_parameters(&mut serial_params, false);
170
171 X8664arch::setup_serial_devices(
172 ProtectionType::Unprotected,
173 &mut irq_chip,
174 &io_bus,
175 &serial_params,
176 None,
177 #[cfg(feature = "swap")]
178 &mut None,
179 )
180 .unwrap();
181
182 let param_args = "nokaslr acpi=noirq";
183
184 let mut cmdline = X8664arch::get_base_linux_cmdline();
185
186 cmdline.insert_str(param_args).unwrap();
187
188 let params = bootparam::boot_params::default();
189 // write our custom kernel code to start_addr
190 guest_mem.write_at_addr(&code[..], start_addr).unwrap();
191 let kernel_end = KERNEL_START_OFFSET + code.len() as u64;
192 let initrd_image = None;
193
194 // alternatively, load a real initrd and kernel from disk
195 // ```
196 // let initrd_image = Some(File::open("/mnt/host/source/src/avd/ramdisk.img").expect("failed to open ramdisk"));
197 // let mut kernel_image = File::open("/mnt/host/source/src/avd/vmlinux.uncompressed").expect("failed to open kernel");
198 // let (params, kernel_end) = X8664arch::load_kernel(&guest_mem, &mut kernel_image).expect("failed to load kernel");
199 // ````
200
201 let max_bus = (read_pcie_cfg_mmio().len().unwrap() / 0x100000 - 1) as u8;
202 let suspend_evt = Event::new().unwrap();
203 let mut resume_notify_devices = Vec::new();
204 let acpi_dev_resource = X8664arch::setup_acpi_devices(
205 pci,
206 &guest_mem,
207 &io_bus,
208 &mut resources,
209 suspend_evt
210 .try_clone()
211 .expect("unable to clone suspend_evt"),
212 exit_evt_wrtube
213 .try_clone()
214 .expect("unable to clone exit_evt_wrtube"),
215 Default::default(),
216 &mut irq_chip,
217 X86_64_SCI_IRQ,
218 (None, None),
219 &mmio_bus,
220 max_bus,
221 &mut resume_notify_devices,
222 #[cfg(feature = "swap")]
223 &mut None,
224 #[cfg(any(target_os = "android", target_os = "linux"))]
225 false,
226 Default::default(),
227 &pci_irqs,
228 )
229 .unwrap();
230
231 X8664arch::setup_system_memory(
232 &guest_mem,
233 &CString::new(cmdline).expect("failed to create cmdline"),
234 initrd_image,
235 None,
236 kernel_end,
237 params,
238 None,
239 Vec::new(),
240 )
241 .expect("failed to setup system_memory");
242
243 // Note that this puts the mptable at 0x9FC00 in guest physical memory.
244 mptable::setup_mptable(&guest_mem, 1, &pci_irqs).expect("failed to setup mptable");
245 smbios::setup_smbios(&guest_mem, &SmbiosOptions::default(), 0).expect("failed to setup smbios");
246
247 let mut apic_ids = Vec::new();
248 acpi::create_acpi_tables(
249 &guest_mem,
250 1,
251 X86_64_SCI_IRQ,
252 0xcf9,
253 6,
254 &acpi_dev_resource.0,
255 None,
256 &mut apic_ids,
257 &pci_irqs,
258 read_pcie_cfg_mmio().start,
259 max_bus,
260 false,
261 );
262
263 let guest_mem2 = guest_mem.clone();
264
265 let handle = thread::Builder::new()
266 .name("crosvm_simple_vm_vcpu".to_string())
267 .spawn(move || {
268 let mut vcpu = *vm
269 .create_vcpu(0)
270 .expect("failed to create vcpu")
271 .downcast::<Vcpu>()
272 .map_err(|_| ())
273 .expect("failed to downcast vcpu");
274
275 irq_chip
276 .add_vcpu(0, &vcpu)
277 .expect("failed to add vcpu to irqchip");
278
279 let cpu_config = CpuConfigX86_64::new(false, false, false, false, false, None);
280 if !vm.check_capability(VmCap::EarlyInitCpuid) {
281 setup_cpuid(&hyp, &irq_chip, &vcpu, 0, 1, cpu_config).unwrap();
282 }
283
284 let mut msrs = BTreeMap::new();
285 set_long_mode_msrs(&mut msrs);
286 set_mtrr_msrs(&mut msrs, &vm, read_pci_mmio_before_32bit().start);
287 for (msr_index, value) in msrs {
288 vcpu.set_msr(msr_index, value).unwrap();
289 }
290
291 let mut vcpu_regs = Regs {
292 rip: start_addr.offset(),
293 rsp: BOOT_STACK_POINTER,
294 rsi: ZERO_PAGE_OFFSET,
295 ..Default::default()
296 };
297
298 // instruction is
299 // mov [eax],ebx
300 // so we're writing 0x12 (the contents of ebx) to the address
301 // in eax (write_addr).
302 vcpu_regs.rax = write_addr.offset();
303 vcpu_regs.rbx = 0x12;
304 // ecx will contain 0, but after the second instruction it will
305 // also contain 0x12
306 vcpu_regs.rcx = 0x0;
307 vcpu.set_regs(&vcpu_regs).expect("set regs failed");
308
309 let vcpu_fpu_regs = Default::default();
310 vcpu.set_fpu(&vcpu_fpu_regs).expect("set fpu regs failed");
311
312 let mut sregs = vcpu.get_sregs().expect("get sregs failed");
313 configure_segments_and_sregs(&guest_mem, &mut sregs).unwrap();
314 setup_page_tables(&guest_mem, &mut sregs).unwrap();
315 vcpu.set_sregs(&sregs).expect("set sregs failed");
316
317 set_lint(0, &mut irq_chip).unwrap();
318
319 loop {
320 match vcpu.run().expect("run failed") {
321 VcpuExit::Io => {
322 vcpu.handle_io(&mut |IoParams {
323 address,
324 size,
325 operation: direction,
326 }| {
327 match direction {
328 IoOperation::Write { data } => {
329 // We consider this test to be done when this particular
330 // one-byte port-io to port 0xff with the value of 0x12, which
331 // was in register eax
332 assert_eq!(address, 0xff);
333 assert_eq!(size, 1);
334 assert_eq!(data[0], 0x12);
335 }
336 _ => panic!("unexpected direction {:?}", direction),
337 }
338 None
339 })
340 .expect("vcpu.handle_io failed");
341 break;
342 }
343 r => {
344 panic!("unexpected exit {:?}", r);
345 }
346 }
347 }
348 let regs = vcpu.get_regs().unwrap();
349 // ecx and eax should now contain 0x12
350 assert_eq!(regs.rcx, 0x12);
351 assert_eq!(regs.rax, 0x12);
352 })
353 .unwrap();
354
355 if let Err(e) = handle.join() {
356 panic!("failed to join vcpu thread: {:?}", e);
357 }
358
359 assert_eq!(
360 guest_mem2.read_obj_from_addr::<u64>(write_addr).unwrap(),
361 0x12
362 );
363 }
364