summaryrefslogtreecommitdiff
path: root/kernel
diff options
context:
space:
mode:
Diffstat (limited to 'kernel')
-rw-r--r--kernel/Cargo.lock63
-rw-r--r--kernel/Cargo.toml6
-rw-r--r--kernel/default.nix1
-rw-r--r--kernel/src/console.rs179
-rw-r--r--kernel/src/lib.rs15
-rw-r--r--kernel/src/panic.rs14
-rw-r--r--kernel/src/util.rs50
7 files changed, 315 insertions, 13 deletions
diff --git a/kernel/Cargo.lock b/kernel/Cargo.lock
index 0dd1539..c4e38bd 100644
--- a/kernel/Cargo.lock
+++ b/kernel/Cargo.lock
@@ -3,5 +3,68 @@
version = 3
[[package]]
+name = "contracts"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1d1429e3bd78171c65aa010eabcdf8f863ba3254728dbfb0ad4b1545beac15c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "kernel"
version = "0.1.0"
+dependencies = [
+ "contracts",
+ "log",
+ "spin",
+]
+
+[[package]]
+name = "log"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "spin"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml
index cef382e..d6449e8 100644
--- a/kernel/Cargo.toml
+++ b/kernel/Cargo.toml
@@ -7,6 +7,12 @@ edition = "2021"
crate-type = ["staticlib"]
[dependencies]
+contracts = { version = "0.6.3", default-features = false }
+log = { version = "0.4.20", default-features = false }
+spin = { version = "0.9.8", default-features = false, features = ["mutex", "use_ticket_mutex"] }
[profile.release]
+codegen-units = 1
debug = true
+lto = true
+overflow-checks = true
diff --git a/kernel/default.nix b/kernel/default.nix
index 46352fb..0949b58 100644
--- a/kernel/default.nix
+++ b/kernel/default.nix
@@ -6,4 +6,5 @@ in rust.buildRustPackage {
version = toml.package.version;
src = ./.;
cargoLock.lockFile = ./Cargo.lock;
+ dontFixup = true;
}
diff --git a/kernel/src/console.rs b/kernel/src/console.rs
new file mode 100644
index 0000000..7364807
--- /dev/null
+++ b/kernel/src/console.rs
@@ -0,0 +1,179 @@
+//! The console subsystem, used for the kernel to log things.
+
+use core::fmt::Write;
+
+use crate::util::likely;
+use contracts::{ensures, invariant};
+use log::Log;
+use spin::Mutex;
+
+/// The console singleton.
+static CONSOLE: Console = Console(Mutex::new(ConsoleInner::new()));
+
+/// 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");
+}
+
+/// 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 body = |line| {
+ let level = match record.level() {
+ log::Level::Error => "ERR",
+ log::Level::Warn => "WRN",
+ log::Level::Info => "INF",
+ log::Level::Debug => "DBG",
+ 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!(self.0.lock(), "[{level}][{file}:{line}]")
+ } else {
+ writeln!(self.0.lock(), "[{level}][{file}:{line}] {args}")
+ };
+
+ // Since the fmt::Write impl for ConsoleInner has a contract
+ // promising that it won't ever return an error, this should be
+ // unreachable.
+ result.expect("ConsoleInner failed to write");
+ };
+
+ // 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!("???")),
+ }
+ }
+
+ fn flush(&self) {
+ todo!()
+ }
+}
+
+/// 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,
+ }
+ }
+
+ /// 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);
+}
+
+/// The trait that a console backend must implement.
+pub trait ConsoleBackend {}
diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs
index bd5a19f..4a2483a 100644
--- a/kernel/src/lib.rs
+++ b/kernel/src/lib.rs
@@ -1,16 +1,19 @@
#![no_std]
+pub mod console;
+pub mod util;
+
+#[cfg(not(test))]
mod panic;
/// The entrypoint to the kernel. This should be executed by hart0 alone. It performs some early
/// boot tasks, then wakes up any other harts.
#[no_mangle]
-pub extern "C" fn hart0_boot() {
- for byte in "Hello, world!\n".bytes() {
- unsafe {
- core::ptr::write_volatile(0x10000000 as *mut u8, byte);
- }
- }
+pub extern "C" fn hart0_boot(device_tree: *const u32) {
+ console::init();
+
+ log::info!("device_tree = {device_tree:?}");
+ dbg!(42);
todo!()
}
diff --git a/kernel/src/panic.rs b/kernel/src/panic.rs
index ed6e5d4..9aabb01 100644
--- a/kernel/src/panic.rs
+++ b/kernel/src/panic.rs
@@ -1,12 +1,12 @@
-use core::panic::PanicInfo;
+//! The kernel panic handler.
+
+use core::{arch::asm, panic::PanicInfo};
#[panic_handler]
-fn panic(_info: &PanicInfo) -> ! {
+fn panic(info: &PanicInfo) -> ! {
+ log::error!("{info:?}");
+
loop {
- for byte in "panic\n".bytes() {
- unsafe {
- core::ptr::write_volatile(0x10000000 as *mut u8, byte);
- }
- }
+ unsafe { asm!("wfi") }
}
}
diff --git a/kernel/src/util.rs b/kernel/src/util.rs
new file mode 100644
index 0000000..93c684d
--- /dev/null
+++ b/kernel/src/util.rs
@@ -0,0 +1,50 @@
+//! Miscellaneous utilities.
+
+#[cold]
+#[inline(always)]
+fn cold() {}
+
+/// A hint that `b` is likely to be true. See `core::intrinsics::likely`.
+#[inline(always)]
+pub fn likely(b: bool) -> bool {
+ if !b {
+ cold()
+ }
+ b
+}
+
+/// A hint that `b` is likely to be false. See `core::intrinsics::unlikely`.
+#[inline(always)]
+pub fn unlikely(b: bool) -> bool {
+ if b {
+ cold()
+ }
+ b
+}
+
+/// A version of `std::dbg` built on top of `log::debug` instead of
+/// `std::eprintln`.
+///
+/// This code is copied from libstd, and inherits its copyright.
+#[macro_export]
+macro_rules! dbg {
+ // NOTE: We cannot use `concat!` to make a static string as a format
+ // argument of `log::debug!` because the `$expr` expression could be a
+ // block (`{ .. }`), in which case the format string will be malformed.
+ () => {
+ log::debug!("")
+ };
+ ($expr:expr $(,)?) => {
+ // Use of `match` here is intentional because it affects the lifetimes
+ // of temporaries - https://stackoverflow.com/a/48732525/1063961
+ match $expr {
+ tmp => {
+ log::debug!("{} = {:#?}", core::stringify!($expr), &tmp);
+ tmp
+ }
+ }
+ };
+ ($($expr:expr),+ $(,)?) => {
+ ($($crate::dbg!($expr)),+,)
+ };
+}