From 3f4f594ee5965d1a4acefa6128752e3321913557 Mon Sep 17 00:00:00 2001 From: Jason Couture Date: Mon, 9 Jan 2023 12:02:39 -0500 Subject: [PATCH 01/17] Revert "Merge pull request #304 from AlexJMohr/cargo-features" This reverts commit 5e42ad0c8b2db1e11bc6b25b007e5372b85f6fea, reversing changes made to 94c71d5d1534c559dac1d1ce96ef8c9a4a944521. --- .github/workflows/ci.yml | 8 --- Cargo.toml | 9 +-- build.rs | 83 ++++++++++++------------ src/bios/mod.rs | 67 -------------------- src/{uefi => }/gpt.rs | 0 src/lib.rs | 134 ++++++++++++++++++++++++++++++++++++--- src/{bios => }/mbr.rs | 0 src/{uefi => }/pxe.rs | 0 src/uefi/mod.rs | 69 -------------------- tests/runner/Cargo.toml | 8 +-- tests/runner/src/lib.rs | 42 +++++------- 11 files changed, 185 insertions(+), 235 deletions(-) delete mode 100644 src/bios/mod.rs rename src/{uefi => }/gpt.rs (100%) rename src/{bios => }/mbr.rs (100%) rename src/{uefi => }/pxe.rs (100%) delete mode 100644 src/uefi/mod.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index db938864..ec952c33 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,14 +62,6 @@ jobs: - name: Run integration tests run: cargo test - # test feature gates (only on one OS is enough) - - name: Test with only UEFI feature - if: runner.os == 'Linux' - run: cargo test --no-default-features --features uefi - - name: Test with only BIOS feature - if: runner.os == 'Linux' - run: cargo test --no-default-features --features bios - fmt: name: Check Formatting runs-on: ubuntu-latest diff --git a/Cargo.toml b/Cargo.toml index e1364c64..4bea9b23 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,17 +37,12 @@ bootloader_api = { version = "0.11.0", path = "api" } bootloader-x86_64-common = { version = "0.11.0", path = "common" } bootloader-x86_64-bios-common = { version = "0.11.0", path = "bios/common" } -[features] -default = ["bios", "uefi"] -bios = ["dep:mbrman", "bootloader_test_runner/bios"] -uefi = ["dep:gpt", "bootloader_test_runner/uefi"] - [dependencies] anyhow = "1.0.32" fatfs = "0.3.4" +gpt = "3.0.0" +mbrman = "0.5.1" tempfile = "3.3.0" -mbrman = { version = "0.5.1", optional = true } -gpt = { version = "3.0.0", optional = true } [dev-dependencies] bootloader_test_runner = { path = "tests/runner" } diff --git a/build.rs b/build.rs index e752d28f..7f04dddf 100644 --- a/build.rs +++ b/build.rs @@ -3,49 +3,47 @@ use std::{ process::Command, }; -const BOOTLOADER_VERSION: &str = env!("CARGO_PKG_VERSION"); +const BOOTLOADER_X86_64_UEFI_VERSION: &str = env!("CARGO_PKG_VERSION"); + +const BOOTLOADER_X86_64_BIOS_BOOT_SECTOR_VERSION: &str = env!("CARGO_PKG_VERSION"); +const BOOTLOADER_X86_64_BIOS_STAGE_2_VERSION: &str = env!("CARGO_PKG_VERSION"); +const BOOTLOADER_X86_64_BIOS_STAGE_3_VERSION: &str = env!("CARGO_PKG_VERSION"); +const BOOTLOADER_X86_64_BIOS_STAGE_4_VERSION: &str = env!("CARGO_PKG_VERSION"); fn main() { let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); - #[cfg(feature = "uefi")] - { - let uefi_path = build_uefi_bootloader(&out_dir); - println!( - "cargo:rustc-env=UEFI_BOOTLOADER_PATH={}", - uefi_path.display() - ); - } + let uefi_path = build_uefi_bootloader(&out_dir); + println!( + "cargo:rustc-env=UEFI_BOOTLOADER_PATH={}", + uefi_path.display() + ); - #[cfg(feature = "bios")] - { - let bios_boot_sector_path = build_bios_boot_sector(&out_dir); - println!( - "cargo:rustc-env=BIOS_BOOT_SECTOR_PATH={}", - bios_boot_sector_path.display() - ); - let bios_stage_2_path = build_bios_stage_2(&out_dir); - println!( - "cargo:rustc-env=BIOS_STAGE_2_PATH={}", - bios_stage_2_path.display() - ); + let bios_boot_sector_path = build_bios_boot_sector(&out_dir); + println!( + "cargo:rustc-env=BIOS_BOOT_SECTOR_PATH={}", + bios_boot_sector_path.display() + ); + let bios_stage_2_path = build_bios_stage_2(&out_dir); + println!( + "cargo:rustc-env=BIOS_STAGE_2_PATH={}", + bios_stage_2_path.display() + ); - let bios_stage_3_path = build_bios_stage_3(&out_dir); - println!( - "cargo:rustc-env=BIOS_STAGE_3_PATH={}", - bios_stage_3_path.display() - ); + let bios_stage_3_path = build_bios_stage_3(&out_dir); + println!( + "cargo:rustc-env=BIOS_STAGE_3_PATH={}", + bios_stage_3_path.display() + ); - let bios_stage_4_path = build_bios_stage_4(&out_dir); - println!( - "cargo:rustc-env=BIOS_STAGE_4_PATH={}", - bios_stage_4_path.display() - ); - } + let bios_stage_4_path = build_bios_stage_4(&out_dir); + println!( + "cargo:rustc-env=BIOS_STAGE_4_PATH={}", + bios_stage_4_path.display() + ); } #[cfg(not(docsrs_dummy_build))] -#[cfg(feature = "uefi")] fn build_uefi_bootloader(out_dir: &Path) -> PathBuf { let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()); let mut cmd = Command::new(cargo); @@ -55,7 +53,7 @@ fn build_uefi_bootloader(out_dir: &Path) -> PathBuf { cmd.arg("--path").arg("uefi"); println!("cargo:rerun-if-changed=uefi"); } else { - cmd.arg("--version").arg(BOOTLOADER_VERSION); + cmd.arg("--version").arg(BOOTLOADER_X86_64_UEFI_VERSION); } cmd.arg("--locked"); cmd.arg("--target").arg("x86_64-unknown-uefi"); @@ -80,7 +78,6 @@ fn build_uefi_bootloader(out_dir: &Path) -> PathBuf { } #[cfg(not(docsrs_dummy_build))] -#[cfg(feature = "bios")] fn build_bios_boot_sector(out_dir: &Path) -> PathBuf { let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()); let mut cmd = Command::new(cargo); @@ -93,7 +90,8 @@ fn build_bios_boot_sector(out_dir: &Path) -> PathBuf { cmd.arg("--path").arg(&local_path); println!("cargo:rerun-if-changed={}", local_path.display()); } else { - cmd.arg("--version").arg(BOOTLOADER_VERSION); + cmd.arg("--version") + .arg(BOOTLOADER_X86_64_BIOS_BOOT_SECTOR_VERSION); } cmd.arg("--locked"); cmd.arg("--target").arg("i386-code16-boot-sector.json"); @@ -123,7 +121,6 @@ fn build_bios_boot_sector(out_dir: &Path) -> PathBuf { } #[cfg(not(docsrs_dummy_build))] -#[cfg(feature = "bios")] fn build_bios_stage_2(out_dir: &Path) -> PathBuf { let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()); let mut cmd = Command::new(cargo); @@ -136,7 +133,8 @@ fn build_bios_stage_2(out_dir: &Path) -> PathBuf { cmd.arg("--path").arg(&local_path); println!("cargo:rerun-if-changed={}", local_path.display()); } else { - cmd.arg("--version").arg(BOOTLOADER_VERSION); + cmd.arg("--version") + .arg(BOOTLOADER_X86_64_BIOS_STAGE_2_VERSION); } cmd.arg("--locked"); cmd.arg("--target").arg("i386-code16-stage-2.json"); @@ -164,7 +162,6 @@ fn build_bios_stage_2(out_dir: &Path) -> PathBuf { } #[cfg(not(docsrs_dummy_build))] -#[cfg(feature = "bios")] fn build_bios_stage_3(out_dir: &Path) -> PathBuf { let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()); let mut cmd = Command::new(cargo); @@ -177,7 +174,8 @@ fn build_bios_stage_3(out_dir: &Path) -> PathBuf { cmd.arg("--path").arg(&local_path); println!("cargo:rerun-if-changed={}", local_path.display()); } else { - cmd.arg("--version").arg(BOOTLOADER_VERSION); + cmd.arg("--version") + .arg(BOOTLOADER_X86_64_BIOS_STAGE_3_VERSION); } cmd.arg("--locked"); cmd.arg("--target").arg("i686-stage-3.json"); @@ -205,7 +203,6 @@ fn build_bios_stage_3(out_dir: &Path) -> PathBuf { } #[cfg(not(docsrs_dummy_build))] -#[cfg(feature = "bios")] fn build_bios_stage_4(out_dir: &Path) -> PathBuf { let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()); let mut cmd = Command::new(cargo); @@ -218,7 +215,8 @@ fn build_bios_stage_4(out_dir: &Path) -> PathBuf { cmd.arg("--path").arg(&local_path); println!("cargo:rerun-if-changed={}", local_path.display()); } else { - cmd.arg("--version").arg(BOOTLOADER_VERSION); + cmd.arg("--version") + .arg(BOOTLOADER_X86_64_BIOS_STAGE_4_VERSION); } cmd.arg("--locked"); cmd.arg("--target").arg("x86_64-stage-4.json"); @@ -246,7 +244,6 @@ fn build_bios_stage_4(out_dir: &Path) -> PathBuf { convert_elf_to_bin(elf_path) } -#[cfg(feature = "bios")] fn convert_elf_to_bin(elf_path: PathBuf) -> PathBuf { let flat_binary_path = elf_path.with_extension("bin"); diff --git a/src/bios/mod.rs b/src/bios/mod.rs deleted file mode 100644 index 03952152..00000000 --- a/src/bios/mod.rs +++ /dev/null @@ -1,67 +0,0 @@ -use crate::fat; -use anyhow::Context; -use std::{ - collections::BTreeMap, - path::{Path, PathBuf}, -}; -use tempfile::NamedTempFile; - -mod mbr; - -const BIOS_STAGE_3: &str = "boot-stage-3"; -const BIOS_STAGE_4: &str = "boot-stage-4"; - -/// Create disk images for booting on legacy BIOS systems. -pub struct BiosBoot { - kernel: PathBuf, -} - -impl BiosBoot { - /// Start creating a disk image for the given bootloader ELF executable. - pub fn new(kernel_path: &Path) -> Self { - Self { - kernel: kernel_path.to_owned(), - } - } - - /// Create a bootable UEFI disk image at the given path. - pub fn create_disk_image(&self, out_path: &Path) -> anyhow::Result<()> { - let bootsector_path = Path::new(env!("BIOS_BOOT_SECTOR_PATH")); - let stage_2_path = Path::new(env!("BIOS_STAGE_2_PATH")); - - let fat_partition = self - .create_fat_partition() - .context("failed to create FAT partition")?; - - mbr::create_mbr_disk( - bootsector_path, - stage_2_path, - fat_partition.path(), - out_path, - ) - .context("failed to create BIOS MBR disk image")?; - - fat_partition - .close() - .context("failed to delete FAT partition after disk image creation")?; - - Ok(()) - } - - /// Creates an BIOS-bootable FAT partition with the kernel. - fn create_fat_partition(&self) -> anyhow::Result { - let stage_3_path = Path::new(env!("BIOS_STAGE_3_PATH")); - let stage_4_path = Path::new(env!("BIOS_STAGE_4_PATH")); - - let mut files = BTreeMap::new(); - files.insert(crate::KERNEL_FILE_NAME, self.kernel.as_path()); - files.insert(BIOS_STAGE_3, stage_3_path); - files.insert(BIOS_STAGE_4, stage_4_path); - - let out_file = NamedTempFile::new().context("failed to create temp file")?; - fat::create_fat_filesystem(files, out_file.path()) - .context("failed to create BIOS FAT filesystem")?; - - Ok(out_file) - } -} diff --git a/src/uefi/gpt.rs b/src/gpt.rs similarity index 100% rename from src/uefi/gpt.rs rename to src/gpt.rs diff --git a/src/lib.rs b/src/lib.rs index cd41805a..11d1ebf0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,16 +4,132 @@ An experimental x86_64 bootloader that works on both BIOS and UEFI systems. #![warn(missing_docs)] -#[cfg(feature = "bios")] -mod bios; +use anyhow::Context; +use std::{ + collections::BTreeMap, + path::{Path, PathBuf}, +}; +use tempfile::NamedTempFile; + mod fat; -#[cfg(feature = "uefi")] -mod uefi; +mod gpt; +mod mbr; +mod pxe; + +const KERNEL_FILE_NAME: &str = "kernel-x86_64"; +const BIOS_STAGE_3: &str = "boot-stage-3"; +const BIOS_STAGE_4: &str = "boot-stage-4"; -#[cfg(feature = "bios")] -pub use bios::BiosBoot; +/// Create disk images for booting on legacy BIOS systems. +pub struct BiosBoot { + kernel: PathBuf, +} -#[cfg(feature = "uefi")] -pub use uefi::UefiBoot; +impl BiosBoot { + /// Start creating a disk image for the given bootloader ELF executable. + pub fn new(kernel_path: &Path) -> Self { + Self { + kernel: kernel_path.to_owned(), + } + } -const KERNEL_FILE_NAME: &str = "kernel-x86_64"; + /// Create a bootable UEFI disk image at the given path. + pub fn create_disk_image(&self, out_path: &Path) -> anyhow::Result<()> { + let bootsector_path = Path::new(env!("BIOS_BOOT_SECTOR_PATH")); + let stage_2_path = Path::new(env!("BIOS_STAGE_2_PATH")); + + let fat_partition = self + .create_fat_partition() + .context("failed to create FAT partition")?; + + mbr::create_mbr_disk( + bootsector_path, + stage_2_path, + fat_partition.path(), + out_path, + ) + .context("failed to create BIOS MBR disk image")?; + + fat_partition + .close() + .context("failed to delete FAT partition after disk image creation")?; + + Ok(()) + } + + /// Creates an BIOS-bootable FAT partition with the kernel. + fn create_fat_partition(&self) -> anyhow::Result { + let stage_3_path = Path::new(env!("BIOS_STAGE_3_PATH")); + let stage_4_path = Path::new(env!("BIOS_STAGE_4_PATH")); + + let mut files = BTreeMap::new(); + files.insert(KERNEL_FILE_NAME, self.kernel.as_path()); + files.insert(BIOS_STAGE_3, stage_3_path); + files.insert(BIOS_STAGE_4, stage_4_path); + + let out_file = NamedTempFile::new().context("failed to create temp file")?; + fat::create_fat_filesystem(files, out_file.path()) + .context("failed to create BIOS FAT filesystem")?; + + Ok(out_file) + } +} + +/// Create disk images for booting on UEFI systems. +pub struct UefiBoot { + kernel: PathBuf, +} + +impl UefiBoot { + /// Start creating a disk image for the given bootloader ELF executable. + pub fn new(kernel_path: &Path) -> Self { + Self { + kernel: kernel_path.to_owned(), + } + } + + /// Create a bootable BIOS disk image at the given path. + pub fn create_disk_image(&self, out_path: &Path) -> anyhow::Result<()> { + let fat_partition = self + .create_fat_partition() + .context("failed to create FAT partition")?; + + gpt::create_gpt_disk(fat_partition.path(), out_path) + .context("failed to create UEFI GPT disk image")?; + + fat_partition + .close() + .context("failed to delete FAT partition after disk image creation")?; + + Ok(()) + } + + /// Prepare a folder for use with booting over UEFI_PXE. + /// + /// This places the bootloader executable under the path "bootloader". The + /// DHCP server should set the filename option to that path, otherwise the + /// bootloader won't be found. + pub fn create_pxe_tftp_folder(&self, out_path: &Path) -> anyhow::Result<()> { + let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH")); + + pxe::create_uefi_tftp_folder(bootloader_path, self.kernel.as_path(), out_path) + .context("failed to create UEFI PXE tftp folder")?; + + Ok(()) + } + + /// Creates an UEFI-bootable FAT partition with the kernel. + fn create_fat_partition(&self) -> anyhow::Result { + let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH")); + + let mut files = BTreeMap::new(); + files.insert("efi/boot/bootx64.efi", bootloader_path); + files.insert(KERNEL_FILE_NAME, self.kernel.as_path()); + + let out_file = NamedTempFile::new().context("failed to create temp file")?; + fat::create_fat_filesystem(files, out_file.path()) + .context("failed to create UEFI FAT filesystem")?; + + Ok(out_file) + } +} diff --git a/src/bios/mbr.rs b/src/mbr.rs similarity index 100% rename from src/bios/mbr.rs rename to src/mbr.rs diff --git a/src/uefi/pxe.rs b/src/pxe.rs similarity index 100% rename from src/uefi/pxe.rs rename to src/pxe.rs diff --git a/src/uefi/mod.rs b/src/uefi/mod.rs deleted file mode 100644 index cfbc8c9f..00000000 --- a/src/uefi/mod.rs +++ /dev/null @@ -1,69 +0,0 @@ -use crate::fat; -use anyhow::Context; -use std::{ - collections::BTreeMap, - path::{Path, PathBuf}, -}; -use tempfile::NamedTempFile; - -mod gpt; -mod pxe; - -/// Create disk images for booting on UEFI systems. -pub struct UefiBoot { - kernel: PathBuf, -} - -impl UefiBoot { - /// Start creating a disk image for the given bootloader ELF executable. - pub fn new(kernel_path: &Path) -> Self { - Self { - kernel: kernel_path.to_owned(), - } - } - - /// Create a bootable BIOS disk image at the given path. - pub fn create_disk_image(&self, out_path: &Path) -> anyhow::Result<()> { - let fat_partition = self - .create_fat_partition() - .context("failed to create FAT partition")?; - - gpt::create_gpt_disk(fat_partition.path(), out_path) - .context("failed to create UEFI GPT disk image")?; - - fat_partition - .close() - .context("failed to delete FAT partition after disk image creation")?; - - Ok(()) - } - - /// Prepare a folder for use with booting over UEFI_PXE. - /// - /// This places the bootloader executable under the path "bootloader". The - /// DHCP server should set the filename option to that path, otherwise the - /// bootloader won't be found. - pub fn create_pxe_tftp_folder(&self, out_path: &Path) -> anyhow::Result<()> { - let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH")); - - pxe::create_uefi_tftp_folder(bootloader_path, self.kernel.as_path(), out_path) - .context("failed to create UEFI PXE tftp folder")?; - - Ok(()) - } - - /// Creates an UEFI-bootable FAT partition with the kernel. - fn create_fat_partition(&self) -> anyhow::Result { - let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH")); - - let mut files = BTreeMap::new(); - files.insert("efi/boot/bootx64.efi", bootloader_path); - files.insert(crate::KERNEL_FILE_NAME, self.kernel.as_path()); - - let out_file = NamedTempFile::new().context("failed to create temp file")?; - fat::create_fat_filesystem(files, out_file.path()) - .context("failed to create UEFI FAT filesystem")?; - - Ok(out_file) - } -} diff --git a/tests/runner/Cargo.toml b/tests/runner/Cargo.toml index 127184a6..51421aa0 100644 --- a/tests/runner/Cargo.toml +++ b/tests/runner/Cargo.toml @@ -6,11 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[features] -bios = ["bootloader/bios"] -uefi = ["bootloader/uefi", "dep:ovmf-prebuilt"] - [dependencies] -bootloader = { path = "../..", default-features = false } +bootloader = { path = "../.." } strip-ansi-escapes = "0.1.1" -ovmf-prebuilt = { version = "0.1.0-alpha.1", optional = true } +ovmf-prebuilt = "0.1.0-alpha.1" diff --git a/tests/runner/src/lib.rs b/tests/runner/src/lib.rs index 3b580173..7f50cd42 100644 --- a/tests/runner/src/lib.rs +++ b/tests/runner/src/lib.rs @@ -13,35 +13,27 @@ const QEMU_ARGS: &[&str] = &[ pub fn run_test_kernel(kernel_binary_path: &str) { let kernel_path = Path::new(kernel_binary_path); - #[cfg(feature = "uefi")] - { - // create a GPT disk image for UEFI booting - let gpt_path = kernel_path.with_extension("gpt"); - let uefi_builder = bootloader::UefiBoot::new(kernel_path); - uefi_builder.create_disk_image(&gpt_path).unwrap(); - - // create a TFTP folder with the kernel executable and UEFI bootloader for - // UEFI PXE booting - let tftp_path = kernel_path.with_extension(".tftp"); - uefi_builder.create_pxe_tftp_folder(&tftp_path).unwrap(); + // create an MBR disk image for legacy BIOS booting + let mbr_path = kernel_path.with_extension("mbr"); + bootloader::BiosBoot::new(kernel_path) + .create_disk_image(&mbr_path) + .unwrap(); - run_test_kernel_on_uefi(&gpt_path); - run_test_kernel_on_uefi_pxe(&tftp_path); - } + // create a GPT disk image for UEFI booting + let gpt_path = kernel_path.with_extension("gpt"); + let uefi_builder = bootloader::UefiBoot::new(kernel_path); + uefi_builder.create_disk_image(&gpt_path).unwrap(); - #[cfg(feature = "bios")] - { - // create an MBR disk image for legacy BIOS booting - let mbr_path = kernel_path.with_extension("mbr"); - bootloader::BiosBoot::new(kernel_path) - .create_disk_image(&mbr_path) - .unwrap(); + // create a TFTP folder with the kernel executable and UEFI bootloader for + // UEFI PXE booting + let tftp_path = kernel_path.with_extension(".tftp"); + uefi_builder.create_pxe_tftp_folder(&tftp_path).unwrap(); - run_test_kernel_on_bios(&mbr_path); - } + run_test_kernel_on_uefi(&gpt_path); + run_test_kernel_on_bios(&mbr_path); + run_test_kernel_on_uefi_pxe(&tftp_path); } -#[cfg(feature = "uefi")] pub fn run_test_kernel_on_uefi(out_gpt_path: &Path) { let mut run_cmd = Command::new("qemu-system-x86_64"); run_cmd @@ -65,7 +57,6 @@ pub fn run_test_kernel_on_uefi(out_gpt_path: &Path) { } } -#[cfg(feature = "bios")] pub fn run_test_kernel_on_bios(out_mbr_path: &Path) { let mut run_cmd = Command::new("qemu-system-x86_64"); run_cmd @@ -88,7 +79,6 @@ pub fn run_test_kernel_on_bios(out_mbr_path: &Path) { } } -#[cfg(feature = "uefi")] pub fn run_test_kernel_on_uefi_pxe(out_tftp_path: &Path) { let mut run_cmd = Command::new("qemu-system-x86_64"); run_cmd.arg("-netdev").arg(format!( From a2d24c763fceff6ccc5aea06f8ebec6366408f91 Mon Sep 17 00:00:00 2001 From: Jason Couture Date: Thu, 29 Dec 2022 19:53:27 -0500 Subject: [PATCH 02/17] Add ramdisk support This adds support for loading a ramdisk/initrd, and an API to add one to the boot image. --- Cargo.lock | 18 +- Cargo.toml | 2 + api/build.rs | 7 +- api/src/config.rs | 22 +- api/src/info.rs | 6 + bios/common/src/lib.rs | 1 + bios/stage-2/src/main.rs | 37 +- bios/stage-4/src/main.rs | 15 +- common/src/lib.rs | 47 ++- src/lib.rs | 46 ++- src/pxe.rs | 11 + tests/default_settings.rs | 21 +- tests/higher_half.rs | 24 +- tests/lto.rs | 2 +- tests/map_phys_mem.rs | 14 +- tests/pie.rs | 8 +- tests/ramdisk.rs | 18 + tests/ramdisk.txt | 1 + tests/runner/src/lib.rs | 22 +- .../default_settings/src/bin/basic_boot.rs | 10 +- tests/test_kernels/ramdisk/Cargo.toml | 13 + .../ramdisk/src/bin/basic_boot.rs | 21 ++ tests/test_kernels/ramdisk/src/bin/ramdisk.rs | 34 ++ tests/test_kernels/ramdisk/src/lib.rs | 29 ++ uefi/Cargo.toml | 2 +- uefi/src/main.rs | 351 +++++++++++------- 26 files changed, 586 insertions(+), 196 deletions(-) create mode 100644 tests/ramdisk.rs create mode 100644 tests/ramdisk.txt create mode 100644 tests/test_kernels/ramdisk/Cargo.toml create mode 100644 tests/test_kernels/ramdisk/src/bin/basic_boot.rs create mode 100644 tests/test_kernels/ramdisk/src/bin/ramdisk.rs create mode 100644 tests/test_kernels/ramdisk/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index a9a70725..415b0b55 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,6 +68,7 @@ dependencies = [ "test_kernel_higher_half", "test_kernel_map_phys_mem", "test_kernel_pie", + "test_kernel_ramdisk", ] [[package]] @@ -597,6 +598,15 @@ dependencies = [ "x86_64", ] +[[package]] +name = "test_kernel_ramdisk" +version = "0.1.0" +dependencies = [ + "bootloader_api", + "uart_16550", + "x86_64", +] + [[package]] name = "thiserror" version = "1.0.30" @@ -649,9 +659,9 @@ dependencies = [ [[package]] name = "uefi" -version = "0.16.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "705535cf386e4b033cc7acdea55ec8710f3dde2f07457218791aac35c83be21f" +checksum = "07b87700863d65dd4841556be3374d8d4f9f8dbb577ad93a39859e70b3b91f35" dependencies = [ "bitflags", "log", @@ -661,9 +671,9 @@ dependencies = [ [[package]] name = "uefi-macros" -version = "0.7.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9917831bc5abb78c2e6a0f4fba2be165105ed53d288718c999e0efbd433bb7" +checksum = "275f054a1d9fd7e43a2ce91cc24298a87b281117dea8afc120ae95faa0e96b94" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 4bea9b23..3046cec0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ members = [ "tests/test_kernels/higher_half", "tests/test_kernels/pie", "tests/test_kernels/lto", + "tests/test_kernels/ramdisk" ] exclude = ["examples/basic", "examples/test_framework"] @@ -50,6 +51,7 @@ test_kernel_default_settings = { path = "tests/test_kernels/default_settings", a test_kernel_higher_half = { path = "tests/test_kernels/higher_half", artifact = "bin", target = "x86_64-unknown-none" } test_kernel_map_phys_mem = { path = "tests/test_kernels/map_phys_mem", artifact = "bin", target = "x86_64-unknown-none" } test_kernel_pie = { path = "tests/test_kernels/pie", artifact = "bin", target = "x86_64-unknown-none" } +test_kernel_ramdisk = { path = "tests/test_kernels/ramdisk", artifact = "bin", target = "x86_64-unknown-none" } [profile.dev] panic = "abort" diff --git a/api/build.rs b/api/build.rs index f2eb9548..99235653 100644 --- a/api/build.rs +++ b/api/build.rs @@ -22,9 +22,10 @@ fn main() { (88, 9), (97, 9), (106, 9), - (115, 1), - (116, 1), - (117, 1), + (115, 9), + (124, 1), + (125, 1), + (126, 1), ]; let mut code = String::new(); diff --git a/api/src/config.rs b/api/src/config.rs index a94ebdaf..7b91a4bd 100644 --- a/api/src/config.rs +++ b/api/src/config.rs @@ -49,7 +49,7 @@ impl BootloaderConfig { 0x3D, ]; #[doc(hidden)] - pub const SERIALIZED_LEN: usize = 118; + pub const SERIALIZED_LEN: usize = 127; /// Creates a new default configuration with the following values: /// @@ -97,6 +97,7 @@ impl BootloaderConfig { aslr, dynamic_range_start, dynamic_range_end, + ramdisk_memory, } = mappings; let FrameBuffer { minimum_framebuffer_height, @@ -145,7 +146,9 @@ impl BootloaderConfig { }, ); - let buf = concat_97_9( + let buf = concat_97_9(buf, ramdisk_memory.serialize()); + + let buf = concat_106_9( buf, match minimum_framebuffer_height { Option::None => [0; 9], @@ -153,7 +156,7 @@ impl BootloaderConfig { }, ); - let buf = concat_106_9( + let buf = concat_115_9( buf, match minimum_framebuffer_width { Option::None => [0; 9], @@ -161,12 +164,12 @@ impl BootloaderConfig { }, ); - let log_level = concat_115_1(buf, (*log_level as u8).to_le_bytes()); + let log_level = concat_124_1(buf, (*log_level as u8).to_le_bytes()); let frame_buffer_logger_status = - concat_116_1(log_level, (*frame_buffer_logger_status as u8).to_le_bytes()); + concat_125_1(log_level, (*frame_buffer_logger_status as u8).to_le_bytes()); - concat_117_1( + concat_126_1( frame_buffer_logger_status, (*serial_logger_status as u8).to_le_bytes(), ) @@ -227,6 +230,7 @@ impl BootloaderConfig { let (&dynamic_range_start, s) = split_array_ref(s); let (&dynamic_range_end_some, s) = split_array_ref(s); let (&dynamic_range_end, s) = split_array_ref(s); + let (&ramdisk_memory, s) = split_array_ref(s); let mappings = Mappings { kernel_stack: Mapping::deserialize(&kernel_stack)?, @@ -257,6 +261,7 @@ impl BootloaderConfig { [1] => Option::Some(u64::from_le_bytes(dynamic_range_end)), _ => return Err("invalid dynamic range end value"), }, + ramdisk_memory: Mapping::deserialize(&ramdisk_memory)?, }; (mappings, s) }; @@ -439,6 +444,9 @@ pub struct Mappings { /// /// Defaults to `0xffff_ffff_ffff_f000`. pub dynamic_range_end: Option, + /// Virtual address to map ramdisk image, if present on disk + /// Defaults to dynamic + pub ramdisk_memory: Mapping, } impl Mappings { @@ -455,6 +463,7 @@ impl Mappings { aslr: false, dynamic_range_start: None, dynamic_range_end: None, + ramdisk_memory: Mapping::new_default(), } } @@ -487,6 +496,7 @@ impl Mappings { } else { Option::None }, + ramdisk_memory: Mapping::random(), } } } diff --git a/api/src/info.rs b/api/src/info.rs index 248362ab..020644bd 100644 --- a/api/src/info.rs +++ b/api/src/info.rs @@ -52,6 +52,10 @@ pub struct BootInfo { pub rsdp_addr: Optional, /// The thread local storage (TLS) template of the kernel executable, if present. pub tls_template: Optional, + /// Ramdisk address, if loaded + pub ramdisk_addr: Optional, + /// Ramdisk image size, set to 0 if addr is None + pub ramdisk_len: u64, } impl BootInfo { @@ -67,6 +71,8 @@ impl BootInfo { recursive_index: Optional::None, rsdp_addr: Optional::None, tls_template: Optional::None, + ramdisk_addr: Optional::None, + ramdisk_len: 0, } } } diff --git a/bios/common/src/lib.rs b/bios/common/src/lib.rs index c55c6d31..59a7737d 100644 --- a/bios/common/src/lib.rs +++ b/bios/common/src/lib.rs @@ -7,6 +7,7 @@ pub mod racy_cell; pub struct BiosInfo { pub stage_4: Region, pub kernel: Region, + pub ramdisk: Region, pub framebuffer: BiosFramebufferInfo, pub memory_map_addr: u32, pub memory_map_len: u16, diff --git a/bios/stage-2/src/main.rs b/bios/stage-2/src/main.rs index b8c85bd7..ca439af5 100644 --- a/bios/stage-2/src/main.rs +++ b/bios/stage-2/src/main.rs @@ -27,6 +27,7 @@ const BOOTLOADER_SECOND_STAGE_PARTITION_TYPE: u8 = 0x20; const STAGE_3_DST: *mut u8 = 0x0010_0000 as *mut u8; // 1MiB (typically 14MiB accessible here) const STAGE_4_DST: *mut u8 = 0x0020_0000 as *mut u8; // 2MiB (typically still 13MiB accessible here) const KERNEL_DST: *mut u8 = 0x0100_0000 as *mut u8; // 16MiB +const RAMDISK_DST: *mut u8 = 0x0400_0000 as *mut u8; // 64MiB static mut DISK_BUFFER: AlignedArrayBuffer<0x4000> = AlignedArrayBuffer { buffer: [0; 0x4000], @@ -98,6 +99,17 @@ fn start(disk_number: u16, partition_table_start: *const u8) -> ! { writeln!(screen::Writer, "loading kernel...").unwrap(); let kernel_len = load_file("kernel-x86_64", KERNEL_DST, &mut fs, &mut disk, disk_buffer); writeln!(screen::Writer, "kernel loaded at {KERNEL_DST:#p}").unwrap(); + writeln!(screen::Writer, "Loading ramdisk...").unwrap(); + let ramdisk_len = match try_load_file("ramdisk", RAMDISK_DST, &mut fs, &mut disk, disk_buffer) { + Some(s) => s, + None => 0u64, + }; + + if ramdisk_len == 0 { + writeln!(screen::Writer, "No ramdisk found, skipping.").unwrap(); + } else { + writeln!(screen::Writer, "Loaded ramdisk at {RAMDISK_DST:#p}").unwrap(); + } let memory_map = unsafe { memory_map::query_memory_map() }.unwrap(); writeln!(screen::Writer, "{memory_map:x?}").unwrap(); @@ -129,6 +141,10 @@ fn start(disk_number: u16, partition_table_start: *const u8) -> ! { start: KERNEL_DST as u64, len: kernel_len, }, + ramdisk: Region { + start: RAMDISK_DST as u64, + len: ramdisk_len, + }, memory_map_addr: memory_map.as_mut_ptr() as u32, memory_map_len: memory_map.len().try_into().unwrap(), framebuffer: BiosFramebufferInfo { @@ -151,17 +167,16 @@ fn start(disk_number: u16, partition_table_start: *const u8) -> ! { } } -fn load_file( +fn try_load_file( file_name: &str, dst: *mut u8, fs: &mut fat::FileSystem, disk: &mut disk::DiskAccess, disk_buffer: &mut AlignedArrayBuffer<16384>, -) -> u64 { +) -> Option { let disk_buffer_size = disk_buffer.buffer.len(); - let file = fs - .find_file_in_root_dir(file_name, disk_buffer) - .expect("file not found"); + let file = fs.find_file_in_root_dir(file_name, disk_buffer)?; + let file_size = file.file_size().into(); let mut total_offset = 0; @@ -195,7 +210,17 @@ fn load_file( total_offset += usize::try_from(len).unwrap(); } } - file_size + Some(file_size) +} + +fn load_file( + file_name: &str, + dst: *mut u8, + fs: &mut fat::FileSystem, + disk: &mut disk::DiskAccess, + disk_buffer: &mut AlignedArrayBuffer<16384>, +) -> u64 { + try_load_file(file_name, dst, fs, disk, disk_buffer).expect("file not found") } /// Taken from https://github.com/rust-lang/rust/blob/e100ec5bc7cd768ec17d75448b29c9ab4a39272b/library/core/src/slice/mod.rs#L1673-L1677 diff --git a/bios/stage-4/src/main.rs b/bios/stage-4/src/main.rs index d5685e9d..ddc45eef 100644 --- a/bios/stage-4/src/main.rs +++ b/bios/stage-4/src/main.rs @@ -56,13 +56,21 @@ pub extern "C" fn _start(info: &mut BiosInfo) -> ! { PhysAddr::new(info.kernel.start) }; let kernel_size = info.kernel.len; - let mut frame_allocator = { + let mut frame_allocator = if info.ramdisk.start == 0 { let kernel_end = PhysFrame::containing_address(kernel_start + kernel_size - 1u64); let next_free = kernel_end + 1; LegacyFrameAllocator::new_starting_at( next_free, memory_map.iter().copied().map(MemoryRegion), ) + } else { + let ramdisk_end = + PhysFrame::containing_address(PhysAddr::new(info.ramdisk.start + info.ramdisk.len)); + let next_free = ramdisk_end + 1; + LegacyFrameAllocator::new_starting_at( + next_free, + memory_map.iter().copied().map(MemoryRegion), + ) }; // We identity-mapped all memory, so the offset between physical and virtual addresses is 0 @@ -126,6 +134,11 @@ pub extern "C" fn _start(info: &mut BiosInfo) -> ! { info: framebuffer_info, }), rsdp_addr: detect_rsdp(), + ramdisk_addr: match info.ramdisk.len { + 0 => None, + _ => Some(info.ramdisk.start), + }, + ramdisk_len: info.ramdisk.len, }; load_and_switch_to_kernel(kernel, frame_allocator, page_tables, system_info); diff --git a/common/src/lib.rs b/common/src/lib.rs index b431215e..93c63fb6 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -77,6 +77,8 @@ pub struct SystemInfo { pub framebuffer: Option, /// Address of the _Root System Description Pointer_ structure of the ACPI standard. pub rsdp_addr: Option, + pub ramdisk_addr: Option, + pub ramdisk_len: u64, } /// The physical address of the framebuffer and information about the framebuffer. @@ -137,6 +139,7 @@ where &mut page_tables, system_info.framebuffer.as_ref(), &config, + &system_info, ); let boot_info = create_boot_info( &config, @@ -168,6 +171,7 @@ pub fn set_up_mappings( page_tables: &mut PageTables, framebuffer: Option<&RawFrameBufferInfo>, config: &BootloaderConfig, + system_info: &SystemInfo, ) -> Mappings where I: ExactSizeIterator + Clone, @@ -199,7 +203,6 @@ where ) .expect("no entry point"); log::info!("Entry point at: {:#x}", entry_point.as_u64()); - // create a stack let stack_start_addr = mapping_addr( config.mappings.kernel_stack, @@ -284,6 +287,39 @@ where } else { None }; + let ramdisk_slice_len = system_info.ramdisk_len; + let ramdisk_slice_start = if let Some(ramdisk_address) = system_info.ramdisk_addr { + let ramdisk_address_start = mapping_addr( + config.mappings.ramdisk_memory, + system_info.ramdisk_len, + 8, + &mut used_entries, + ); + let physical_address = PhysAddr::new(ramdisk_address); + let ramdisk_physical_start_page: PhysFrame = + PhysFrame::containing_address(physical_address); + let ramdisk_page_count = (system_info.ramdisk_len - 1 / Size4KiB::SIZE) + 1; + let ramdisk_physical_end_page = ramdisk_physical_start_page + ramdisk_page_count; + let start_page = Page::containing_address(ramdisk_address_start); + + let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE; + for (i, frame) in + PhysFrame::range_inclusive(ramdisk_physical_start_page, ramdisk_physical_end_page) + .enumerate() + { + let page = start_page + i as u64; + match unsafe { kernel_page_table.map_to(page, frame, flags, frame_allocator) } { + Ok(tlb) => tlb.ignore(), + Err(err) => panic!( + "Failed to map page {:?} to frame {:?}: {:?}", + page, frame, err + ), + }; + } + Some(ramdisk_address_start) + } else { + None + }; let physical_memory_offset = if let Some(mapping) = config.mappings.physical_memory { log::info!("Map physical memory"); @@ -358,6 +394,8 @@ where kernel_slice_start, kernel_slice_len, + ramdisk_slice_start, + ramdisk_slice_len, } } @@ -383,6 +421,8 @@ pub struct Mappings { pub kernel_slice_start: u64, /// Size of the kernel slice allocation in memory. pub kernel_slice_len: u64, + pub ramdisk_slice_start: Option, + pub ramdisk_slice_len: u64, } /// Allocates and initializes the boot info struct and the memory map. @@ -492,6 +532,11 @@ where info.recursive_index = mappings.recursive_index.map(Into::into).into(); info.rsdp_addr = system_info.rsdp_addr.map(|addr| addr.as_u64()).into(); info.tls_template = mappings.tls_template.into(); + info.ramdisk_addr = mappings + .ramdisk_slice_start + .map(|addr| addr.as_u64()) + .into(); + info.ramdisk_len = mappings.ramdisk_slice_len; info }); diff --git a/src/lib.rs b/src/lib.rs index 11d1ebf0..a650d6f2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,12 +17,14 @@ mod mbr; mod pxe; const KERNEL_FILE_NAME: &str = "kernel-x86_64"; +const RAMDISK_FILE_NAME: &str = "ramdisk"; const BIOS_STAGE_3: &str = "boot-stage-3"; const BIOS_STAGE_4: &str = "boot-stage-4"; /// Create disk images for booting on legacy BIOS systems. pub struct BiosBoot { kernel: PathBuf, + ramdisk: Option, } impl BiosBoot { @@ -30,9 +32,16 @@ impl BiosBoot { pub fn new(kernel_path: &Path) -> Self { Self { kernel: kernel_path.to_owned(), + ramdisk: None, } } + /// Add a ramdisk file to the image + pub fn set_ramdisk(&mut self, ramdisk_path: &Path) -> &mut Self { + self.ramdisk = Some(ramdisk_path.to_owned()); + self + } + /// Create a bootable UEFI disk image at the given path. pub fn create_disk_image(&self, out_path: &Path) -> anyhow::Result<()> { let bootsector_path = Path::new(env!("BIOS_BOOT_SECTOR_PATH")); @@ -61,12 +70,15 @@ impl BiosBoot { fn create_fat_partition(&self) -> anyhow::Result { let stage_3_path = Path::new(env!("BIOS_STAGE_3_PATH")); let stage_4_path = Path::new(env!("BIOS_STAGE_4_PATH")); + let kernel_path = self.kernel.as_path(); let mut files = BTreeMap::new(); - files.insert(KERNEL_FILE_NAME, self.kernel.as_path()); + files.insert(KERNEL_FILE_NAME, kernel_path); files.insert(BIOS_STAGE_3, stage_3_path); files.insert(BIOS_STAGE_4, stage_4_path); - + if let Some(ramdisk_path) = &self.ramdisk { + files.insert(RAMDISK_FILE_NAME, ramdisk_path); + } let out_file = NamedTempFile::new().context("failed to create temp file")?; fat::create_fat_filesystem(files, out_file.path()) .context("failed to create BIOS FAT filesystem")?; @@ -78,6 +90,7 @@ impl BiosBoot { /// Create disk images for booting on UEFI systems. pub struct UefiBoot { kernel: PathBuf, + ramdisk: Option, } impl UefiBoot { @@ -85,9 +98,16 @@ impl UefiBoot { pub fn new(kernel_path: &Path) -> Self { Self { kernel: kernel_path.to_owned(), + ramdisk: None, } } + /// Add a ramdisk file to the disk image + pub fn set_ramdisk(&mut self, ramdisk_path: &Path) -> &mut Self { + self.ramdisk = Some(ramdisk_path.to_owned()); + self + } + /// Create a bootable BIOS disk image at the given path. pub fn create_disk_image(&self, out_path: &Path) -> anyhow::Result<()> { let fat_partition = self @@ -111,9 +131,17 @@ impl UefiBoot { /// bootloader won't be found. pub fn create_pxe_tftp_folder(&self, out_path: &Path) -> anyhow::Result<()> { let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH")); - - pxe::create_uefi_tftp_folder(bootloader_path, self.kernel.as_path(), out_path) - .context("failed to create UEFI PXE tftp folder")?; + let ramdisk_path = match self.ramdisk.as_ref() { + Some(rd) => Some(rd.as_path()), + None => None, + }; + pxe::create_uefi_tftp_folder( + bootloader_path, + self.kernel.as_path(), + ramdisk_path, + out_path, + ) + .context("failed to create UEFI PXE tftp folder")?; Ok(()) } @@ -121,10 +149,14 @@ impl UefiBoot { /// Creates an UEFI-bootable FAT partition with the kernel. fn create_fat_partition(&self) -> anyhow::Result { let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH")); - + let kernel_path = self.kernel.as_path(); let mut files = BTreeMap::new(); files.insert("efi/boot/bootx64.efi", bootloader_path); - files.insert(KERNEL_FILE_NAME, self.kernel.as_path()); + files.insert(KERNEL_FILE_NAME, kernel_path); + + if let Some(ramdisk_path) = &self.ramdisk { + files.insert(RAMDISK_FILE_NAME, ramdisk_path); + } let out_file = NamedTempFile::new().context("failed to create temp file")?; fat::create_fat_filesystem(files, out_file.path()) diff --git a/src/pxe.rs b/src/pxe.rs index 9329cec2..84c7b0d6 100644 --- a/src/pxe.rs +++ b/src/pxe.rs @@ -5,6 +5,7 @@ use anyhow::Context; pub fn create_uefi_tftp_folder( bootloader_path: &Path, kernel_binary: &Path, + ramdisk_path: Option<&Path>, out_path: &Path, ) -> anyhow::Result<()> { std::fs::create_dir_all(out_path) @@ -27,6 +28,16 @@ pub fn create_uefi_tftp_folder( to.display() ) })?; + let to = out_path.join("ramdisk"); + if let Some(rp) = ramdisk_path { + std::fs::copy(rp, &to).with_context(|| { + format!( + "failed to copy ramdisk from {} to {}", + rp.display(), + to.display() + ) + })?; + } Ok(()) } diff --git a/tests/default_settings.rs b/tests/default_settings.rs index d610508c..4fa5f5f3 100644 --- a/tests/default_settings.rs +++ b/tests/default_settings.rs @@ -2,21 +2,24 @@ use bootloader_test_runner::run_test_kernel; #[test] fn basic_boot() { - run_test_kernel(env!( - "CARGO_BIN_FILE_TEST_KERNEL_DEFAULT_SETTINGS_basic_boot" - )); + run_test_kernel( + env!("CARGO_BIN_FILE_TEST_KERNEL_DEFAULT_SETTINGS_basic_boot"), + Some("tests/ramdisk.txt"), + ); } #[test] fn should_panic() { - run_test_kernel(env!( - "CARGO_BIN_FILE_TEST_KERNEL_DEFAULT_SETTINGS_should_panic" - )); + run_test_kernel( + env!("CARGO_BIN_FILE_TEST_KERNEL_DEFAULT_SETTINGS_should_panic"), + None, + ); } #[test] fn check_boot_info() { - run_test_kernel(env!( - "CARGO_BIN_FILE_TEST_KERNEL_DEFAULT_SETTINGS_check_boot_info" - )); + run_test_kernel( + env!("CARGO_BIN_FILE_TEST_KERNEL_DEFAULT_SETTINGS_check_boot_info"), + None, + ); } diff --git a/tests/higher_half.rs b/tests/higher_half.rs index c2b9ac91..ca5b2a47 100644 --- a/tests/higher_half.rs +++ b/tests/higher_half.rs @@ -2,24 +2,32 @@ use bootloader_test_runner::run_test_kernel; #[test] fn basic_boot() { - run_test_kernel(env!("CARGO_BIN_FILE_TEST_KERNEL_HIGHER_HALF_basic_boot")); + run_test_kernel( + env!("CARGO_BIN_FILE_TEST_KERNEL_HIGHER_HALF_basic_boot"), + None, + ); } #[test] fn should_panic() { - run_test_kernel(env!("CARGO_BIN_FILE_TEST_KERNEL_HIGHER_HALF_should_panic")); + run_test_kernel( + env!("CARGO_BIN_FILE_TEST_KERNEL_HIGHER_HALF_should_panic"), + None, + ); } #[test] fn check_boot_info() { - run_test_kernel(env!( - "CARGO_BIN_FILE_TEST_KERNEL_HIGHER_HALF_check_boot_info" - )); + run_test_kernel( + env!("CARGO_BIN_FILE_TEST_KERNEL_HIGHER_HALF_check_boot_info"), + None, + ); } #[test] fn verify_higher_half() { - run_test_kernel(env!( - "CARGO_BIN_FILE_TEST_KERNEL_HIGHER_HALF_verify_higher_half" - )); + run_test_kernel( + env!("CARGO_BIN_FILE_TEST_KERNEL_HIGHER_HALF_verify_higher_half"), + None, + ); } diff --git a/tests/lto.rs b/tests/lto.rs index 00cfe60f..fd3854f5 100644 --- a/tests/lto.rs +++ b/tests/lto.rs @@ -21,5 +21,5 @@ fn basic_boot() { .join("basic_boot"); assert!(kernel_path.exists()); - run_test_kernel(kernel_path.as_path().to_str().unwrap()); + run_test_kernel(kernel_path.as_path().to_str().unwrap(), None); } diff --git a/tests/map_phys_mem.rs b/tests/map_phys_mem.rs index b19ba987..97252764 100644 --- a/tests/map_phys_mem.rs +++ b/tests/map_phys_mem.rs @@ -2,14 +2,16 @@ use bootloader_test_runner::run_test_kernel; #[test] fn check_boot_info() { - run_test_kernel(env!( - "CARGO_BIN_FILE_TEST_KERNEL_MAP_PHYS_MEM_check_boot_info" - )); + run_test_kernel( + env!("CARGO_BIN_FILE_TEST_KERNEL_MAP_PHYS_MEM_check_boot_info"), + None, + ); } #[test] fn access_phys_mem() { - run_test_kernel(env!( - "CARGO_BIN_FILE_TEST_KERNEL_MAP_PHYS_MEM_access_phys_mem" - )); + run_test_kernel( + env!("CARGO_BIN_FILE_TEST_KERNEL_MAP_PHYS_MEM_access_phys_mem"), + None, + ); } diff --git a/tests/pie.rs b/tests/pie.rs index c2d30d80..0801d3e1 100644 --- a/tests/pie.rs +++ b/tests/pie.rs @@ -2,20 +2,20 @@ use bootloader_test_runner::run_test_kernel; #[test] fn basic_boot() { - run_test_kernel(env!("CARGO_BIN_FILE_TEST_KERNEL_PIE_basic_boot")); + run_test_kernel(env!("CARGO_BIN_FILE_TEST_KERNEL_PIE_basic_boot"), None); } #[test] fn should_panic() { - run_test_kernel(env!("CARGO_BIN_FILE_TEST_KERNEL_PIE_should_panic")); + run_test_kernel(env!("CARGO_BIN_FILE_TEST_KERNEL_PIE_should_panic"), None); } #[test] fn check_boot_info() { - run_test_kernel(env!("CARGO_BIN_FILE_TEST_KERNEL_PIE_check_boot_info")); + run_test_kernel(env!("CARGO_BIN_FILE_TEST_KERNEL_PIE_check_boot_info"), None); } #[test] fn global_variable() { - run_test_kernel(env!("CARGO_BIN_FILE_TEST_KERNEL_PIE_global_variable")); + run_test_kernel(env!("CARGO_BIN_FILE_TEST_KERNEL_PIE_global_variable"), None); } diff --git a/tests/ramdisk.rs b/tests/ramdisk.rs new file mode 100644 index 00000000..7aa46a1a --- /dev/null +++ b/tests/ramdisk.rs @@ -0,0 +1,18 @@ +use bootloader_test_runner::run_test_kernel; +static RAMDISK_PATH: &str = "tests/ramdisk.txt"; + +#[test] +fn basic_boot() { + run_test_kernel( + env!("CARGO_BIN_FILE_TEST_KERNEL_RAMDISK_basic_boot"), + Some(RAMDISK_PATH), + ); +} + +#[test] +fn check_ramdisk() { + run_test_kernel( + env!("CARGO_BIN_FILE_TEST_KERNEL_RAMDISK_ramdisk"), + Some(RAMDISK_PATH), + ); +} diff --git a/tests/ramdisk.txt b/tests/ramdisk.txt new file mode 100644 index 00000000..09e6dbc1 --- /dev/null +++ b/tests/ramdisk.txt @@ -0,0 +1 @@ +Test ramdisk. \ No newline at end of file diff --git a/tests/runner/src/lib.rs b/tests/runner/src/lib.rs index 7f50cd42..a9c785fc 100644 --- a/tests/runner/src/lib.rs +++ b/tests/runner/src/lib.rs @@ -8,20 +8,32 @@ const QEMU_ARGS: &[&str] = &[ "-display", "none", "--no-reboot", + "-m", + "size=2048", ]; -pub fn run_test_kernel(kernel_binary_path: &str) { +pub fn run_test_kernel(kernel_binary_path: &str, ramdisk_path: Option<&str>) { let kernel_path = Path::new(kernel_binary_path); + let ramdisk_path = match ramdisk_path { + Some(rdp) => Some(Path::new(rdp)), + None => None, + }; // create an MBR disk image for legacy BIOS booting let mbr_path = kernel_path.with_extension("mbr"); - bootloader::BiosBoot::new(kernel_path) - .create_disk_image(&mbr_path) - .unwrap(); + let mut bios_builder = bootloader::BiosBoot::new(kernel_path); // create a GPT disk image for UEFI booting let gpt_path = kernel_path.with_extension("gpt"); - let uefi_builder = bootloader::UefiBoot::new(kernel_path); + let mut uefi_builder = bootloader::UefiBoot::new(kernel_path); + + // Set ramdisk for test, if supplied. + if let Some(rdp) = ramdisk_path { + bios_builder.set_ramdisk(rdp); + uefi_builder.set_ramdisk(rdp); + } + + bios_builder.create_disk_image(&mbr_path).unwrap(); uefi_builder.create_disk_image(&gpt_path).unwrap(); // create a TFTP folder with the kernel executable and UEFI bootloader for diff --git a/tests/test_kernels/default_settings/src/bin/basic_boot.rs b/tests/test_kernels/default_settings/src/bin/basic_boot.rs index e6bd3a0b..8924e1c0 100644 --- a/tests/test_kernels/default_settings/src/bin/basic_boot.rs +++ b/tests/test_kernels/default_settings/src/bin/basic_boot.rs @@ -2,11 +2,13 @@ #![no_main] // disable all Rust-level entry points use bootloader_api::{entry_point, BootInfo}; -use test_kernel_default_settings::{exit_qemu, QemuExitCode}; +use core::fmt::Write; +use test_kernel_default_settings::{exit_qemu, serial, QemuExitCode}; entry_point!(kernel_main); -fn kernel_main(_boot_info: &'static mut BootInfo) -> ! { +fn kernel_main(boot_info: &'static mut BootInfo) -> ! { + writeln!(serial(), "Entered kernel with boot info: {:?}", boot_info).unwrap(); exit_qemu(QemuExitCode::Success); } @@ -14,8 +16,6 @@ fn kernel_main(_boot_info: &'static mut BootInfo) -> ! { #[panic_handler] #[cfg(not(test))] fn panic(info: &core::panic::PanicInfo) -> ! { - use core::fmt::Write; - - let _ = writeln!(test_kernel_default_settings::serial(), "PANIC: {}", info); + let _ = writeln!(serial(), "PANIC: {}", info); exit_qemu(QemuExitCode::Failed); } diff --git a/tests/test_kernels/ramdisk/Cargo.toml b/tests/test_kernels/ramdisk/Cargo.toml new file mode 100644 index 00000000..4258b0a5 --- /dev/null +++ b/tests/test_kernels/ramdisk/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "test_kernel_ramdisk" +version = "0.1.0" +authors = ["Philipp Oppermann "] +edition = "2021" + +[dependencies] +bootloader_api = { path = "../../../api" } +x86_64 = { version = "0.14.7", default-features = false, features = [ + "instructions", + "inline_asm", +] } +uart_16550 = "0.2.10" diff --git a/tests/test_kernels/ramdisk/src/bin/basic_boot.rs b/tests/test_kernels/ramdisk/src/bin/basic_boot.rs new file mode 100644 index 00000000..515cd22d --- /dev/null +++ b/tests/test_kernels/ramdisk/src/bin/basic_boot.rs @@ -0,0 +1,21 @@ +#![no_std] // don't link the Rust standard library +#![no_main] // disable all Rust-level entry points + +use bootloader_api::{entry_point, BootInfo}; +use test_kernel_ramdisk::{exit_qemu, QemuExitCode}; + +entry_point!(kernel_main); + +fn kernel_main(_boot_info: &'static mut BootInfo) -> ! { + exit_qemu(QemuExitCode::Success); +} + +/// This function is called on panic. +#[panic_handler] +#[cfg(not(test))] +fn panic(info: &core::panic::PanicInfo) -> ! { + use core::fmt::Write; + + let _ = writeln!(test_kernel_ramdisk::serial(), "PANIC: {}", info); + exit_qemu(QemuExitCode::Failed); +} diff --git a/tests/test_kernels/ramdisk/src/bin/ramdisk.rs b/tests/test_kernels/ramdisk/src/bin/ramdisk.rs new file mode 100644 index 00000000..d64d5592 --- /dev/null +++ b/tests/test_kernels/ramdisk/src/bin/ramdisk.rs @@ -0,0 +1,34 @@ +#![no_std] // don't link the Rust standard library +#![no_main] // disable all Rust-level entry points + +use bootloader_api::{entry_point, BootInfo}; +use core::{fmt::Write, ptr::slice_from_raw_parts}; +use test_kernel_ramdisk::{exit_qemu, serial, QemuExitCode, RAMDISK_CONTENTS}; + +entry_point!(kernel_main); + +fn kernel_main(boot_info: &'static mut BootInfo) -> ! { + writeln!(serial(), "Boot info: {:?}", boot_info).unwrap(); + assert!(boot_info.ramdisk_addr.into_option().is_some()); + assert_eq!(boot_info.ramdisk_len as usize, RAMDISK_CONTENTS.len()); + let actual_ramdisk = unsafe { + &*slice_from_raw_parts( + boot_info.ramdisk_addr.into_option().unwrap() as *const u8, + boot_info.ramdisk_len as usize, + ) + }; + writeln!(serial(), "Actual contents: {:?}", actual_ramdisk).unwrap(); + assert_eq!(RAMDISK_CONTENTS, actual_ramdisk); + + exit_qemu(QemuExitCode::Success); +} + +/// This function is called on panic. +#[cfg(not(test))] +#[panic_handler] +fn panic(info: &core::panic::PanicInfo) -> ! { + use core::fmt::Write; + + let _ = writeln!(test_kernel_ramdisk::serial(), "PANIC: {}", info); + exit_qemu(QemuExitCode::Failed); +} diff --git a/tests/test_kernels/ramdisk/src/lib.rs b/tests/test_kernels/ramdisk/src/lib.rs new file mode 100644 index 00000000..00ea92a6 --- /dev/null +++ b/tests/test_kernels/ramdisk/src/lib.rs @@ -0,0 +1,29 @@ +#![no_std] + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u32)] +pub enum QemuExitCode { + Success = 0x10, + Failed = 0x11, +} + +pub static RAMDISK_CONTENTS: &[u8] = include_bytes!("../../../ramdisk.txt"); + +pub fn exit_qemu(exit_code: QemuExitCode) -> ! { + use x86_64::instructions::{nop, port::Port}; + + unsafe { + let mut port = Port::new(0xf4); + port.write(exit_code as u32); + } + + loop { + nop(); + } +} + +pub fn serial() -> uart_16550::SerialPort { + let mut port = unsafe { uart_16550::SerialPort::new(0x3F8) }; + port.init(); + port +} diff --git a/uefi/Cargo.toml b/uefi/Cargo.toml index a2dfe632..a637db6e 100644 --- a/uefi/Cargo.toml +++ b/uefi/Cargo.toml @@ -12,5 +12,5 @@ repository.workspace = true bootloader_api = { workspace = true } bootloader-x86_64-common = { workspace = true } log = "0.4.14" -uefi = "0.16.0" +uefi = "0.18.0" x86_64 = "0.14.8" diff --git a/uefi/src/main.rs b/uefi/src/main.rs index 0f7cd469..f23df5ff 100644 --- a/uefi/src/main.rs +++ b/uefi/src/main.rs @@ -8,7 +8,12 @@ use bootloader_api::{info::FrameBufferInfo, BootloaderConfig}; use bootloader_x86_64_common::{ legacy_memory_region::LegacyFrameAllocator, Kernel, RawFrameBufferInfo, SystemInfo, }; -use core::{cell::UnsafeCell, fmt::Write, mem, ptr, slice}; +use core::{ + cell::UnsafeCell, + fmt::Write, + ops::{Deref, DerefMut}, + ptr, slice, +}; use uefi::{ prelude::{entry, Boot, Handle, Status, SystemTable}, proto::{ @@ -23,9 +28,10 @@ use uefi::{ pxe::{BaseCode, DhcpV4Packet}, IpAddress, }, + ProtocolPointer, }, table::boot::{ - AllocateType, MemoryDescriptor, MemoryType, OpenProtocolAttributes, OpenProtocolParams, + AllocateType, MemoryType, OpenProtocolAttributes, OpenProtocolParams, ScopedProtocol, }, CStr16, CStr8, }; @@ -63,39 +69,77 @@ fn efi_main(image: Handle, st: SystemTable) -> Status { fn main_inner(image: Handle, mut st: SystemTable) -> Status { // temporarily clone the y table for printing panics + unsafe { *SYSTEM_TABLE.get() = Some(st.unsafe_clone()); } + let stdout = unsafe { &mut *SYSTEM_TABLE.get() }; + let stdout = stdout.as_mut().unwrap(); + let stdout = stdout.stdout(); + stdout.clear().unwrap(); + writeln!(stdout, "UEFI bootloader started; trying to load kernel").unwrap(); + + let mut boot_mode = BootMode::Disk; + let mut kernel = load_kernel(image, &mut st, boot_mode); + if kernel.is_none() { + writeln!( + stdout, + "Failed to load kernel via {:?}, trying TFTP", + boot_mode + ) + .unwrap(); + // Try TFTP boot + boot_mode = BootMode::Tftp; + kernel = load_kernel(image, &mut st, boot_mode); + } + let kernel = kernel.expect("Failed to load kernel"); + writeln!(stdout, "Trying to load ramdisk via {:?}", boot_mode).unwrap(); + // Ramdisk must load from same source, or not at all. + let ramdisk = load_ramdisk(image, &mut st, boot_mode); - st.stdout().clear().unwrap(); writeln!( - st.stdout(), - "UEFI bootloader started; trying to load kernel" + stdout, + "{}", + match ramdisk { + Some(_) => "Loaded ramdisk", + None => "Ramdisk not found.", + } ) .unwrap(); - let kernel = load_kernel(image, &st); - let framebuffer = init_logger(&st, kernel.config); - - // we no longer need the system table for printing panics unsafe { *SYSTEM_TABLE.get() = None; } - log::info!("UEFI bootloader started"); log::info!("Reading kernel and configuration from disk was successful"); if let Some(framebuffer) = framebuffer { log::info!("Using framebuffer at {:#x}", framebuffer.addr); } - let mmap_storage = { - let max_mmap_size = - st.boot_services().memory_map_size().map_size + 8 * mem::size_of::(); - let ptr = st - .boot_services() - .allocate_pool(MemoryType::LOADER_DATA, max_mmap_size)?; - unsafe { slice::from_raw_parts_mut(ptr, max_mmap_size) } + let mut memory_map_size = st.boot_services().memory_map_size(); + let mut target_size = memory_map_size.map_size + (8 * memory_map_size.entry_size); + let mut storage: &mut [u8]; + loop { + let ptr = st + .boot_services() + .allocate_pool(MemoryType::LOADER_DATA, target_size) + .expect("Failed to allocate memory for mmap storage"); + storage = unsafe { slice::from_raw_parts_mut(ptr, target_size) }; + if let Err(_) = st.boot_services().memory_map(storage) { + memory_map_size = st.boot_services().memory_map_size(); + // By measuring the size here, we can find out exactly how much we need. + // We may hit this code twice, if the map allocation ends up spanning more pages. + let next_target_size = memory_map_size.map_size + (8 * memory_map_size.entry_size); + target_size = next_target_size; + st.boot_services() + .free_pool(ptr) + .expect("Failed to free temporary memory for memory map!"); + continue; + } + break; + } + storage }; log::trace!("exiting boot services"); @@ -107,7 +151,13 @@ fn main_inner(image: Handle, mut st: SystemTable) -> Status { LegacyFrameAllocator::new(memory_map.copied().map(UefiMemoryDescriptor)); let page_tables = create_page_tables(&mut frame_allocator); - + let mut ramdisk_len = 0u64; + let ramdisk_addr = if let Some(rd) = ramdisk { + ramdisk_len = rd.len() as u64; + Some(rd.as_ptr() as usize as u64) + } else { + None + }; let system_info = SystemInfo { framebuffer, rsdp_addr: { @@ -120,6 +170,8 @@ fn main_inner(image: Handle, mut st: SystemTable) -> Status { .or_else(|| config_entries.find(|entry| matches!(entry.guid, cfg::ACPI_GUID))); rsdp.map(|entry| PhysAddr::new(entry.address as u64)) }, + ramdisk_addr: ramdisk_addr, + ramdisk_len: ramdisk_len, }; bootloader_x86_64_common::load_and_switch_to_kernel( @@ -130,51 +182,101 @@ fn main_inner(image: Handle, mut st: SystemTable) -> Status { ); } -fn load_kernel(image: Handle, st: &SystemTable) -> Kernel<'static> { - let kernel_slice = load_kernel_file(image, st).expect("couldn't find kernel"); - Kernel::parse(kernel_slice) +#[derive(Clone, Copy, Debug)] +pub enum BootMode { + Disk, + Tftp, } -/// Try to load a kernel file from the boot device. -fn load_kernel_file(image: Handle, st: &SystemTable) -> Option<&'static mut [u8]> { - load_kernel_file_from_disk(image, st) - .or_else(|| load_kernel_file_from_tftp_boot_server(image, st)) +fn load_ramdisk( + image: Handle, + st: &mut SystemTable, + boot_mode: BootMode, +) -> Option<&'static mut [u8]> { + load_file_from_boot_method(image, st, "ramdisk\0", boot_mode) } -fn load_kernel_file_from_disk(image: Handle, st: &SystemTable) -> Option<&'static mut [u8]> { - let file_system_raw = { - let this = st.boot_services(); - let loaded_image = this - .open_protocol::( - OpenProtocolParams { - handle: image, - agent: image, - controller: None, - }, - OpenProtocolAttributes::Exclusive, - ) - .expect("Failed to retrieve `LoadedImage` protocol from handle"); - let loaded_image = unsafe { &*loaded_image.interface.get() }; +fn load_kernel( + image: Handle, + st: &mut SystemTable, + boot_mode: BootMode, +) -> Option> { + let kernel_slice = load_file_from_boot_method(image, st, "kernel-x86_64\0", boot_mode)?; + Some(Kernel::parse(kernel_slice)) +} - let device_handle = loaded_image.device(); +fn load_file_from_boot_method( + image: Handle, + st: &mut SystemTable, + filename: &str, + boot_mode: BootMode, +) -> Option<&'static mut [u8]> { + match boot_mode { + BootMode::Disk => load_file_from_disk(filename, image, st), + BootMode::Tftp => load_file_from_tftp_boot_server(filename, image, st), + } +} - let device_path = this - .open_protocol::( - OpenProtocolParams { - handle: device_handle, - agent: image, - controller: None, - }, - OpenProtocolAttributes::Exclusive, - ) - .expect("Failed to retrieve `DevicePath` protocol from image's device handle"); - let mut device_path = unsafe { &*device_path.interface.get() }; +fn open_device_path_protocol( + image: Handle, + st: &SystemTable, +) -> Option> { + let this = st.boot_services(); + let loaded_image = unsafe { + this.open_protocol::( + OpenProtocolParams { + handle: image, + agent: image, + controller: None, + }, + OpenProtocolAttributes::Exclusive, + ) + }; - let fs_handle = this - .locate_device_path::(&mut device_path) - .ok()?; + if loaded_image.is_err() { + log::error!("Failed to open protocol LoadedImage"); + return None; + } + let loaded_image = loaded_image.unwrap(); + let loaded_image = loaded_image.deref(); + + let device_handle = loaded_image.device(); - this.open_protocol::( + let device_path = unsafe { + this.open_protocol::( + OpenProtocolParams { + handle: device_handle, + agent: image, + controller: None, + }, + OpenProtocolAttributes::Exclusive, + ) + }; + if device_path.is_err() { + log::error!("Failed to open protocol DevicePath"); + return None; + } + Some(device_path.unwrap()) +} + +fn locate_and_open_protocol( + image: Handle, + st: &SystemTable, +) -> Option> { + let this = st.boot_services(); + let mut device_path = open_device_path_protocol(image, st)?; + let mut device_path = device_path.deref(); + + let fs_handle = this.locate_device_path::

(&mut device_path); + if fs_handle.is_err() { + log::error!("Failed to open device path"); + return None; + } + + let fs_handle = fs_handle.unwrap(); + + let opened_handle = unsafe { + this.open_protocol::

( OpenProtocolParams { handle: fs_handle, agent: image, @@ -182,121 +284,101 @@ fn load_kernel_file_from_disk(image: Handle, st: &SystemTable) -> Option<& }, OpenProtocolAttributes::Exclusive, ) + }; + + if opened_handle.is_err() { + log::error!("Failed to open protocol {}", core::any::type_name::

()); + return None; } - .unwrap(); - let file_system = unsafe { &mut *file_system_raw.interface.get() }; + Some(opened_handle.unwrap()) +} + +fn load_file_from_disk( + name: &str, + image: Handle, + st: &SystemTable, +) -> Option<&'static mut [u8]> { + let mut file_system_raw = locate_and_open_protocol::(image, st)?; + let file_system = file_system_raw.deref_mut(); let mut root = file_system.open_volume().unwrap(); - let mut buf = [0; 14 * 2]; - let filename = CStr16::from_str_with_buf("kernel-x86_64", &mut buf).unwrap(); - let kernel_file_handle = root - .open(filename, FileMode::Read, FileAttribute::empty()) - .expect("Failed to load kernel (expected file named `kernel-x86_64`)"); - let mut kernel_file = match kernel_file_handle.into_type().unwrap() { + let mut buf = [0u16; 256]; + assert!(name.len() < 256); + let filename = CStr16::from_str_with_buf(name.trim_end_matches('\0'), &mut buf) + .expect("Failed to convert string to utf16"); + + let file_handle_result = root.open(filename, FileMode::Read, FileAttribute::empty()); + + if file_handle_result.is_err() { + return None; + } + + let file_handle = file_handle_result.unwrap(); + + let mut file = match file_handle.into_type().unwrap() { uefi::proto::media::file::FileType::Regular(f) => f, uefi::proto::media::file::FileType::Dir(_) => panic!(), }; let mut buf = [0; 500]; - let kernel_info: &mut FileInfo = kernel_file.get_info(&mut buf).unwrap(); - let kernel_size = usize::try_from(kernel_info.file_size()).unwrap(); + let file_info: &mut FileInfo = file.get_info(&mut buf).unwrap(); + let file_size = usize::try_from(file_info.file_size()).unwrap(); - let kernel_ptr = st + let file_ptr = st .boot_services() .allocate_pages( AllocateType::AnyPages, MemoryType::LOADER_DATA, - ((kernel_size - 1) / 4096) + 1, + ((file_size - 1) / 4096) + 1, ) .unwrap() as *mut u8; - unsafe { ptr::write_bytes(kernel_ptr, 0, kernel_size) }; - let kernel_slice = unsafe { slice::from_raw_parts_mut(kernel_ptr, kernel_size) }; - kernel_file.read(kernel_slice).unwrap(); + unsafe { ptr::write_bytes(file_ptr, 0, file_size) }; + let file_slice = unsafe { slice::from_raw_parts_mut(file_ptr, file_size) }; + file.read(file_slice).unwrap(); - Some(kernel_slice) + Some(file_slice) } /// Try to load a kernel from a TFTP boot server. -fn load_kernel_file_from_tftp_boot_server( +fn load_file_from_tftp_boot_server( + name: &str, image: Handle, st: &SystemTable, ) -> Option<&'static mut [u8]> { - let this = st.boot_services(); - - // Try to locate a `BaseCode` protocol on the boot device. - - let loaded_image = this - .open_protocol::( - OpenProtocolParams { - handle: image, - agent: image, - controller: None, - }, - OpenProtocolAttributes::Exclusive, - ) - .expect("Failed to retrieve `LoadedImage` protocol from handle"); - let loaded_image = unsafe { &*loaded_image.interface.get() }; - - let device_handle = loaded_image.device(); - - let device_path = this - .open_protocol::( - OpenProtocolParams { - handle: device_handle, - agent: image, - controller: None, - }, - OpenProtocolAttributes::Exclusive, - ) - .expect("Failed to retrieve `DevicePath` protocol from image's device handle"); - let mut device_path = unsafe { &*device_path.interface.get() }; - - let base_code_handle = this.locate_device_path::(&mut device_path).ok()?; - - let base_code_raw = this - .open_protocol::( - OpenProtocolParams { - handle: base_code_handle, - agent: image, - controller: None, - }, - OpenProtocolAttributes::Exclusive, - ) - .unwrap(); - let base_code = unsafe { &mut *base_code_raw.interface.get() }; + let mut base_code_raw = locate_and_open_protocol::(image, st)?; + let base_code = base_code_raw.deref_mut(); // Find the TFTP boot server. let mode = base_code.mode(); assert!(mode.dhcp_ack_received); let dhcpv4: &DhcpV4Packet = mode.dhcp_ack.as_ref(); let server_ip = IpAddress::new_v4(dhcpv4.bootp_si_addr); + let mut buf = [0u8; 256]; + assert!(name.len() < 256); - let filename = CStr8::from_bytes_with_nul(b"kernel-x86_64\0").unwrap(); + let filename = CStr8::from_bytes_with_nul(name.as_bytes()).unwrap(); // Determine the kernel file size. - let file_size = base_code - .tftp_get_file_size(&server_ip, filename) - .expect("Failed to query the kernel file size"); - let kernel_size = - usize::try_from(file_size).expect("The kernel file size should fit into usize"); + let file_size = base_code.tftp_get_file_size(&server_ip, &filename).ok()?; + let kernel_size = usize::try_from(file_size).expect("The file size should fit into usize"); // Allocate some memory for the kernel file. - let kernel_ptr = st + let ptr = st .boot_services() .allocate_pages( AllocateType::AnyPages, MemoryType::LOADER_DATA, ((kernel_size - 1) / 4096) + 1, ) - .expect("Failed to allocate memory for the kernel file") as *mut u8; - let kernel_slice = unsafe { slice::from_raw_parts_mut(kernel_ptr, kernel_size) }; + .expect("Failed to allocate memory for the file") as *mut u8; + let slice = unsafe { slice::from_raw_parts_mut(ptr, kernel_size) }; // Load the kernel file. base_code - .tftp_read_file(&server_ip, filename, Some(kernel_slice)) + .tftp_read_file(&server_ip, &filename, Some(slice)) .expect("Failed to read kernel file from the TFTP boot server"); - Some(kernel_slice) + Some(slice) } /// Creates page table abstraction types for both the bootloader and kernel page tables. @@ -366,11 +448,22 @@ fn create_page_tables( } fn init_logger(st: &SystemTable, config: BootloaderConfig) -> Option { - let gop = st + let gop_handle = st .boot_services() - .locate_protocol::() + .get_handle_for_protocol::() .ok()?; - let gop = unsafe { &mut *gop.get() }; + let mut gop = unsafe { + st.boot_services() + .open_protocol::( + OpenProtocolParams { + handle: gop_handle, + agent: st.boot_services().image_handle(), + controller: None, + }, + OpenProtocolAttributes::Exclusive, + ) + .ok()? + }; let mode = { let modes = gop.modes(); From d2680923fa9894a346f42dc10e54333bb0c13ed8 Mon Sep 17 00:00:00 2001 From: Jason Couture Date: Wed, 4 Jan 2023 10:44:01 -0500 Subject: [PATCH 03/17] Apply review comments Co-authored-by: Philipp Oppermann --- common/src/lib.rs | 7 ++++--- src/lib.rs | 7 ++----- uefi/src/main.rs | 39 +++++++++++++++++++-------------------- 3 files changed, 25 insertions(+), 28 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index 93c63fb6..a7e08429 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -292,15 +292,16 @@ where let ramdisk_address_start = mapping_addr( config.mappings.ramdisk_memory, system_info.ramdisk_len, - 8, + Size4KiB::SIZE, &mut used_entries, ); let physical_address = PhysAddr::new(ramdisk_address); let ramdisk_physical_start_page: PhysFrame = PhysFrame::containing_address(physical_address); - let ramdisk_page_count = (system_info.ramdisk_len - 1 / Size4KiB::SIZE) + 1; + let ramdisk_page_count = ((system_info.ramdisk_len - 1) / Size4KiB::SIZE) + 1; let ramdisk_physical_end_page = ramdisk_physical_start_page + ramdisk_page_count; - let start_page = Page::containing_address(ramdisk_address_start); + let start_page = Page::from_start_address(ramdisk_address_start) + .expect("the ramdisk start address must be page aligned"); let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE; for (i, frame) in diff --git a/src/lib.rs b/src/lib.rs index a650d6f2..907a39ef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -108,7 +108,7 @@ impl UefiBoot { self } - /// Create a bootable BIOS disk image at the given path. + /// Create a bootable UEFI disk image at the given path. pub fn create_disk_image(&self, out_path: &Path) -> anyhow::Result<()> { let fat_partition = self .create_fat_partition() @@ -131,10 +131,7 @@ impl UefiBoot { /// bootloader won't be found. pub fn create_pxe_tftp_folder(&self, out_path: &Path) -> anyhow::Result<()> { let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH")); - let ramdisk_path = match self.ramdisk.as_ref() { - Some(rd) => Some(rd.as_path()), - None => None, - }; + let ramdisk_path = self.ramdisk.as_deref(); pxe::create_uefi_tftp_folder( bootloader_path, self.kernel.as_path(), diff --git a/uefi/src/main.rs b/uefi/src/main.rs index f23df5ff..b7ee3a2c 100644 --- a/uefi/src/main.rs +++ b/uefi/src/main.rs @@ -119,27 +119,28 @@ fn main_inner(image: Handle, mut st: SystemTable) -> Status { let mmap_storage = { let mut memory_map_size = st.boot_services().memory_map_size(); let mut target_size = memory_map_size.map_size + (8 * memory_map_size.entry_size); - let mut storage: &mut [u8]; loop { let ptr = st .boot_services() .allocate_pool(MemoryType::LOADER_DATA, target_size) .expect("Failed to allocate memory for mmap storage"); - storage = unsafe { slice::from_raw_parts_mut(ptr, target_size) }; - if let Err(_) = st.boot_services().memory_map(storage) { - memory_map_size = st.boot_services().memory_map_size(); - // By measuring the size here, we can find out exactly how much we need. - // We may hit this code twice, if the map allocation ends up spanning more pages. - let next_target_size = memory_map_size.map_size + (8 * memory_map_size.entry_size); - target_size = next_target_size; - st.boot_services() - .free_pool(ptr) - .expect("Failed to free temporary memory for memory map!"); - continue; + let storage = unsafe { slice::from_raw_parts_mut(ptr, target_size) }; + if st.boot_services().memory_map(storage).is_ok() { + break storage; } - break; + // allocated memory region was not big enough -> free it again + st.boot_services() + .free_pool(ptr) + .expect("Failed to free temporary memory for memory map!"); + + // By measuring the size here, we can find out exactly how much we need. + // We may hit this code twice, if the map allocation ends up spanning more pages. + memory_map_size = st.boot_services().memory_map_size(); + + let next_target_size = memory_map_size.map_size + (8 * memory_map_size.entry_size); + target_size = next_target_size; + } - storage }; log::trace!("exiting boot services"); @@ -309,11 +310,10 @@ fn load_file_from_disk( let file_handle_result = root.open(filename, FileMode::Read, FileAttribute::empty()); - if file_handle_result.is_err() { - return None; - } - - let file_handle = file_handle_result.unwrap(); + let file_handle = match file_handle_result { + Err(_) => return None, + Ok(handle) => handle, + }; let mut file = match file_handle.into_type().unwrap() { uefi::proto::media::file::FileType::Regular(f) => f, @@ -353,7 +353,6 @@ fn load_file_from_tftp_boot_server( assert!(mode.dhcp_ack_received); let dhcpv4: &DhcpV4Packet = mode.dhcp_ack.as_ref(); let server_ip = IpAddress::new_v4(dhcpv4.bootp_si_addr); - let mut buf = [0u8; 256]; assert!(name.len() < 256); let filename = CStr8::from_bytes_with_nul(name.as_bytes()).unwrap(); From 253f9b04fa6da95b0f44eab7a02e2e490ab5001d Mon Sep 17 00:00:00 2001 From: Jason Couture Date: Wed, 4 Jan 2023 15:41:59 -0500 Subject: [PATCH 04/17] Measure memory map size before freeing memory to avoid an endless loop, and remove the extra unnecessary variable --- uefi/src/main.rs | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/uefi/src/main.rs b/uefi/src/main.rs index b7ee3a2c..b0c3c00b 100644 --- a/uefi/src/main.rs +++ b/uefi/src/main.rs @@ -69,7 +69,6 @@ fn efi_main(image: Handle, st: SystemTable) -> Status { fn main_inner(image: Handle, mut st: SystemTable) -> Status { // temporarily clone the y table for printing panics - unsafe { *SYSTEM_TABLE.get() = Some(st.unsafe_clone()); } @@ -118,28 +117,25 @@ fn main_inner(image: Handle, mut st: SystemTable) -> Status { } let mmap_storage = { let mut memory_map_size = st.boot_services().memory_map_size(); - let mut target_size = memory_map_size.map_size + (8 * memory_map_size.entry_size); loop { let ptr = st .boot_services() - .allocate_pool(MemoryType::LOADER_DATA, target_size) + .allocate_pool(MemoryType::LOADER_DATA, memory_map_size.map_size) .expect("Failed to allocate memory for mmap storage"); - let storage = unsafe { slice::from_raw_parts_mut(ptr, target_size) }; + + let storage = unsafe { slice::from_raw_parts_mut(ptr, memory_map_size.map_size) }; + if st.boot_services().memory_map(storage).is_ok() { break storage; } - // allocated memory region was not big enough -> free it again - st.boot_services() - .free_pool(ptr) - .expect("Failed to free temporary memory for memory map!"); // By measuring the size here, we can find out exactly how much we need. // We may hit this code twice, if the map allocation ends up spanning more pages. memory_map_size = st.boot_services().memory_map_size(); - - let next_target_size = memory_map_size.map_size + (8 * memory_map_size.entry_size); - target_size = next_target_size; - + // allocated memory region was not big enough -> free it again + st.boot_services() + .free_pool(ptr) + .expect("Failed to free temporary memory for memory map!"); } }; @@ -265,7 +261,7 @@ fn locate_and_open_protocol( st: &SystemTable, ) -> Option> { let this = st.boot_services(); - let mut device_path = open_device_path_protocol(image, st)?; + let device_path = open_device_path_protocol(image, st)?; let mut device_path = device_path.deref(); let fs_handle = this.locate_device_path::

(&mut device_path); From 17055d36384780bdee0a62391e3cc2a1844cb985 Mon Sep 17 00:00:00 2001 From: Jason Couture Date: Wed, 4 Jan 2023 16:01:14 -0500 Subject: [PATCH 05/17] Simplify math to caluclate the page count for the ramdisk Rather than replace range_inclusive, we can remove the nesting doll math from ramdisk_page_count instead. IMO this makes the code a bit more readable. --- common/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index a7e08429..59d12e35 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -298,7 +298,7 @@ where let physical_address = PhysAddr::new(ramdisk_address); let ramdisk_physical_start_page: PhysFrame = PhysFrame::containing_address(physical_address); - let ramdisk_page_count = ((system_info.ramdisk_len - 1) / Size4KiB::SIZE) + 1; + let ramdisk_page_count = system_info.ramdisk_len / Size4KiB::SIZE; let ramdisk_physical_end_page = ramdisk_physical_start_page + ramdisk_page_count; let start_page = Page::from_start_address(ramdisk_address_start) .expect("the ramdisk start address must be page aligned"); From ced00988fa0d57f5086362710897f289264a8ef3 Mon Sep 17 00:00:00 2001 From: Jason Couture Date: Thu, 5 Jan 2023 07:35:23 -0500 Subject: [PATCH 06/17] Make end page exclusive when computing the end page for the ramdisk Co-authored-by: Philipp Oppermann --- common/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index 59d12e35..ad92556f 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -298,7 +298,7 @@ where let physical_address = PhysAddr::new(ramdisk_address); let ramdisk_physical_start_page: PhysFrame = PhysFrame::containing_address(physical_address); - let ramdisk_page_count = system_info.ramdisk_len / Size4KiB::SIZE; + let ramdisk_page_count = (system_info.ramdisk_len - 1) / Size4KiB::SIZE; let ramdisk_physical_end_page = ramdisk_physical_start_page + ramdisk_page_count; let start_page = Page::from_start_address(ramdisk_address_start) .expect("the ramdisk start address must be page aligned"); From 5b5d6499b7e84f2cf4554aaf95722f40a2cbc19e Mon Sep 17 00:00:00 2001 From: Jason Couture Date: Thu, 5 Jan 2023 07:35:43 -0500 Subject: [PATCH 07/17] Remove increased memory used to debug test failures Co-authored-by: Philipp Oppermann --- tests/runner/src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/runner/src/lib.rs b/tests/runner/src/lib.rs index a9c785fc..02c6c5b8 100644 --- a/tests/runner/src/lib.rs +++ b/tests/runner/src/lib.rs @@ -8,8 +8,6 @@ const QEMU_ARGS: &[&str] = &[ "-display", "none", "--no-reboot", - "-m", - "size=2048", ]; pub fn run_test_kernel(kernel_binary_path: &str, ramdisk_path: Option<&str>) { From 0e210cf577fb72ee09d605e0946c92bbba53ef21 Mon Sep 17 00:00:00 2001 From: Jason Couture Date: Thu, 5 Jan 2023 20:36:29 -0500 Subject: [PATCH 08/17] Update test runner to take an Option<&Path> instead of Option<&str> for the ramdisk path Co-authored-by: Philipp Oppermann --- tests/ramdisk.rs | 6 ++++-- tests/runner/src/lib.rs | 6 +----- tests/test_kernels/ramdisk/src/bin/ramdisk.rs | 2 -- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/tests/ramdisk.rs b/tests/ramdisk.rs index 7aa46a1a..a32f0d66 100644 --- a/tests/ramdisk.rs +++ b/tests/ramdisk.rs @@ -1,3 +1,5 @@ +use std::path::Path; + use bootloader_test_runner::run_test_kernel; static RAMDISK_PATH: &str = "tests/ramdisk.txt"; @@ -5,7 +7,7 @@ static RAMDISK_PATH: &str = "tests/ramdisk.txt"; fn basic_boot() { run_test_kernel( env!("CARGO_BIN_FILE_TEST_KERNEL_RAMDISK_basic_boot"), - Some(RAMDISK_PATH), + Some(&Path::new(RAMDISK_PATH)), ); } @@ -13,6 +15,6 @@ fn basic_boot() { fn check_ramdisk() { run_test_kernel( env!("CARGO_BIN_FILE_TEST_KERNEL_RAMDISK_ramdisk"), - Some(RAMDISK_PATH), + Some(&Path::new(RAMDISK_PATH)), ); } diff --git a/tests/runner/src/lib.rs b/tests/runner/src/lib.rs index 02c6c5b8..f5099f79 100644 --- a/tests/runner/src/lib.rs +++ b/tests/runner/src/lib.rs @@ -10,12 +10,8 @@ const QEMU_ARGS: &[&str] = &[ "--no-reboot", ]; -pub fn run_test_kernel(kernel_binary_path: &str, ramdisk_path: Option<&str>) { +pub fn run_test_kernel(kernel_binary_path: &str, ramdisk_path: Option<&Path>) { let kernel_path = Path::new(kernel_binary_path); - let ramdisk_path = match ramdisk_path { - Some(rdp) => Some(Path::new(rdp)), - None => None, - }; // create an MBR disk image for legacy BIOS booting let mbr_path = kernel_path.with_extension("mbr"); diff --git a/tests/test_kernels/ramdisk/src/bin/ramdisk.rs b/tests/test_kernels/ramdisk/src/bin/ramdisk.rs index d64d5592..d2bbda87 100644 --- a/tests/test_kernels/ramdisk/src/bin/ramdisk.rs +++ b/tests/test_kernels/ramdisk/src/bin/ramdisk.rs @@ -27,8 +27,6 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! { #[cfg(not(test))] #[panic_handler] fn panic(info: &core::panic::PanicInfo) -> ! { - use core::fmt::Write; - let _ = writeln!(test_kernel_ramdisk::serial(), "PANIC: {}", info); exit_qemu(QemuExitCode::Failed); } From 9e28603f1479f8409905565b2906c682db088fce Mon Sep 17 00:00:00 2001 From: Jason Couture Date: Thu, 5 Jan 2023 20:50:38 -0500 Subject: [PATCH 09/17] Remove test debugging changes. --- tests/default_settings.rs | 2 +- uefi/src/main.rs | 13 +++++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/tests/default_settings.rs b/tests/default_settings.rs index 4fa5f5f3..5d5a6395 100644 --- a/tests/default_settings.rs +++ b/tests/default_settings.rs @@ -4,7 +4,7 @@ use bootloader_test_runner::run_test_kernel; fn basic_boot() { run_test_kernel( env!("CARGO_BIN_FILE_TEST_KERNEL_DEFAULT_SETTINGS_basic_boot"), - Some("tests/ramdisk.txt"), + None ); } diff --git a/uefi/src/main.rs b/uefi/src/main.rs index b0c3c00b..2baa87ba 100644 --- a/uefi/src/main.rs +++ b/uefi/src/main.rs @@ -72,17 +72,14 @@ fn main_inner(image: Handle, mut st: SystemTable) -> Status { unsafe { *SYSTEM_TABLE.get() = Some(st.unsafe_clone()); } - let stdout = unsafe { &mut *SYSTEM_TABLE.get() }; - let stdout = stdout.as_mut().unwrap(); - let stdout = stdout.stdout(); - stdout.clear().unwrap(); - writeln!(stdout, "UEFI bootloader started; trying to load kernel").unwrap(); + st.stdout().clear().unwrap(); + writeln!(st.stdout(), "UEFI bootloader started; trying to load kernel").unwrap(); let mut boot_mode = BootMode::Disk; let mut kernel = load_kernel(image, &mut st, boot_mode); if kernel.is_none() { writeln!( - stdout, + st.stdout(), "Failed to load kernel via {:?}, trying TFTP", boot_mode ) @@ -92,12 +89,12 @@ fn main_inner(image: Handle, mut st: SystemTable) -> Status { kernel = load_kernel(image, &mut st, boot_mode); } let kernel = kernel.expect("Failed to load kernel"); - writeln!(stdout, "Trying to load ramdisk via {:?}", boot_mode).unwrap(); + writeln!(st.stdout(), "Trying to load ramdisk via {:?}", boot_mode).unwrap(); // Ramdisk must load from same source, or not at all. let ramdisk = load_ramdisk(image, &mut st, boot_mode); writeln!( - stdout, + st.stdout(), "{}", match ramdisk { Some(_) => "Loaded ramdisk", From d5feb67ff0d5a19835afe9f931fe233d992ad321 Mon Sep 17 00:00:00 2001 From: Jason Couture Date: Thu, 5 Jan 2023 20:53:03 -0500 Subject: [PATCH 10/17] Load ramdisk starting at the page following the kernel when booting via bios --- bios/stage-2/src/main.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/bios/stage-2/src/main.rs b/bios/stage-2/src/main.rs index ca439af5..b2bc5e6a 100644 --- a/bios/stage-2/src/main.rs +++ b/bios/stage-2/src/main.rs @@ -27,7 +27,6 @@ const BOOTLOADER_SECOND_STAGE_PARTITION_TYPE: u8 = 0x20; const STAGE_3_DST: *mut u8 = 0x0010_0000 as *mut u8; // 1MiB (typically 14MiB accessible here) const STAGE_4_DST: *mut u8 = 0x0020_0000 as *mut u8; // 2MiB (typically still 13MiB accessible here) const KERNEL_DST: *mut u8 = 0x0100_0000 as *mut u8; // 16MiB -const RAMDISK_DST: *mut u8 = 0x0400_0000 as *mut u8; // 64MiB static mut DISK_BUFFER: AlignedArrayBuffer<0x4000> = AlignedArrayBuffer { buffer: [0; 0x4000], @@ -99,8 +98,10 @@ fn start(disk_number: u16, partition_table_start: *const u8) -> ! { writeln!(screen::Writer, "loading kernel...").unwrap(); let kernel_len = load_file("kernel-x86_64", KERNEL_DST, &mut fs, &mut disk, disk_buffer); writeln!(screen::Writer, "kernel loaded at {KERNEL_DST:#p}").unwrap(); + let kernel_page_size = (((kernel_len - 1) / 4096) + 1) as usize; + let ramdisk_start = KERNEL_DST.wrapping_add(kernel_page_size * 4096); writeln!(screen::Writer, "Loading ramdisk...").unwrap(); - let ramdisk_len = match try_load_file("ramdisk", RAMDISK_DST, &mut fs, &mut disk, disk_buffer) { + let ramdisk_len = match try_load_file("ramdisk", KERNEL_DST.wrapping_add(kernel_page_size * 4096), &mut fs, &mut disk, disk_buffer) { Some(s) => s, None => 0u64, }; @@ -108,7 +109,7 @@ fn start(disk_number: u16, partition_table_start: *const u8) -> ! { if ramdisk_len == 0 { writeln!(screen::Writer, "No ramdisk found, skipping.").unwrap(); } else { - writeln!(screen::Writer, "Loaded ramdisk at {RAMDISK_DST:#p}").unwrap(); + writeln!(screen::Writer, "Loaded ramdisk at {ramdisk_start:#p}").unwrap(); } let memory_map = unsafe { memory_map::query_memory_map() }.unwrap(); @@ -142,7 +143,7 @@ fn start(disk_number: u16, partition_table_start: *const u8) -> ! { len: kernel_len, }, ramdisk: Region { - start: RAMDISK_DST as u64, + start: ramdisk_start as u64, len: ramdisk_len, }, memory_map_addr: memory_map.as_mut_ptr() as u32, From 914d9920532ff9b88fab86e8e45d05f06277e0dc Mon Sep 17 00:00:00 2001 From: Jason Couture Date: Thu, 5 Jan 2023 20:57:30 -0500 Subject: [PATCH 11/17] Base ramdisk presence on length, not address. --- bios/stage-4/src/main.rs | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/bios/stage-4/src/main.rs b/bios/stage-4/src/main.rs index ddc45eef..5fc29933 100644 --- a/bios/stage-4/src/main.rs +++ b/bios/stage-4/src/main.rs @@ -56,22 +56,14 @@ pub extern "C" fn _start(info: &mut BiosInfo) -> ! { PhysAddr::new(info.kernel.start) }; let kernel_size = info.kernel.len; - let mut frame_allocator = if info.ramdisk.start == 0 { - let kernel_end = PhysFrame::containing_address(kernel_start + kernel_size - 1u64); - let next_free = kernel_end + 1; - LegacyFrameAllocator::new_starting_at( - next_free, - memory_map.iter().copied().map(MemoryRegion), - ) - } else { - let ramdisk_end = - PhysFrame::containing_address(PhysAddr::new(info.ramdisk.start + info.ramdisk.len)); - let next_free = ramdisk_end + 1; - LegacyFrameAllocator::new_starting_at( - next_free, - memory_map.iter().copied().map(MemoryRegion), - ) + let next_free_frame = match info.ramdisk.len { + 0 => PhysFrame::containing_address(kernel_start + kernel_size - 1u64) + 1, + _ => PhysFrame::containing_address(PhysAddr::new(info.ramdisk.start + info.ramdisk.len)) + 1 }; + let mut frame_allocator = LegacyFrameAllocator::new_starting_at( + next_free_frame, + memory_map.iter().copied().map(MemoryRegion), + ); // We identity-mapped all memory, so the offset between physical and virtual addresses is 0 let phys_offset = VirtAddr::new(0); From c4f33c996e90190728387120cee843290fa2bf0f Mon Sep 17 00:00:00 2001 From: Jason Couture Date: Thu, 5 Jan 2023 20:58:41 -0500 Subject: [PATCH 12/17] Subtract 1 from ramdisk end address to get an inclusive address --- bios/stage-4/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bios/stage-4/src/main.rs b/bios/stage-4/src/main.rs index 5fc29933..6dbc23f5 100644 --- a/bios/stage-4/src/main.rs +++ b/bios/stage-4/src/main.rs @@ -58,7 +58,7 @@ pub extern "C" fn _start(info: &mut BiosInfo) -> ! { let kernel_size = info.kernel.len; let next_free_frame = match info.ramdisk.len { 0 => PhysFrame::containing_address(kernel_start + kernel_size - 1u64) + 1, - _ => PhysFrame::containing_address(PhysAddr::new(info.ramdisk.start + info.ramdisk.len)) + 1 + _ => PhysFrame::containing_address(PhysAddr::new(info.ramdisk.start + info.ramdisk.len - 1u64)) + 1 }; let mut frame_allocator = LegacyFrameAllocator::new_starting_at( next_free_frame, From 7b40929bcb803e2e284de71a95d8795635fc23e5 Mon Sep 17 00:00:00 2001 From: Jason Couture Date: Thu, 5 Jan 2023 21:12:02 -0500 Subject: [PATCH 13/17] Pass image handle, instead of fetching from boot services. --- uefi/src/main.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uefi/src/main.rs b/uefi/src/main.rs index 2baa87ba..a11f90b7 100644 --- a/uefi/src/main.rs +++ b/uefi/src/main.rs @@ -103,7 +103,7 @@ fn main_inner(image: Handle, mut st: SystemTable) -> Status { ) .unwrap(); - let framebuffer = init_logger(&st, kernel.config); + let framebuffer = init_logger(image, &st, kernel.config); unsafe { *SYSTEM_TABLE.get() = None; } @@ -439,7 +439,7 @@ fn create_page_tables( } } -fn init_logger(st: &SystemTable, config: BootloaderConfig) -> Option { +fn init_logger(image_handle: Handle, st: &SystemTable, config: BootloaderConfig) -> Option { let gop_handle = st .boot_services() .get_handle_for_protocol::() @@ -449,7 +449,7 @@ fn init_logger(st: &SystemTable, config: BootloaderConfig) -> Option( OpenProtocolParams { handle: gop_handle, - agent: st.boot_services().image_handle(), + agent: image_handle, controller: None, }, OpenProtocolAttributes::Exclusive, From 50551badbce6952ea637389dcf2d2246aa5b2c97 Mon Sep 17 00:00:00 2001 From: Jason Couture Date: Thu, 5 Jan 2023 21:16:04 -0500 Subject: [PATCH 14/17] Run cargo fmt --- bios/stage-2/src/main.rs | 8 +++++++- bios/stage-4/src/main.rs | 6 +++++- tests/default_settings.rs | 2 +- uefi/src/main.rs | 12 ++++++++++-- 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/bios/stage-2/src/main.rs b/bios/stage-2/src/main.rs index b2bc5e6a..a1ba8566 100644 --- a/bios/stage-2/src/main.rs +++ b/bios/stage-2/src/main.rs @@ -101,7 +101,13 @@ fn start(disk_number: u16, partition_table_start: *const u8) -> ! { let kernel_page_size = (((kernel_len - 1) / 4096) + 1) as usize; let ramdisk_start = KERNEL_DST.wrapping_add(kernel_page_size * 4096); writeln!(screen::Writer, "Loading ramdisk...").unwrap(); - let ramdisk_len = match try_load_file("ramdisk", KERNEL_DST.wrapping_add(kernel_page_size * 4096), &mut fs, &mut disk, disk_buffer) { + let ramdisk_len = match try_load_file( + "ramdisk", + KERNEL_DST.wrapping_add(kernel_page_size * 4096), + &mut fs, + &mut disk, + disk_buffer, + ) { Some(s) => s, None => 0u64, }; diff --git a/bios/stage-4/src/main.rs b/bios/stage-4/src/main.rs index 6dbc23f5..d9e10843 100644 --- a/bios/stage-4/src/main.rs +++ b/bios/stage-4/src/main.rs @@ -58,7 +58,11 @@ pub extern "C" fn _start(info: &mut BiosInfo) -> ! { let kernel_size = info.kernel.len; let next_free_frame = match info.ramdisk.len { 0 => PhysFrame::containing_address(kernel_start + kernel_size - 1u64) + 1, - _ => PhysFrame::containing_address(PhysAddr::new(info.ramdisk.start + info.ramdisk.len - 1u64)) + 1 + _ => { + PhysFrame::containing_address(PhysAddr::new( + info.ramdisk.start + info.ramdisk.len - 1u64, + )) + 1 + } }; let mut frame_allocator = LegacyFrameAllocator::new_starting_at( next_free_frame, diff --git a/tests/default_settings.rs b/tests/default_settings.rs index 5d5a6395..0a7e6c47 100644 --- a/tests/default_settings.rs +++ b/tests/default_settings.rs @@ -4,7 +4,7 @@ use bootloader_test_runner::run_test_kernel; fn basic_boot() { run_test_kernel( env!("CARGO_BIN_FILE_TEST_KERNEL_DEFAULT_SETTINGS_basic_boot"), - None + None, ); } diff --git a/uefi/src/main.rs b/uefi/src/main.rs index a11f90b7..e6da4b50 100644 --- a/uefi/src/main.rs +++ b/uefi/src/main.rs @@ -73,7 +73,11 @@ fn main_inner(image: Handle, mut st: SystemTable) -> Status { *SYSTEM_TABLE.get() = Some(st.unsafe_clone()); } st.stdout().clear().unwrap(); - writeln!(st.stdout(), "UEFI bootloader started; trying to load kernel").unwrap(); + writeln!( + st.stdout(), + "UEFI bootloader started; trying to load kernel" + ) + .unwrap(); let mut boot_mode = BootMode::Disk; let mut kernel = load_kernel(image, &mut st, boot_mode); @@ -439,7 +443,11 @@ fn create_page_tables( } } -fn init_logger(image_handle: Handle, st: &SystemTable, config: BootloaderConfig) -> Option { +fn init_logger( + image_handle: Handle, + st: &SystemTable, + config: BootloaderConfig, +) -> Option { let gop_handle = st .boot_services() .get_handle_for_protocol::() From d23bd8574477583fef87acad091c23b49d82d4bc Mon Sep 17 00:00:00 2001 From: Jason Couture Date: Wed, 4 Jan 2023 18:53:08 -0500 Subject: [PATCH 15/17] Replace UefiBoot and BiosBoot with DiskImageBuilder --- src/lib.rs | 202 ++++++++++++++++++++-------------------- tests/runner/src/lib.rs | 34 ++++--- 2 files changed, 118 insertions(+), 118 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 907a39ef..c5efedf7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,103 +20,111 @@ const KERNEL_FILE_NAME: &str = "kernel-x86_64"; const RAMDISK_FILE_NAME: &str = "ramdisk"; const BIOS_STAGE_3: &str = "boot-stage-3"; const BIOS_STAGE_4: &str = "boot-stage-4"; +const UEFI_BOOT_FILENAME: &str = "efi/boot/bootx64.efi"; +const UEFI_TFTP_BOOT_FILENAME: &str = "bootloader"; -/// Create disk images for booting on legacy BIOS systems. -pub struct BiosBoot { - kernel: PathBuf, - ramdisk: Option, +struct DiskImageFile<'a> { + source: &'a PathBuf, + destination: &'a str, } -impl BiosBoot { - /// Start creating a disk image for the given bootloader ELF executable. - pub fn new(kernel_path: &Path) -> Self { - Self { - kernel: kernel_path.to_owned(), - ramdisk: None, - } +/// DiskImageBuilder helps create disk images for a specified set of files. +/// It can currently create MBR (BIOS), GPT (UEFI), and TFTP (UEFI) images. +pub struct DiskImageBuilder<'a> { + files: Vec>, +} + +impl<'a> DiskImageBuilder<'a> { + /// Create a new instance of DiskImageBuilder, with the specified kernel. + pub fn new(kernel: &'a PathBuf) -> Self { + let mut obj = Self::empty(); + obj.set_kernel(kernel); + obj + } + + /// Create a new, empty instance of DiskImageBuilder + pub fn empty() -> Self { + Self { files: Vec::new() } + } + /// Add or replace a ramdisk to be included in the final image. + pub fn set_ramdisk(&mut self, path: &'a PathBuf) { + self.add_or_replace_file(path, RAMDISK_FILE_NAME); + } + + /// Add or replace a kernel to be included in the final image. + fn set_kernel(&mut self, path: &'a PathBuf) { + self.add_or_replace_file(path, KERNEL_FILE_NAME) } - /// Add a ramdisk file to the image - pub fn set_ramdisk(&mut self, ramdisk_path: &Path) -> &mut Self { - self.ramdisk = Some(ramdisk_path.to_owned()); - self + /// Add or replace arbitrary files. + /// NOTE: You can overwrite internal files if you choose, such as EFI/BOOT/BOOTX64.EFI + /// This can be useful in situations where you want to generate an image, but not use the provided bootloader. + fn add_or_replace_file(&mut self, path: &'a PathBuf, target: &'a str) { + self.files.insert( + 0, + DiskImageFile::<'a> { + source: &path, + destination: &target, + }, + ); } + fn create_fat_filesystem_image( + &self, + internal_files: BTreeMap<&'a str, &'a Path>, + ) -> anyhow::Result { + let mut local_map = BTreeMap::new(); + + for k in internal_files { + local_map.insert(k.0, k.1); + } + + for f in self.files.as_slice() { + local_map.insert(f.destination, &f.source.as_path()); + } + + let out_file = NamedTempFile::new().context("failed to create temp file")?; + fat::create_fat_filesystem(local_map, out_file.path()) + .context("failed to create BIOS FAT filesystem")?; - /// Create a bootable UEFI disk image at the given path. - pub fn create_disk_image(&self, out_path: &Path) -> anyhow::Result<()> { + Ok(out_file) + } + + /// Create an MBR disk image for booting on BIOS systems. + pub fn create_bios_image(&self, image_filename: &Path) -> anyhow::Result<()> { let bootsector_path = Path::new(env!("BIOS_BOOT_SECTOR_PATH")); let stage_2_path = Path::new(env!("BIOS_STAGE_2_PATH")); + let stage_3_path = Path::new(env!("BIOS_STAGE_3_PATH")); + let stage_4_path = Path::new(env!("BIOS_STAGE_4_PATH")); + let mut internal_files = BTreeMap::new(); + internal_files.insert(BIOS_STAGE_3, stage_3_path); + internal_files.insert(BIOS_STAGE_4, stage_4_path); let fat_partition = self - .create_fat_partition() + .create_fat_filesystem_image(internal_files) .context("failed to create FAT partition")?; - mbr::create_mbr_disk( bootsector_path, stage_2_path, fat_partition.path(), - out_path, + image_filename, ) .context("failed to create BIOS MBR disk image")?; fat_partition .close() .context("failed to delete FAT partition after disk image creation")?; - Ok(()) } - - /// Creates an BIOS-bootable FAT partition with the kernel. - fn create_fat_partition(&self) -> anyhow::Result { - let stage_3_path = Path::new(env!("BIOS_STAGE_3_PATH")); - let stage_4_path = Path::new(env!("BIOS_STAGE_4_PATH")); - let kernel_path = self.kernel.as_path(); - - let mut files = BTreeMap::new(); - files.insert(KERNEL_FILE_NAME, kernel_path); - files.insert(BIOS_STAGE_3, stage_3_path); - files.insert(BIOS_STAGE_4, stage_4_path); - if let Some(ramdisk_path) = &self.ramdisk { - files.insert(RAMDISK_FILE_NAME, ramdisk_path); - } - let out_file = NamedTempFile::new().context("failed to create temp file")?; - fat::create_fat_filesystem(files, out_file.path()) - .context("failed to create BIOS FAT filesystem")?; - - Ok(out_file) - } -} - -/// Create disk images for booting on UEFI systems. -pub struct UefiBoot { - kernel: PathBuf, - ramdisk: Option, -} - -impl UefiBoot { - /// Start creating a disk image for the given bootloader ELF executable. - pub fn new(kernel_path: &Path) -> Self { - Self { - kernel: kernel_path.to_owned(), - ramdisk: None, - } - } - - /// Add a ramdisk file to the disk image - pub fn set_ramdisk(&mut self, ramdisk_path: &Path) -> &mut Self { - self.ramdisk = Some(ramdisk_path.to_owned()); - self - } - - /// Create a bootable UEFI disk image at the given path. - pub fn create_disk_image(&self, out_path: &Path) -> anyhow::Result<()> { + /// Create a GPT disk image for booting on UEFI systems. + pub fn create_uefi_image(&self, image_filename: &Path) -> anyhow::Result<()> { + let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH")); + let mut internal_files = BTreeMap::new(); + internal_files.insert(UEFI_BOOT_FILENAME, bootloader_path); let fat_partition = self - .create_fat_partition() + .create_fat_filesystem_image(internal_files) .context("failed to create FAT partition")?; - - gpt::create_gpt_disk(fat_partition.path(), out_path) + gpt::create_gpt_disk(fat_partition.path(), image_filename) .context("failed to create UEFI GPT disk image")?; - fat_partition .close() .context("failed to delete FAT partition after disk image creation")?; @@ -124,41 +132,29 @@ impl UefiBoot { Ok(()) } - /// Prepare a folder for use with booting over UEFI_PXE. - /// - /// This places the bootloader executable under the path "bootloader". The - /// DHCP server should set the filename option to that path, otherwise the - /// bootloader won't be found. - pub fn create_pxe_tftp_folder(&self, out_path: &Path) -> anyhow::Result<()> { + /// Create a folder containing the needed files for UEFI TFTP/PXE booting. + pub fn create_uefi_tftp_folder(&self, tftp_path: &Path) -> anyhow::Result<()> { let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH")); - let ramdisk_path = self.ramdisk.as_deref(); - pxe::create_uefi_tftp_folder( - bootloader_path, - self.kernel.as_path(), - ramdisk_path, - out_path, - ) - .context("failed to create UEFI PXE tftp folder")?; - - Ok(()) - } - - /// Creates an UEFI-bootable FAT partition with the kernel. - fn create_fat_partition(&self) -> anyhow::Result { - let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH")); - let kernel_path = self.kernel.as_path(); - let mut files = BTreeMap::new(); - files.insert("efi/boot/bootx64.efi", bootloader_path); - files.insert(KERNEL_FILE_NAME, kernel_path); - - if let Some(ramdisk_path) = &self.ramdisk { - files.insert(RAMDISK_FILE_NAME, ramdisk_path); + std::fs::create_dir_all(tftp_path) + .with_context(|| format!("failed to create out dir at {}", tftp_path.display()))?; + + let to = tftp_path.join(UEFI_TFTP_BOOT_FILENAME); + std::fs::copy(bootloader_path, &to).with_context(|| { + format!( + "failed to copy bootloader from {} to {}", + bootloader_path.display(), + to.display() + ) + })?; + + for f in self.files.as_slice() { + let to = tftp_path.join(f.destination); + let result = std::fs::copy(f.source, to); + if result.is_err() { + return Err(anyhow::Error::from(result.unwrap_err())); + } } - let out_file = NamedTempFile::new().context("failed to create temp file")?; - fat::create_fat_filesystem(files, out_file.path()) - .context("failed to create UEFI FAT filesystem")?; - - Ok(out_file) + Ok(()) } } diff --git a/tests/runner/src/lib.rs b/tests/runner/src/lib.rs index f5099f79..2df603ba 100644 --- a/tests/runner/src/lib.rs +++ b/tests/runner/src/lib.rs @@ -1,4 +1,8 @@ -use std::{io::Write, path::Path, process::Command}; +use std::{ + io::Write, + path::{Path, PathBuf}, + process::Command, +}; const QEMU_ARGS: &[&str] = &[ "-device", @@ -11,29 +15,29 @@ const QEMU_ARGS: &[&str] = &[ ]; pub fn run_test_kernel(kernel_binary_path: &str, ramdisk_path: Option<&Path>) { + use bootloader::DiskImageBuilder; let kernel_path = Path::new(kernel_binary_path); + let ramdisk_path_buf = match ramdisk_path { + Some(rdp) => Some(rdp.to_path_buf()), + None => None, + }; + let ramdisk_path_buf = ramdisk_path_buf.as_ref(); // create an MBR disk image for legacy BIOS booting let mbr_path = kernel_path.with_extension("mbr"); - let mut bios_builder = bootloader::BiosBoot::new(kernel_path); - - // create a GPT disk image for UEFI booting let gpt_path = kernel_path.with_extension("gpt"); - let mut uefi_builder = bootloader::UefiBoot::new(kernel_path); + let tftp_path = kernel_path.with_extension("tftp"); + let kernel_path_buf = kernel_path.to_path_buf(); + let mut image_builder = DiskImageBuilder::new(&kernel_path_buf); // Set ramdisk for test, if supplied. - if let Some(rdp) = ramdisk_path { - bios_builder.set_ramdisk(rdp); - uefi_builder.set_ramdisk(rdp); + if let Some(rdp) = ramdisk_path_buf { + image_builder.set_ramdisk(rdp); } - bios_builder.create_disk_image(&mbr_path).unwrap(); - uefi_builder.create_disk_image(&gpt_path).unwrap(); - - // create a TFTP folder with the kernel executable and UEFI bootloader for - // UEFI PXE booting - let tftp_path = kernel_path.with_extension(".tftp"); - uefi_builder.create_pxe_tftp_folder(&tftp_path).unwrap(); + image_builder.create_bios_image(&mbr_path).unwrap(); + image_builder.create_uefi_image(&gpt_path).unwrap(); + image_builder.create_uefi_tftp_folder(&tftp_path).unwrap(); run_test_kernel_on_uefi(&gpt_path); run_test_kernel_on_bios(&mbr_path); From 79c48688337ee4fa4bff771aa8a97cad97209476 Mon Sep 17 00:00:00 2001 From: Jason Couture Date: Wed, 4 Jan 2023 18:53:41 -0500 Subject: [PATCH 16/17] Expose support for changing kernel, and adding arbitrary files --- src/lib.rs | 5 ++--- src/pxe.rs | 43 ------------------------------------------- 2 files changed, 2 insertions(+), 46 deletions(-) delete mode 100644 src/pxe.rs diff --git a/src/lib.rs b/src/lib.rs index c5efedf7..c8045d38 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,6 @@ use tempfile::NamedTempFile; mod fat; mod gpt; mod mbr; -mod pxe; const KERNEL_FILE_NAME: &str = "kernel-x86_64"; const RAMDISK_FILE_NAME: &str = "ramdisk"; @@ -52,14 +51,14 @@ impl<'a> DiskImageBuilder<'a> { } /// Add or replace a kernel to be included in the final image. - fn set_kernel(&mut self, path: &'a PathBuf) { + pub fn set_kernel(&mut self, path: &'a PathBuf) { self.add_or_replace_file(path, KERNEL_FILE_NAME) } /// Add or replace arbitrary files. /// NOTE: You can overwrite internal files if you choose, such as EFI/BOOT/BOOTX64.EFI /// This can be useful in situations where you want to generate an image, but not use the provided bootloader. - fn add_or_replace_file(&mut self, path: &'a PathBuf, target: &'a str) { + pub fn add_or_replace_file(&mut self, path: &'a PathBuf, target: &'a str) { self.files.insert( 0, DiskImageFile::<'a> { diff --git a/src/pxe.rs b/src/pxe.rs deleted file mode 100644 index 84c7b0d6..00000000 --- a/src/pxe.rs +++ /dev/null @@ -1,43 +0,0 @@ -use std::path::Path; - -use anyhow::Context; - -pub fn create_uefi_tftp_folder( - bootloader_path: &Path, - kernel_binary: &Path, - ramdisk_path: Option<&Path>, - out_path: &Path, -) -> anyhow::Result<()> { - std::fs::create_dir_all(out_path) - .with_context(|| format!("failed to create out dir at {}", out_path.display()))?; - - let to = out_path.join("bootloader"); - std::fs::copy(bootloader_path, &to).with_context(|| { - format!( - "failed to copy bootloader from {} to {}", - bootloader_path.display(), - to.display() - ) - })?; - - let to = out_path.join("kernel-x86_64"); - std::fs::copy(kernel_binary, &to).with_context(|| { - format!( - "failed to copy kernel from {} to {}", - kernel_binary.display(), - to.display() - ) - })?; - let to = out_path.join("ramdisk"); - if let Some(rp) = ramdisk_path { - std::fs::copy(rp, &to).with_context(|| { - format!( - "failed to copy ramdisk from {} to {}", - rp.display(), - to.display() - ) - })?; - } - - Ok(()) -} From 9731b6841b5dea10c2629dc085a3832d258e969f Mon Sep 17 00:00:00 2001 From: Jason Couture Date: Wed, 4 Jan 2023 19:26:52 -0500 Subject: [PATCH 17/17] Add pxe, bios, and uefi features, all enabled by default --- Cargo.toml | 11 +++++++-- build.rs | 70 +++++++++++++++++++++++------------------------------- src/lib.rs | 16 +++++++++---- 3 files changed, 50 insertions(+), 47 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3046cec0..b2c31837 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,12 +38,19 @@ bootloader_api = { version = "0.11.0", path = "api" } bootloader-x86_64-common = { version = "0.11.0", path = "common" } bootloader-x86_64-bios-common = { version = "0.11.0", path = "bios/common" } + +[features] +default = ["bios", "uefi", "pxe"] +bios = ["mbrman"] +uefi = ["gpt"] +pxe = ["uefi"] + [dependencies] anyhow = "1.0.32" fatfs = "0.3.4" -gpt = "3.0.0" -mbrman = "0.5.1" tempfile = "3.3.0" +mbrman = { version = "0.5.1", optional = true } +gpt = { version = "3.0.0", optional = true } [dev-dependencies] bootloader_test_runner = { path = "tests/runner" } diff --git a/build.rs b/build.rs index 7f04dddf..bb2118c0 100644 --- a/build.rs +++ b/build.rs @@ -3,22 +3,28 @@ use std::{ process::Command, }; -const BOOTLOADER_X86_64_UEFI_VERSION: &str = env!("CARGO_PKG_VERSION"); - -const BOOTLOADER_X86_64_BIOS_BOOT_SECTOR_VERSION: &str = env!("CARGO_PKG_VERSION"); -const BOOTLOADER_X86_64_BIOS_STAGE_2_VERSION: &str = env!("CARGO_PKG_VERSION"); -const BOOTLOADER_X86_64_BIOS_STAGE_3_VERSION: &str = env!("CARGO_PKG_VERSION"); -const BOOTLOADER_X86_64_BIOS_STAGE_4_VERSION: &str = env!("CARGO_PKG_VERSION"); +const BOOTLOADER_VERSION: &str = env!("CARGO_PKG_VERSION"); fn main() { - let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); + #[cfg(any(feature = "uefi", feature = "pxe"))] + uefi_main(); + #[cfg(feature = "bios")] + bios_main(); +} +#[cfg(all(any(feature = "uefi", feature = "pxe"), not(docsrs_dummy_build)))] +fn uefi_main() { + let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); let uefi_path = build_uefi_bootloader(&out_dir); println!( "cargo:rustc-env=UEFI_BOOTLOADER_PATH={}", uefi_path.display() ); +} +#[cfg(all(feature = "bios", not(docsrs_dummy_build)))] +fn bios_main() { + let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); let bios_boot_sector_path = build_bios_boot_sector(&out_dir); println!( "cargo:rustc-env=BIOS_BOOT_SECTOR_PATH={}", @@ -43,7 +49,7 @@ fn main() { ); } -#[cfg(not(docsrs_dummy_build))] +#[cfg(all(any(feature = "uefi", feature = "pxe"), not(docsrs_dummy_build)))] fn build_uefi_bootloader(out_dir: &Path) -> PathBuf { let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()); let mut cmd = Command::new(cargo); @@ -53,7 +59,7 @@ fn build_uefi_bootloader(out_dir: &Path) -> PathBuf { cmd.arg("--path").arg("uefi"); println!("cargo:rerun-if-changed=uefi"); } else { - cmd.arg("--version").arg(BOOTLOADER_X86_64_UEFI_VERSION); + cmd.arg("--version").arg(BOOTLOADER_VERSION); } cmd.arg("--locked"); cmd.arg("--target").arg("x86_64-unknown-uefi"); @@ -77,7 +83,7 @@ fn build_uefi_bootloader(out_dir: &Path) -> PathBuf { } } -#[cfg(not(docsrs_dummy_build))] +#[cfg(all(feature = "bios", not(docsrs_dummy_build)))] fn build_bios_boot_sector(out_dir: &Path) -> PathBuf { let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()); let mut cmd = Command::new(cargo); @@ -90,8 +96,7 @@ fn build_bios_boot_sector(out_dir: &Path) -> PathBuf { cmd.arg("--path").arg(&local_path); println!("cargo:rerun-if-changed={}", local_path.display()); } else { - cmd.arg("--version") - .arg(BOOTLOADER_X86_64_BIOS_BOOT_SECTOR_VERSION); + cmd.arg("--version").arg(BOOTLOADER_VERSION); } cmd.arg("--locked"); cmd.arg("--target").arg("i386-code16-boot-sector.json"); @@ -120,7 +125,7 @@ fn build_bios_boot_sector(out_dir: &Path) -> PathBuf { convert_elf_to_bin(elf_path) } -#[cfg(not(docsrs_dummy_build))] +#[cfg(all(feature = "bios", not(docsrs_dummy_build)))] fn build_bios_stage_2(out_dir: &Path) -> PathBuf { let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()); let mut cmd = Command::new(cargo); @@ -133,8 +138,7 @@ fn build_bios_stage_2(out_dir: &Path) -> PathBuf { cmd.arg("--path").arg(&local_path); println!("cargo:rerun-if-changed={}", local_path.display()); } else { - cmd.arg("--version") - .arg(BOOTLOADER_X86_64_BIOS_STAGE_2_VERSION); + cmd.arg("--version").arg(BOOTLOADER_VERSION); } cmd.arg("--locked"); cmd.arg("--target").arg("i386-code16-stage-2.json"); @@ -161,7 +165,7 @@ fn build_bios_stage_2(out_dir: &Path) -> PathBuf { convert_elf_to_bin(elf_path) } -#[cfg(not(docsrs_dummy_build))] +#[cfg(all(feature = "bios", not(docsrs_dummy_build)))] fn build_bios_stage_3(out_dir: &Path) -> PathBuf { let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()); let mut cmd = Command::new(cargo); @@ -174,8 +178,7 @@ fn build_bios_stage_3(out_dir: &Path) -> PathBuf { cmd.arg("--path").arg(&local_path); println!("cargo:rerun-if-changed={}", local_path.display()); } else { - cmd.arg("--version") - .arg(BOOTLOADER_X86_64_BIOS_STAGE_3_VERSION); + cmd.arg("--version").arg(BOOTLOADER_VERSION); } cmd.arg("--locked"); cmd.arg("--target").arg("i686-stage-3.json"); @@ -202,7 +205,7 @@ fn build_bios_stage_3(out_dir: &Path) -> PathBuf { convert_elf_to_bin(elf_path) } -#[cfg(not(docsrs_dummy_build))] +#[cfg(all(feature = "bios", not(docsrs_dummy_build)))] fn build_bios_stage_4(out_dir: &Path) -> PathBuf { let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()); let mut cmd = Command::new(cargo); @@ -215,8 +218,7 @@ fn build_bios_stage_4(out_dir: &Path) -> PathBuf { cmd.arg("--path").arg(&local_path); println!("cargo:rerun-if-changed={}", local_path.display()); } else { - cmd.arg("--version") - .arg(BOOTLOADER_X86_64_BIOS_STAGE_4_VERSION); + cmd.arg("--version").arg(BOOTLOADER_VERSION); } cmd.arg("--locked"); cmd.arg("--target").arg("x86_64-stage-4.json"); @@ -244,6 +246,7 @@ fn build_bios_stage_4(out_dir: &Path) -> PathBuf { convert_elf_to_bin(elf_path) } +#[cfg(all(feature = "bios", not(docsrs_dummy_build)))] fn convert_elf_to_bin(elf_path: PathBuf) -> PathBuf { let flat_binary_path = elf_path.with_extension("bin"); @@ -273,23 +276,10 @@ fn convert_elf_to_bin(elf_path: PathBuf) -> PathBuf { // dummy implementations because docsrs builds have no network access -#[cfg(docsrs_dummy_build)] -fn build_uefi_bootloader(_out_dir: &Path) -> PathBuf { - PathBuf::new() -} -#[cfg(docsrs_dummy_build)] -fn build_bios_boot_sector(_out_dir: &Path) -> PathBuf { - PathBuf::new() -} -#[cfg(docsrs_dummy_build)] -fn build_bios_stage_2(_out_dir: &Path) -> PathBuf { - PathBuf::new() -} -#[cfg(docsrs_dummy_build)] -fn build_bios_stage_3(_out_dir: &Path) -> PathBuf { - PathBuf::new() -} -#[cfg(docsrs_dummy_build)] -fn build_bios_stage_4(_out_dir: &Path) -> PathBuf { - PathBuf::new() +#[cfg(all(feature = "uefi", docsrs_dummy_build))] +fn uefi_main() { + // stub } + +#[cfg(all(feature = "bios", docsrs_dummy_build))] +fn bios_main() {} diff --git a/src/lib.rs b/src/lib.rs index c8045d38..4f172791 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,15 +12,14 @@ use std::{ use tempfile::NamedTempFile; mod fat; +#[cfg(feature = "uefi")] mod gpt; + +#[cfg(feature = "bios")] mod mbr; const KERNEL_FILE_NAME: &str = "kernel-x86_64"; const RAMDISK_FILE_NAME: &str = "ramdisk"; -const BIOS_STAGE_3: &str = "boot-stage-3"; -const BIOS_STAGE_4: &str = "boot-stage-4"; -const UEFI_BOOT_FILENAME: &str = "efi/boot/bootx64.efi"; -const UEFI_TFTP_BOOT_FILENAME: &str = "bootloader"; struct DiskImageFile<'a> { source: &'a PathBuf, @@ -87,9 +86,11 @@ impl<'a> DiskImageBuilder<'a> { Ok(out_file) } - + #[cfg(feature = "bios")] /// Create an MBR disk image for booting on BIOS systems. pub fn create_bios_image(&self, image_filename: &Path) -> anyhow::Result<()> { + const BIOS_STAGE_3: &str = "boot-stage-3"; + const BIOS_STAGE_4: &str = "boot-stage-4"; let bootsector_path = Path::new(env!("BIOS_BOOT_SECTOR_PATH")); let stage_2_path = Path::new(env!("BIOS_STAGE_2_PATH")); let stage_3_path = Path::new(env!("BIOS_STAGE_3_PATH")); @@ -114,8 +115,11 @@ impl<'a> DiskImageBuilder<'a> { .context("failed to delete FAT partition after disk image creation")?; Ok(()) } + + #[cfg(feature = "uefi")] /// Create a GPT disk image for booting on UEFI systems. pub fn create_uefi_image(&self, image_filename: &Path) -> anyhow::Result<()> { + const UEFI_BOOT_FILENAME: &str = "efi/boot/bootx64.efi"; let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH")); let mut internal_files = BTreeMap::new(); internal_files.insert(UEFI_BOOT_FILENAME, bootloader_path); @@ -131,8 +135,10 @@ impl<'a> DiskImageBuilder<'a> { Ok(()) } + #[cfg(all(feature = "uefi", feature = "pxe"))] /// Create a folder containing the needed files for UEFI TFTP/PXE booting. pub fn create_uefi_tftp_folder(&self, tftp_path: &Path) -> anyhow::Result<()> { + const UEFI_TFTP_BOOT_FILENAME: &str = "bootloader"; let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH")); std::fs::create_dir_all(tftp_path) .with_context(|| format!("failed to create out dir at {}", tftp_path.display()))?;