loader/
pcat.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! PCAT specific loader definitions and implementation.
5
6use crate::importer::BootPageAcceptance;
7use crate::importer::ImageLoad;
8use crate::importer::SegmentRegister;
9use crate::importer::X86Register;
10use hvdef::HV_PAGE_SIZE;
11use thiserror::Error;
12
13const STARTUP_IMAGE_TOP_PAGES: u64 = 0x100000 / HV_PAGE_SIZE; // 1MB
14const STARTUP_IMAGE_MAX_PAGES: u64 = 0x20000 / HV_PAGE_SIZE; // 128KB
15const IMAGE_SIZE: u64 = 0x40000; // 256KB
16const FOUR_GB: u64 = 0x100000000;
17
18#[derive(Debug, Error)]
19pub enum Error {
20    #[error("failed to read firmware")]
21    Firmware(#[source] std::io::Error),
22    #[error("Firmware size invalid")]
23    InvalidImageSize,
24    #[error("Max ram below 4GB invalid")]
25    InvalidMaxRamBelow4gb,
26    #[error("Importer error")]
27    Importer(#[source] anyhow::Error),
28}
29
30/// Load a PCAT BIOS image with the provided config type.
31///
32/// In cases where the image is preloaded into RAM or is being provided by a
33/// ROM, `image` can be set to None.
34pub fn load(
35    importer: &mut dyn ImageLoad<X86Register>,
36    image: Option<&[u8]>,
37    max_ram_below_4gb: Option<u64>,
38) -> Result<(), Error> {
39    if let Some(image) = image {
40        if (image.len() as u64) != IMAGE_SIZE {
41            return Err(Error::InvalidImageSize);
42        }
43
44        let Some(max_ram_below_4gb) = max_ram_below_4gb else {
45            return Err(Error::InvalidMaxRamBelow4gb);
46        };
47
48        if (max_ram_below_4gb % HV_PAGE_SIZE != 0)
49            || (max_ram_below_4gb == 0)
50            || (max_ram_below_4gb > FOUR_GB)
51        {
52            return Err(Error::InvalidMaxRamBelow4gb);
53        }
54
55        let image_page_count = image.len() as u64 / HV_PAGE_SIZE;
56        let max_page_below_4gb = max_ram_below_4gb / HV_PAGE_SIZE;
57        let page_base = max_page_below_4gb - image_page_count;
58        tracing::trace!(
59            image_page_count,
60            max_page_below_4gb,
61            page_base,
62            "pcat pre-import",
63        );
64
65        importer
66            .import_pages(
67                page_base,
68                image_page_count,
69                "pcat-image",
70                BootPageAcceptance::Exclusive,
71                image,
72            )
73            .map_err(Error::Importer)?;
74
75        tracing::trace!("max below 4gb bios import complete");
76
77        let image_page_count = image_page_count.min(STARTUP_IMAGE_MAX_PAGES);
78        let page_base = STARTUP_IMAGE_TOP_PAGES - image_page_count;
79        let start = image.len() - ((image_page_count * HV_PAGE_SIZE) as usize);
80
81        tracing::trace!(image_page_count, page_base, "pcat import",);
82
83        importer
84            .import_pages(
85                page_base,
86                image_page_count,
87                "pcat-top-pages",
88                BootPageAcceptance::Exclusive,
89                &image[start..],
90            )
91            .map_err(Error::Importer)?;
92
93        tracing::trace!("below 1mb bios import complete");
94    }
95
96    // Enable MTRRs, default MTRR is uncached, and set lowest 640KB and highest 128KB as WB
97    let mut import_reg = |register| {
98        importer
99            .import_vp_register(register)
100            .map_err(Error::Importer)
101    };
102    import_reg(X86Register::MtrrDefType(0xc00))?;
103    import_reg(X86Register::MtrrFix64k00000(0x0606060606060606))?;
104    import_reg(X86Register::MtrrFix16k80000(0x0606060606060606))?;
105    import_reg(X86Register::MtrrFix4kE0000(0x0606060606060606))?;
106    import_reg(X86Register::MtrrFix4kE8000(0x0606060606060606))?;
107    import_reg(X86Register::MtrrFix4kF0000(0x0606060606060606))?;
108    import_reg(X86Register::MtrrFix4kF8000(0x0606060606060606))?;
109
110    // The PCAT bios expects the reset vector to be mapped at the top of 1MB, not the architectural reset
111    // value at the top of 4GB, so set CS to the same values HyperV does to ensure things work across all
112    // virtualization stacks.
113    import_reg(X86Register::Cs(SegmentRegister {
114        base: 0xF0000,
115        limit: 0xFFFF,
116        selector: 0xF000,
117        attributes: 0x9B,
118    }))?;
119
120    Ok(())
121}