⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

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}