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
Show file tree
Hide file tree
Changes from 3 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
Expand Up @@ -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 = []
Expand Down
35 changes: 30 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
64 marked this conversation as resolved.
Show resolved Hide resolved

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:
Expand All @@ -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.
Expand Down Expand Up @@ -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:
Expand Down
145 changes: 112 additions & 33 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,35 +10,82 @@ fn main() {
compile_error!("This crate only supports the x86_64 architecture.");
}

#[derive(Default)]
struct BootloaderConfig {
physical_memory_offset: Option<u64>,
kernel_stack_address: Option<u64>,
kernel_stack_size: Option<u64>,
}

#[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
));
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(feature = "map_physical_memory")]
{
cfg.physical_memory_offset = Some(parse_aligned_addr(key.as_str(), &s));
}

Some(num)
#[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 {
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
);
}
}
}
}
Expand All @@ -47,11 +94,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)
Expand Down Expand Up @@ -185,22 +233,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 \
64 marked this conversation as resolved.
Show resolved Hide resolved
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(),
)
Expand All @@ -214,9 +295,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");
}