Skip to content

Commit

Permalink
Turn the standalone kernel into a library (#697)
Browse files Browse the repository at this point in the history
* Turn the standalone kernel into a library

* Fix kernel

* Rustfmt

* Typos and style

* Replace TODO with another
  • Loading branch information
tomaka authored Jan 1, 2021
1 parent dd3b7f1 commit 489f892
Show file tree
Hide file tree
Showing 10 changed files with 264 additions and 213 deletions.
5 changes: 4 additions & 1 deletion core-proc-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,10 @@ pub fn build_wasm_module(tokens: proc_macro::TokenStream) -> proc_macro::TokenSt
// TODO: this is missing Cargo.tomls and stuff I think
list_iter
.filter_map(|file| {
if Path::new(file).exists() {
if !Path::new(file).is_absolute() {
// TODO: relative paths cause issues ; figure out why some paths are relative
None
} else if Path::new(file).exists() {
// TODO: figure out why some files are missing
Some(file.to_owned())
} else {
Expand Down
10 changes: 10 additions & 0 deletions kernel/standalone-builder/Cargo.lock

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

1 change: 1 addition & 0 deletions kernel/standalone-builder/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ serde_json = "1.0.61"
structopt = "0.3.21"
tempdir = "0.3.7"
thiserror = "1.0"
toml = "0.5.8"
walkdir = "2.3.1"

[profile.dev]
Expand Down
10 changes: 8 additions & 2 deletions kernel/standalone-builder/src/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,13 @@ use structopt::StructOpt;
enum CliOptions {
/// Builds and runs the kernel in an emulator.
EmulatorRun {
/// Location of the Cargo.toml of the standalone kernel.
/// Location of the Cargo.toml of the standalone kernel library.
///
/// If no value is passed, this the file structure is the one of the upstream repository
/// and try to find the path in a sibling directory.
///
/// It is intended that in the future this can be substituted with the path to a build
/// directory, in which case the standalone kernel library gets fetched from crates.io.
#[structopt(long, parse(from_os_str))]
kernel_cargo_toml: Option<PathBuf>,

Expand All @@ -50,10 +53,13 @@ enum CliOptions {

/// Builds a bootable image.
BuildImage {
/// Location of the Cargo.toml of the standalone kernel.
/// Location of the Cargo.toml of the standalone kernel library.
///
/// If no value is passed, this the file structure is the one of the upstream repository
/// and try to find the path in a sibling directory.
///
/// It is intended that in the future this can be substituted with the path to a build
/// directory, in which case the standalone kernel library gets fetched from crates.io.
#[structopt(long, parse(from_os_str))]
kernel_cargo_toml: Option<PathBuf>,

Expand Down
147 changes: 87 additions & 60 deletions kernel/standalone-builder/src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ use std::{
/// Configuration for building the kernel.
#[derive(Debug)]
pub struct Config<'a> {
/// Path to the `Cargo.toml` of the standalone kernel.
/// Path to the `Cargo.toml` of the standalone kernel library.
// TODO: once the standalone kernel is on crates.io, make it possible for the kernel builder to run as a completely stand-alone program and pass a build directory instead
pub kernel_cargo_toml: &'a Path,

/// If true, compiles with `--release`.
Expand Down Expand Up @@ -57,14 +58,8 @@ pub enum Error {
#[error("Failed to get metadata about the kernel Cargo.toml")]
MetadataFailed,

#[error("kernel_cargo_toml must not point to a workspace")]
UnexpectedWorkspace,

#[error("No binary target found at the kernel standalone path")]
NoBinTarget,

#[error("Multiple binary targets found")]
MultipleBinTargets,
#[error("Invalid kernel Cargo.toml path")]
BadKernelCargoTomlPath,

#[error("{0}")]
Io(#[from] io::Error),
Expand All @@ -75,74 +70,96 @@ pub fn build(cfg: Config) -> Result<BuildOutput, Error> {
assert_ne!(cfg.target_name, "debug");
assert_ne!(cfg.target_name, "release");

// Get the package ID of the package requested by the user.
let pkg_id = {
let output = Command::new("cargo")
.arg("read-manifest")
.arg("--manifest-path")
.arg(cfg.kernel_cargo_toml)
.output()
.map_err(Error::CargoNotFound)?;
if !output.status.success() {
return Err(Error::MetadataFailed);
}
let json: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap();
json.as_object()
.unwrap()
.get("id")
.unwrap()
.as_str()
.unwrap()
.to_owned()
};

// Determine the path to the file that Cargo will generate.
let (output_file, target_dir_with_target, bin_target) = {
let (output_file, target_dir_with_target) = {
let metadata = cargo_metadata::MetadataCommand::new()
.manifest_path(&cfg.kernel_cargo_toml)
.no_deps()
.exec()
.map_err(|_| Error::MetadataFailed)?;

let package = metadata
.packages
.iter()
.find(|p| p.id.repr == pkg_id)
.unwrap();

let bin_target = {
let mut iter = package
.targets
.iter()
.filter(|t| t.kind.iter().any(|k| k == "bin"));
let target = iter.next().ok_or(Error::NoBinTarget)?;
if iter.next().is_some() {
return Err(Error::MultipleBinTargets);
}
target
};

let output_file = metadata
let target_dir_with_target = metadata
.target_directory
.join(cfg.target_name)
.join(if cfg.release { "release" } else { "debug" })
.join(bin_target.name.clone());
.join("project");

let target_dir_with_target = metadata.target_directory.join(cfg.target_name);
let output_file = target_dir_with_target
.join("target")
.join(cfg.target_name)
.join(if cfg.release { "release" } else { "debug" })
.join("kernel");

(output_file, target_dir_with_target, bin_target.name.clone())
(output_file, target_dir_with_target)
};

// Create and fill the directory for the target specifications.
// Create and fill the directory where various source files are put.
fs::create_dir_all(&target_dir_with_target)?;
fs::write(
write_if_changed(
(&target_dir_with_target).join(format!("{}.json", cfg.target_name)),
cfg.target_specs.as_bytes(),
)?;
fs::write(
write_if_changed(
(&target_dir_with_target).join("link.ld"),
cfg.link_script.as_bytes(),
)?;
{
let mut cargo_toml_prototype = toml::value::Table::new();
// TODO: should write `[profile]` in there
cargo_toml_prototype.insert("package".into(), {
let mut package = toml::value::Table::new();
package.insert("name".into(), "kernel".into());
package.insert("version".into(), "1.0.0".into());
package.insert("edition".into(), "2018".into());
package.into()
});
cargo_toml_prototype.insert("dependencies".into(), {
let mut dependencies = toml::value::Table::new();
dependencies.insert("redshirt-standalone-kernel".into(), {
let mut wasm_project = toml::value::Table::new();
wasm_project.insert(
"path".into(),
cfg.kernel_cargo_toml
.parent()
.ok_or(Error::BadKernelCargoTomlPath)?
.display()
.to_string()
.into(),
);
wasm_project.into()
});
dependencies.into()
});
cargo_toml_prototype.insert("workspace".into(), toml::value::Table::new().into());
write_if_changed(
target_dir_with_target.join("Cargo.toml"),
toml::to_string_pretty(&cargo_toml_prototype).unwrap(),
)?;
}
{
fs::create_dir_all(&target_dir_with_target.join("src"))?;
let src = format!(
r#"
#![no_std]
#![no_main]
// TODO: these features are necessary because of the fact that we use a macro
#![feature(asm)] // TODO: https://github.com/rust-lang/rust/issues/72016
#![feature(naked_functions)] // TODO: https://github.com/rust-lang/rust/issues/32408
redshirt_standalone_kernel::__gen_boot! {{
entry: redshirt_standalone_kernel::run,
memory_zeroing_start: __bss_start,
memory_zeroing_end: __bss_end,
}}
extern "C" {{
static mut __bss_start: u8;
static mut __bss_end: u8;
}}
"#
);
write_if_changed(target_dir_with_target.join("src").join("main.rs"), src)?;
}

// Actually build the kernel.
let build_status = Command::new("cargo")
Expand All @@ -159,12 +176,10 @@ pub fn build(cfg: Config) -> Result<BuildOutput, Error> {
target_dir_with_target.join("link.ld").display()
),
)
.arg("--bin")
.arg(bin_target)
.arg("--target")
.arg(cfg.target_name)
.arg("--manifest-path")
.arg(cfg.kernel_cargo_toml)
.arg(target_dir_with_target.join("Cargo.toml"))
.args(if cfg.release {
&["--release"][..]
} else {
Expand All @@ -183,3 +198,15 @@ pub fn build(cfg: Config) -> Result<BuildOutput, Error> {
out_kernel_path: output_file,
})
}

/// Write to the given `file` if the `content` is different.
///
/// This function is used in order to not make Cargo trigger a rebuild by writing over a file
/// with the same content as it already has.
fn write_if_changed(file: impl AsRef<Path>, content: impl AsRef<[u8]>) -> Result<(), io::Error> {
if fs::read(file.as_ref()).ok().as_deref() != Some(content.as_ref()) {
fs::write(file, content.as_ref())?;
}

Ok(())
}
45 changes: 0 additions & 45 deletions kernel/standalone-builder/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,51 +14,6 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.

//! Collection of commands that can build a kernel.
//!
//! # Kernel environment
//!
//! This crate doesn't contain the source code of the kernel. Instead, many of the commands require
//! you to pass the location of a `Cargo.toml` that will build this kernel.
//!
//! This crate, however, is responsible for building bootable images and setting up the boot
//! process on various targets. It therefore sets up an environment that the kernel can expect
//! to be there.
//!
//! This environment is the following:
//!
//! - The kernel must provide a symbol named `_start`. Execution will jump to this symbol, after
//! which the kernel is in total control of the hardware.
//! - The kernel cannot make any assumption about the state of the registers, memory, or hardware
//! when `_start` is executed, with some exceptions depending on the target.
//! - The symbols `__bss_start` and `__bss_end` exist and correspond to the beginning and end
//! of the BSS section (see below).
//!
//! ## BSS section
//!
//! The BSS section is the section, in an ELF binary, where all the static variables whose initial
//! value is all zeroes are located.
//!
//! Normally, it is the role of the ELF loader (e.g. the Linux kernel) to ensure that this section
//! is initialized with zeroes. Operating systems, however, are generally not loaded by an ELF
//! loader.
//!
//! Consequently, when the kernel starts, it **must** write the memory between the `__bss_start`
//! and `__bss_end` symbols with all zeroes.
//!
//! This can be done like this:
//!
//! ```norun
//! let mut ptr = &mut __bss_start as *mut u8;
//! while ptr < &mut __bss_end as *mut u8 {
//! ptr.write_volatile(0);
//! ptr = ptr.add(1);
//! }
//!
//! extern "C" {
//! static mut __bss_start: u8;
//! static mut __bss_end: u8;
//! }
//! ```
pub mod binary;
pub mod build;
Expand Down
29 changes: 16 additions & 13 deletions kernel/standalone/src/arch/arm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,21 @@ pub mod time_arm;

mod misc;

#[macro_export]
macro_rules! __gen_boot {
(
entry: $entry:ident,
bss_start: $bss_start:ident,
bss_end: $bss_end:ident,
entry: $entry:path,
memory_zeroing_start: $memory_zeroing_start:path,
memory_zeroing_end: $memory_zeroing_end:path,
) => {
const _: () = {
use crate::klog::KLogger;
extern crate alloc;

use alloc::sync::Arc;
use core::{convert::TryFrom as _, iter, num::NonZeroU32, pin::Pin};
use futures::prelude::*;
use redshirt_kernel_log_interface::ffi::{KernelLogMethod, UartInfo};
use $crate::futures::prelude::*;
use $crate::klog::KLogger;
use $crate::redshirt_kernel_log_interface::ffi::{KernelLogMethod, UartInfo};

/// This is the main entry point of the kernel for ARM 32bits architectures.
#[cfg(target_arch = "arm")]
Expand Down Expand Up @@ -75,10 +78,10 @@ macro_rules! __gen_boot {

// Only one CPU reaches here.

// Zero the BSS segment.
// Zero the memory requested to be zero'ed.
// TODO: that's illegal ; naked functions must only contain an asm! block (for good reasons)
let mut ptr = &mut $bss_start as *mut u8;
while ptr < &mut $bss_end as *mut u8 {
let mut ptr = &mut $memory_zeroing_start as *mut u8;
while ptr < &mut $memory_zeroing_end as *mut u8 {
ptr.write_volatile(0);
ptr = ptr.add(1);
}
Expand Down Expand Up @@ -115,10 +118,10 @@ macro_rules! __gen_boot {

// Only one CPU reaches here.

// Zero the BSS segment.
// Zero the memory requested to be zero'ed.
// TODO: that's illegal ; naked functions must only contain an asm! block (for good reasons)
let mut ptr = &mut $bss_start as *mut u8;
while ptr < &mut $bss_end as *mut u8 {
let mut ptr = &mut $memory_zeroing_start as *mut u8;
while ptr < &mut $memory_zeroing_end as *mut u8 {
ptr.write_volatile(0);
ptr = ptr.add(1);
}
Expand All @@ -145,7 +148,7 @@ macro_rules! __gen_boot {

// TODO: RAM starts at 0, but we start later to avoid the kernel
// TODO: make this is a cleaner way
crate::mem_alloc::initialize(iter::once(0xa000000..0x40000000));
$crate::mem_alloc::initialize(iter::once(0xa000000..0x40000000));

let time = $crate::arch::arm::time::TimeControl::init();

Expand Down
Loading

0 comments on commit 489f892

Please sign in to comment.