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::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
39/// Construct a zero page from the following parameters.
40/// TODO: support different acpi_base other than 0xe0000
41pub 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    // TODO: support better e820 building, for now acpi_base must be 0xe0000
71    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    /// The address to load the zero page at.
145    pub address: u64,
146    /// The memory layout used to build the e820 map.
147    pub mem_layout: &'a MemoryLayout,
148    /// The base address acpi tables are loaded at.
149    pub acpi_base_address: u64,
150    /// The overall size of acpi tables.
151    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    /// Load the initrd after the kernel at the next 2MB aligned address.
167    AfterKernel,
168    /// Load the initrd at the specified address.
169    Address(u64),
170}
171
172pub struct InitrdConfig<'a> {
173    pub initrd_address: InitrdAddressType,
174    pub initrd: &'a [u8],
175}
176
177/// Information returned about the kernel loaded.
178#[derive(Debug, Default)]
179pub struct KernelInfo {
180    /// The base gpa the kernel was loaded at.
181    pub gpa: u64,
182    /// The size in bytes of the region the kernel was loaded at.
183    pub size: u64,
184    /// The gpa of the entrypoint of the kernel.
185    pub entrypoint: u64,
186}
187
188/// Information returned about the initrd loaded.
189#[derive(Debug, Default)]
190pub struct InitrdInfo {
191    /// The gpa the initrd was loaded at.
192    pub gpa: u64,
193    /// The size in bytes of the initrd loaded. Note that the region imported is aligned up to page size.
194    pub size: u64,
195}
196
197/// Information returned about where certain parts were loaded.
198#[derive(Debug, Default)]
199pub struct LoadInfo {
200    /// The information about the kernel loaded.
201    pub kernel: KernelInfo,
202    /// The information about the initrd loaded.
203    pub initrd: Option<InitrdInfo>,
204    /// The information about the device tree blob loaded.
205    pub dtb: Option<std::ops::Range<u64>>,
206}
207
208/// Check if an address is aligned to a page.
209fn 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
217/// Import initrd
218fn 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
253/// Load only a Linux kernel and optional initrd to VTL0.
254/// This does not setup register state or any other config information.
255///
256/// # Arguments
257///
258/// * `importer` - The importer to use.
259/// * `kernel_image` - Uncompressed ELF image for the kernel.
260/// * `kernel_minimum_start_address` - The minimum address the kernel can load at.
261///   It cannot contain an entrypoint or program headers that refer to memory below this address.
262/// * `initrd` - The initrd config, optional.
263pub 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
302/// Load the configuration info and registers for the Linux kernel based on the provided LoadInfo.
303///
304/// # Arguments
305/// * `importer` - The importer to use.
306/// * `load_info` - The kernel load info that contains information on where the kernel and initrd are.
307/// * `command_line` - The kernel command line.
308/// * `zero_page` - The kernel zero page.
309/// * `registers` - X86Register config.
310pub 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    // Only import the cmdline if it actually contains something.
320    // TODO: This should use the IGVM parameter instead?
321    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    // NOTE: A whole page is given to the RDSP for simplicity.
361    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    // Set common X64 registers. Segments already set by default gdt.
403    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    // Set rip to entry point and rsi to zero page.
421    import_reg(X86Register::Rip(load_info.kernel.entrypoint))?;
422    import_reg(X86Register::Rsi(zero_page.address))?;
423
424    // No firmware will set MTRR values for the BSP.  Replicate what UEFI does here.
425    // (enable MTRRs, default MTRR is uncached, and set lowest 640KB as WB)
426    import_reg(X86Register::MtrrDefType(0xc00))?;
427    import_reg(X86Register::MtrrFix64k00000(0x0606060606060606))?;
428    import_reg(X86Register::MtrrFix16k80000(0x0606060606060606))?;
429
430    Ok(())
431}
432
433/// Load a Linux kernel into VTL0.
434///
435/// # Arguments
436///
437/// * `importer` - The importer to use.
438/// * `kernel_image` - Uncompressed ELF image for the kernel.
439/// * `kernel_minimum_start_address` - The minimum address the kernel can load at.
440///   It cannot contain an entrypoint or program headers that refer to memory below this address.
441/// * `initrd` - The initrd config, optional.
442/// * `command_line` - The kernel command line.
443/// * `zero_page` - The kernel zero page.
444/// * `acpi` - The acpi config.
445/// * `registers` - X86Register config.
446pub 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/// Arm64 flat kernel `Image` flags.
496#[bitfield(u64)]
497struct Aarch64ImageFlags {
498    /// Bit 0:	Kernel endianness.  1 if BE, 0 if LE.
499    #[bits(1)]
500    pub big_endian: bool,
501    /// Bit 1-2:	Kernel Page size.
502    ///           0 - Unspecified.
503    ///           1 - 4K
504    ///           2 - 16K
505    ///           3 - 64K
506    #[bits(2)]
507    pub page_size: Aarch64ImagePageSize,
508    /// Bit 3:	Kernel physical placement
509    ///           0 - 2MB aligned base should be as close as possible
510    ///               to the base of DRAM, since memory below it is not
511    ///               accessible via the linear mapping
512    ///           1 - 2MB aligned base may be anywhere in physical
513    ///               memory
514    #[bits(1)]
515    pub any_start_address: bool,
516    /// Bits 4-63:	Reserved.
517    #[bits(60)]
518    pub _padding: u64,
519}
520
521// Kernel boot protocol is specified in the Linux kernel
522// Documentation/arm64/booting.txt.
523#[derive(Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
524#[repr(C)]
525struct Aarch64ImageHeader {
526    /// Executable code
527    _code0: u32,
528    /// Executable code
529    _code1: u32,
530    /// Image load offset, little endian
531    text_offset: u64,
532    /// Effective Image size, little endian
533    image_size: u64,
534    /// kernel flags, little endian
535    flags: u64,
536    /// reserved
537    _res2: u64,
538    /// reserved
539    _res3: u64,
540    /// reserved
541    _res4: u64,
542    /// Magic number, little endian, "ARM\x64"
543    magic: [u8; 4],
544    /// reserved (used for PE COFF offset)
545    _res5: u32,
546}
547
548const AARCH64_MAGIC_NUMBER: &[u8] = b"ARM\x64";
549
550/// Load only an arm64 the flat Linux kernel `Image` and optional initrd.
551/// This does not setup register state or any other config information.
552///
553/// # Arguments
554///
555/// * `importer` - The importer to use.
556/// * `kernel_image` - Uncompressed ELF image for the kernel.
557/// * `kernel_minimum_start_address` - The minimum address the kernel can load at.
558///   It cannot contain an entrypoint or program headers that refer to memory below this address.
559/// * `initrd` - The initrd config, optional.
560/// * `device_tree_blob` - The device tree blob, optional.
561pub 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    // The `Image` must be placed `text_offset` bytes from a 2MB aligned base
608    // address anywhere in usable system RAM and called there.
609
610    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
677/// Load the configuration info and registers for the Linux kernel based on the provided LoadInfo.
678/// Parameters:
679/// * `importer` - The importer to use.
680/// * `load_info` - The kernel load info that contains information on where the kernel and initrd are.
681/// * `vtl` - The target VTL.
682pub 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            // MMU is disabled for EL1&0 stage 1 address translation.
706            // The family of the `at` instructions and the `PAR_EL1` register are
707            // useful for debugging MMU issues when it's on.
708            .with_m(false)
709            // Stage 1 Cacheability control, for data accesses.
710            .with_c(true)
711            // Stage 1 Cacheability control, for code.
712            .with_i(true)
713            // Reserved flags, must be set
714            .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            // Disable TTBR0_EL1 walks (i.e. the lower half).
730            .with_epd0(1)
731            // Disable TTBR1_EL1 walks (i.e. the upper half).
732            .with_epd1(1)
733            // Due to erratum #822227, need to set a valid TG1 regardless of EPD1.
734            .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}