1pub mod cpu_model;
12mod gdt;
13pub mod interrupts;
15pub mod kvm;
17pub mod layout;
19mod mptable;
20pub mod msr;
22pub mod regs;
24pub mod vcpu;
26pub mod vm;
28pub mod xstate;
30
31#[allow(missing_docs)]
32pub mod generated;
33
34use std::cmp::max;
35use std::fs::File;
36
37use kvm::Kvm;
38use layout::{
39 CMDLINE_START, MMIO32_MEM_SIZE, MMIO32_MEM_START, MMIO64_MEM_SIZE, MMIO64_MEM_START,
40 PCI_MMCONFIG_SIZE, PCI_MMCONFIG_START,
41};
42use linux_loader::configurator::linux::LinuxBootConfigurator;
43use linux_loader::configurator::pvh::PvhBootConfigurator;
44use linux_loader::configurator::{BootConfigurator, BootParams};
45use linux_loader::loader::bootparam::boot_params;
46use linux_loader::loader::elf::Elf as Loader;
47use linux_loader::loader::elf::start_info::{
48 hvm_memmap_table_entry, hvm_modlist_entry, hvm_start_info,
49};
50use linux_loader::loader::{Cmdline, KernelLoader, PvhBootCapability, load_cmdline};
51use log::debug;
52
53use super::EntryPoint;
54use crate::acpi::create_acpi_tables;
55use crate::arch::{BootProtocol, SYSTEM_MEM_SIZE, SYSTEM_MEM_START, arch_memory_regions_with_gap};
56use crate::cpu_config::templates::{CustomCpuTemplate, GuestConfigError};
57use crate::cpu_config::x86_64::CpuConfiguration;
58use crate::device_manager::DeviceManager;
59use crate::initrd::InitrdConfig;
60use crate::utils::{align_down, u64_to_usize, usize_to_u64};
61use crate::vmm_config::machine_config::MachineConfig;
62use crate::vstate::memory::{
63 Address, GuestAddress, GuestMemory, GuestMemoryMmap, GuestMemoryRegion, GuestRegionType,
64};
65use crate::vstate::vcpu::KvmVcpuConfigureError;
66use crate::{Vcpu, VcpuConfig, Vm, logger};
67
68const E820_RAM: u32 = 1;
71
72const E820_RESERVED: u32 = 2;
74const MEMMAP_TYPE_RAM: u32 = 1;
75
76#[derive(Debug, thiserror::Error, displaydoc::Display)]
78pub enum ConfigurationError {
79 E820Configuration,
81 MpTableSetup(#[from] mptable::MptableError),
83 ZeroPageSetup,
85 ModlistSetup,
87 MemmapTableSetup,
89 StartInfoSetup,
91 KernelFile,
93 KernelLoader(linux_loader::loader::Error),
95 LoadCommandline(linux_loader::loader::Error),
97 CreateGuestConfig(#[from] GuestConfigError),
99 VcpuConfigure(#[from] KvmVcpuConfigureError),
101 Acpi(#[from] crate::acpi::AcpiError),
103}
104
105pub fn arch_memory_regions(size: usize) -> Vec<(GuestAddress, usize)> {
110 assert!(size > 0, "Attempt to allocate guest memory of length 0");
113
114 let dram_size = std::cmp::min(
115 usize::MAX - u64_to_usize(MMIO32_MEM_SIZE) - u64_to_usize(MMIO64_MEM_SIZE),
116 size,
117 );
118
119 if dram_size != size {
120 logger::warn!(
121 "Requested memory size {} exceeds architectural maximum (1022GiB). Size has been \
122 truncated to {}",
123 size,
124 dram_size
125 );
126 }
127
128 let mut regions = vec![];
129
130 if let Some((start_past_32bit_gap, remaining_past_32bit_gap)) = arch_memory_regions_with_gap(
131 &mut regions,
132 0,
133 dram_size,
134 u64_to_usize(MMIO32_MEM_START),
135 u64_to_usize(MMIO32_MEM_SIZE),
136 ) && let Some((start_past_64bit_gap, remaining_past_64bit_gap)) =
137 arch_memory_regions_with_gap(
138 &mut regions,
139 start_past_32bit_gap,
140 remaining_past_32bit_gap,
141 u64_to_usize(MMIO64_MEM_START),
142 u64_to_usize(MMIO64_MEM_SIZE),
143 )
144 {
145 regions.push((
146 GuestAddress(start_past_64bit_gap as u64),
147 remaining_past_64bit_gap,
148 ));
149 }
150
151 regions
152}
153
154pub fn get_kernel_start() -> u64 {
156 layout::HIMEM_START
157}
158
159pub fn initrd_load_addr(guest_mem: &GuestMemoryMmap, initrd_size: usize) -> Option<u64> {
161 let first_region = guest_mem.find_region(GuestAddress::new(0))?;
162 let lowmem_size = u64_to_usize(first_region.len());
163
164 if lowmem_size < initrd_size {
165 return None;
166 }
167
168 Some(align_down(
169 usize_to_u64(lowmem_size - initrd_size),
170 usize_to_u64(super::GUEST_PAGE_SIZE),
171 ))
172}
173
174#[allow(clippy::too_many_arguments)]
176pub fn configure_system_for_boot(
177 kvm: &Kvm,
178 vm: &Vm,
179 device_manager: &mut DeviceManager,
180 vcpus: &mut [Vcpu],
181 machine_config: &MachineConfig,
182 cpu_template: &CustomCpuTemplate,
183 entry_point: EntryPoint,
184 initrd: &Option<InitrdConfig>,
185 boot_cmdline: Cmdline,
186) -> Result<(), ConfigurationError> {
187 let cpu_config = CpuConfiguration::new(kvm.supported_cpuid.clone(), cpu_template, &vcpus[0])?;
189 let cpu_config = CpuConfiguration::apply_template(cpu_config, cpu_template)?;
191
192 let vcpu_config = VcpuConfig {
193 vcpu_count: machine_config.vcpu_count,
194 smt: machine_config.smt,
195 cpu_config,
196 };
197
198 for vcpu in vcpus.iter_mut() {
200 vcpu.kvm_vcpu
201 .configure(vm.guest_memory(), entry_point, &vcpu_config)?;
202 }
203
204 let cmdline_size = boot_cmdline
207 .as_cstring()
208 .map(|cmdline_cstring| cmdline_cstring.as_bytes_with_nul().len())
209 .expect("Cannot create cstring from cmdline string");
210
211 load_cmdline(
212 vm.guest_memory(),
213 GuestAddress(crate::arch::x86_64::layout::CMDLINE_START),
214 &boot_cmdline,
215 )
216 .map_err(ConfigurationError::LoadCommandline)?;
217
218 mptable::setup_mptable(
220 vm.guest_memory(),
221 &mut vm.resource_allocator(),
222 vcpu_config.vcpu_count,
223 )
224 .map_err(ConfigurationError::MpTableSetup)?;
225
226 match entry_point.protocol {
227 BootProtocol::PvhBoot => {
228 configure_pvh(vm.guest_memory(), GuestAddress(CMDLINE_START), initrd)?;
229 }
230 BootProtocol::LinuxBoot => {
231 configure_64bit_boot(
232 vm.guest_memory(),
233 GuestAddress(CMDLINE_START),
234 cmdline_size,
235 initrd,
236 )?;
237 }
238 }
239
240 create_acpi_tables(
243 vm.guest_memory(),
244 device_manager,
245 &mut vm.resource_allocator(),
246 vcpus,
247 )?;
248 Ok(())
249}
250
251fn configure_pvh(
252 guest_mem: &GuestMemoryMmap,
253 cmdline_addr: GuestAddress,
254 initrd: &Option<InitrdConfig>,
255) -> Result<(), ConfigurationError> {
256 const XEN_HVM_START_MAGIC_VALUE: u32 = 0x336e_c578;
257 let himem_start = GuestAddress(layout::HIMEM_START);
258
259 let mut modules: Vec<hvm_modlist_entry> = Vec::new();
261 if let Some(initrd_config) = initrd {
262 modules.push(hvm_modlist_entry {
265 paddr: initrd_config.address.raw_value(),
266 size: initrd_config.size as u64,
267 ..Default::default()
268 });
269 }
270
271 let mut memmap: Vec<hvm_memmap_table_entry> = Vec::new();
274
275 memmap.push(hvm_memmap_table_entry {
277 addr: 0,
278 size: SYSTEM_MEM_START,
279 type_: MEMMAP_TYPE_RAM,
280 ..Default::default()
281 });
282 memmap.push(hvm_memmap_table_entry {
283 addr: SYSTEM_MEM_START,
284 size: SYSTEM_MEM_SIZE,
285 type_: E820_RESERVED,
286 ..Default::default()
287 });
288 memmap.push(hvm_memmap_table_entry {
289 addr: PCI_MMCONFIG_START,
290 size: PCI_MMCONFIG_SIZE,
291 type_: E820_RESERVED,
292 ..Default::default()
293 });
294
295 for region in guest_mem
296 .iter()
297 .filter(|region| region.region_type == GuestRegionType::Dram)
298 {
299 let addr = max(himem_start, region.start_addr());
301 memmap.push(hvm_memmap_table_entry {
302 addr: addr.raw_value(),
303 size: region.last_addr().unchecked_offset_from(addr) + 1,
304 type_: MEMMAP_TYPE_RAM,
305 ..Default::default()
306 });
307 }
308
309 #[allow(clippy::cast_possible_truncation)] let mut start_info = hvm_start_info {
315 magic: XEN_HVM_START_MAGIC_VALUE,
316 version: 1,
317 cmdline_paddr: cmdline_addr.raw_value(),
318 memmap_paddr: layout::MEMMAP_START,
319 memmap_entries: memmap.len() as u32,
320 nr_modules: modules.len() as u32,
321 ..Default::default()
322 };
323 if !modules.is_empty() {
324 start_info.modlist_paddr = layout::MODLIST_START;
325 }
326 let mut boot_params =
327 BootParams::new::<hvm_start_info>(&start_info, GuestAddress(layout::PVH_INFO_START));
328
329 boot_params.set_sections::<hvm_memmap_table_entry>(&memmap, GuestAddress(layout::MEMMAP_START));
332
333 boot_params.set_modules::<hvm_modlist_entry>(&modules, GuestAddress(layout::MODLIST_START));
337
338 PvhBootConfigurator::write_bootparams(&boot_params, guest_mem)
340 .map_err(|_| ConfigurationError::StartInfoSetup)
341}
342
343fn configure_64bit_boot(
344 guest_mem: &GuestMemoryMmap,
345 cmdline_addr: GuestAddress,
346 cmdline_size: usize,
347 initrd: &Option<InitrdConfig>,
348) -> Result<(), ConfigurationError> {
349 const KERNEL_BOOT_FLAG_MAGIC: u16 = 0xaa55;
350 const KERNEL_HDR_MAGIC: u32 = 0x5372_6448;
351 const KERNEL_LOADER_OTHER: u8 = 0xff;
352 const KERNEL_MIN_ALIGNMENT_BYTES: u32 = 0x0100_0000; let himem_start = GuestAddress(layout::HIMEM_START);
355
356 let mut params = boot_params {
358 acpi_rsdp_addr: layout::RSDP_ADDR,
359 ..Default::default()
360 };
361
362 params.hdr.type_of_loader = KERNEL_LOADER_OTHER;
363 params.hdr.boot_flag = KERNEL_BOOT_FLAG_MAGIC;
364 params.hdr.header = KERNEL_HDR_MAGIC;
365 params.hdr.cmd_line_ptr = u32::try_from(cmdline_addr.raw_value()).unwrap();
366 params.hdr.cmdline_size = u32::try_from(cmdline_size).unwrap();
367 params.hdr.kernel_alignment = KERNEL_MIN_ALIGNMENT_BYTES;
368 if let Some(initrd_config) = initrd {
369 params.hdr.ramdisk_image = u32::try_from(initrd_config.address.raw_value()).unwrap();
370 params.hdr.ramdisk_size = u32::try_from(initrd_config.size).unwrap();
371 }
372
373 add_e820_entry(&mut params, 0, layout::SYSTEM_MEM_START, E820_RAM)?;
377 add_e820_entry(
378 &mut params,
379 layout::SYSTEM_MEM_START,
380 layout::SYSTEM_MEM_SIZE,
381 E820_RESERVED,
382 )?;
383 add_e820_entry(
384 &mut params,
385 PCI_MMCONFIG_START,
386 PCI_MMCONFIG_SIZE,
387 E820_RESERVED,
388 )?;
389
390 for region in guest_mem
391 .iter()
392 .filter(|region| region.region_type == GuestRegionType::Dram)
393 {
394 let addr = max(himem_start, region.start_addr());
396 add_e820_entry(
397 &mut params,
398 addr.raw_value(),
399 region.last_addr().unchecked_offset_from(addr) + 1,
400 E820_RAM,
401 )?;
402 }
403
404 LinuxBootConfigurator::write_bootparams(
405 &BootParams::new(¶ms, GuestAddress(layout::ZERO_PAGE_START)),
406 guest_mem,
407 )
408 .map_err(|_| ConfigurationError::ZeroPageSetup)
409}
410
411fn add_e820_entry(
414 params: &mut boot_params,
415 addr: u64,
416 size: u64,
417 mem_type: u32,
418) -> Result<(), ConfigurationError> {
419 if params.e820_entries as usize >= params.e820_table.len() {
420 return Err(ConfigurationError::E820Configuration);
421 }
422
423 params.e820_table[params.e820_entries as usize].addr = addr;
424 params.e820_table[params.e820_entries as usize].size = size;
425 params.e820_table[params.e820_entries as usize].type_ = mem_type;
426 params.e820_entries += 1;
427
428 Ok(())
429}
430
431pub fn load_kernel(
433 kernel: &File,
434 guest_memory: &GuestMemoryMmap,
435) -> Result<EntryPoint, ConfigurationError> {
436 let mut kernel_file = kernel
439 .try_clone()
440 .map_err(|_| ConfigurationError::KernelFile)?;
441
442 let entry_addr = Loader::load(
443 guest_memory,
444 None,
445 &mut kernel_file,
446 Some(GuestAddress(get_kernel_start())),
447 )
448 .map_err(ConfigurationError::KernelLoader)?;
449
450 let mut entry_point_addr: GuestAddress = entry_addr.kernel_load;
451 let mut boot_prot: BootProtocol = BootProtocol::LinuxBoot;
452 if let PvhBootCapability::PvhEntryPresent(pvh_entry_addr) = entry_addr.pvh_boot_cap {
453 entry_point_addr = pvh_entry_addr;
455 boot_prot = BootProtocol::PvhBoot;
456 }
457
458 debug!("Kernel loaded using {boot_prot}");
459
460 Ok(EntryPoint {
461 entry_addr: entry_point_addr,
462 protocol: boot_prot,
463 })
464}
465
466#[cfg(kani)]
467mod verification {
468
469 use crate::arch::arch_memory_regions;
470 use crate::arch::x86_64::layout::{
471 FIRST_ADDR_PAST_32BITS, FIRST_ADDR_PAST_64BITS_MMIO, MMIO32_MEM_SIZE, MMIO32_MEM_START,
472 MMIO64_MEM_SIZE, MMIO64_MEM_START,
473 };
474 use crate::utils::u64_to_usize;
475
476 #[kani::proof]
477 #[kani::unwind(4)]
478 fn verify_arch_memory_regions() {
479 let len: u64 = kani::any::<u64>();
480
481 kani::assume(len > 0);
482
483 let regions = arch_memory_regions(len as usize);
484
485 assert!(regions.len() <= 3);
487 assert!(regions.len() >= 1);
488
489 assert_eq!(regions[0].0.0, 0);
491
492 let actual_size = regions.iter().map(|&(_, len)| len).sum::<usize>();
494 assert!(actual_size <= len as usize);
495 if actual_size < u64_to_usize(len) {
496 assert_eq!(
497 actual_size,
498 usize::MAX - u64_to_usize(MMIO32_MEM_SIZE) - u64_to_usize(MMIO64_MEM_SIZE)
499 );
500 }
501
502 assert!(
504 regions
505 .iter()
506 .all(|&(start, len)| (start.0 >= FIRST_ADDR_PAST_32BITS
507 || start.0 + len as u64 <= MMIO32_MEM_START)
508 && (start.0 >= FIRST_ADDR_PAST_64BITS_MMIO
509 || start.0 + len as u64 <= MMIO64_MEM_START))
510 );
511
512 assert!(regions.iter().all(|&(_, len)| len > 0));
514
515 if regions.len() >= 2 {
517 kani::cover!();
518
519 assert_eq!(regions[0].0.0 + regions[0].1 as u64, MMIO32_MEM_START);
520 assert_eq!(regions[1].0.0, FIRST_ADDR_PAST_32BITS);
521 }
522
523 if regions.len() == 3 {
526 kani::cover!();
527
528 assert_eq!(regions[1].0.0 + regions[1].1 as u64, MMIO64_MEM_START);
529 assert_eq!(regions[2].0.0, FIRST_ADDR_PAST_64BITS_MMIO);
530 }
531 }
532}
533
534#[cfg(test)]
535mod tests {
536 use linux_loader::loader::bootparam::boot_e820_entry;
537
538 use super::*;
539 use crate::arch::x86_64::layout::FIRST_ADDR_PAST_32BITS;
540 use crate::test_utils::{arch_mem, single_region_mem};
541 use crate::utils::mib_to_bytes;
542 use crate::vstate::resources::ResourceAllocator;
543
544 #[test]
545 fn regions_lt_4gb() {
546 let regions = arch_memory_regions(1usize << 29);
547 assert_eq!(1, regions.len());
548 assert_eq!(GuestAddress(0), regions[0].0);
549 assert_eq!(1usize << 29, regions[0].1);
550 }
551
552 #[test]
553 fn regions_gt_4gb() {
554 const MEMORY_SIZE: usize = (1 << 32) + 0x8000;
555
556 let regions = arch_memory_regions(MEMORY_SIZE);
557 assert_eq!(2, regions.len());
558 assert_eq!(GuestAddress(0), regions[0].0);
559 assert_eq!(GuestAddress(1u64 << 32), regions[1].0);
560
561 assert_eq!(
562 regions[1],
563 (
564 GuestAddress(FIRST_ADDR_PAST_32BITS),
565 MEMORY_SIZE - regions[0].1
566 )
567 )
568 }
569
570 #[test]
571 fn test_system_configuration() {
572 let no_vcpus = 4;
573 let gm = single_region_mem(0x10000);
574 let mut resource_allocator = ResourceAllocator::new();
575 let err = mptable::setup_mptable(&gm, &mut resource_allocator, 1);
576 assert!(matches!(
577 err.unwrap_err(),
578 mptable::MptableError::NotEnoughMemory
579 ));
580
581 let mem_size = mib_to_bytes(128);
583 let gm = arch_mem(mem_size);
584 let mut resource_allocator = ResourceAllocator::new();
585 mptable::setup_mptable(&gm, &mut resource_allocator, no_vcpus).unwrap();
586 configure_64bit_boot(&gm, GuestAddress(0), 0, &None).unwrap();
587 configure_pvh(&gm, GuestAddress(0), &None).unwrap();
588
589 let mem_size = mib_to_bytes(3328);
591 let gm = arch_mem(mem_size);
592 let mut resource_allocator = ResourceAllocator::new();
593 mptable::setup_mptable(&gm, &mut resource_allocator, no_vcpus).unwrap();
594 configure_64bit_boot(&gm, GuestAddress(0), 0, &None).unwrap();
595 configure_pvh(&gm, GuestAddress(0), &None).unwrap();
596
597 let mem_size = mib_to_bytes(3330);
599 let gm = arch_mem(mem_size);
600 let mut resource_allocator = ResourceAllocator::new();
601 mptable::setup_mptable(&gm, &mut resource_allocator, no_vcpus).unwrap();
602 configure_64bit_boot(&gm, GuestAddress(0), 0, &None).unwrap();
603 configure_pvh(&gm, GuestAddress(0), &None).unwrap();
604 }
605
606 #[test]
607 fn test_add_e820_entry() {
608 let e820_map = [(boot_e820_entry {
609 addr: 0x1,
610 size: 4,
611 type_: 1,
612 }); 128];
613
614 let expected_params = boot_params {
615 e820_table: e820_map,
616 e820_entries: 1,
617 ..Default::default()
618 };
619
620 let mut params: boot_params = Default::default();
621 add_e820_entry(
622 &mut params,
623 e820_map[0].addr,
624 e820_map[0].size,
625 e820_map[0].type_,
626 )
627 .unwrap();
628 assert_eq!(
629 format!("{:?}", params.e820_table[0]),
630 format!("{:?}", expected_params.e820_table[0])
631 );
632 assert_eq!(params.e820_entries, expected_params.e820_entries);
633
634 params.e820_entries = u8::try_from(params.e820_table.len()).unwrap() + 1;
637 assert!(
638 add_e820_entry(
639 &mut params,
640 e820_map[0].addr,
641 e820_map[0].size,
642 e820_map[0].type_
643 )
644 .is_err()
645 );
646 }
647}