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