Skip to main content

kernel/
printf.rs

1use core::fmt::{self, Write};
2use core::sync::atomic::{AtomicBool, Ordering};
3
4use crate::proc;
5use crate::spinlock::SpinLock;
6use crate::uart;
7
8/// Wrapper around `uart::write_sync` that implements `fmt::Write`.
9///
10/// Held behind `Printf::writer` so that concurrent kernel `print!` calls do not interleave their
11/// formatted output across multiple `write_str` calls.
12pub struct Writer;
13
14impl Write for Writer {
15    fn write_str(&mut self, s: &str) -> fmt::Result {
16        uart::write_sync(s.as_bytes());
17        Ok(())
18    }
19}
20
21pub static PRINTF: Printf = Printf {
22    writer: SpinLock::new(Writer, "printf"),
23    panicking: AtomicBool::new(false),
24    panicked: AtomicBool::new(false),
25};
26
27pub struct Printf {
28    /// Serializes concurrent kernel `print!` calls so their output does not interleave.
29    writer: SpinLock<Writer>,
30    /// Set to true before the panic message is printed.
31    ///
32    /// Causes `print()` to bypass `writer` (avoiding a deadlock if the panicking hart already
33    /// holds it) and causes `uart::write()` to abort and release `UART.lock()` so that
34    /// `uart::write_sync()` can acquire it to print the panic message.
35    panicking: AtomicBool,
36    /// Set to true after the panic message has been printed.
37    ///
38    /// Causes `uart::write_sync()` to freeze all harts, preventing any output from appearing
39    /// after the panic message.
40    panicked: AtomicBool,
41}
42
43impl Printf {
44    pub fn is_panicking(&self) -> bool {
45        self.panicking.load(Ordering::Relaxed)
46    }
47
48    pub fn is_panicked(&self) -> bool {
49        self.panicked.load(Ordering::Relaxed)
50    }
51}
52
53/// Prints formatted output to the console.
54///
55/// Acquires `PRINTF.writer` to prevent interleaving with concurrent kernel prints. When panicking,
56/// bypasses the lock to avoid deadlocking if this hart already holds it.
57pub fn print(args: fmt::Arguments<'_>) {
58    let writer = if !PRINTF.is_panicking() {
59        &mut *PRINTF.writer.lock()
60    } else {
61        // Safety: interrupts are disabled by the caller or we are panicking on a single hart.
62        unsafe { PRINTF.writer.get_mut_unchecked() }
63    };
64
65    writer.write_fmt(args).unwrap();
66}
67
68#[macro_export]
69macro_rules! print {
70    ($($arg:tt)*) => {{
71        $crate::printf::print(format_args!($($arg)*));
72    }};
73}
74
75#[macro_export]
76macro_rules! println {
77    ($fmt:literal $(,$($arg:tt)+)?) => {
78        $crate::printf::print(format_args!(concat!($fmt, "\n") $(,$($arg)+)?))
79    };
80}
81
82/// Stack buffer used to pre-format the panic message before writing it to the UART.
83///
84/// Pre-formatting lets `uart::write_sync()` be called once with the complete message, so
85/// `UART.lock()` is held for the entire output and user-space writes cannot interleave.
86struct PanicBuffer<const N: usize> {
87    data: [u8; N],
88    len: usize,
89}
90
91impl<const N: usize> Write for PanicBuffer<N> {
92    fn write_str(&mut self, s: &str) -> fmt::Result {
93        let bytes = s.as_bytes();
94        let space = self.data.len() - self.len;
95        let n = bytes.len().min(space);
96        self.data[self.len..self.len + n].copy_from_slice(&bytes[..n]);
97        self.len += n;
98        Ok(())
99    }
100}
101
102pub fn panic(info: &core::panic::PanicInfo) -> ! {
103    PRINTF.panicking.store(true, Ordering::Relaxed);
104
105    // Safety: normally requires interrupts disabled to prevent the hart id from becoming stale
106    // due to preemption, but in a panic context a stale value is acceptable.
107    let cpu_id = unsafe { proc::current_id() };
108
109    let mut buf = PanicBuffer::<256> {
110        data: [0; 256],
111        len: 0,
112    };
113    let _ = writeln!(buf, "\n! hart {} {}", cpu_id, info);
114    uart::write_sync(&buf.data[..buf.len]);
115
116    PRINTF.panicked.store(true, Ordering::Relaxed);
117
118    loop {
119        core::hint::spin_loop();
120    }
121}