/// 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 { 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)); } }