veridian_kernel/arch/x86_64/early_serial.rs
1#![allow(clippy::macro_metavars_in_unsafe)]
2
3// Early serial output for x86_64 boot debugging
4// This bypasses lazy_static to allow output before static initialization
5
6use core::fmt::Write;
7
8/// Early serial port at 0x3F8 (COM1)
9pub struct EarlySerial {
10 base: u16,
11}
12
13impl Default for EarlySerial {
14 fn default() -> Self {
15 Self::new()
16 }
17}
18
19impl EarlySerial {
20 /// Create early serial port
21 pub const fn new() -> Self {
22 Self { base: 0x3F8 }
23 }
24
25 /// Initialize the serial port
26 pub fn init(&mut self) {
27 // SAFETY: Direct I/O port writes to configure the 16550 UART at the
28 // base address (COM1 = 0x3F8). The initialization sequence (disable
29 // IRQs, set baud rate via DLAB, configure line control, enable FIFO,
30 // loopback test) is well-defined by the 16550 UART specification.
31 unsafe {
32 // Disable interrupts
33 outb(self.base + 1, 0x00);
34
35 // Enable DLAB (set baud rate divisor)
36 outb(self.base + 3, 0x80);
37
38 // Set divisor to 3 (lo byte) 38400 baud
39 outb(self.base, 0x03);
40 outb(self.base + 1, 0x00); // (hi byte)
41
42 // 8 bits, no parity, one stop bit
43 outb(self.base + 3, 0x03);
44
45 // Enable FIFO, clear them, with 14-byte threshold
46 outb(self.base + 2, 0xC7);
47
48 // Enable IRQs, set RTS/DSR
49 outb(self.base + 4, 0x0B);
50
51 // Set loopback mode, test the serial chip
52 outb(self.base + 4, 0x1E);
53
54 // Test serial chip (send 0xAE and check if we receive it back)
55 outb(self.base, 0xAE);
56
57 // Check if we received the correct byte back
58 if inb(self.base) != 0xAE {
59 // Serial port is faulty, but continue anyway
60 }
61
62 // Set normal operation mode (not loopback)
63 outb(self.base + 4, 0x0F);
64 }
65 }
66
67 /// Write a single byte
68 pub fn write_byte(&mut self, byte: u8) {
69 // SAFETY: I/O port reads (line status register) and writes (transmit
70 // buffer) to the 16550 UART. The port must have been initialized first.
71 // We spin-wait until the transmit buffer is empty before sending.
72 unsafe {
73 // Wait for transmit buffer to be empty
74 while (inb(self.base + 5) & 0x20) == 0 {
75 core::hint::spin_loop();
76 }
77
78 // Send byte
79 outb(self.base, byte);
80 }
81 }
82
83 /// Write a string
84 pub fn write_str(&mut self, s: &str) {
85 for byte in s.bytes() {
86 self.write_byte(byte);
87 }
88 }
89}
90
91impl Write for EarlySerial {
92 fn write_str(&mut self, s: &str) -> core::fmt::Result {
93 self.write_str(s);
94 Ok(())
95 }
96}
97
98// I/O port functions: delegate to the canonical implementations in
99// the parent module (arch/x86_64/mod.rs) to avoid duplication.
100use super::{inb, outb};
101
102/// Global early serial port instance
103///
104/// SAFETY JUSTIFICATION: This static mut is intentionally kept because:
105/// 1. Used for early boot debugging output before heap is available
106/// 2. Required by early_print!/early_println! macros via addr_of_mut!
107/// 3. Only accessed during single-threaded early boot (before SMP init)
108/// 4. Cannot use OnceLock/Mutex as those require heap allocation
109/// 5. After boot, output switches to the proper serial driver
110#[allow(static_mut_refs)]
111pub(crate) static mut EARLY_SERIAL: EarlySerial = EarlySerial::new();
112
113/// Initialize early serial output
114pub fn init() {
115 // SAFETY: EARLY_SERIAL is a static mut EarlySerial initialized once during
116 // early boot. addr_of_mut! avoids creating a mutable reference to the static
117 // mut, instead operating through a raw pointer. No concurrent access is
118 // possible during early single-threaded boot.
119 unsafe {
120 let serial = core::ptr::addr_of_mut!(EARLY_SERIAL);
121 (*serial).init();
122 (*serial).write_str("EARLY_SERIAL_OK\n");
123 }
124}
125
126/// Early print macro for debugging
127#[macro_export]
128#[allow(clippy::macro_metavars_in_unsafe)]
129macro_rules! early_print {
130 ($($arg:tt)*) => {
131 // SAFETY: addr_of_mut! accesses the EARLY_SERIAL static mut through
132 // a raw pointer, avoiding a mutable reference. The write is safe
133 // because this macro is only used during early single-threaded boot
134 // before any concurrent access is possible.
135 #[allow(clippy::macro_metavars_in_unsafe)]
136 unsafe {
137 use core::fmt::Write;
138 let serial = core::ptr::addr_of_mut!($crate::arch::x86_64::early_serial::EARLY_SERIAL);
139 let _ = write!(*serial, $($arg)*);
140 }
141 };
142}
143
144/// Early println macro for debugging
145#[macro_export]
146macro_rules! early_println {
147 () => ($crate::early_print!("\n"));
148 ($($arg:tt)*) => ($crate::early_print!("{}\n", format_args!($($arg)*)));
149}