//! 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 { 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)*))); }