Skip to main content

strat9_kernel/arch/x86_64/
serial.rs

1use core::{
2    fmt,
3    sync::atomic::{AtomicBool, Ordering},
4};
5use spin::Mutex;
6use uart_16550::SerialPort;
7
8/// Global serial port instance
9static SERIAL1: Mutex<SerialPort> = Mutex::new(unsafe { SerialPort::new(0x3F8) });
10
11/// Flag indicating if the kernel is in a panic state.
12/// When true, serial output bypasses all locks to ensure messages are displayed.
13static PANIC_IN_PROGRESS: AtomicBool = AtomicBool::new(false);
14
15const ANSI_RESET: &str = "\x1b[0m";
16const ANSI_RED: &str = "\x1b[31m";
17const ANSI_GREEN: &str = "\x1b[32m";
18const ANSI_VIOLET: &str = "\x1b[35m";
19const TOKEN_BUF_CAP: usize = 64;
20
21/// Signal that the kernel has entered an emergency panic state.
22pub fn enter_emergency_mode() {
23    PANIC_IN_PROGRESS.store(true, Ordering::SeqCst);
24}
25
26/// Returns whether token char.
27#[inline]
28fn is_token_char(b: u8) -> bool {
29    b.is_ascii_alphanumeric() || b == b'_'
30}
31
32/// Returns whether hex word.
33#[inline]
34fn is_hex_word(s: &str) -> bool {
35    if s.len() <= 2 {
36        return false;
37    }
38    let bytes = s.as_bytes();
39    if bytes[0] != b'0' || (bytes[1] != b'x' && bytes[1] != b'X') {
40        return false;
41    }
42    bytes[2..].iter().all(|b| b.is_ascii_hexdigit())
43}
44
45struct AnsiStylingWriter<'a, W: fmt::Write> {
46    inner: &'a mut W,
47    in_escape: bool,
48    token_buf: [u8; TOKEN_BUF_CAP],
49    token_len: usize,
50    token_passthrough: bool,
51}
52
53impl<'a, W: fmt::Write> AnsiStylingWriter<'a, W> {
54    /// Creates a new instance.
55    fn new(inner: &'a mut W) -> Self {
56        Self {
57            inner,
58            in_escape: false,
59            token_buf: [0u8; TOKEN_BUF_CAP],
60            token_len: 0,
61            token_passthrough: false,
62        }
63    }
64
65    /// Performs the flush token operation.
66    fn flush_token(&mut self) -> fmt::Result {
67        if self.token_len == 0 {
68            return Ok(());
69        }
70        let token = unsafe { core::str::from_utf8_unchecked(&self.token_buf[..self.token_len]) };
71        if token == "PASS" {
72            self.inner.write_str(ANSI_GREEN)?;
73            self.inner.write_str(token)?;
74            self.inner.write_str(ANSI_RESET)?;
75        } else if token == "FAIL" {
76            self.inner.write_str(ANSI_RED)?;
77            self.inner.write_str(token)?;
78            self.inner.write_str(ANSI_RESET)?;
79        } else if is_hex_word(token) {
80            self.inner.write_str(ANSI_VIOLET)?;
81            self.inner.write_str(token)?;
82            self.inner.write_str(ANSI_RESET)?;
83        } else {
84            self.inner.write_str(token)?;
85        }
86        self.token_len = 0;
87        Ok(())
88    }
89
90    /// Writes byte raw.
91    fn write_byte_raw(&mut self, b: u8) -> fmt::Result {
92        self.inner.write_char(b as char)
93    }
94
95    /// Performs the finish operation.
96    fn finish(&mut self) -> fmt::Result {
97        self.flush_token()
98    }
99}
100
101impl<W: fmt::Write> fmt::Write for AnsiStylingWriter<'_, W> {
102    /// Writes str.
103    fn write_str(&mut self, s: &str) -> fmt::Result {
104        for &b in s.as_bytes() {
105            if self.in_escape {
106                self.write_byte_raw(b)?;
107                if (b as char).is_ascii_alphabetic() {
108                    self.in_escape = false;
109                }
110                continue;
111            }
112
113            if b == 0x1b {
114                self.flush_token()?;
115                self.token_passthrough = false;
116                self.in_escape = true;
117                self.write_byte_raw(b)?;
118                continue;
119            }
120
121            if is_token_char(b) {
122                if self.token_passthrough {
123                    self.write_byte_raw(b)?;
124                    continue;
125                }
126                if self.token_len < TOKEN_BUF_CAP {
127                    self.token_buf[self.token_len] = b;
128                    self.token_len += 1;
129                } else {
130                    self.flush_token()?;
131                    self.token_passthrough = true;
132                    self.write_byte_raw(b)?;
133                }
134            } else {
135                self.flush_token()?;
136                self.token_passthrough = false;
137                self.write_byte_raw(b)?;
138            }
139        }
140        Ok(())
141    }
142}
143
144/// Initialize the serial port
145pub fn init() {
146    SERIAL1.lock().init();
147}
148
149/// Print to serial port
150#[doc(hidden)]
151pub fn _print(args: fmt::Arguments) {
152    use core::fmt::Write;
153
154    // Check if we are in emergency panic mode.
155    if PANIC_IN_PROGRESS.load(Ordering::Relaxed) {
156        // SAFETY: In emergency mode, we bypass the mutex to ensure output.
157        // We re-initialize a local SerialPort instance pointing to the same IO port.
158        let mut port = unsafe { SerialPort::new(0x3F8) };
159        // We don't use AnsiStylingWriter here to minimize risk of further panics/complex logic.
160        let _ = port.write_fmt(args);
161        return;
162    }
163
164    // Normal mode: Use try_lock to avoid deadlock in interrupt handlers.
165    if let Some(mut port) = SERIAL1.try_lock() {
166        let mut writer = AnsiStylingWriter::new(&mut *port);
167        let _ = writer.write_fmt(args);
168        let _ = writer.finish();
169    }
170}
171
172/// Print to serial port bypassing the shared mutex.
173#[doc(hidden)]
174pub fn _print_force(args: fmt::Arguments) {
175    use core::fmt::Write;
176
177    // SAFETY: This is debug-only style output intended for deadlock forensics.
178    // It may interleave with normal serial output, but it must never block on
179    // the SERIAL1 mutex.
180    let mut port = unsafe { SerialPort::new(0x3F8) };
181    let _ = port.write_fmt(args);
182}
183
184/// Print to serial port
185#[macro_export]
186macro_rules! serial_print {
187    ($($arg:tt)*) => {
188        $crate::arch::x86_64::serial::_print(format_args!($($arg)*))
189    };
190}
191
192/// Print to serial port with newline
193#[macro_export]
194macro_rules! serial_println {
195    () => ($crate::serial_print!("\n"));
196    ($($arg:tt)*) => ($crate::serial_print!("{}\n", format_args!($($arg)*)));
197}
198
199/// Print to serial port with newline, bypassing the shared mutex.
200#[macro_export]
201macro_rules! serial_force_println {
202    () => ($crate::arch::x86_64::serial::_print_force(format_args!("\n")));
203    ($($arg:tt)*) => ($crate::arch::x86_64::serial::_print_force(format_args!("{}\n", format_args!($($arg)*))));
204}