summaryrefslogtreecommitdiff
path: root/kernel/src
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/src')
-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
4 files changed, 245 insertions, 13 deletions
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)),+,)
+ };
+}