aboutsummaryrefslogtreecommitdiff
path: root/StrixKernel/src/serial.rs
blob: ca6514e331d53552e1ae8f6fda16370900506a87 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
//! # Serial Port Communication
//!
//! This module provides serial port output for debugging and test reporting.
//! It implements [`serial_print!`] and [`serial_println!`] macros that write
//! to the first serial port (COM1).
//!
//! ## Why Serial Output?
//!
//! Serial port output is essential for kernel development because:
//!
//! 1. **Host-readable**: QEMU can redirect serial output to the host terminal,
//!    making it easy to capture kernel output.
//!
//! 2. **Test automation**: The test framework uses serial output to report
//!    test results, which can be parsed by the test runner.
//!
//! 3. **Early boot debugging**: Serial works before VGA initialization.
//!
//! 4. **No interference**: Doesn't affect VGA output, allowing separate
//!    debug and user-facing channels.
//!
//! ## UART 16550
//!
//! The serial port uses the UART 16550 chip (or compatible). This is a standard
//! serial communication interface that has been part of PCs since the IBM PC/AT.
//!
//! ### COM Ports and I/O Addresses
//!
//! | Port | I/O Base | IRQ | Typical Use            |
//! |------|----------|-----|------------------------|
//! | COM1 | 0x3F8    | 4   | Serial console (used)  |
//! | COM2 | 0x2F8    | 3   | Modem/auxiliary        |
//! | COM3 | 0x3E8    | 4   | Varies                 |
//! | COM4 | 0x2E8    | 3   | Varies                 |
//!
//! ## QEMU Configuration
//!
//! When running in QEMU, serial output is typically redirected to stdio:
//!
//! ```text
//! qemu-system-x86_64 ... -serial stdio
//! ```
//!
//! This allows kernel serial output to appear in the terminal running QEMU.
//!
//! ## Usage
//!
//! ```ignore
//! use strix_os::{serial_print, serial_println};
//!
//! serial_println!("Debug: entering function foo");
//! serial_print!("Value: ");
//! serial_println!("{}", some_value);
//! ```

use lazy_static::lazy_static;
use spin::Mutex;
use uart_16550::SerialPort;

lazy_static! {
    /// The global serial port instance for COM1.
    ///
    /// This provides access to the first serial port (COM1) at I/O base address
    /// 0x3F8. The port is wrapped in a [`spin::Mutex`] for thread-safe access
    /// from interrupt handlers and concurrent code.
    ///
    /// ## Initialization
    ///
    /// The serial port is initialized using the `uart_16550` crate's `init()`
    /// method, which configures the UART with standard settings:
    /// - 38400 baud (default for the crate)
    /// - 8 data bits
    /// - 1 stop bit
    /// - No parity
    /// - FIFOs enabled
    ///
    /// ## Safety
    ///
    /// Creating the `SerialPort` is unsafe because:
    /// - We're specifying a raw I/O port address
    /// - The port must actually exist and be a UART 16550
    /// - Only one `SerialPort` should exist per physical port
    pub static ref SERIAL1: Mutex<SerialPort> = {
        // SAFETY: 0x3F8 is the standard COM1 port address.
        // This is always present on x86 PCs (real or emulated).
        let mut serial_port = unsafe { SerialPort::new(0x3F8) };

        // Initialize the UART (set baud rate, enable FIFOs, etc.)
        serial_port.init();

        Mutex::new(serial_port)
    };
}

/// Internal function used by the serial print macros.
///
/// Acquires the [`SERIAL1`] lock and writes the formatted arguments to the
/// serial port. Interrupts are disabled during the write to prevent deadlocks.
///
/// # Arguments
///
/// * `args` - The pre-formatted arguments from `format_args!`
///
/// # Panics
///
/// Panics with the message "Printing to serial failed" if writing fails.
/// This typically indicates a hardware problem or misconfiguration.
#[doc(hidden)]
pub fn _print(args: ::core::fmt::Arguments) {
    use core::fmt::Write;
    use x86_64::instructions::interrupts;

    // Disable interrupts while holding the lock to prevent deadlock.
    // Without this, an interrupt handler trying to print while we hold
    // the lock would spin forever on the same CPU.
    interrupts::without_interrupts(|| {
        SERIAL1
            .lock()
            .write_fmt(args)
            .expect("Printing to serial failed");
    });
}

/// Prints formatted text to the serial port (without newline).
///
/// This macro works like the standard library's [`print!`](std::print), but
/// outputs to the COM1 serial port instead of stdout.
///
/// ## Output Destination
///
/// When running in QEMU with `-serial stdio`, output appears in the terminal
/// running QEMU. This is separate from VGA output shown in the QEMU window.
///
/// # Example
///
/// ```ignore
/// serial_print!("Debug: ");
/// serial_print!("value = {}", 42);
/// // Serial output: Debug: value = 42
/// ```
#[macro_export]
macro_rules! serial_print {
    ($($arg:tt)*) => {
        $crate::serial::_print(format_args!($($arg)*));
    };
}

/// Prints formatted text to the serial port with a trailing newline.
///
/// This macro works like the standard library's [`println!`](std::println), but
/// outputs to the COM1 serial port instead of stdout.
///
/// ## Use Cases
///
/// - **Test output**: Report test pass/fail status
/// - **Debugging**: Print variable values and execution flow
/// - **Logging**: Record events for post-mortem analysis
///
/// # Example
///
/// ```ignore
/// serial_println!("Kernel initialized");
/// serial_println!("Memory: {} bytes available", free_memory);
/// serial_println!();  // Just a newline
/// ```
#[macro_export]
macro_rules! serial_println {
    () => ($crate::serial_print!("\n"));
    ($fmt:expr) => ($crate::serial_print!(concat!($fmt, "\n")));
    ($fmt:expr, $($arg:tt)*) => ($crate::serial_print!(
        concat!($fmt, "\n"), $($arg)*));
}