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
|
/// UART 16550 serial driver — port-mapped I/O on COM1 (0x3F8)
use core::fmt;
const COM1: u16 = 0x3F8;
const DATA_REG: u16 = COM1;
const INTR_ENABLE: u16 = COM1 + 1;
const FIFO_CONTROL: u16 = COM1 + 2;
const LINE_CONTROL: u16 = COM1 + 3;
const MODEM_CONTROL: u16 = COM1 + 4;
const LINE_STATUS: u16 = COM1 + 5;
const LSR_TX_EMPTY: u8 = 0x20;
const LSR_DATA_READY: u8 = 0x01;
/// Initialise the UART with 115200 baud, 8N1.
pub fn init() {
unsafe {
// Disable interrupts
port_out(INTR_ENABLE, 0x00);
// Set DLAB=1 (baud rate divisor)
port_out(LINE_CONTROL, 0x80);
// Divisor = 1 → 115200 baud (with 1.8432 MHz clock)
port_out(DATA_REG, 0x01);
port_out(INTR_ENABLE, 0x00);
// 8-bit, 1 stop, no parity, DLAB=0
port_out(LINE_CONTROL, 0x03);
// Enable FIFO, clear, 14-byte threshold
port_out(FIFO_CONTROL, 0xC7);
// RTS/DSR set
port_out(MODEM_CONTROL, 0x0B);
}
}
/// Write a single byte to the serial port (blocking poll).
pub fn put_byte(b: u8) {
unsafe {
// Wait for transmitter holding register empty
while port_in(LINE_STATUS) & LSR_TX_EMPTY == 0 {}
port_out(DATA_REG, b);
}
}
/// Read a single byte from the serial port (blocking poll).
pub fn get_byte() -> u8 {
unsafe {
while port_in(LINE_STATUS) & LSR_DATA_READY == 0 {}
port_in(DATA_REG)
}
}
/// Non-blocking read: returns Some(byte) if data ready, None otherwise.
pub fn try_get_byte() -> Option<u8> {
unsafe {
if port_in(LINE_STATUS) & LSR_DATA_READY != 0 {
Some(port_in(DATA_REG))
} else {
None
}
}
}
/// Write a string to serial.
pub fn write_str(s: &str) {
for &b in s.as_bytes() {
if b == b'\n' {
put_byte(b'\r');
}
put_byte(b);
}
}
// ---------------------------------------------------------------------------
// fmt::Write implementation
// ---------------------------------------------------------------------------
pub struct SerialWriter;
impl fmt::Write for SerialWriter {
fn write_str(&mut self, s: &str) -> fmt::Result {
write_str(s);
Ok(())
}
}
/// Helper macro — prints to serial via format_args!.
#[macro_export]
macro_rules! serial_print {
($($arg:tt)*) => {{
use core::fmt::Write;
let _ = write!(&mut $crate::serial::SerialWriter, $($arg)*);
}};
}
/// Prints a line to serial and appends it to the kernel log ring
/// (klog — see /dev/klog). Every kernel-side message funnels through
/// here, so the ring captures the whole boot sequence for free.
/// User-facing shell output must NOT use this macro (shell output would
/// pollute the boot log); it writes to the serial port directly.
#[macro_export]
macro_rules! serial_println {
() => {
$crate::serial::write_str("\r\n");
$crate::klog::append_fmt(core::format_args!(""));
};
($($arg:tt)*) => {{
$crate::serial_print!($($arg)*);
$crate::serial::write_str("\r\n");
$crate::klog::append_fmt(core::format_args!($($arg)*));
}};
}
// ---------------------------------------------------------------------------
// Port I/O helpers
// ---------------------------------------------------------------------------
unsafe fn port_in(port: u16) -> u8 {
let val: u8;
unsafe {
core::arch::asm!("in al, dx", out("al") val, in("dx") port, options(nostack, preserves_flags));
}
val
}
unsafe fn port_out(port: u16, val: u8) {
unsafe {
core::arch::asm!("out dx, al", in("al") val, in("dx") port, options(nostack, preserves_flags));
}
}
|