• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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