diff options
-rw-r--r-- | arches.nix (renamed from crates/arches.nix) | 0 | ||||
-rw-r--r-- | boards/configure.py | 45 | ||||
-rw-r--r-- | boards/default.nix | 72 | ||||
-rw-r--r-- | boards/qemu-virt/default.nix | 11 | ||||
-rw-r--r-- | boards/qemu-virt/qemu-virt.s | 21 | ||||
-rw-r--r-- | crates/default.nix | 3 | ||||
-rw-r--r-- | flake.nix | 35 | ||||
-rw-r--r-- | kernel/Cargo.toml | 24 | ||||
-rw-r--r-- | kernel/default.nix | 10 | ||||
-rw-r--r-- | kernel/src/console.rs | 211 | ||||
-rw-r--r-- | nix/miri.nix | 5 |
11 files changed, 126 insertions, 311 deletions
diff --git a/crates/arches.nix b/arches.nix index 257ad7d..257ad7d 100644 --- a/crates/arches.nix +++ b/arches.nix diff --git a/boards/configure.py b/boards/configure.py new file mode 100644 index 0000000..5b01652 --- /dev/null +++ b/boards/configure.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +from argparse import ArgumentParser +from ninja.ninja_syntax import Writer as Ninja +from os import getenv +from pathlib import Path + + +def main(): + arg_parser = ArgumentParser() + arg_parser.add_argument("arch_name") + arg_parser.add_argument("board_name") + arg_parser.add_argument("libkernel") + arg_parser.add_argument("srcdir") + args = arg_parser.parse_args() + + libkernel = Path(args.libkernel).resolve() / "lib/libvernos_kernel.a" + srcdir = Path(args.srcdir).resolve() + objdir = Path("obj") + objdir.mkdir() + + linker_script = str(srcdir / f"{args.board_name}.ld") + + objs: list[str] = [] + with open("build.ninja", "w") as f: + ninja = Ninja(f) + + # Inherit some environment variables. + for var in ["AS", "ASFLAGS", "LD", "LDFLAGS"]: + ninja.variable(var, getenv(var)) + + # Add rules to assemble and link. + ninja.rule("as", "$AS $ASFLAGS -g -o $out $in") + ninja.rule("ld", f"$LD $LDFLAGS -T{linker_script} -o $out $in") + + for path in srcdir.iterdir(): + if path.suffix != ".s": + continue + obj = str(objdir / f"{path.stem}.o") + ninja.build(obj, "as", str(path)) + objs.append(obj) + ninja.build("kernel.elf", "ld", objs + [str(libkernel)]) + + +if __name__ == "__main__": + main() diff --git a/boards/default.nix b/boards/default.nix index dd09255..6c8c8a9 100644 --- a/boards/default.nix +++ b/boards/default.nix @@ -1,33 +1,55 @@ -{ libkernel, pkgs }: +{ + arches, + libkernel, + nixpkgs, + pkgs, + python, + system, +}: let - mkKernel = { name, asmFile, linkerScript }: - pkgs.stdenvNoCC.mkDerivation { - pname = "${libkernel.pname}-${name}"; - version = libkernel.version; - inherit asmFile linkerScript; + mkKernel = + path: + let + args = import path; + arch = arches.${args.vernos-arch}; + libkernel-arch = libkernel.${args.vernos-arch}; + pkgs-arch = import nixpkgs { + inherit system; + crossSystem.config = arch.crossSystem; + }; + in - nativeBuildInputs = [ pkgs.stdenv.cc.bintools.bintools ]; + pkgs-arch.stdenvNoCC.mkDerivation ( + { + pname = "vernos-kernel-${args.vernos-board}"; + version = libkernel-arch.version; - dontUnpack = true; - buildPhase = '' - runHook preBuild + nativeBuildInputs = [ + pkgs.ninja + pkgs-arch.stdenv.cc.bintools.bintools + python + ]; - riscv64-unknown-none-elf-as -g -march=rv64gc -mabi=lp64d -o asm.o \ - $asmFile + dontUnpack = true; + configurePhase = '' + runHook preConfigure + python3 ${./configure.py} ${args.vernos-arch} ${args.vernos-board} \ + ${libkernel-arch} $src + runHook postConfigure + ''; + installPhase = '' + runHook preInstall + install -Dt $out kernel.elf + runHook postInstall + ''; - riscv64-unknown-none-elf-ld --gc-sections -T$linkerScript -o kernel.elf \ - asm.o ${libkernel}/lib/libkernel.a + AS = "${arch.crossSystem}-as"; + LD = "${arch.crossSystem}-ld"; + } + // args + ); - runHook postBuild - ''; - installPhase = '' - runHook preInstall - install -Dt $out kernel.elf - runHook postInstall - ''; - }; +in -in pkgs.lib.recurseIntoAttrs { - qemu-virt = pkgs.callPackage ./qemu-virt { inherit mkKernel; }; -} +nixpkgs.lib.recurseIntoAttrs { qemu-virt = mkKernel ./qemu-virt; } diff --git a/boards/qemu-virt/default.nix b/boards/qemu-virt/default.nix index e41d924..d80d5d4 100644 --- a/boards/qemu-virt/default.nix +++ b/boards/qemu-virt/default.nix @@ -1,7 +1,8 @@ -{ mkKernel }: +{ + vernos-arch = "riscv64"; + vernos-board = "qemu-virt"; + src = ./.; -mkKernel { - name = "qemu-virt"; - asmFile = ./qemu-virt.s; - linkerScript = ./qemu-virt.ld; + ASFLAGS = "-march=rv64gc -mabi=lp64d"; + LDFLAGS = "--gc-sections"; } diff --git a/boards/qemu-virt/qemu-virt.s b/boards/qemu-virt/qemu-virt.s index c7e7642..0d69d4d 100644 --- a/boards/qemu-virt/qemu-virt.s +++ b/boards/qemu-virt/qemu-virt.s @@ -11,11 +11,6 @@ _start: csrr tp, mhartid bnez tp, wait_for_hart0 - ## Set up console_strict_flush. - la t0, console_strict_flush - la t1, CONSOLE_STRICT_FLUSH - sd t0, (t1) - ## Set up hart0's stack. la sp, hart0_initial_stack_top @@ -89,19 +84,3 @@ wait_for_hart0: wfi j wait_for_hart0 .size wait_for_hart0, . - wait_for_hart0 - -.section .text - -.type console_strict_flush, STT_FUNC -console_strict_flush: - li t0, 0x10000000 -1: - c.beqz a1, 2f - lb t1, (a0) - sb t1, (t0) - addi a0, a0, 1 - addi a1, a1, -1 - j 1b -2: - ret -.size console_strict_flush, . - console_strict_flush diff --git a/crates/default.nix b/crates/default.nix index d615e29..7204dae 100644 --- a/crates/default.nix +++ b/crates/default.nix @@ -1,12 +1,11 @@ { + arches, fenix, nixpkgs, system, }: let - arches = import ./arches.nix; - toml = builtins.fromTOML (builtins.readFile ./kernel/Cargo.toml); mkLibKernel = @@ -23,6 +23,9 @@ pkgs = nixpkgs.legacyPackages.${system}; fenix = fenix-flake.packages.${system}; + arches = import ./arches.nix; + + python = pkgs.python3.withPackages (ps: [ ps.ninja ]); rust-toolchain = fenix.combine ( [ fenix.stable.cargo @@ -30,9 +33,30 @@ fenix.stable.clippy ] ++ (builtins.map ({ rust-target, ... }: fenix.targets.${rust-target}.stable.rust-std) ( - builtins.attrValues (import ./crates/arches.nix) + builtins.attrValues arches )) ); + + packages = rec { + kernel = import ./boards { + inherit + arches + libkernel + nixpkgs + pkgs + python + system + ; + }; + libkernel = import ./crates { + inherit + arches + fenix + nixpkgs + system + ; + }; + }; in /* pkgsHost = import nixpkgs { @@ -115,21 +139,16 @@ */ devShells.default = pkgs.mkShell { - # inputsFrom = builtins.attrValues (flake-utils.lib.flattenTree packages); nativeBuildInputs = [ (pkgs.callPackage ./nix/miri.nix { inherit fenix; }) pkgs.cargo-watch pkgs.qemu + python rust-toolchain - # pkgsHost.stdenv.cc.bintools.bintools ]; - # CARGO_BUILD_TARGET = "riscv64gc-unknown-none-elf"; }; - lib = import ./crates { inherit fenix nixpkgs system; }; - packages = flake-utils.lib.flattenTree { - libkernel = import ./crates { inherit fenix nixpkgs system; }; - }; + packages = flake-utils.lib.flattenTree packages; } ); } diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml deleted file mode 100644 index f2bb9ff..0000000 --- a/kernel/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "kernel" -version = "0.1.0" -edition = "2021" - -[lib] -crate-type = ["staticlib"] - -[dependencies] -allocator-api2 = { version = "0.2.18", default-features = false } -bstr = { version = "1.10.0", default-features = false } -cfg-if = { version = "1.0.0", default-features = false } -contracts = { version = "0.6.3", default-features = false } -either = { version = "1.13.0", default-features = false } -log = { version = "0.4.20", default-features = false } -spin = { version = "0.9.8", default-features = false, features = ["mutex", "use_ticket_mutex"] } -static_assertions = { version = "1.1.0", default-features = false } -void = { version = "1.0.2", default-features = false } - -[profile.release] -codegen-units = 1 -debug = true -lto = true -overflow-checks = true diff --git a/kernel/default.nix b/kernel/default.nix deleted file mode 100644 index 0949b58..0000000 --- a/kernel/default.nix +++ /dev/null @@ -1,10 +0,0 @@ -{ rust }: - -let toml = builtins.fromTOML (builtins.readFile ./Cargo.toml); -in rust.buildRustPackage { - pname = toml.package.name; - version = toml.package.version; - src = ./.; - cargoLock.lockFile = ./Cargo.lock; - dontFixup = true; -} diff --git a/kernel/src/console.rs b/kernel/src/console.rs deleted file mode 100644 index 1c0fe78..0000000 --- a/kernel/src/console.rs +++ /dev/null @@ -1,211 +0,0 @@ -//! The console subsystem, used for the kernel to log things. - -use crate::util::likely; -use contracts::{ensures, invariant}; -use core::fmt::Write; -use log::Log; -use spin::Mutex; - -/// The console singleton. -static CONSOLE: Console = Console(Mutex::new(ConsoleInner::new())); - -/// A function pointer that may be set by the platform-specific initialization -/// code **before** hart0_boot is called. It must never be modified after that -/// point. -/// -/// If present, this function is called after every write to the console with -/// the buffer contents, after which the console buffer is cleared. -#[no_mangle] -static mut CONSOLE_STRICT_FLUSH: Option<unsafe extern "C" fn(*const u8, usize)> = None; - -/// Initializes the console subsystem. This must be done before any logging is -/// performed, and must -/// only be called once. -pub fn init() { - log::set_logger(&CONSOLE).expect("failed to set logger"); - log::set_max_level(log::LevelFilter::Trace); -} - -/// The point of interaction with the console. This is a singleton; see -/// `CONSOLE`. -struct Console(Mutex<ConsoleInner<4096>>); - -impl Log for Console { - fn enabled(&self, _metadata: &log::Metadata) -> bool { - true - } - - fn log(&self, record: &log::Record) { - if !self.enabled(record.metadata()) { - return; - } - - let mut inner = self.0.lock(); - let mut body = |line| { - let level = match record.level() { - log::Level::Error => "\x1b[1;31mERR\x1b[0m", - log::Level::Warn => "\x1b[1;33mWRN\x1b[0m", - log::Level::Info => "\x1b[1;36mINF\x1b[0m", - log::Level::Debug => "\x1b[1;35mDBG\x1b[0m", - log::Level::Trace => "TRC", - }; - let file = record.file().unwrap_or("???"); - let args = record.args(); - - let result = if args.as_str() == Some("") { - // A silly convenience, but don't write the extra space if we're - // not going to write anything anyways. - writeln!(inner, "[{level}][{file}:{line}]") - } else { - writeln!(inner, "[{level}][{file}:{line}] {args}") - }; - - // UNWRAP: Since the fmt::Write impl for ConsoleInner has a - // contract promising that it won't ever return an error, this - // should be unreachable. - result.unwrap(); - }; - - // Some contortions to avoid running afoul of the lifetime requirements - // of format_args... - match record.line() { - Some(line) => body(format_args!("{line}")), - None => body(format_args!("???")), - } - - // Check for a strict flush handler. - // - // SAFETY: We document the function that should be called, as well as - // the requirement that the function pointer cannot be concurrently - // modified. - unsafe { - if let Some(console_strict_flush) = CONSOLE_STRICT_FLUSH { - let buffer = inner.buffer(); - console_strict_flush(buffer.as_ptr(), buffer.len()); - inner.clear_buffer(); - } - } - } - - fn flush(&self) {} -} - -/// The internals of the console. This is put behind a lock. -struct ConsoleInner<const BUFFER_SIZE: usize> { - /// The buffer that gets written to. Wraps on overflow. - buffer: [u8; BUFFER_SIZE], - - /// The number of bytes that have actually been written to. - len: usize, - - /// A flag for whether the log buffer has overflowed since the last time it - /// was cleared. - has_overflowed: bool, -} - -#[invariant(self.len <= BUFFER_SIZE)] -impl<const BUFFER_SIZE: usize> ConsoleInner<BUFFER_SIZE> { - const fn new() -> ConsoleInner<BUFFER_SIZE> { - ConsoleInner { - buffer: [0; BUFFER_SIZE], - len: 0, - has_overflowed: false, - } - } - - /// Returns a reference to the filled portion of the buffer. - fn buffer<'a>(&'a self) -> &'a [u8] { - &self.buffer[..self.len] - } - - /// Marks the buffer as cleared. - fn clear_buffer(&mut self) { - self.len = 0; - if self.has_overflowed { - self.has_overflowed = false; - log::warn!("console buffer overflowed"); - } - } - - /// Writes bytes to the buffer, wrapping and setting the flag on overflow. - fn write(&mut self, bytes: &[u8]) { - // Check if there's enough room for the contents of the buffer. - let remaining = &mut self.buffer[self.len..]; - if likely(bytes.len() <= remaining.len()) { - // If there is, copy the whole buffer in. - remaining[..bytes.len()].copy_from_slice(bytes); - self.len += bytes.len(); - } else if bytes.len() >= BUFFER_SIZE { - // If not, and the bytes to write would fill up the entire buffer, - // just write over the whole thing. (This frees us from handling - // this case below.) - self.buffer - .copy_from_slice(&bytes[bytes.len() - BUFFER_SIZE..]); - self.len = BUFFER_SIZE; - self.has_overflowed = true; - } else { - // If there wasn't enough space, but there still will be enough - // space for some of the current contents, calculate how many bytes - // will be kept. - // - // This won't overflow (or even be zero), because we tested for - // that above. - let bytes_to_keep = BUFFER_SIZE - bytes.len(); - - // Copy those bytes down to the start of the buffer. - self.buffer - .copy_within(self.len - bytes_to_keep..self.len, 0); - - // Copy the new bytes to the end of the buffer. - self.buffer[bytes_to_keep..].copy_from_slice(bytes); - - // Update the length and mark the buffer as having overflowed. - self.len = BUFFER_SIZE; - self.has_overflowed = true; - } - } -} - -impl<const BUFFER_SIZE: usize> Write for ConsoleInner<BUFFER_SIZE> { - #[ensures(ret.is_ok())] - fn write_str(&mut self, s: &str) -> core::fmt::Result { - self.write(s.as_bytes()); - Ok(()) - } -} - -#[test] -fn console_handles_overflow() { - let mut console = ConsoleInner::<8>::new(); - assert_eq!(&console.buffer, b"\0\0\0\0\0\0\0\0"); - assert_eq!(console.len, 0); - assert_eq!(console.has_overflowed, false); - - console.write(b"Hello"); - assert_eq!(&console.buffer, b"Hello\0\0\0"); - assert_eq!(console.len, 5); - assert_eq!(console.has_overflowed, false); - - console.write(b", "); - assert_eq!(&console.buffer, b"Hello, \0"); - assert_eq!(console.len, 7); - assert_eq!(console.has_overflowed, false); - - console.write(b"world!"); - assert_eq!(&console.buffer, b", world!"); - assert_eq!(console.len, 8); - assert_eq!(console.has_overflowed, true); -} - -#[test] -fn console_handles_overflow_larger_than_buffer() { - let mut console = ConsoleInner::<8>::new(); - assert_eq!(&console.buffer, b"\0\0\0\0\0\0\0\0"); - assert_eq!(console.len, 0); - assert_eq!(console.has_overflowed, false); - - console.write("Hello, world!".as_bytes()); - assert_eq!(&console.buffer, b", world!"); - assert_eq!(console.len, 8); - assert_eq!(console.has_overflowed, true); -} diff --git a/nix/miri.nix b/nix/miri.nix index 5a8c39d..73a7e81 100644 --- a/nix/miri.nix +++ b/nix/miri.nix @@ -12,11 +12,6 @@ pkgs.writeShellApplication { name = "cargo-miri"; runtimeInputs = [ rust-nightly-toolchain ]; text = '' - set -x - # https://github.com/proptest-rs/proptest/issues/253#issuecomment-1850534278 - : "''${PROPTEST_DISABLE_FAILURE_PERSISTENCE:=true}" - : "''${MIRIFLAGS:=-Zmiri-env-forward=PROPTEST_DISABLE_FAILURE_PERSISTENCE}" - export PROPTEST_DISABLE_FAILURE_PERSISTENCE MIRIFLAGS exec cargo "$@" ''; } |