1use crate::common::import_default_gdt;
7use crate::elf::load_static_elf;
8use crate::importer::Aarch64Register;
9use crate::importer::BootPageAcceptance;
10use crate::importer::GuestArch;
11use crate::importer::ImageLoad;
12use crate::importer::X86Register;
13use aarch64defs::Cpsr64;
14use aarch64defs::IntermPhysAddrSize;
15use aarch64defs::SctlrEl1;
16use aarch64defs::TranslationBaseEl1;
17use aarch64defs::TranslationControlEl1;
18use aarch64defs::TranslationGranule0;
19use aarch64defs::TranslationGranule1;
20use bitfield_struct::bitfield;
21use hvdef::HV_PAGE_SIZE;
22use loader_defs::linux as defs;
23use page_table::IdentityMapSize;
24use page_table::x64::IdentityMapBuilder;
25use page_table::x64::PAGE_TABLE_MAX_BYTES;
26use page_table::x64::PAGE_TABLE_MAX_COUNT;
27use page_table::x64::PageTable;
28use page_table::x64::align_up_to_large_page_size;
29use page_table::x64::align_up_to_page_size;
30use std::ffi::CString;
31use thiserror::Error;
32use vm_topology::memory::MemoryLayout;
33use zerocopy::FromBytes;
34use zerocopy::FromZeros;
35use zerocopy::Immutable;
36use zerocopy::IntoBytes;
37use zerocopy::KnownLayout;
38
39pub fn build_zero_page(
42 mem_layout: &MemoryLayout,
43 acpi_base: u64,
44 acpi_len: usize,
45 cmdline_config: &CommandLineConfig<'_>,
46 initrd_base: u32,
47 initrd_size: u32,
48) -> defs::boot_params {
49 let mut p = defs::boot_params {
50 hdr: defs::setup_header {
51 type_of_loader: 0xff,
52 boot_flag: 0xaa55.into(),
53 header: 0x53726448.into(),
54 cmd_line_ptr: cmdline_config.address.try_into().expect("must fit in u32"),
55 cmdline_size: (cmdline_config.cmdline.as_bytes().len() as u64)
56 .try_into()
57 .expect("must fit in u32"),
58 ramdisk_image: initrd_base.into(),
59 ramdisk_size: initrd_size.into(),
60 kernel_alignment: 0x100000.into(),
61 ..FromZeros::new_zeroed()
62 },
63 ..FromZeros::new_zeroed()
64 };
65
66 let mut ram = mem_layout.ram().iter().cloned();
67 let range = ram.next().expect("at least one ram range");
68 assert_eq!(range.range.start(), 0);
69 assert!(range.range.end() >= 0x100000);
70 assert_eq!(acpi_base, 0xe0000);
72 p.e820_map[0] = defs::e820entry {
73 addr: 0.into(),
74 size: 0xe0000.into(),
75 typ: defs::E820_RAM.into(),
76 };
77 let aligned_acpi_len = (acpi_len + 0xfff) & !0xfff;
78 p.e820_map[1] = defs::e820entry {
79 addr: 0xe0000.into(),
80 size: (aligned_acpi_len as u64).into(),
81 typ: defs::E820_ACPI.into(),
82 };
83 p.e820_map[2] = defs::e820entry {
84 addr: (0xe0000 + aligned_acpi_len as u64).into(),
85 size: (range.range.end() - 0xe0000 - aligned_acpi_len as u64).into(),
86 typ: defs::E820_RAM.into(),
87 };
88 let mut n = 3;
89 for range in ram {
90 p.e820_map[n] = defs::e820entry {
91 addr: range.range.start().into(),
92 size: range.range.len().into(),
93 typ: defs::E820_RAM.into(),
94 };
95 n += 1;
96 }
97 p.e820_entries = n as u8;
98
99 p
100}
101
102#[derive(Debug, Error)]
103pub enum FlatLoaderError {
104 #[error("unsupported ELF File byte order")]
105 BigEndianElfOnLittle,
106 #[error("error reading kernel data structure")]
107 BadImageMagic,
108 #[error("big-endian kernel image is not supported")]
109 BigEndianKernelImage,
110 #[error("only images with 4K pages are supported")]
111 FourKibPageImageIsRequired,
112 #[error("the kernel is required to run in the low memory; not supported")]
113 LowMemoryKernel,
114 #[error("failed to read kernel image")]
115 ReadKernelImage,
116 #[error("failed to seek to file offset as pointed by the ELF program header")]
117 SeekKernelStart,
118 #[error("failed to seek to offset of kernel image")]
119 SeekKernelImage,
120}
121
122#[derive(Debug, Error)]
123pub enum Error {
124 #[error("elf loader error")]
125 ElfLoader(#[source] crate::elf::Error),
126 #[error("flat loader error")]
127 FlatLoader(#[source] FlatLoaderError),
128 #[error("Address is not page aligned")]
129 UnalignedAddress(u64),
130 #[error("importer error")]
131 Importer(#[source] anyhow::Error),
132 #[error("PageTableBuilder: {0}")]
133 PageTableBuilder(#[from] page_table::Error),
134}
135
136pub struct AcpiConfig<'a> {
137 pub rdsp_address: u64,
138 pub rdsp: &'a [u8],
139 pub tables_address: u64,
140 pub tables: &'a [u8],
141}
142
143pub struct ZeroPageConfig<'a> {
144 pub address: u64,
146 pub mem_layout: &'a MemoryLayout,
148 pub acpi_base_address: u64,
150 pub acpi_len: usize,
152}
153
154pub struct CommandLineConfig<'a> {
155 pub address: u64,
156 pub cmdline: &'a CString,
157}
158
159pub struct RegisterConfig {
160 pub gdt_address: u64,
161 pub page_table_address: u64,
162}
163
164#[derive(Debug, PartialEq, Eq, Clone, Copy)]
165pub enum InitrdAddressType {
166 AfterKernel,
168 Address(u64),
170}
171
172pub struct InitrdConfig<'a> {
173 pub initrd_address: InitrdAddressType,
174 pub initrd: &'a [u8],
175}
176
177#[derive(Debug, Default)]
179pub struct KernelInfo {
180 pub gpa: u64,
182 pub size: u64,
184 pub entrypoint: u64,
186}
187
188#[derive(Debug, Default)]
190pub struct InitrdInfo {
191 pub gpa: u64,
193 pub size: u64,
195}
196
197#[derive(Debug, Default)]
199pub struct LoadInfo {
200 pub kernel: KernelInfo,
202 pub initrd: Option<InitrdInfo>,
204 pub dtb: Option<std::ops::Range<u64>>,
206}
207
208fn check_address_alignment(address: u64) -> Result<(), Error> {
210 if !address.is_multiple_of(HV_PAGE_SIZE) {
211 Err(Error::UnalignedAddress(address))
212 } else {
213 Ok(())
214 }
215}
216
217fn import_initrd<R: GuestArch>(
219 initrd: Option<InitrdConfig<'_>>,
220 next_addr: u64,
221 importer: &mut dyn ImageLoad<R>,
222) -> Result<Option<InitrdInfo>, Error> {
223 let initrd_info = match &initrd {
224 Some(cfg) => {
225 let initrd_address = match cfg.initrd_address {
226 InitrdAddressType::AfterKernel => align_up_to_large_page_size(next_addr),
227 InitrdAddressType::Address(addr) => addr,
228 };
229
230 tracing::trace!(initrd_address, "loading initrd");
231 check_address_alignment(initrd_address)?;
232 let initrd_size_pages = align_up_to_page_size(cfg.initrd.len() as u64) / HV_PAGE_SIZE;
233 importer
234 .import_pages(
235 initrd_address / HV_PAGE_SIZE,
236 initrd_size_pages,
237 "linux-initrd",
238 BootPageAcceptance::Exclusive,
239 cfg.initrd,
240 )
241 .map_err(Error::Importer)?;
242
243 Some(InitrdInfo {
244 gpa: initrd_address,
245 size: cfg.initrd.len() as u64,
246 })
247 }
248 None => None,
249 };
250 Ok(initrd_info)
251}
252
253pub fn load_kernel_and_initrd_x64<F>(
264 importer: &mut dyn ImageLoad<X86Register>,
265 kernel_image: &mut F,
266 kernel_minimum_start_address: u64,
267 initrd: Option<InitrdConfig<'_>>,
268) -> Result<LoadInfo, Error>
269where
270 F: std::io::Read + std::io::Seek,
271{
272 tracing::trace!(kernel_minimum_start_address, "loading x86_64 kernel");
273 let crate::elf::LoadInfo {
274 minimum_address_used: min_addr,
275 next_available_address: next_addr,
276 entrypoint,
277 } = load_static_elf(
278 importer,
279 kernel_image,
280 kernel_minimum_start_address,
281 0,
282 false,
283 BootPageAcceptance::Exclusive,
284 "linux-kernel",
285 )
286 .map_err(Error::ElfLoader)?;
287 tracing::trace!(min_addr, next_addr, entrypoint, "loaded kernel");
288
289 let initrd_info = import_initrd(initrd, next_addr, importer)?;
290
291 Ok(LoadInfo {
292 kernel: KernelInfo {
293 gpa: min_addr,
294 size: next_addr - min_addr,
295 entrypoint,
296 },
297 initrd: initrd_info,
298 dtb: None,
299 })
300}
301
302pub fn load_config(
311 importer: &mut impl ImageLoad<X86Register>,
312 load_info: &LoadInfo,
313 command_line: CommandLineConfig<'_>,
314 zero_page: ZeroPageConfig<'_>,
315 acpi: AcpiConfig<'_>,
316 registers: RegisterConfig,
317) -> Result<(), Error> {
318 tracing::trace!(command_line.address);
319 let raw_cmdline = command_line.cmdline.as_bytes_with_nul();
322 if raw_cmdline.len() > 1 {
323 check_address_alignment(command_line.address)?;
324 let cmdline_size_pages = align_up_to_page_size(raw_cmdline.len() as u64) / HV_PAGE_SIZE;
325 importer
326 .import_pages(
327 command_line.address / HV_PAGE_SIZE,
328 cmdline_size_pages,
329 "linux-commandline",
330 BootPageAcceptance::Exclusive,
331 raw_cmdline,
332 )
333 .map_err(Error::Importer)?;
334 }
335
336 check_address_alignment(registers.gdt_address)?;
337 import_default_gdt(importer, registers.gdt_address / HV_PAGE_SIZE).map_err(Error::Importer)?;
338 check_address_alignment(registers.page_table_address)?;
339 let mut page_table_work_buffer: Vec<PageTable> =
340 vec![PageTable::new_zeroed(); PAGE_TABLE_MAX_COUNT];
341 let mut page_table: Vec<u8> = vec![0; PAGE_TABLE_MAX_BYTES];
342 let page_table_builder = IdentityMapBuilder::new(
343 registers.page_table_address,
344 IdentityMapSize::Size4Gb,
345 page_table_work_buffer.as_mut_slice(),
346 page_table.as_mut_slice(),
347 )?;
348 let page_table = page_table_builder.build();
349 assert!((page_table.len() as u64).is_multiple_of(HV_PAGE_SIZE));
350 importer
351 .import_pages(
352 registers.page_table_address / HV_PAGE_SIZE,
353 page_table.len() as u64 / HV_PAGE_SIZE,
354 "linux-pagetables",
355 BootPageAcceptance::Exclusive,
356 page_table,
357 )
358 .map_err(Error::Importer)?;
359
360 check_address_alignment(acpi.rdsp_address)?;
362 check_address_alignment(acpi.tables_address)?;
363 let acpi_tables_size_pages = align_up_to_page_size(acpi.tables.len() as u64) / HV_PAGE_SIZE;
364 importer
365 .import_pages(
366 acpi.rdsp_address / HV_PAGE_SIZE,
367 1,
368 "linux-rdsp",
369 BootPageAcceptance::Exclusive,
370 acpi.rdsp,
371 )
372 .map_err(Error::Importer)?;
373 importer
374 .import_pages(
375 acpi.tables_address / HV_PAGE_SIZE,
376 acpi_tables_size_pages,
377 "linux-acpi-tables",
378 BootPageAcceptance::Exclusive,
379 acpi.tables,
380 )
381 .map_err(Error::Importer)?;
382
383 check_address_alignment(zero_page.address)?;
384 let boot_params = build_zero_page(
385 zero_page.mem_layout,
386 zero_page.acpi_base_address,
387 zero_page.acpi_len,
388 &command_line,
389 load_info.initrd.as_ref().map(|info| info.gpa).unwrap_or(0) as u32,
390 load_info.initrd.as_ref().map(|info| info.size).unwrap_or(0) as u32,
391 );
392 importer
393 .import_pages(
394 zero_page.address / HV_PAGE_SIZE,
395 1,
396 "linux-zeropage",
397 BootPageAcceptance::Exclusive,
398 boot_params.as_bytes(),
399 )
400 .map_err(Error::Importer)?;
401
402 let mut import_reg = |register| {
404 importer
405 .import_vp_register(register)
406 .map_err(Error::Importer)
407 };
408
409 import_reg(X86Register::Cr0(x86defs::X64_CR0_PG | x86defs::X64_CR0_PE))?;
410 import_reg(X86Register::Cr3(registers.page_table_address))?;
411 import_reg(X86Register::Cr4(x86defs::X64_CR4_PAE))?;
412 import_reg(X86Register::Efer(
413 x86defs::X64_EFER_SCE
414 | x86defs::X64_EFER_LME
415 | x86defs::X64_EFER_LMA
416 | x86defs::X64_EFER_NXE,
417 ))?;
418 import_reg(X86Register::Pat(x86defs::X86X_MSR_DEFAULT_PAT))?;
419
420 import_reg(X86Register::Rip(load_info.kernel.entrypoint))?;
422 import_reg(X86Register::Rsi(zero_page.address))?;
423
424 import_reg(X86Register::MtrrDefType(0xc00))?;
427 import_reg(X86Register::MtrrFix64k00000(0x0606060606060606))?;
428 import_reg(X86Register::MtrrFix16k80000(0x0606060606060606))?;
429
430 Ok(())
431}
432
433pub fn load_x86<F>(
447 importer: &mut impl ImageLoad<X86Register>,
448 kernel_image: &mut F,
449 kernel_minimum_start_address: u64,
450 initrd: Option<InitrdConfig<'_>>,
451 command_line: CommandLineConfig<'_>,
452 zero_page: ZeroPageConfig<'_>,
453 acpi: AcpiConfig<'_>,
454 registers: RegisterConfig,
455) -> Result<LoadInfo, Error>
456where
457 F: std::io::Read + std::io::Seek,
458{
459 let load_info =
460 load_kernel_and_initrd_x64(importer, kernel_image, kernel_minimum_start_address, initrd)?;
461
462 load_config(
463 importer,
464 &load_info,
465 command_line,
466 zero_page,
467 acpi,
468 registers,
469 )?;
470
471 Ok(load_info)
472}
473
474open_enum::open_enum! {
475 #[derive(IntoBytes, Immutable, KnownLayout, FromBytes)]
476 pub enum Aarch64ImagePageSize: u64 {
477 UNSPECIFIED = 0,
478 PAGE4_K = 1,
479 PAGE16_K = 2,
480 PAGE64_K = 3,
481 }
482
483}
484
485impl Aarch64ImagePageSize {
486 const fn into_bits(self) -> u64 {
487 self.0
488 }
489
490 const fn from_bits(bits: u64) -> Self {
491 Self(bits)
492 }
493}
494
495#[bitfield(u64)]
497struct Aarch64ImageFlags {
498 #[bits(1)]
500 pub big_endian: bool,
501 #[bits(2)]
507 pub page_size: Aarch64ImagePageSize,
508 #[bits(1)]
515 pub any_start_address: bool,
516 #[bits(60)]
518 pub _padding: u64,
519}
520
521#[derive(Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
524#[repr(C)]
525struct Aarch64ImageHeader {
526 _code0: u32,
528 _code1: u32,
530 text_offset: u64,
532 image_size: u64,
534 flags: u64,
536 _res2: u64,
538 _res3: u64,
540 _res4: u64,
542 magic: [u8; 4],
544 _res5: u32,
546}
547
548const AARCH64_MAGIC_NUMBER: &[u8] = b"ARM\x64";
549
550pub fn load_kernel_and_initrd_arm64<F>(
562 importer: &mut dyn ImageLoad<Aarch64Register>,
563 kernel_image: &mut F,
564 kernel_minimum_start_address: u64,
565 initrd: Option<InitrdConfig<'_>>,
566 device_tree_blob: Option<&[u8]>,
567) -> Result<LoadInfo, Error>
568where
569 F: std::io::Read + std::io::Seek,
570{
571 tracing::trace!(kernel_minimum_start_address, "loading aarch64 kernel");
572
573 assert_eq!(
574 kernel_minimum_start_address & ((1 << 21) - 1),
575 0,
576 "Start offset must be aligned on the 2MiB boundary"
577 );
578
579 kernel_image
580 .seek(std::io::SeekFrom::Start(0))
581 .map_err(|_| Error::FlatLoader(FlatLoaderError::SeekKernelStart))?;
582
583 let mut header = Aarch64ImageHeader::new_zeroed();
584 kernel_image
585 .read_exact(header.as_mut_bytes())
586 .map_err(|_| Error::FlatLoader(FlatLoaderError::ReadKernelImage))?;
587
588 tracing::debug!("aarch64 kernel header {header:x?}");
589
590 if header.magic != AARCH64_MAGIC_NUMBER {
591 return Err(Error::FlatLoader(FlatLoaderError::BadImageMagic));
592 }
593
594 let flags = Aarch64ImageFlags::from(header.flags);
595 if flags.big_endian() {
596 return Err(Error::FlatLoader(FlatLoaderError::BigEndianKernelImage));
597 }
598 if flags.page_size() != Aarch64ImagePageSize::PAGE4_K {
599 return Err(Error::FlatLoader(
600 FlatLoaderError::FourKibPageImageIsRequired,
601 ));
602 }
603 if !flags.any_start_address() {
604 return Err(Error::FlatLoader(FlatLoaderError::LowMemoryKernel));
605 }
606
607 kernel_image
611 .seek(std::io::SeekFrom::Start(0))
612 .map_err(|_| Error::FlatLoader(FlatLoaderError::SeekKernelStart))?;
613
614 let mut image = Vec::new();
615 kernel_image
616 .read_to_end(&mut image)
617 .map_err(|_| Error::FlatLoader(FlatLoaderError::ReadKernelImage))?;
618
619 let kernel_load_offset = (kernel_minimum_start_address + header.text_offset) as usize;
620 let kernel_size = if header.image_size != 0 {
621 header.image_size
622 } else {
623 image.len() as u64
624 };
625
626 let kernel_size = align_up_to_page_size(kernel_size);
627 importer
628 .import_pages(
629 kernel_load_offset as u64 / HV_PAGE_SIZE,
630 kernel_size / HV_PAGE_SIZE,
631 "linux-kernel",
632 BootPageAcceptance::Exclusive,
633 &image,
634 )
635 .map_err(Error::Importer)?;
636
637 let next_addr = kernel_load_offset as u64 + kernel_size;
638
639 let (next_addr, dtb) = if let Some(device_tree_blob) = device_tree_blob {
640 let dtb_addr = align_up_to_page_size(next_addr);
641 tracing::trace!(dtb_addr, "loading device tree blob at {dtb_addr:x?}");
642
643 check_address_alignment(dtb_addr)?;
644 let dtb_size_pages = align_up_to_page_size(device_tree_blob.len() as u64) / HV_PAGE_SIZE;
645
646 importer
647 .import_pages(
648 dtb_addr / HV_PAGE_SIZE,
649 dtb_size_pages,
650 "linux-device-tree",
651 BootPageAcceptance::Exclusive,
652 device_tree_blob,
653 )
654 .map_err(Error::Importer)?;
655
656 (
657 dtb_addr + device_tree_blob.len() as u64,
658 Some(dtb_addr..dtb_addr + device_tree_blob.len() as u64),
659 )
660 } else {
661 (next_addr, None)
662 };
663
664 let initrd_info = import_initrd(initrd, next_addr, importer)?;
665
666 Ok(LoadInfo {
667 kernel: KernelInfo {
668 gpa: kernel_minimum_start_address,
669 size: kernel_size,
670 entrypoint: kernel_load_offset as u64,
671 },
672 initrd: initrd_info,
673 dtb,
674 })
675}
676
677pub fn set_direct_boot_registers_arm64(
683 importer: &mut impl ImageLoad<Aarch64Register>,
684 load_info: &LoadInfo,
685) -> Result<(), Error> {
686 let mut import_reg = |register| {
687 importer
688 .import_vp_register(register)
689 .map_err(Error::Importer)
690 };
691
692 import_reg(Aarch64Register::Pc(load_info.kernel.entrypoint))?;
693 import_reg(Aarch64Register::Cpsr(
694 Cpsr64::new()
695 .with_sp(true)
696 .with_el(1)
697 .with_f(true)
698 .with_i(true)
699 .with_a(true)
700 .with_d(true)
701 .into(),
702 ))?;
703 import_reg(Aarch64Register::SctlrEl1(
704 SctlrEl1::new()
705 .with_m(false)
709 .with_c(true)
711 .with_i(true)
713 .with_eos(true)
715 .with_tscxt(true)
716 .with_eis(true)
717 .with_span(true)
718 .with_n_tlsmd(true)
719 .with_lsmaoe(true)
720 .into(),
721 ))?;
722 import_reg(Aarch64Register::TcrEl1(
723 TranslationControlEl1::new()
724 .with_t0sz(0x11)
725 .with_irgn0(1)
726 .with_orgn0(1)
727 .with_sh0(3)
728 .with_tg0(TranslationGranule0::TG_4KB)
729 .with_epd0(1)
731 .with_epd1(1)
733 .with_tg1(TranslationGranule1::TG_4KB)
735 .with_ips(IntermPhysAddrSize::IPA_48_BITS_256_TB)
736 .into(),
737 ))?;
738 import_reg(Aarch64Register::Ttbr0El1(TranslationBaseEl1::new().into()))?;
739 import_reg(Aarch64Register::Ttbr1El1(TranslationBaseEl1::new().into()))?;
740 import_reg(Aarch64Register::VbarEl1(0))?;
741
742 if let Some(dtb) = &load_info.dtb {
743 import_reg(Aarch64Register::X0(dtb.start))?;
744 }
745
746 Ok(())
747}