Files
k-os/crates/init/src/output.rs

140 lines
5.0 KiB
Rust

//! What init prints never waits on the log server: init restarts the log
//! server, so a blocking call to it could wait on itself. Text goes into a
//! ring; a thread of init's own takes it to the log and waits there when
//! the log is down, instead of init. Until that thread runs (early boot),
//! init prints to the console directly. The same thread carries the few
//! messages init sends other servers it restarts (`post`).
use core::fmt;
use core::sync::atomic::{AtomicU8, AtomicUsize, Ordering};
use libkos::syscall;
use types::bounded::CapSlot;
const SIZE: usize = 4096;
/// Bytes waiting: written at `TAIL` by init's thread, read at `HEAD` by
/// the printer.
static RING: [AtomicU8; SIZE] = [const { AtomicU8::new(0) }; SIZE];
static HEAD: AtomicUsize = AtomicUsize::new(0);
static TAIL: AtomicUsize = AtomicUsize::new(0);
/// Bytes dropped because the ring was full: the log was down a long time.
static LOST: AtomicUsize = AtomicUsize::new(0);
/// The printer's notification, once it runs.
static WAKE: AtomicUsize = AtomicUsize::new(usize::MAX);
/// Messages waiting: an endpoint slot and four words each.
const POSTS: usize = 16;
static POSTED: [[AtomicUsize; 5]; POSTS] = [const { [const { AtomicUsize::new(0) }; 5] }; POSTS];
static POST_HEAD: AtomicUsize = AtomicUsize::new(0);
static POST_TAIL: AtomicUsize = AtomicUsize::new(0);
/// Has `words` called to `ep` by the printer thread, so init never waits
/// on it; dropped (and said so) if too many are waiting.
pub(crate) fn post(ep: CapSlot, words: [usize; 4]) {
let tail = POST_TAIL.load(Ordering::Relaxed);
if tail - POST_HEAD.load(Ordering::Acquire) == POSTS || wake().is_none() {
crate::outln!("init: a message to a server was dropped");
return;
}
let slot = &POSTED[tail % POSTS];
slot[0].store(ep.value() as usize, Ordering::Relaxed);
for (w, v) in slot[1..].iter().zip(words) {
w.store(v, Ordering::Relaxed);
}
POST_TAIL.store(tail + 1, Ordering::Release);
if let Some(wake) = wake() {
let _ = syscall::signal(wake);
}
}
fn wake() -> Option<CapSlot> {
u8::try_from(WAKE.load(Ordering::Acquire)).ok().and_then(CapSlot::new)
}
pub(crate) fn write(bytes: &[u8]) {
let Some(wake) = wake() else {
libkos::io::write_bytes(bytes);
return;
};
let tail = TAIL.load(Ordering::Relaxed);
let room = SIZE - (tail - HEAD.load(Ordering::Acquire));
let n = bytes.len().min(room);
for (i, b) in bytes[..n].iter().enumerate() {
RING[(tail + i) % SIZE].store(*b, Ordering::Relaxed);
}
TAIL.store(tail + n, Ordering::Release);
LOST.fetch_add(bytes.len() - n, Ordering::Relaxed);
let _ = syscall::signal(wake);
}
struct Ring;
impl fmt::Write for Ring {
fn write_str(&mut self, s: &str) -> fmt::Result {
write(s.as_bytes());
Ok(())
}
}
pub(crate) fn print(args: fmt::Arguments) {
let _ = fmt::Write::write_fmt(&mut Ring, args);
}
/// Starts the printer; from here on init's output goes through the ring.
pub(crate) fn start() -> bool {
let Ok(wake) = syscall::create_notification() else { return false };
WAKE.store(wake.value() as usize, Ordering::Release);
if libkos::thread::spawn(&WAKE, printer) {
return true;
}
WAKE.store(usize::MAX, Ordering::Release);
false
}
fn printer(_: &'static AtomicUsize) -> ! {
let mut chunk = [0u8; 256];
loop {
if let Some(wake) = wake() {
let _ = syscall::wait(wake);
}
loop {
let (head, tail) = (HEAD.load(Ordering::Relaxed), TAIL.load(Ordering::Acquire));
if head == tail {
break;
}
let n = (tail - head).min(chunk.len());
for (i, b) in chunk[..n].iter_mut().enumerate() {
*b = RING[(head + i) % SIZE].load(Ordering::Relaxed);
}
HEAD.store(head + n, Ordering::Release);
// Blocks while the log server is down; init goes on.
libkos::io::write_bytes(&chunk[..n]);
}
let lost = LOST.swap(0, Ordering::Relaxed);
if lost > 0 {
libkos::println!("init: {} bytes of output lost while the log was down", lost);
}
while POST_HEAD.load(Ordering::Relaxed) != POST_TAIL.load(Ordering::Acquire) {
let head = POST_HEAD.load(Ordering::Relaxed);
let slot = &POSTED[head % POSTS];
let words = core::array::from_fn(|i| slot[i + 1].load(Ordering::Relaxed));
let ep = u8::try_from(slot[0].load(Ordering::Relaxed)).ok().and_then(CapSlot::new);
POST_HEAD.store(head + 1, Ordering::Release);
if let Some(ep) = ep {
// Waits while that server is down; init goes on.
let _ = syscall::request(ep, words);
}
}
}
}
#[macro_export]
macro_rules! out {
($($arg:tt)*) => ($crate::output::print(format_args!($($arg)*)));
}
#[macro_export]
macro_rules! outln {
() => ($crate::out!("\n"));
($($arg:tt)*) => ($crate::out!("{}\n", format_args!($($arg)*)));
}