strat9_kernel/arch/x86_64/
serial.rs1use core::{
2 fmt,
3 sync::atomic::{AtomicBool, AtomicU8, AtomicUsize, Ordering},
4};
5use spin::Mutex;
6use uart_16550::SerialPort;
7
8static SERIAL1: Mutex<SerialPort> = Mutex::new(unsafe { SerialPort::new(0x3F8) });
10
11const TX_CHUNK_LEN: usize = 256;
14const TX_QUEUE_LEN: usize = 256;
15#[derive(Clone, Copy)]
16struct TxChunk {
17 bytes: [u8; TX_CHUNK_LEN],
18 len: usize,
19}
20impl TxChunk {
21 const EMPTY: Self = Self {
22 bytes: [0; TX_CHUNK_LEN],
23 len: 0,
24 };
25}
26#[expect(
27 deprecated,
28 reason = "bounded diagnostics allow drops and delayed dequeue"
29)]
30static TX_QUEUE: heapless::mpmc::Queue<TxChunk, TX_QUEUE_LEN> = heapless::mpmc::Queue::new();
31static ASYNC_OUTPUT: AtomicBool = AtomicBool::new(false);
32static TX_FORMAT: Mutex<()> = Mutex::new(());
33static TX_DROPPED: AtomicUsize = AtomicUsize::new(0);
34
35struct QueuedWriter {
36 chunk: TxChunk,
37}
38impl QueuedWriter {
39 fn flush(&mut self) -> fmt::Result {
40 if self.chunk.len != 0 {
41 let chunk = core::mem::replace(&mut self.chunk, TxChunk::EMPTY);
42 if TX_QUEUE.enqueue(chunk).is_err() {
43 TX_DROPPED.fetch_add(1, Ordering::Relaxed);
44 return Err(fmt::Error);
45 }
46 }
47 Ok(())
48 }
49 fn byte(&mut self, byte: u8) -> fmt::Result {
50 self.chunk.bytes[self.chunk.len] = byte;
51 self.chunk.len += 1;
52 if self.chunk.len == TX_CHUNK_LEN {
53 self.flush()?;
54 }
55 Ok(())
56 }
57}
58impl fmt::Write for QueuedWriter {
59 fn write_str(&mut self, text: &str) -> fmt::Result {
60 for byte in text.bytes() {
61 match byte {
63 b'\n' => {
64 self.byte(b'\r')?;
65 self.byte(b'\n')?;
66 }
67 8 | 127 => {
68 self.byte(8)?;
69 self.byte(b' ')?;
70 self.byte(8)?;
71 }
72 byte => self.byte(byte)?,
73 }
74 }
75 Ok(())
76 }
77}
78
79pub fn dropped_output() -> usize {
81 TX_DROPPED.load(Ordering::Relaxed)
82}
83
84pub extern "C" fn serial_task_main() -> ! {
86 let mut chunk = TxChunk::EMPTY;
87 let mut offset = 0;
88 ASYNC_OUTPUT.store(true, Ordering::Release);
89 loop {
90 if !PANIC_IN_PROGRESS.load(Ordering::Relaxed) {
91 if let Some(mut port) = SERIAL1.try_lock() {
92 for _ in 0..TX_CHUNK_LEN {
93 if offset == chunk.len {
94 match TX_QUEUE.dequeue() {
95 Some(next) => {
96 chunk = next;
97 offset = 0;
98 }
99 None => break,
100 }
101 }
102 if port.try_send_raw(chunk.bytes[offset]).is_err() {
103 break;
104 }
105 offset += 1;
106 }
107 }
108 }
109 crate::process::yield_task();
110 }
111}
112
113static CMDLINE_BUF: [u8; 2048] = [0; 2048];
117static CMDLINE_LEN: AtomicUsize = AtomicUsize::new(0);
118static CMDLINE_READY: AtomicBool = AtomicBool::new(false);
119
120static PANIC_IN_PROGRESS: AtomicBool = AtomicBool::new(false);
123static BOOT_LOG_PREFIX_ENABLED: AtomicBool = AtomicBool::new(false);
124static SERIAL_AT_LINE_START: AtomicBool = AtomicBool::new(true);
125
126static FORCE_LOCK: AtomicU8 = AtomicU8::new(0);
134
135#[inline(always)]
136fn force_lock_acquire() -> u64 {
137 let saved = crate::arch::x86_64::save_flags_and_cli();
140 while FORCE_LOCK
141 .compare_exchange(0, 1, Ordering::Acquire, Ordering::Relaxed)
142 .is_err()
143 {
144 core::hint::spin_loop();
145 }
146 saved
147}
148
149#[inline(always)]
150fn force_lock_release(saved_flags: u64) {
151 FORCE_LOCK.store(0, Ordering::Release);
152 crate::arch::x86_64::restore_flags(saved_flags);
154}
155
156const ANSI_RESET: &str = "\x1b[0m";
157const ANSI_RED: &str = "\x1b[31m";
158const ANSI_GREEN: &str = "\x1b[32m";
159const ANSI_VIOLET: &str = "\x1b[35m";
160const TOKEN_BUF_CAP: usize = 64;
161
162pub fn putc(byte: u8) {
165 unsafe {
167 core::arch::asm!("out 0xe9, al", in("al") byte, options(nomem, nostack));
168 }
169}
170
171pub fn enter_emergency_mode() {
172 PANIC_IN_PROGRESS.store(true, Ordering::SeqCst);
173}
174
175pub fn set_boot_log_prefix_enabled(enabled: bool) {
177 BOOT_LOG_PREFIX_ENABLED.store(enabled, Ordering::SeqCst);
178 SERIAL_AT_LINE_START.store(true, Ordering::SeqCst);
179}
180
181#[inline]
183fn is_token_char(b: u8) -> bool {
184 b.is_ascii_alphanumeric() || b == b'_'
185}
186
187#[inline]
189fn is_hex_word(s: &str) -> bool {
190 if s.len() <= 2 {
191 return false;
192 }
193 let bytes = s.as_bytes();
194 if bytes[0] != b'0' || (bytes[1] != b'x' && bytes[1] != b'X') {
195 return false;
196 }
197 bytes[2..].iter().all(|b| b.is_ascii_hexdigit())
198}
199
200struct AnsiStylingWriter<'a, W: fmt::Write> {
201 inner: &'a mut W,
202 in_escape: bool,
203 token_buf: [u8; TOKEN_BUF_CAP],
204 token_len: usize,
205 token_passthrough: bool,
206}
207
208impl<'a, W: fmt::Write> AnsiStylingWriter<'a, W> {
209 fn new(inner: &'a mut W) -> Self {
211 Self {
212 inner,
213 in_escape: false,
214 token_buf: [0u8; TOKEN_BUF_CAP],
215 token_len: 0,
216 token_passthrough: false,
217 }
218 }
219
220 fn flush_token(&mut self) -> fmt::Result {
222 if self.token_len == 0 {
223 return Ok(());
224 }
225 let token = unsafe { core::str::from_utf8_unchecked(&self.token_buf[..self.token_len]) };
226 if token == "PASS" {
227 self.inner.write_str(ANSI_GREEN)?;
228 self.inner.write_str(token)?;
229 self.inner.write_str(ANSI_RESET)?;
230 } else if token == "FAIL" {
231 self.inner.write_str(ANSI_RED)?;
232 self.inner.write_str(token)?;
233 self.inner.write_str(ANSI_RESET)?;
234 } else if is_hex_word(token) {
235 self.inner.write_str(ANSI_VIOLET)?;
236 self.inner.write_str(token)?;
237 self.inner.write_str(ANSI_RESET)?;
238 } else {
239 self.inner.write_str(token)?;
240 }
241 self.token_len = 0;
242 Ok(())
243 }
244
245 fn write_byte_raw(&mut self, b: u8) -> fmt::Result {
247 self.inner.write_char(b as char)
248 }
249
250 fn finish(&mut self) -> fmt::Result {
252 self.flush_token()
253 }
254}
255
256impl<W: fmt::Write> fmt::Write for AnsiStylingWriter<'_, W> {
257 fn write_str(&mut self, s: &str) -> fmt::Result {
259 for &b in s.as_bytes() {
260 if self.in_escape {
261 self.write_byte_raw(b)?;
262 if (b as char).is_ascii_alphabetic() {
263 self.in_escape = false;
264 }
265 continue;
266 }
267
268 if b == 0x1b {
269 self.flush_token()?;
270 self.token_passthrough = false;
271 self.in_escape = true;
272 self.write_byte_raw(b)?;
273 continue;
274 }
275
276 if is_token_char(b) {
277 if self.token_passthrough {
278 self.write_byte_raw(b)?;
279 continue;
280 }
281 if self.token_len < TOKEN_BUF_CAP {
282 self.token_buf[self.token_len] = b;
283 self.token_len += 1;
284 } else {
285 self.flush_token()?;
286 self.token_passthrough = true;
287 self.write_byte_raw(b)?;
288 }
289 } else {
290 self.flush_token()?;
291 self.token_passthrough = false;
292 self.write_byte_raw(b)?;
293 }
294 }
295 Ok(())
296 }
297}
298
299struct BootPrefixWriter<'a, W: fmt::Write> {
300 inner: &'a mut W,
301 line_start: bool,
302 prefix_enabled: bool,
303}
304
305impl<'a, W: fmt::Write> BootPrefixWriter<'a, W> {
306 fn new(inner: &'a mut W) -> Self {
307 Self {
308 inner,
309 line_start: SERIAL_AT_LINE_START.load(Ordering::Relaxed),
310 prefix_enabled: BOOT_LOG_PREFIX_ENABLED.load(Ordering::Relaxed),
311 }
312 }
313
314 fn write_prefix(&mut self) -> fmt::Result {
315 if !self.prefix_enabled {
316 return Ok(());
317 }
318 let elapsed_us = crate::arch::x86_64::boot_timestamp::elapsed_us();
319 let secs = elapsed_us / 1_000_000;
320 let micros = elapsed_us % 1_000_000;
321 write!(self.inner, "[{:>5}.{:06}] ", secs, micros)
322 }
323
324 fn finish(&mut self) {
325 SERIAL_AT_LINE_START.store(self.line_start, Ordering::Relaxed);
326 }
327}
328
329impl<W: fmt::Write> fmt::Write for BootPrefixWriter<'_, W> {
330 fn write_str(&mut self, s: &str) -> fmt::Result {
331 for ch in s.chars() {
332 if self.line_start && ch != '\n' {
333 self.write_prefix()?;
334 self.line_start = false;
335 }
336 self.inner.write_char(ch)?;
337 if ch == '\n' {
338 self.line_start = true;
339 }
340 }
341 Ok(())
342 }
343}
344
345pub fn init() {
347 unsafe {
348 core::arch::asm!("out 0xe9, al", in("al") b'x', options(nomem, nostack));
349 }
350 SERIAL1.lock();
351 unsafe {
352 core::arch::asm!("out 0xe9, al", in("al") b'y', options(nomem, nostack));
353 }
354 SERIAL1.lock().init();
355 unsafe {
356 core::arch::asm!("out 0xe9, al", in("al") b'z', options(nomem, nostack));
357 }
358}
359
360pub unsafe fn parse_cmdline(ptr: u64, len: u64) {
369 if ptr == 0 || len == 0 {
370 return;
371 }
372
373 let cstr = core::ffi::CStr::from_ptr(ptr as *const core::ffi::c_char);
375 let cmdline = cstr.to_str().unwrap_or("");
376
377 let copy_len = cmdline.len().min(2047);
378 let buf_ptr = CMDLINE_BUF.as_ptr() as *mut u8;
380 core::ptr::copy_nonoverlapping(cmdline.as_ptr(), buf_ptr, copy_len);
381 CMDLINE_LEN.store(copy_len, Ordering::Release);
382 CMDLINE_READY.store(true, Ordering::Release);
383
384 let cmdline_str =
386 core::str::from_utf8_unchecked(core::slice::from_raw_parts(buf_ptr, copy_len));
387
388 let mut has_serial_console = false;
389 let mut baud: Option<u32> = None;
390
391 let mut pos = 0;
392 while pos < cmdline_str.len() {
393 while pos < cmdline_str.len() && cmdline_str.as_bytes()[pos].is_ascii_whitespace() {
394 pos += 1;
395 }
396 if pos >= cmdline_str.len() {
397 break;
398 }
399 let start = pos;
400 while pos < cmdline_str.len() && !cmdline_str.as_bytes()[pos].is_ascii_whitespace() {
401 pos += 1;
402 }
403 let token = &cmdline_str[start..pos];
404
405 if let Some(value) = token.strip_prefix("console=") {
406 if value.starts_with("ttyS0") {
407 has_serial_console = true;
408 if let Some((_, baud_str)) = value.split_once(',') {
409 if let Ok(b) = baud_str.parse::<u32>() {
410 baud = Some(b);
411 }
412 }
413 }
414 }
415 }
416
417 if has_serial_console {
418 if let Some(b) = baud {
419 crate::serial_force_println!("[cmdline] console=ttyS0,{}", b);
420 } else {
421 crate::serial_force_println!("[cmdline] console=ttyS0 (115200 baud)");
422 }
423 } else {
424 crate::serial_force_println!("[cmdline] no serial console detected");
425 }
426}
427
428pub fn get_cmdline() -> &'static str {
430 if !CMDLINE_READY.load(Ordering::Acquire) {
431 return "";
432 }
433 let len = CMDLINE_LEN.load(Ordering::Acquire);
434 unsafe { core::str::from_utf8_unchecked(&CMDLINE_BUF[..len]) }
436}
437
438#[doc(hidden)]
440pub fn _print(args: fmt::Arguments) {
441 use core::fmt::Write;
442
443 if !crate::debug_cfg::SERIAL_ENABLED {
445 return;
446 }
447
448 if PANIC_IN_PROGRESS.load(Ordering::Relaxed) {
450 let mut port = unsafe { SerialPort::new(0x3F8) };
452 let _ = port.write_fmt(args);
453 return;
454 }
455
456 if crate::debug_cfg::is_quiet() {
458 return;
459 }
460
461 if ASYNC_OUTPUT.load(Ordering::Acquire) {
462 let Some(_format_guard) = TX_FORMAT.try_lock() else {
464 TX_DROPPED.fetch_add(1, Ordering::Relaxed);
465 return;
466 };
467 let mut queued = QueuedWriter {
468 chunk: TxChunk::EMPTY,
469 };
470 let mut prefix_writer = BootPrefixWriter::new(&mut queued);
471 let mut writer = AnsiStylingWriter::new(&mut prefix_writer);
472 let _ = writer.write_fmt(args);
473 let _ = writer.finish();
474 prefix_writer.finish();
475 let _ = queued.flush();
476 return;
477 }
478
479 if let Some(mut port) = SERIAL1.try_lock() {
481 let mut prefix_writer = BootPrefixWriter::new(&mut *port);
482 let mut writer = AnsiStylingWriter::new(&mut prefix_writer);
483 let _ = writer.write_fmt(args);
484 let _ = writer.finish();
485 prefix_writer.finish();
486 }
487}
488
489#[doc(hidden)]
496pub fn _print_force(args: fmt::Arguments) {
497 use core::fmt::Write;
498
499 if !crate::debug_cfg::SERIAL_ENABLED {
501 return;
502 }
503
504 if PANIC_IN_PROGRESS.load(Ordering::Relaxed) {
506 let mut port = unsafe { SerialPort::new(0x3F8) };
508 let _ = port.write_fmt(args);
509 return;
510 }
511
512 let saved_flags = force_lock_acquire();
514 let mut port = unsafe { SerialPort::new(0x3F8) };
516 let mut prefix_writer = BootPrefixWriter::new(&mut port);
517 let _ = prefix_writer.write_fmt(args);
518 prefix_writer.finish();
519 force_lock_release(saved_flags);
521}
522
523#[macro_export]
525macro_rules! serial_print {
526 ($($arg:tt)*) => {
527 if $crate::debug_cfg::SERIAL_ENABLED {
528 $crate::arch::x86_64::serial::_print(format_args!($($arg)*))
529 }
530 };
531}
532
533#[macro_export]
535macro_rules! serial_println {
536 () => ($crate::serial_print!("\n"));
537 ($($arg:tt)*) => {
538 if $crate::debug_cfg::SERIAL_ENABLED {
539 $crate::arch::x86_64::serial::_print(format_args!("{}\n", format_args!($($arg)*)))
540 }
541 };
542}
543
544#[macro_export]
546macro_rules! serial_force_println {
547 () => {
548 if $crate::debug_cfg::SERIAL_ENABLED {
549 $crate::arch::x86_64::serial::_print_force(format_args!("\n"))
550 }
551 };
552 ($($arg:tt)*) => {
553 if $crate::debug_cfg::SERIAL_ENABLED {
554 $crate::arch::x86_64::serial::_print_force(format_args!("{}\n", format_args!($($arg)*)))
555 }
556 };
557}