strat9_kernel/boot/logger.rs
1use core::sync::atomic::{AtomicBool, Ordering};
2use log::{Level, LevelFilter, Metadata, Record};
3
4/// Gate for format_args-heavy logging paths.
5///
6/// During early boot, `format_args!` involving nested `fmt::Arguments`
7/// (e.g. `record.args()` inside another `format_args!`) can resolve to
8/// identity-mapped function pointers and #UD. This flag is set to `true`
9/// after `init_cpu_extensions()` enables CR4.OSXSAVE + XCR0, at which
10/// point all formatting is safe.
11static EXTENSIONS_READY: AtomicBool = AtomicBool::new(false);
12
13/// Simple serial port logger
14struct SerialLogger;
15
16static LOGGER: SerialLogger = SerialLogger;
17
18impl log::Log for SerialLogger {
19 /// Performs the enabled operation.
20 fn enabled(&self, metadata: &Metadata) -> bool {
21 metadata.level() <= Level::Trace
22 }
23
24 /// Performs the log operation.
25 ///
26 /// Architecture (FreeBSD vt(4) / Linux nbcon pattern):
27 /// 1. Queue serial output at runtime (synchronous during early boot).
28 /// 2. Enqueue a plain-text copy into the lock-free `vgabuf` ring buffer.
29 /// No lock is acquired here : safe from any context (IRQ, panic).
30 /// 3. The `console-render` task drains `vgabuf` to the framebuffer
31 /// asynchronously via `vgabuf_flush_to_framebuffer()`.
32 ///
33 /// This decouples the hot logging path from framebuffer rendering,
34 /// making deadlocks structurally impossible.
35 fn log(&self, record: &Record) {
36 // Gate: skip format_args-heavy paths until SSE/XSAVE are initialized.
37 if !EXTENSIONS_READY.load(Ordering::Acquire) {
38 return;
39 }
40 if self.enabled(record.metadata()) {
41 // In quiet mode, skip all log output entirely.
42 if crate::debug_cfg::is_quiet() {
43 return;
44 }
45
46 let (level_str, msg_color) = match record.level() {
47 Level::Error => ("\x1b[31mERROR\x1b[0m", "\x1b[31m"),
48 Level::Warn => ("\x1b[33mWARN\x1b[0m", "\x1b[33m"),
49 Level::Info => ("\x1b[32mINFO\x1b[0m", "\x1b[37m"),
50 Level::Debug => ("\x1b[90mDEBUG\x1b[0m", "\x1b[90m"),
51 Level::Trace => ("\x1b[90mTRACE\x1b[0m", "\x1b[90m"),
52 };
53
54 // 1. Serial: bounded, nonblocking enqueue once its worker is running.
55 crate::arch::serial::_print(format_args!(
56 "[{}] {}{}\x1b[0m\n",
57 level_str,
58 msg_color,
59 record.args()
60 ));
61
62 // 1b. Live VGA debug (early boot): write directly to framebuffer
63 // via vga_debug_write. This bypasses VGA_WRITER and works
64 // before the scheduler / status_line_task are running.
65 // Controlled by debug_cfg::VGA_DEBUG_LIVE toggle.
66 if crate::debug_cfg::is_vga_debug_live() && crate::arch::vga::is_available() {
67 let mut line_buf = [0u8; 256];
68 let mut lpos = 0usize;
69 use core::fmt::Write;
70 struct LineWriter<'a> {
71 buf: &'a mut [u8],
72 pos: &'a mut usize,
73 }
74 impl Write for LineWriter<'_> {
75 fn write_str(&mut self, s: &str) -> core::fmt::Result {
76 let bytes = s.as_bytes();
77 let remaining = self.buf.len().saturating_sub(*self.pos);
78 let n = bytes.len().min(remaining);
79 self.buf[*self.pos..*self.pos + n].copy_from_slice(&bytes[..n]);
80 *self.pos += n;
81 Ok(())
82 }
83 }
84 let _ = write!(
85 LineWriter {
86 buf: &mut line_buf,
87 pos: &mut lpos
88 },
89 "[{}] {}",
90 record.level(),
91 record.args(),
92 );
93 if lpos > 0 {
94 if let Ok(s) = core::str::from_utf8(&line_buf[..lpos]) {
95 crate::arch::vga::vga_debug_writeln(s);
96 }
97 }
98 }
99
100 // 2. VGA ring buffer : lock-free enqueue.
101 // Controlled by debug_cfg::VGA_DEBUG_BUFFER toggle.
102 if crate::debug_cfg::is_vga_debug_buffer() && crate::arch::vga::is_available() {
103 let mut vbuf = [0u8; crate::arch::vgabuf::VGABUF_LINE_LEN];
104 let mut vpos = 0usize;
105 use core::fmt::Write;
106 struct VgaBufWriter<'a> {
107 buf: &'a mut [u8],
108 pos: &'a mut usize,
109 }
110 impl Write for VgaBufWriter<'_> {
111 fn write_str(&mut self, s: &str) -> core::fmt::Result {
112 let bytes = s.as_bytes();
113 let remaining = self.buf.len().saturating_sub(*self.pos);
114 let n = bytes.len().min(remaining);
115 self.buf[*self.pos..*self.pos + n].copy_from_slice(&bytes[..n]);
116 *self.pos += n;
117 Ok(())
118 }
119 }
120 let _ = write!(
121 VgaBufWriter {
122 buf: &mut vbuf,
123 pos: &mut vpos
124 },
125 "[{}] {}\n",
126 record.level(),
127 record.args(),
128 );
129 if vpos > 0 {
130 crate::arch::vgabuf::vgabuf_write(&vbuf[..vpos]);
131 }
132 }
133 }
134 }
135
136 /// Performs the flush operation.
137 fn flush(&self) {}
138}
139
140/// Initialize the logger
141pub fn init() {
142 crate::e9_mark!(b'{');
143 log::set_logger(&LOGGER)
144 .map(|()| {
145 crate::e9_mark!(b'|');
146 log::set_max_level(LevelFilter::Trace);
147 crate::e9_mark!(b'}');
148 })
149 .expect("Failed to set logger");
150 crate::e9_mark!(b'~');
151}
152
153/// Mark SSE/XSAVE extensions as ready.
154///
155/// Called once from `kernel_main` after `init_cpu_extensions()` has set
156/// CR4.OSXSAVE and XCR0. Until this is called, all `log::info!` / etc.
157/// fire the `e9_println!("LOG enter")` marker but skip the format_args-heavy
158/// formatting paths that can #UD on early boot.
159pub fn set_extensions_ready() {
160 crate::e9_mark!(b'Z');
161 EXTENSIONS_READY.store(true, Ordering::Release);
162 crate::e9_mark!(b'z');
163}