diff --git a/Cargo.lock b/Cargo.lock index 725654b4..ba3e8d56 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,6 +25,7 @@ dependencies = [ "fixedvec 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "font8x8 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", "llvm-tools 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "toml 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "usize_conversions 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "x86_64 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", "xmas-elf 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -55,6 +56,19 @@ name = "nodrop" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "serde" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "toml" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde 1.0.98 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "usize_conversions" version = "0.2.0" @@ -99,6 +113,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum font8x8 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "44226c40489fb1d602344a1d8f1b544570c3435e396dda1eda7b5ef010d8f1be" "checksum llvm-tools 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "955be5d0ca0465caf127165acb47964f911e2bc26073e865deb8be7189302faf" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" +"checksum serde 1.0.98 (registry+https://github.com/rust-lang/crates.io-index)" = "7fe5626ac617da2f2d9c48af5515a21d5a480dbd151e01bb1c355e26a3e68113" +"checksum toml 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b8c96d7873fa7ef8bdeb3a9cda3ac48389b4154f32b9803b4bc26220b677b039" "checksum usize_conversions 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f70329e2cbe45d6c97a5112daad40c34cd9a4e18edb5a2a18fefeb584d8d25e5" "checksum ux 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "88dfeb711b61ce620c0cb6fd9f8e3e678622f0c971da2a63c4b3e25e88ed012f" "checksum x86_64 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1ad37c1665071808d64e65f7cdae32afcdc90fd7ae7fa402bbda36b824f1add6" diff --git a/Cargo.toml b/Cargo.toml index c20df3c8..1c2804b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,10 +26,11 @@ optional = true [build-dependencies] llvm-tools = { version = "0.1", optional = true } +toml = { version = "0.5.1", optional = true } [features] default = [] -binary = ["xmas-elf", "x86_64", "usize_conversions", "fixedvec", "llvm-tools"] +binary = ["xmas-elf", "x86_64", "usize_conversions", "fixedvec", "llvm-tools", "toml"] vga_320x200 = ["font8x8"] recursive_page_table = [] map_physical_memory = [] diff --git a/README.md b/README.md index a9c48327..510c65f6 100644 --- a/README.md +++ b/README.md @@ -10,18 +10,43 @@ Written for the [second edition](https://github.com/phil-opp/blog_os/issues/360) TODO +## Configuration + +The bootloader exposes a few variables which can be configured through the `Cargo.toml` of your kernel: + +```toml +[package.metadata.bootloader] +# The address at which the kernel stack is placed. If not provided, the bootloader +# dynamically searches for a location. +kernel-stack-address = "0xFFFFFF8000000000" + +# The size of the kernel stack, given in number of 4KiB pages. Defaults to 512. +kernel-stack-size = 128 + +# The virtual address offset from which physical memory is mapped, as described in +# https://os.phil-opp.com/paging-implementation/#map-the-complete-physical-memory +# Only applies if the `map_physical_memory` feature of the crate is enabled. +# If not provided, the bootloader dynamically searches for a location. +physical-memory-offset = "0xFFFF800000000000" +``` + +Note that the addresses **must** be given as strings (in either hex or decimal format), as [TOML](https://github.com/toml-lang/toml) does not support unsigned 64-bit integers. + ## Requirements You need a nightly [Rust](https://www.rust-lang.org) compiler and [cargo xbuild](https://github.com/rust-osdev/cargo-xbuild). You also need the `llvm-tools-preview` component, which can be installed through `rustup component add llvm-tools-preview`. ## Build -The simplest way to use the bootloader is in combination with the [bootimage](https://github.com/rust-osdev/bootimage) tool. This crate **requires at least bootimage 0.7.6**. With the tool installed, you can add a normal cargo dependency on the `bootloader` crate to your kernel and then run `bootimage build` to create a bootable disk image. You can also execute `bootimage run` to run your kernel in [QEMU](https://www.qemu.org/) (needs to be installed). +The simplest way to use the bootloader is in combination with the [bootimage](https://github.com/rust-osdev/bootimage) tool. This crate **requires at least bootimage 0.7.7**. With the tool installed, you can add a normal cargo dependency on the `bootloader` crate to your kernel and then run `bootimage build` to create a bootable disk image. You can also execute `bootimage run` to run your kernel in [QEMU](https://www.qemu.org/) (needs to be installed). -To compile the bootloader manually, you need to invoke `cargo xbuild` with a `KERNEL` environment variable that points to your kernel executable (in the ELF format): +To compile the bootloader manually, you need to invoke `cargo xbuild` with two environment variables: +* `KERNEL`: points to your kernel executable (in the ELF format) +* `KERNEL_MANIFEST`: points to the `Cargo.toml` describing your kernel +For example: ``` -KERNEL=/path/to/your/kernel/target/debug/your_kernel cargo xbuild +KERNEL=/path/to/your/kernel/target/debug/your_kernel KERNEL_MANIFEST=/path/to/your/kernel/Cargo.toml cargo xbuild ``` As an example, you can build the bootloader with example kernel from the `example-kernel` directory with the following commands: @@ -30,7 +55,7 @@ As an example, you can build the bootloader with example kernel from the `exampl cd example-kernel cargo xbuild cd .. -KERNEL=example-kernel/target/x86_64-example-kernel/debug/example-kernel cargo xbuild --release --features binary +KERNEL=example-kernel/target/x86_64-example-kernel/debug/example-kernel KERNEL_MANIFEST=example-kernel/Cargo.toml cargo xbuild --release --features binary ``` The `binary` feature is required to enable the dependencies required for compiling the bootloader executable. The command results in a bootloader executable at `target/x86_64-bootloader.json/release/bootloader`. This executable is still an ELF file, which can't be run directly. @@ -64,7 +89,7 @@ The bootloader crate can be configured through some cargo features: - `vga_320x200`: This feature switches the VGA hardware to mode 0x13, a graphics mode with resolution 320x200 and 256 colors per pixel. The framebuffer is linear and lives at address `0xa0000`. - `recursive_page_table`: Maps the level 4 page table recursively and adds the [`recursive_page_table_address`](https://docs.rs/bootloader/0.4.0/bootloader/bootinfo/struct.BootInfo.html#structfield.recursive_page_table_addr) field to the passed `BootInfo`. - `map_physical_memory`: Maps the complete physical memory in the virtual address space and passes a [`physical_memory_offset`](https://docs.rs/bootloader/0.4.0/bootloader/bootinfo/struct.BootInfo.html#structfield.physical_memory_offset) field in the `BootInfo`. - - The virtual address where the physical memory should be mapped is configurable by setting the `BOOTLOADER_PHYSICAL_MEMORY_OFFSET` environment variable (supports decimal and hex numbers (prefixed with `0x`)). + - The virtual address where the physical memory should be mapped is configurable by setting the `physical-memory-offset` field in the kernel's `Cargo.toml`, as explained in [Configuration](#Configuration). ## Advanced Documentation See these guides for advanced usage of this crate: diff --git a/azure-pipelines.yml b/azure-pipelines.yml index f989c5f6..6a6fa827 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -96,7 +96,9 @@ steps: - script: cargo xbuild --bin bootloader --features binary --release displayName: 'Build Bootloader' - env: { KERNEL: "test-kernel/target/x86_64-test-kernel/debug/test-kernel" } + env: + KERNEL: "test-kernel/target/x86_64-test-kernel/debug/test-kernel" + KERNEL_MANIFEST: "test-kernel/Cargo.toml" - script: cargo objcopy -- -I elf64-x86-64 -O binary --binary-architecture=i386:x86-64 target/x86_64-bootloader/release/bootloader target/x86_64-bootloader/release/bootloader.bin displayName: 'Convert Bootloader ELF to Binary' diff --git a/build.rs b/build.rs index 6d79b5c1..a83dc9a2 100644 --- a/build.rs +++ b/build.rs @@ -11,34 +11,79 @@ fn main() { } #[cfg(feature = "binary")] -fn num_from_env(env: &'static str, aligned: bool) -> Option { - use std::env; - match env::var(env) { - Err(env::VarError::NotPresent) => None, - Err(env::VarError::NotUnicode(_)) => { - panic!("The `{}` environment variable must be valid unicode", env,) - } - Ok(s) => { - let num = if s.starts_with("0x") { - u64::from_str_radix(&s[2..], 16) - } else { - u64::from_str_radix(&s, 10) - }; - - let num = num.expect(&format!( - "The `{}` environment variable must be an integer\ - (is `{}`).", - env, s - )); +#[derive(Default)] +struct BootloaderConfig { + physical_memory_offset: Option, + kernel_stack_address: Option, + kernel_stack_size: Option, +} + +#[cfg(feature = "binary")] +fn parse_aligned_addr(key: &str, value: &str) -> u64 { + let num = if value.starts_with("0x") { + u64::from_str_radix(&value[2..], 16) + } else { + u64::from_str_radix(&value, 10) + }; - if aligned && num % 0x1000 != 0 { + let num = num.expect(&format!( + "`{}` in the kernel manifest must be an integer (is `{}`)", + key, value + )); + + if num % 0x1000 != 0 { + panic!( + "`{}` in the kernel manifest must be aligned to 4KiB (is `{}`)", + key, value + ); + } else { + num + } +} + +#[cfg(feature = "binary")] +fn parse_to_config(cfg: &mut BootloaderConfig, table: &toml::value::Table) { + use toml::Value; + + for (key, value) in table { + match (key.as_str(), value.clone()) { + ("kernel-stack-address", Value::Integer(i)) + | ("physical-memory-offset", Value::Integer(i)) => { panic!( - "The `{}` environment variable must be aligned to 0x1000 (is `{:#x}`).", - env, num + "`{0}` in the kernel manifest must be given as a string, \ + as toml does not support unsigned 64-bit integers (try `{0} = \"{1}\"`)", + key.as_str(), + i + ); + } + ("kernel-stack-address", Value::String(s)) => { + cfg.kernel_stack_address = Some(parse_aligned_addr(key.as_str(), &s)); + } + #[cfg(not(feature = "map_physical_memory"))] + ("physical-memory-offset", Value::String(_)) => { + panic!( + "`physical-memory-offset` is only supported when the `map_physical_memory` \ + feature of the crate is enabled" + ); + } + #[cfg(feature = "map_physical_memory")] + ("physical-memory-offset", Value::String(s)) => { + cfg.physical_memory_offset = Some(parse_aligned_addr(key.as_str(), &s)); + } + ("kernel-stack-size", Value::Integer(i)) => { + if i <= 0 { + panic!("`kernel-stack-size` in kernel manifest must be positive"); + } else { + cfg.kernel_stack_size = Some(i as u64); + } + } + (s, _) => { + panic!( + "unknown key '{}' in kernel manifest \ + - you may need to update the bootloader crate", + s ); } - - Some(num) } } } @@ -47,11 +92,12 @@ fn num_from_env(env: &'static str, aligned: bool) -> Option { fn main() { use std::{ env, - fs::File, + fs::{self, File}, io::Write, path::{Path, PathBuf}, process::{self, Command}, }; + use toml::Value; let target = env::var("TARGET").expect("TARGET not set"); if Path::new(&target) @@ -185,22 +231,55 @@ fn main() { process::exit(1); } + // Parse the kernel's Cargo.toml which is given to us by bootimage + let mut bootloader_config = BootloaderConfig::default(); + + match env::var("KERNEL_MANIFEST") { + Err(env::VarError::NotPresent) => { + panic!("The KERNEL_MANIFEST environment variable must be set for building the bootloader.\n\n\ + If you use `bootimage` for building you need at least version 0.7.7. You can \ + update `bootimage` by running `cargo install bootimage --force`."); + } + Err(env::VarError::NotUnicode(_)) => { + panic!("The KERNEL_MANIFEST environment variable contains invalid unicode") + } + Ok(path) => { + println!("cargo:rerun-if-changed={}", path); + + let contents = fs::read_to_string(&path).expect(&format!( + "failed to read kernel manifest file (path: {})", + path + )); + + let manifest = contents + .parse::() + .expect("failed to parse kernel's Cargo.toml"); + + let table = manifest + .get("package") + .and_then(|table| table.get("metadata")) + .and_then(|table| table.get("bootloader")) + .and_then(|table| table.as_table()); + + if let Some(table) = table { + parse_to_config(&mut bootloader_config, table); + } + } + } + // Configure constants for the bootloader // We leave some variables as Option rather than hardcoding their defaults so that they // can be calculated dynamically by the bootloader. let file_path = out_dir.join("bootloader_config.rs"); let mut file = File::create(file_path).expect("failed to create bootloader_config.rs"); - let physical_memory_offset = num_from_env("BOOTLOADER_PHYSICAL_MEMORY_OFFSET", true); - let kernel_stack_address = num_from_env("BOOTLOADER_KERNEL_STACK_ADDRESS", true); - let kernel_stack_size = num_from_env("BOOTLOADER_KERNEL_STACK_SIZE", false); file.write_all( format!( "const PHYSICAL_MEMORY_OFFSET: Option = {:?}; const KERNEL_STACK_ADDRESS: Option = {:?}; const KERNEL_STACK_SIZE: u64 = {};", - physical_memory_offset, - kernel_stack_address, - kernel_stack_size.unwrap_or(512), // size is in number of pages + bootloader_config.physical_memory_offset, + bootloader_config.kernel_stack_address, + bootloader_config.kernel_stack_size.unwrap_or(512), // size is in number of pages ) .as_bytes(), ) @@ -214,9 +293,7 @@ fn main() { ); println!("cargo:rerun-if-env-changed=KERNEL"); - println!("cargo:rerun-if-env-changed=BOOTLOADER_PHYSICAL_MEMORY_OFFSET"); - println!("cargo:rerun-if-env-changed=BOOTLOADER_KERNEL_STACK_ADDRESS"); - println!("cargo:rerun-if-env-changed=BOOTLOADER_KERNEL_STACK_SIZE"); + println!("cargo:rerun-if-env-changed=KERNEL_MANIFEST"); println!("cargo:rerun-if-changed={}", kernel.display()); println!("cargo:rerun-if-changed=build.rs"); }