Skip to content

Commit

Permalink
feat: safe globals implemented via NullLock<T>
Browse files Browse the repository at this point in the history
  • Loading branch information
orzklv committed Jan 6, 2025
1 parent 473403a commit c4706d5
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 16 deletions.
86 changes: 80 additions & 6 deletions src/bsp/raspberrypi/console.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,71 @@
//! BSP console facilities.
use crate::console;
use crate::{console as gcon, synchronization, synchronization::NullLock};
use core::fmt;

//----------------------------------------------------------------------------
// Private Definitions
//----------------------------------------------------------------------------

/// A mystical magical device for generating QEMU output out of the void.
struct QEMUOutput;
///
/// Mutex protected part.
struct QEMUOutputInner {
chars_written: usize,
}

//----------------------------------------------------------------------------
// Public Definitions
//----------------------------------------------------------------------------

/// The main struct.
pub struct QEMUOutput {
inner: NullLock<QEMUOutputInner>,
}

//----------------------------------------------------------------------------
// Global instances
//----------------------------------------------------------------------------

static QEMU_OUTPUT: QEMUOutput = QEMUOutput::new();

//----------------------------------------------------------------------------
// Private Code
//----------------------------------------------------------------------------

impl QEMUOutputInner {
const fn new() -> QEMUOutputInner {
QEMUOutputInner { chars_written: 0 }
}

/// Send a character.
fn write_char(&mut self, c: char) {
unsafe {
core::ptr::write_volatile(0x3F20_1000 as *mut u8, c as u8);
}

self.chars_written += 1;
}
}

/// Implementing `core::fmt::Write` enables usage of the `format_args!` macros, whic in turn are
/// used to implement the `kernel`'s `print!` and `println!` macros. By implementing `write_str()`,
/// we get `write_fmt()` automatically.
///
/// The function takes an `&mut self`, so it must be implemented for the inner struct.
///
/// See [`src/print.rs`].
///
/// [`src/print.rs`]: ../../print/index.html
impl fmt::Write for QEMUOutput {
impl fmt::Write for QEMUOutputInner {
fn write_str(&mut self, s: &str) -> fmt::Result {
for c in s.chars() {
unsafe { core::ptr::write_volatile(0x3F20_1000 as *mut u8, c as u8) }
// Convert newline to a carrige return + newline.
if c == '\n' {
self.write_char('\r');
}

self.write_char(c);
}

Ok(())
Expand All @@ -35,7 +76,40 @@ impl fmt::Write for QEMUOutput {
// Public Code
//----------------------------------------------------------------------------

impl QEMUOutput {
/// Create a new instance
pub const fn new() -> QEMUOutput {
QEMUOutput {
inner: NullLock::new(QEMUOutputInner::new()),
}
}
}

/// Return a reference to the console.
pub fn console() -> impl console::interface::Write {
QEMUOutput {}
pub fn console() -> &'static dyn gcon::interface::All {
&QEMU_OUTPUT
}

//----------------------------------------------------------------------------
// Operating System Interface Code
//----------------------------------------------------------------------------

use synchronization::interface::Mutex;

/// Passthrough of `args` to the `core::fmt::Write` implementation, but guarded by a Mutex to
/// serialize access.
impl gcon::interface::Write for QEMUOutput {
fn write_fmt(&self, args: fmt::Arguments) -> fmt::Result {
// Fully qualified syntax for the call to `core::fmt::Write::write_fmt()` to increase
// readability.
self.inner.lock(|inner| fmt::Write::write_fmt(inner, args))
}
}

impl gcon::interface::Statistics for QEMUOutput {
fn chars_written(&self) -> usize {
self.inner.lock(|inner| inner.chars_written)
}
}

impl gcon::interface::All for QEMUOutput {}
24 changes: 18 additions & 6 deletions src/console.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,24 @@ use crate::bsp;

/// Console interfaces.
pub mod interface {
use core::fmt;

/// Console write functions.
///
/// `core::fmt::Write` is exactly what we need for now. Re-export it here because
/// implementing `console::Write` gives a better hint to the reader about the
/// intention.
pub use core::fmt::Write;
pub trait Write {
/// Write a Rust format string.
fn write_fmt(&self, args: fmt::Arguments) -> fmt::Result;
}

/// Console statistics.
pub trait Statistics {
/// Return the number of character written.
fn chars_written(&self) -> usize {
0
}
}

/// Trait alias for a full-fledged console.
pub trait All: Write + Statistics {}
}

//----------------------------------------------------------------------------
Expand All @@ -23,6 +35,6 @@ pub mod interface {
/// Return a reference to the console
///
/// This is the global console used by all printing macros.
pub fn console() -> impl interface::Write {
pub fn console() -> &'static dyn interface::All {
bsp::console::console()
}
11 changes: 9 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,23 +106,30 @@
#![allow(unused_imports)]
// Enabled Features
#![feature(format_args_nl)]
#![feature(trait_alias)]
// Single standing binary
#![no_main]
#![no_std]

use console::console;

mod bsp;
mod console;
mod cpu;
mod panic_wait;
mod print;
mod synchronization;

/// Early init code.
///
/// # Safety
///
/// - Only a single core must be active and running this function
unsafe fn kernel_init() -> ! {
println!("Hello from Sark!");
println!("[0] Hello from Sark!");
println!("[1] Chars written: {}", console().chars_written());
println!("[2] Stopping here.");

panic!("Stopping here.")
cpu::wait_forever()
// panic!("Stopping here.")
}
2 changes: 0 additions & 2 deletions src/print.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ use core::fmt;

#[doc(hidden)]
pub fn _print(args: fmt::Arguments) {
use console::interface::Write;

console::console().write_fmt(args).unwrap();
}

Expand Down
73 changes: 73 additions & 0 deletions src/synchronization.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//! Synchronization primitives.
//!
//! # Resources
//!
//! - <https://doc.rust-lang.org/book/ch16-04-extensible-concurrency-sync-and-send.html>
//! - <https://stackoverflow.com/questions/59428096/understanding-the-send-trait>
//! - <https://doc.rust-lang.org/std/cell/index.html>
//----------------------------------------------------------------------------
// Public Definitions
//----------------------------------------------------------------------------

use core::cell::UnsafeCell;

/// Synchronization interfaces.
pub mod interface {

/// Any object implementing this trait guarantees exclusive access to the data wrapped within
/// the Mutex for the duration of the provided closure.
pub trait Mutex {
/// The type of the data that is wrapped by this mutex.
type Data;

/// Locks the mutex and grants the closure temporary mutable access to the wrapped data.
fn lock<'a, R>(&'a self, f: impl FnOnce(&'a mut Self::Data) -> R) -> R;
}
}

/// A pseudo-lock for experimental purposes.
///
/// In contrast to a real Mutex implementation, does not protect against concurrent access from
/// other cores to the contained data. This part is preserved for later inspections.
///
/// The lock will only be used as long as it is safe to do so, i.e. as long as the kernel is
/// executing single-threaded, aka only running on a single core with interrupts disabled.
pub struct NullLock<T>
where
T: ?Sized,
{
data: UnsafeCell<T>,
}

//----------------------------------------------------------------------------
// Public Code
//----------------------------------------------------------------------------

unsafe impl<T> Send for NullLock<T> where T: ?Sized + Send {}
unsafe impl<T> Sync for NullLock<T> where T: ?Sized + Send {}

impl<T> NullLock<T> {
/// Create an instance
pub const fn new(data: T) -> Self {
Self {
data: UnsafeCell::new(data),
}
}
}

//----------------------------------------------------------------------------
// Operating System Interface Code
//----------------------------------------------------------------------------

impl<T> interface::Mutex for NullLock<T> {
type Data = T;

fn lock<'a, R>(&'a self, f: impl FnOnce(&'a mut Self::Data) -> R) -> R {
// In a real lock, there would be code encapsulating this line that ensures that this
// mutable reference will ever only be given out once at a time.
let data = unsafe { &mut *self.data.get() };

f(data)
}
}

0 comments on commit c4706d5

Please sign in to comment.