From 3115471b62c26e707cb0a2262db95d17ae5c0ab0 Mon Sep 17 00:00:00 2001 From: Matt Taylor Date: Mon, 5 Aug 2019 22:01:07 +0100 Subject: [PATCH 1/5] Parse Cargo.toml from KERNEL_MANIFEST env var --- Cargo.lock | 16 +++++++ Cargo.toml | 3 +- build.rs | 136 +++++++++++++++++++++++++++++++++++++++-------------- 3 files changed, 120 insertions(+), 35 deletions(-) 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/build.rs b/build.rs index 6d79b5c1..79f1d8d4 100644 --- a/build.rs +++ b/build.rs @@ -10,35 +10,71 @@ fn main() { compile_error!("This crate only supports the x86_64 architecture."); } +#[derive(Default)] +struct BootloaderConfig { + physical_memory_offset: Option, + kernel_stack_address: Option, + kernel_stack_size: Option, +} + #[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 - )); +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) + }; + + let num = num.expect(&format!( + "`{}` in the kernel manifest must be an integer (is `{}`)", + key, value + )); - if aligned && num % 0x1000 != 0 { + 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)); + } + ("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 +83,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 +222,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 PLACEHOLDER. 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 +284,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"); } From e379504fae6e9eb6c7891f853f932c175e46c298 Mon Sep 17 00:00:00 2001 From: Matt Taylor Date: Mon, 5 Aug 2019 23:22:18 +0100 Subject: [PATCH 2/5] Add documentation in README --- README.md | 35 ++++++++++++++++++++++++++++++----- build.rs | 7 +++++++ 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a9c48327..279186dc 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 PLACEHOLDER**. 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/build.rs b/build.rs index 79f1d8d4..9f512e6e 100644 --- a/build.rs +++ b/build.rs @@ -59,7 +59,14 @@ fn parse_to_config(cfg: &mut BootloaderConfig, table: &toml::value::Table) { cfg.kernel_stack_address = Some(parse_aligned_addr(key.as_str(), &s)); } ("physical-memory-offset", Value::String(s)) => { + #[cfg(feature = "map_physical_memory")] cfg.physical_memory_offset = Some(parse_aligned_addr(key.as_str(), &s)); + + #[cfg(not(feature = "map_physical_memory"))] + panic!( + "`physical-memory-offset` is only supported when the `map_physical_memory` \ + feature of the crate is enabled" + ); } ("kernel-stack-size", Value::Integer(i)) => { if i <= 0 { From 51a3223fbd264ba580a854a492d7adae85488c98 Mon Sep 17 00:00:00 2001 From: Matt Taylor Date: Mon, 5 Aug 2019 23:44:38 +0100 Subject: [PATCH 3/5] Fix attribute on expression error --- build.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/build.rs b/build.rs index 9f512e6e..450ed17e 100644 --- a/build.rs +++ b/build.rs @@ -60,13 +60,17 @@ fn parse_to_config(cfg: &mut BootloaderConfig, table: &toml::value::Table) { } ("physical-memory-offset", Value::String(s)) => { #[cfg(feature = "map_physical_memory")] - cfg.physical_memory_offset = Some(parse_aligned_addr(key.as_str(), &s)); + { + cfg.physical_memory_offset = Some(parse_aligned_addr(key.as_str(), &s)); + } #[cfg(not(feature = "map_physical_memory"))] - panic!( - "`physical-memory-offset` is only supported when the `map_physical_memory` \ - feature of the crate is enabled" - ); + { + panic!( + "`physical-memory-offset` is only supported when the `map_physical_memory` \ + feature of the crate is enabled" + ); + } } ("kernel-stack-size", Value::Integer(i)) => { if i <= 0 { From be55b0906eddbb8e95dd445757089cc97a334e1f Mon Sep 17 00:00:00 2001 From: Matt Taylor Date: Tue, 6 Aug 2019 16:40:19 +0100 Subject: [PATCH 4/5] Remove bootimage version placeholders --- README.md | 2 +- azure-pipelines.yml | 4 +++- build.rs | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 279186dc..510c65f6 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ You need a nightly [Rust](https://www.rust-lang.org) compiler and [cargo xbuild] ## 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 PLACEHOLDER**. 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 two environment variables: * `KERNEL`: points to your kernel executable (in the ELF format) 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 450ed17e..fd73ce51 100644 --- a/build.rs +++ b/build.rs @@ -239,7 +239,7 @@ fn main() { 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 PLACEHOLDER. You can \ + 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(_)) => { From fcc50da2805784945215b12d9de9eb3a1cac1b16 Mon Sep 17 00:00:00 2001 From: Matt Taylor Date: Tue, 6 Aug 2019 17:34:39 +0100 Subject: [PATCH 5/5] Fix warnings --- build.rs | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/build.rs b/build.rs index fd73ce51..a83dc9a2 100644 --- a/build.rs +++ b/build.rs @@ -10,6 +10,7 @@ fn main() { compile_error!("This crate only supports the x86_64 architecture."); } +#[cfg(feature = "binary")] #[derive(Default)] struct BootloaderConfig { physical_memory_offset: Option, @@ -58,19 +59,16 @@ fn parse_to_config(cfg: &mut BootloaderConfig, table: &toml::value::Table) { ("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(feature = "map_physical_memory")] - { - cfg.physical_memory_offset = Some(parse_aligned_addr(key.as_str(), &s)); - } - - #[cfg(not(feature = "map_physical_memory"))] - { - panic!( - "`physical-memory-offset` is only supported when the `map_physical_memory` \ - feature of the crate is enabled" - ); - } + cfg.physical_memory_offset = Some(parse_aligned_addr(key.as_str(), &s)); } ("kernel-stack-size", Value::Integer(i)) => { if i <= 0 {