Skip to main content

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    bzimage_header: Option<&defs::setup_header>,
55) -> defs::boot_params {
56    // Loader type 0xff = unregistered bootloader, used for both ELF and
57    // bzImage paths since OpenVMM does not have a registered Linux
58    // bootloader ID.
59    const LOADER_TYPE_UNREGISTERED: u8 = 0xff;
60
61    // Start with the bzImage setup header if available, otherwise build
62    // a minimal default header.
63    let mut hdr = match bzimage_header {
64        Some(orig) => *orig,
65        None => defs::setup_header {
66            boot_flag: 0xaa55.into(),
67            header: 0x53726448.into(),
68            kernel_alignment: 0x100000.into(),
69            ..FromZeros::new_zeroed()
70        },
71    };
72
73    // Set bootloader-owned fields regardless of kernel format.
74    hdr.type_of_loader = LOADER_TYPE_UNREGISTERED;
75    hdr.cmd_line_ptr = cmdline_config.address.try_into().expect("must fit in u32");
76    hdr.cmdline_size = (cmdline_config.cmdline.as_bytes().len() as u64)
77        .try_into()
78        .expect("must fit in u32");
79    hdr.ramdisk_image = initrd_base.into();
80    hdr.ramdisk_size = initrd_size.into();
81
82    let mut p = defs::boot_params {
83        hdr,
84        ..FromZeros::new_zeroed()
85    };
86
87    let mut ram = mem_layout.ram().iter().cloned();
88    let range = ram.next().expect("at least one ram range");
89    assert_eq!(range.range.start(), 0);
90    assert!(range.range.end() >= 0x100000);
91    // TODO: support better e820 building, for now acpi_base must be 0xe0000
92    assert_eq!(acpi_base, 0xe0000);
93    p.e820_map[0] = defs::e820entry {
94        addr: 0.into(),
95        size: 0xe0000.into(),
96        typ: defs::E820_RAM.into(),
97    };
98    let aligned_acpi_len = (acpi_len + 0xfff) & !0xfff;
99    p.e820_map[1] = defs::e820entry {
100        addr: 0xe0000.into(),
101        size: (aligned_acpi_len as u64).into(),
102        typ: defs::E820_ACPI.into(),
103    };
104    p.e820_map[2] = defs::e820entry {
105        addr: (0xe0000 + aligned_acpi_len as u64).into(),
106        size: (range.range.end() - 0xe0000 - aligned_acpi_len as u64).into(),
107        typ: defs::E820_RAM.into(),
108    };
109    let mut n = 3;
110    for range in ram {
111        p.e820_map[n] = defs::e820entry {
112            addr: range.range.start().into(),
113            size: range.range.len().into(),
114            typ: defs::E820_RAM.into(),
115        };
116        n += 1;
117    }
118    p.e820_entries = n as u8;
119
120    p
121}
122
123#[derive(Debug, Error)]
124pub enum FlatLoaderError {
125    #[error("unsupported ELF File byte order")]
126    BigEndianElfOnLittle,
127    #[error("error reading kernel data structure")]
128    BadImageMagic,
129    #[error("big-endian kernel image is not supported")]
130    BigEndianKernelImage,
131    #[error("only images with 4K pages are supported")]
132    FourKibPageImageIsRequired,
133    #[error("the kernel is required to run in the low memory; not supported")]
134    LowMemoryKernel,
135    #[error("failed to read kernel image")]
136    ReadKernelImage,
137    #[error("failed to seek to file offset as pointed by the ELF program header")]
138    SeekKernelStart,
139    #[error("failed to seek to offset of kernel image")]
140    SeekKernelImage,
141}
142
143#[derive(Debug, Error)]
144pub enum Error {
145    #[error("elf loader error")]
146    ElfLoader(#[source] crate::elf::Error),
147    #[error("bzImage parse error")]
148    BzImage(#[source] crate::bzimage::Error),
149    #[error("flat loader error")]
150    FlatLoader(#[source] FlatLoaderError),
151    #[error("Address is not page aligned")]
152    UnalignedAddress(u64),
153    #[error("importer error")]
154    Importer(#[source] anyhow::Error),
155    #[error("failed to import initrd")]
156    ImportInitrd(#[source] ImportFileRegionError),
157    #[error("failed to import bzImage payload")]
158    ImportBzImage(#[source] ImportFileRegionError),
159    #[error("PageTableBuilder: {0}")]
160    PageTableBuilder(#[from] page_table::Error),
161}
162
163pub struct AcpiConfig<'a> {
164    pub rdsp_address: u64,
165    pub rdsp: &'a [u8],
166    pub tables_address: u64,
167    pub tables: &'a [u8],
168}
169
170pub struct ZeroPageConfig<'a> {
171    /// The address to load the zero page at.
172    pub address: u64,
173    /// The memory layout used to build the e820 map.
174    pub mem_layout: &'a MemoryLayout,
175    /// The base address acpi tables are loaded at.
176    pub acpi_base_address: u64,
177    /// The overall size of acpi tables.
178    pub acpi_len: usize,
179}
180
181pub struct CommandLineConfig<'a> {
182    pub address: u64,
183    pub cmdline: &'a CString,
184}
185
186pub struct RegisterConfig {
187    pub gdt_address: u64,
188    pub page_table_address: u64,
189}
190
191#[derive(Debug, PartialEq, Eq, Clone, Copy)]
192pub enum InitrdAddressType {
193    /// Load the initrd after the kernel at the next 2MB aligned address.
194    AfterKernel,
195    /// Load the initrd at the specified address.
196    Address(u64),
197}
198
199pub struct InitrdConfig<'a> {
200    pub initrd_address: InitrdAddressType,
201    pub initrd: &'a mut dyn ReadSeek,
202    pub size: u64,
203}
204
205/// Information returned about the kernel loaded.
206#[derive(Debug, Default)]
207pub struct KernelInfo {
208    /// The base gpa the kernel was loaded at.
209    pub gpa: u64,
210    /// The size in bytes of the region the kernel was loaded at.
211    pub size: u64,
212    /// The gpa of the entrypoint of the kernel.
213    pub entrypoint: u64,
214}
215
216/// Information returned about the initrd loaded.
217#[derive(Debug, Default)]
218pub struct InitrdInfo {
219    /// The gpa the initrd was loaded at.
220    pub gpa: u64,
221    /// The size in bytes of the initrd loaded. Note that the region imported is aligned up to page size.
222    pub size: u64,
223}
224
225/// Information returned about where certain parts were loaded.
226#[derive(Debug, Default)]
227pub struct LoadInfo {
228    /// The information about the kernel loaded.
229    pub kernel: KernelInfo,
230    /// The information about the initrd loaded.
231    pub initrd: Option<InitrdInfo>,
232    /// The information about the device tree blob loaded.
233    pub dtb: Option<std::ops::Range<u64>>,
234    /// If a bzImage was loaded, the original setup header from the image.
235    /// This must be placed into the zero page so the kernel's startup code
236    /// can read its own configuration.
237    pub bzimage_setup_header: Option<defs::setup_header>,
238}
239
240/// Check if an address is aligned to a page.
241fn check_address_alignment(address: u64) -> Result<(), Error> {
242    if !address.is_multiple_of(HV_PAGE_SIZE) {
243        Err(Error::UnalignedAddress(address))
244    } else {
245        Ok(())
246    }
247}
248
249/// Import initrd
250fn import_initrd<R: GuestArch>(
251    initrd: Option<InitrdConfig<'_>>,
252    next_addr: u64,
253    importer: &mut dyn ImageLoad<R>,
254) -> Result<Option<InitrdInfo>, Error> {
255    let initrd_info = match initrd {
256        Some(cfg) => {
257            let initrd_address = match cfg.initrd_address {
258                InitrdAddressType::AfterKernel => align_up_to_large_page_size(next_addr),
259                InitrdAddressType::Address(addr) => addr,
260            };
261
262            tracing::trace!(initrd_address, "loading initrd");
263            check_address_alignment(initrd_address)?;
264
265            ChunkBuf::new()
266                .import_file_region(
267                    importer,
268                    ImportFileRegion {
269                        file: cfg.initrd,
270                        file_offset: 0,
271                        file_length: cfg.size,
272                        gpa: initrd_address,
273                        memory_length: cfg.size,
274                        acceptance: BootPageAcceptance::Exclusive,
275                        tag: "linux-initrd",
276                    },
277                )
278                .map_err(Error::ImportInitrd)?;
279
280            Some(InitrdInfo {
281                gpa: initrd_address,
282                size: cfg.size,
283            })
284        }
285        None => None,
286    };
287    Ok(initrd_info)
288}
289
290/// Load only a Linux kernel and optional initrd to VTL0.
291/// This does not setup register state or any other config information.
292///
293/// The kernel image may be either an uncompressed ELF (`vmlinux`) or a
294/// compressed bzImage. If a bzImage is detected, the bzImage payload is
295/// loaded directly into guest memory and the kernel's own decompressor
296/// runs at boot time.
297///
298/// # Arguments
299///
300/// * `importer` - The importer to use.
301/// * `kernel_image` - Kernel image (uncompressed ELF or bzImage).
302/// * `kernel_minimum_start_address` - The minimum address the kernel can load at.
303///   It cannot contain an entrypoint or program headers that refer to memory below this address.
304/// * `initrd` - The initrd config, optional.
305pub fn load_kernel_and_initrd_x64<F>(
306    importer: &mut dyn ImageLoad<X86Register>,
307    kernel_image: &mut F,
308    kernel_minimum_start_address: u64,
309    initrd: Option<InitrdConfig<'_>>,
310) -> Result<LoadInfo, Error>
311where
312    F: Read + Seek,
313{
314    tracing::trace!(kernel_minimum_start_address, "loading x86_64 kernel");
315
316    if crate::bzimage::is_bzimage(kernel_image).map_err(Error::BzImage)? {
317        tracing::info!("detected bzImage format, loading via Linux boot protocol");
318        return load_bzimage(importer, kernel_image, kernel_minimum_start_address, initrd);
319    }
320
321    let elf_load_info = load_static_elf(
322        importer,
323        kernel_image,
324        kernel_minimum_start_address,
325        0,
326        false,
327        BootPageAcceptance::Exclusive,
328        "linux-kernel",
329    )
330    .map_err(Error::ElfLoader)?;
331
332    let crate::elf::LoadInfo {
333        minimum_address_used: min_addr,
334        next_available_address: next_addr,
335        entrypoint,
336    } = elf_load_info;
337    tracing::trace!(min_addr, next_addr, entrypoint, "loaded kernel");
338
339    let initrd_info = import_initrd(initrd, next_addr, importer)?;
340
341    Ok(LoadInfo {
342        kernel: KernelInfo {
343            gpa: min_addr,
344            size: next_addr - min_addr,
345            entrypoint,
346        },
347        initrd: initrd_info,
348        dtb: None,
349        bzimage_setup_header: None,
350    })
351}
352
353/// Load a bzImage by placing its payload directly into guest memory at the
354/// load address and following the Linux boot protocol. The kernel's built-in
355/// decompressor handles the rest at boot time.
356fn load_bzimage(
357    importer: &mut dyn ImageLoad<X86Register>,
358    kernel_image: &mut (impl Read + Seek),
359    kernel_start_address: u64,
360    initrd: Option<InitrdConfig<'_>>,
361) -> Result<LoadInfo, Error> {
362    let info = crate::bzimage::parse_bzimage(kernel_image).map_err(Error::BzImage)?;
363
364    check_address_alignment(kernel_start_address)?;
365
366    let payload_offset = (info.setup_sects as u64 + 1) * 512;
367    let payload_len = info.protected_mode_size;
368    let payload_memory_len = align_up_to_page_size(payload_len);
369    let entrypoint = kernel_start_address + info.entry_offset;
370
371    tracing::info!(
372        kernel_start_address = format_args!("{:#x}", kernel_start_address),
373        payload_offset,
374        payload_len,
375        entrypoint = format_args!("{:#x}", entrypoint),
376        "loading bzImage payload into guest memory"
377    );
378
379    ChunkBuf::new()
380        .import_file_region(
381            importer,
382            ImportFileRegion {
383                file: kernel_image,
384                file_offset: payload_offset,
385                file_length: payload_len,
386                gpa: kernel_start_address,
387                memory_length: payload_memory_len,
388                acceptance: BootPageAcceptance::Exclusive,
389                tag: "linux-kernel",
390            },
391        )
392        .map_err(Error::ImportBzImage)?;
393
394    // Place initrd after the kernel's init_size region to avoid being
395    // overwritten during decompression.
396    let next_addr = kernel_start_address + payload_memory_len;
397    let pref_address: u64 = info.setup_header.pref_address.into();
398    let init_end = kernel_start_address
399        .max(pref_address)
400        .saturating_add(info.init_size as u64);
401    let next_addr = next_addr.max(init_end);
402    let initrd_info = import_initrd(initrd, next_addr, importer)?;
403
404    Ok(LoadInfo {
405        kernel: KernelInfo {
406            gpa: kernel_start_address,
407            size: payload_memory_len,
408            entrypoint,
409        },
410        initrd: initrd_info,
411        dtb: None,
412        bzimage_setup_header: Some(info.setup_header),
413    })
414}
415
416/// Load the configuration info and registers for the Linux kernel based on the provided LoadInfo.
417///
418/// # Arguments
419/// * `importer` - The importer to use.
420/// * `load_info` - The kernel load info that contains information on where the kernel and initrd are.
421/// * `command_line` - The kernel command line.
422/// * `zero_page` - The kernel zero page.
423/// * `registers` - X86Register config.
424pub fn load_config(
425    importer: &mut impl ImageLoad<X86Register>,
426    load_info: &LoadInfo,
427    command_line: CommandLineConfig<'_>,
428    zero_page: ZeroPageConfig<'_>,
429    acpi: AcpiConfig<'_>,
430    registers: RegisterConfig,
431) -> Result<(), Error> {
432    tracing::trace!(command_line.address);
433    // Only import the cmdline if it actually contains something.
434    // TODO: This should use the IGVM parameter instead?
435    let raw_cmdline = command_line.cmdline.as_bytes_with_nul();
436    if raw_cmdline.len() > 1 {
437        check_address_alignment(command_line.address)?;
438        let cmdline_size_pages = align_up_to_page_size(raw_cmdline.len() as u64) / HV_PAGE_SIZE;
439        importer
440            .import_pages(
441                command_line.address / HV_PAGE_SIZE,
442                cmdline_size_pages,
443                "linux-commandline",
444                BootPageAcceptance::Exclusive,
445                raw_cmdline,
446            )
447            .map_err(Error::Importer)?;
448    }
449
450    check_address_alignment(registers.gdt_address)?;
451    import_default_gdt(importer, registers.gdt_address / HV_PAGE_SIZE).map_err(Error::Importer)?;
452    check_address_alignment(registers.page_table_address)?;
453    let mut page_table_work_buffer: Vec<PageTable> =
454        vec![PageTable::new_zeroed(); PAGE_TABLE_MAX_COUNT];
455    let mut page_table: Vec<u8> = vec![0; PAGE_TABLE_MAX_BYTES];
456    let page_table_builder = IdentityMapBuilder::new(
457        registers.page_table_address,
458        IdentityMapSize::Size4Gb,
459        page_table_work_buffer.as_mut_slice(),
460        page_table.as_mut_slice(),
461    )?;
462    let page_table = page_table_builder.build();
463    assert!((page_table.len() as u64).is_multiple_of(HV_PAGE_SIZE));
464    importer
465        .import_pages(
466            registers.page_table_address / HV_PAGE_SIZE,
467            page_table.len() as u64 / HV_PAGE_SIZE,
468            "linux-pagetables",
469            BootPageAcceptance::Exclusive,
470            page_table,
471        )
472        .map_err(Error::Importer)?;
473
474    // NOTE: A whole page is given to the RDSP for simplicity.
475    check_address_alignment(acpi.rdsp_address)?;
476    check_address_alignment(acpi.tables_address)?;
477    let acpi_tables_size_pages = align_up_to_page_size(acpi.tables.len() as u64) / HV_PAGE_SIZE;
478    importer
479        .import_pages(
480            acpi.rdsp_address / HV_PAGE_SIZE,
481            1,
482            "linux-rdsp",
483            BootPageAcceptance::Exclusive,
484            acpi.rdsp,
485        )
486        .map_err(Error::Importer)?;
487    importer
488        .import_pages(
489            acpi.tables_address / HV_PAGE_SIZE,
490            acpi_tables_size_pages,
491            "linux-acpi-tables",
492            BootPageAcceptance::Exclusive,
493            acpi.tables,
494        )
495        .map_err(Error::Importer)?;
496
497    check_address_alignment(zero_page.address)?;
498    let boot_params = build_zero_page(
499        zero_page.mem_layout,
500        zero_page.acpi_base_address,
501        zero_page.acpi_len,
502        &command_line,
503        load_info.initrd.as_ref().map(|info| info.gpa).unwrap_or(0) as u32,
504        load_info.initrd.as_ref().map(|info| info.size).unwrap_or(0) as u32,
505        load_info.bzimage_setup_header.as_ref(),
506    );
507    importer
508        .import_pages(
509            zero_page.address / HV_PAGE_SIZE,
510            1,
511            "linux-zeropage",
512            BootPageAcceptance::Exclusive,
513            boot_params.as_bytes(),
514        )
515        .map_err(Error::Importer)?;
516
517    // Set common X64 registers. Segments already set by default gdt.
518    let mut import_reg = |register| {
519        importer
520            .import_vp_register(register)
521            .map_err(Error::Importer)
522    };
523
524    import_reg(X86Register::Cr0(x86defs::X64_CR0_PG | x86defs::X64_CR0_PE))?;
525    import_reg(X86Register::Cr3(registers.page_table_address))?;
526    import_reg(X86Register::Cr4(x86defs::X64_CR4_PAE))?;
527    import_reg(X86Register::Efer(
528        x86defs::X64_EFER_SCE
529            | x86defs::X64_EFER_LME
530            | x86defs::X64_EFER_LMA
531            | x86defs::X64_EFER_NXE,
532    ))?;
533    import_reg(X86Register::Pat(x86defs::X86X_MSR_DEFAULT_PAT))?;
534
535    // Set rip to entry point and rsi to zero page.
536    import_reg(X86Register::Rip(load_info.kernel.entrypoint))?;
537    import_reg(X86Register::Rsi(zero_page.address))?;
538
539    // No firmware will set MTRR values for the BSP.  Replicate what UEFI does here.
540    // (enable MTRRs, default MTRR is uncached, and set lowest 640KB as WB)
541    import_reg(X86Register::MtrrDefType(0xc00))?;
542    import_reg(X86Register::MtrrFix64k00000(0x0606060606060606))?;
543    import_reg(X86Register::MtrrFix16k80000(0x0606060606060606))?;
544
545    Ok(())
546}
547
548/// Load a Linux kernel into VTL0.
549///
550/// The kernel image may be either an uncompressed ELF (`vmlinux`) or a
551/// compressed bzImage. If a bzImage is detected, its protected-mode code
552/// is loaded directly into guest memory and the kernel's built-in
553/// decompressor handles the rest at boot time.
554///
555/// # Arguments
556///
557/// * `importer` - The importer to use.
558/// * `kernel_image` - Kernel image (uncompressed ELF or bzImage).
559/// * `kernel_minimum_start_address` - The minimum address the kernel can load at.
560///   It cannot contain an entrypoint or program headers that refer to memory below this address.
561/// * `initrd` - The initrd config, optional.
562/// * `command_line` - The kernel command line.
563/// * `zero_page` - The kernel zero page.
564/// * `acpi` - The acpi config.
565/// * `registers` - X86Register config.
566pub fn load_x86<F>(
567    importer: &mut impl ImageLoad<X86Register>,
568    kernel_image: &mut F,
569    kernel_minimum_start_address: u64,
570    initrd: Option<InitrdConfig<'_>>,
571    command_line: CommandLineConfig<'_>,
572    zero_page: ZeroPageConfig<'_>,
573    acpi: AcpiConfig<'_>,
574    registers: RegisterConfig,
575) -> Result<LoadInfo, Error>
576where
577    F: Read + Seek,
578{
579    let load_info =
580        load_kernel_and_initrd_x64(importer, kernel_image, kernel_minimum_start_address, initrd)?;
581
582    load_config(
583        importer,
584        &load_info,
585        command_line,
586        zero_page,
587        acpi,
588        registers,
589    )?;
590
591    Ok(load_info)
592}
593
594open_enum::open_enum! {
595    #[derive(IntoBytes, Immutable, KnownLayout, FromBytes)]
596    pub enum Aarch64ImagePageSize: u64 {
597        UNSPECIFIED = 0,
598        PAGE4_K = 1,
599        PAGE16_K = 2,
600        PAGE64_K = 3,
601    }
602
603}
604
605impl Aarch64ImagePageSize {
606    const fn into_bits(self) -> u64 {
607        self.0
608    }
609
610    const fn from_bits(bits: u64) -> Self {
611        Self(bits)
612    }
613}
614
615/// Arm64 flat kernel `Image` flags.
616#[bitfield(u64)]
617struct Aarch64ImageFlags {
618    /// Bit 0:	Kernel endianness.  1 if BE, 0 if LE.
619    #[bits(1)]
620    pub big_endian: bool,
621    /// Bit 1-2:	Kernel Page size.
622    ///           0 - Unspecified.
623    ///           1 - 4K
624    ///           2 - 16K
625    ///           3 - 64K
626    #[bits(2)]
627    pub page_size: Aarch64ImagePageSize,
628    /// Bit 3:	Kernel physical placement
629    ///           0 - 2MB aligned base should be as close as possible
630    ///               to the base of DRAM, since memory below it is not
631    ///               accessible via the linear mapping
632    ///           1 - 2MB aligned base may be anywhere in physical
633    ///               memory
634    #[bits(1)]
635    pub any_start_address: bool,
636    /// Bits 4-63:	Reserved.
637    #[bits(60)]
638    pub _padding: u64,
639}
640
641// Kernel boot protocol is specified in the Linux kernel
642// Documentation/arm64/booting.txt.
643#[derive(Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
644#[repr(C)]
645struct Aarch64ImageHeader {
646    /// Executable code
647    _code0: u32,
648    /// Executable code
649    _code1: u32,
650    /// Image load offset, little endian
651    text_offset: u64,
652    /// Effective Image size, little endian
653    image_size: u64,
654    /// kernel flags, little endian
655    flags: u64,
656    /// reserved
657    _res2: u64,
658    /// reserved
659    _res3: u64,
660    /// reserved
661    _res4: u64,
662    /// Magic number, little endian, "ARM\x64"
663    magic: [u8; 4],
664    /// reserved (used for PE COFF offset)
665    _res5: u32,
666}
667
668const AARCH64_MAGIC_NUMBER: &[u8] = b"ARM\x64";
669
670/// Load only an arm64 the flat Linux kernel `Image` and optional initrd.
671/// This does not setup register state or any other config information.
672///
673/// # Arguments
674///
675/// * `importer` - The importer to use.
676/// * `kernel_image` - Uncompressed ELF image for the kernel.
677/// * `kernel_minimum_start_address` - The minimum address the kernel can load at.
678///   It cannot contain an entrypoint or program headers that refer to memory below this address.
679/// * `initrd` - The initrd config, optional.
680/// * `device_tree_blob` - The device tree blob, optional.
681pub fn load_kernel_and_initrd_arm64<F>(
682    importer: &mut dyn ImageLoad<Aarch64Register>,
683    kernel_image: &mut F,
684    kernel_minimum_start_address: u64,
685    initrd: Option<InitrdConfig<'_>>,
686    device_tree_blob: Option<&[u8]>,
687) -> Result<LoadInfo, Error>
688where
689    F: Read + Seek,
690{
691    tracing::trace!(kernel_minimum_start_address, "loading aarch64 kernel");
692
693    assert_eq!(
694        kernel_minimum_start_address & ((1 << 21) - 1),
695        0,
696        "Start offset must be aligned on the 2MiB boundary"
697    );
698
699    kernel_image
700        .seek(std::io::SeekFrom::Start(0))
701        .map_err(|_| Error::FlatLoader(FlatLoaderError::SeekKernelStart))?;
702
703    let mut header = Aarch64ImageHeader::new_zeroed();
704    kernel_image
705        .read_exact(header.as_mut_bytes())
706        .map_err(|_| Error::FlatLoader(FlatLoaderError::ReadKernelImage))?;
707
708    tracing::debug!("aarch64 kernel header {header:x?}");
709
710    if header.magic != AARCH64_MAGIC_NUMBER {
711        return Err(Error::FlatLoader(FlatLoaderError::BadImageMagic));
712    }
713
714    let flags = Aarch64ImageFlags::from(header.flags);
715    if flags.big_endian() {
716        return Err(Error::FlatLoader(FlatLoaderError::BigEndianKernelImage));
717    }
718    if flags.page_size() != Aarch64ImagePageSize::PAGE4_K {
719        return Err(Error::FlatLoader(
720            FlatLoaderError::FourKibPageImageIsRequired,
721        ));
722    }
723    if !flags.any_start_address() {
724        return Err(Error::FlatLoader(FlatLoaderError::LowMemoryKernel));
725    }
726
727    // The `Image` must be placed `text_offset` bytes from a 2MB aligned base
728    // address anywhere in usable system RAM and called there.
729
730    kernel_image
731        .seek(std::io::SeekFrom::Start(0))
732        .map_err(|_| Error::FlatLoader(FlatLoaderError::SeekKernelStart))?;
733
734    let mut image = Vec::new();
735    kernel_image
736        .read_to_end(&mut image)
737        .map_err(|_| Error::FlatLoader(FlatLoaderError::ReadKernelImage))?;
738
739    let kernel_load_offset = (kernel_minimum_start_address + header.text_offset) as usize;
740    let kernel_size = if header.image_size != 0 {
741        header.image_size
742    } else {
743        image.len() as u64
744    };
745
746    let kernel_size = align_up_to_page_size(kernel_size);
747    importer
748        .import_pages(
749            kernel_load_offset as u64 / HV_PAGE_SIZE,
750            kernel_size / HV_PAGE_SIZE,
751            "linux-kernel",
752            BootPageAcceptance::Exclusive,
753            &image,
754        )
755        .map_err(Error::Importer)?;
756
757    let next_addr = kernel_load_offset as u64 + kernel_size;
758
759    let (next_addr, dtb) = if let Some(device_tree_blob) = device_tree_blob {
760        let dtb_addr = align_up_to_page_size(next_addr);
761        tracing::trace!(dtb_addr, "loading device tree blob at {dtb_addr:x?}");
762
763        check_address_alignment(dtb_addr)?;
764        let dtb_size_pages = align_up_to_page_size(device_tree_blob.len() as u64) / HV_PAGE_SIZE;
765
766        importer
767            .import_pages(
768                dtb_addr / HV_PAGE_SIZE,
769                dtb_size_pages,
770                "linux-device-tree",
771                BootPageAcceptance::Exclusive,
772                device_tree_blob,
773            )
774            .map_err(Error::Importer)?;
775
776        (
777            dtb_addr + device_tree_blob.len() as u64,
778            Some(dtb_addr..dtb_addr + device_tree_blob.len() as u64),
779        )
780    } else {
781        (next_addr, None)
782    };
783
784    let initrd_info = import_initrd(initrd, next_addr, importer)?;
785
786    Ok(LoadInfo {
787        kernel: KernelInfo {
788            gpa: kernel_minimum_start_address,
789            size: kernel_size,
790            entrypoint: kernel_load_offset as u64,
791        },
792        initrd: initrd_info,
793        dtb,
794        bzimage_setup_header: None,
795    })
796}
797
798/// Load the configuration info and registers for the Linux kernel based on the provided LoadInfo.
799/// Parameters:
800/// * `importer` - The importer to use.
801/// * `load_info` - The kernel load info that contains information on where the kernel and initrd are.
802/// * `vtl` - The target VTL.
803pub fn set_direct_boot_registers_arm64(
804    importer: &mut impl ImageLoad<Aarch64Register>,
805    load_info: &LoadInfo,
806) -> Result<(), Error> {
807    let mut import_reg = |register| {
808        importer
809            .import_vp_register(register)
810            .map_err(Error::Importer)
811    };
812
813    import_reg(Aarch64Register::Pc(load_info.kernel.entrypoint))?;
814    import_reg(Aarch64Register::Cpsr(
815        Cpsr64::new()
816            .with_sp(true)
817            .with_el(1)
818            .with_f(true)
819            .with_i(true)
820            .with_a(true)
821            .with_d(true)
822            .into(),
823    ))?;
824    import_reg(Aarch64Register::SctlrEl1(
825        SctlrEl1::new()
826            // MMU is disabled for EL1&0 stage 1 address translation.
827            // The family of the `at` instructions and the `PAR_EL1` register are
828            // useful for debugging MMU issues when it's on.
829            .with_m(false)
830            // Stage 1 Cacheability control, for data accesses.
831            .with_c(true)
832            // Stage 1 Cacheability control, for code.
833            .with_i(true)
834            // Reserved flags, must be set
835            .with_eos(true)
836            .with_tscxt(true)
837            .with_eis(true)
838            .with_span(true)
839            .with_n_tlsmd(true)
840            .with_lsmaoe(true)
841            .into(),
842    ))?;
843    import_reg(Aarch64Register::TcrEl1(
844        TranslationControlEl1::new()
845            .with_t0sz(0x11)
846            .with_irgn0(1)
847            .with_orgn0(1)
848            .with_sh0(3)
849            .with_tg0(TranslationGranule0::TG_4KB)
850            // Disable TTBR0_EL1 walks (i.e. the lower half).
851            .with_epd0(1)
852            // Disable TTBR1_EL1 walks (i.e. the upper half).
853            .with_epd1(1)
854            // Due to erratum #822227, need to set a valid TG1 regardless of EPD1.
855            .with_tg1(TranslationGranule1::TG_4KB)
856            .with_ips(IntermPhysAddrSize::IPA_48_BITS_256_TB)
857            .into(),
858    ))?;
859    import_reg(Aarch64Register::Ttbr0El1(TranslationBaseEl1::new().into()))?;
860    import_reg(Aarch64Register::Ttbr1El1(TranslationBaseEl1::new().into()))?;
861    import_reg(Aarch64Register::VbarEl1(0))?;
862
863    if let Some(dtb) = &load_info.dtb {
864        import_reg(Aarch64Register::X0(dtb.start))?;
865    }
866
867    Ok(())
868}