From 76103215711d268b4b02f62e900f2be3032cf295 Mon Sep 17 00:00:00 2001 From: salmonik Date: Fri, 31 Jul 2026 02:18:30 +0200 Subject: Initial commit: raisernics toy kernel - Bare-metal x86_64 UEFI kernel (PE32+, no libc) - GOP framebuffer + 8x16 bitmap font - PS/2 keyboard (polling, scancode set 1) - UART 16550 serial driver (COM1, 115200 8N1) - Bump allocator as #[global_allocator] (alloc::String, Vec, format!) - UEFI memory map capture (static buffer, ADR 0001) - Shell with 7 commands (help, echo, clear, hello, info, mem, halt) - Dual output: GOP framebuffer + serial console - Makefile: run, run-gui, debug, release, image targets - tools/: qemu.sh, boot-image.sh, debug-kernel.py --- src/serial.rs | 118 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 src/serial.rs (limited to 'src/serial.rs') diff --git a/src/serial.rs b/src/serial.rs new file mode 100644 index 0000000..03fa570 --- /dev/null +++ b/src/serial.rs @@ -0,0 +1,118 @@ +/// 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)*); + }}; +} + +#[macro_export] +macro_rules! serial_println { + () => { $crate::serial::write_str("\r\n"); }; + ($($arg:tt)*) => {{ + $crate::serial_print!($($arg)*); + $crate::serial::write_str("\r\n"); + }}; +} + +// --------------------------------------------------------------------------- +// 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)); + } +} -- cgit v1.2.3