Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parse bootloader configuration from kernel's Cargo.toml #73

Merged
merged 5 commits into from
Aug 6, 2019
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 = []
35 changes: 30 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -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:
4 changes: 3 additions & 1 deletion azure-pipelines.yml
Original file line number Diff line number Diff line change
@@ -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'
145 changes: 111 additions & 34 deletions build.rs
Original file line number Diff line number Diff line change
@@ -11,34 +11,79 @@ fn main() {
}

#[cfg(feature = "binary")]
fn num_from_env(env: &'static str, aligned: bool) -> Option<u64> {
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<u64>,
kernel_stack_address: Option<u64>,
kernel_stack_size: Option<u64>,
}

#[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<u64> {
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::<Value>()
.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<T> 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<u64> = {:?};
const KERNEL_STACK_ADDRESS: Option<u64> = {:?};
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");
}