loader/
linux.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Linux specific loader definitions and implementation.
5
6use 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
45/// Construct a zero page from the following parameters.
46/// TODO: support different acpi_base other than 0xe0000
47pub 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    // TODO: support better e820 building, for now acpi_base must be 0xe0000
77    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    /// The address to load the zero page at.
153    pub address: u64,
154    /// The memory layout used to build the e820 map.
155    pub mem_layout: &'a MemoryLayout,
156    /// The base address acpi tables are loaded at.
157    pub acpi_base_address: u64,
158    /// The overall size of acpi tables.
159    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    /// Load the initrd after the kernel at the next 2MB aligned address.
175    AfterKernel,
176    /// Load the initrd at the specified address.
177    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/// Information returned about the kernel loaded.
187#[derive(Debug, Default)]
188pub struct KernelInfo {
189    /// The base gpa the kernel was loaded at.
190    pub gpa: u64,
191    /// The size in bytes of the region the kernel was loaded at.
192    pub size: u64,
193    /// The gpa of the entrypoint of the kernel.
194    pub entrypoint: u64,
195}
196
197/// Information returned about the initrd loaded.
198#[derive(Debug, Default)]
199pub struct InitrdInfo {
200    /// The gpa the initrd was loaded at.
201    pub gpa: u64,
202    /// The size in bytes of the initrd loaded. Note that the region imported is aligned up to page size.
203    pub size: u64,
204}
205
206/// Information returned about where certain parts were loaded.
207#[derive(Debug, Default)]
208pub struct LoadInfo {
209    /// The information about the kernel loaded.
210    pub kernel: KernelInfo,
211    /// The information about the initrd loaded.
212    pub initrd: Option<InitrdInfo>,
213    /// The information about the device tree blob loaded.
214    pub dtb: Option<std::ops::Range<u64>>,
215}
216
217/// Check if an address is aligned to a page.
218fn 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
226/// Import initrd
227fn 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
267/// Load only a Linux kernel and optional initrd to VTL0.
268/// This does not setup register state or any other config information.
269///
270/// # Arguments
271///
272/// * `importer` - The importer to use.
273/// * `kernel_image` - Uncompressed ELF image for the kernel.
274/// * `kernel_minimum_start_address` - The minimum address the kernel can load at.
275///   It cannot contain an entrypoint or program headers that refer to memory below this address.
276/// * `initrd` - The initrd config, optional.
277pub 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
316/// Load the configuration info and registers for the Linux kernel based on the provided LoadInfo.
317///
318/// # Arguments
319/// * `importer` - The importer to use.
320/// * `load_info` - The kernel load info that contains information on where the kernel and initrd are.
321/// * `command_line` - The kernel command line.
322/// * `zero_page` - The kernel zero page.
323/// * `registers` - X86Register config.
324pub 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    // Only import the cmdline if it actually contains something.
334    // TODO: This should use the IGVM parameter instead?
335    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    // NOTE: A whole page is given to the RDSP for simplicity.
375    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    // Set common X64 registers. Segments already set by default gdt.
417    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    // Set rip to entry point and rsi to zero page.
435    import_reg(X86Register::Rip(load_info.kernel.entrypoint))?;
436    import_reg(X86Register::Rsi(zero_page.address))?;
437
438    // No firmware will set MTRR values for the BSP.  Replicate what UEFI does here.
439    // (enable MTRRs, default MTRR is uncached, and set lowest 640KB as WB)
440    import_reg(X86Register::MtrrDefType(0xc00))?;
441    import_reg(X86Register::MtrrFix64k00000(0x0606060606060606))?;
442    import_reg(X86Register::MtrrFix16k80000(0x0606060606060606))?;
443
444    Ok(())
445}
446
447/// Load a Linux kernel into VTL0.
448///
449/// # Arguments
450///
451/// * `importer` - The importer to use.
452/// * `kernel_image` - Uncompressed ELF image for the kernel.
453/// * `kernel_minimum_start_address` - The minimum address the kernel can load at.
454///   It cannot contain an entrypoint or program headers that refer to memory below this address.
455/// * `initrd` - The initrd config, optional.
456/// * `command_line` - The kernel command line.
457/// * `zero_page` - The kernel zero page.
458/// * `acpi` - The acpi config.
459/// * `registers` - X86Register config.
460pub 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/// Arm64 flat kernel `Image` flags.
510#[bitfield(u64)]
511struct Aarch64ImageFlags {
512    /// Bit 0:	Kernel endianness.  1 if BE, 0 if LE.
513    #[bits(1)]
514    pub big_endian: bool,
515    /// Bit 1-2:	Kernel Page size.
516    ///           0 - Unspecified.
517    ///           1 - 4K
518    ///           2 - 16K
519    ///           3 - 64K
520    #[bits(2)]
521    pub page_size: Aarch64ImagePageSize,
522    /// Bit 3:	Kernel physical placement
523    ///           0 - 2MB aligned base should be as close as possible
524    ///               to the base of DRAM, since memory below it is not
525    ///               accessible via the linear mapping
526    ///           1 - 2MB aligned base may be anywhere in physical
527    ///               memory
528    #[bits(1)]
529    pub any_start_address: bool,
530    /// Bits 4-63:	Reserved.
531    #[bits(60)]
532    pub _padding: u64,
533}
534
535// Kernel boot protocol is specified in the Linux kernel
536// Documentation/arm64/booting.txt.
537#[derive(Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
538#[repr(C)]
539struct Aarch64ImageHeader {
540    /// Executable code
541    _code0: u32,
542    /// Executable code
543    _code1: u32,
544    /// Image load offset, little endian
545    text_offset: u64,
546    /// Effective Image size, little endian
547    image_size: u64,
548    /// kernel flags, little endian
549    flags: u64,
550    /// reserved
551    _res2: u64,
552    /// reserved
553    _res3: u64,
554    /// reserved
555    _res4: u64,
556    /// Magic number, little endian, "ARM\x64"
557    magic: [u8; 4],
558    /// reserved (used for PE COFF offset)
559    _res5: u32,
560}
561
562const AARCH64_MAGIC_NUMBER: &[u8] = b"ARM\x64";
563
564/// Load only an arm64 the flat Linux kernel `Image` and optional initrd.
565/// This does not setup register state or any other config information.
566///
567/// # Arguments
568///
569/// * `importer` - The importer to use.
570/// * `kernel_image` - Uncompressed ELF image for the kernel.
571/// * `kernel_minimum_start_address` - The minimum address the kernel can load at.
572///   It cannot contain an entrypoint or program headers that refer to memory below this address.
573/// * `initrd` - The initrd config, optional.
574/// * `device_tree_blob` - The device tree blob, optional.
575pub 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    // The `Image` must be placed `text_offset` bytes from a 2MB aligned base
622    // address anywhere in usable system RAM and called there.
623
624    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
691/// Load the configuration info and registers for the Linux kernel based on the provided LoadInfo.
692/// Parameters:
693/// * `importer` - The importer to use.
694/// * `load_info` - The kernel load info that contains information on where the kernel and initrd are.
695/// * `vtl` - The target VTL.
696pub 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            // MMU is disabled for EL1&0 stage 1 address translation.
720            // The family of the `at` instructions and the `PAR_EL1` register are
721            // useful for debugging MMU issues when it's on.
722            .with_m(false)
723            // Stage 1 Cacheability control, for data accesses.
724            .with_c(true)
725            // Stage 1 Cacheability control, for code.
726            .with_i(true)
727            // Reserved flags, must be set
728            .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            // Disable TTBR0_EL1 walks (i.e. the lower half).
744            .with_epd0(1)
745            // Disable TTBR1_EL1 walks (i.e. the upper half).
746            .with_epd1(1)
747            // Due to erratum #822227, need to set a valid TG1 regardless of EPD1.
748            .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}