//! Serial console (16550-compatible UART) — the kernel's machine-readable output //! channel. Unlike the framebuffer console, serial text can be captured to a file //! by QEMU (`-serial file:...`), which is what the test harness asserts on. //! //! The UART defaults to the legacy PC COM1 at I/O port `0x3F8`, but a UEFI Class 3 //! (legacy-free) machine may have no COM1 — or its debug UART somewhere else, and //! reachable via MMIO rather than port I/O. So the location is a runtime value: //! `reconfigure` repoints it once ACPI's SPCR table has been read. Early boot logs //! optimistically to COM1 (harmless if absent); the framebuffer console is the //! always-present log. const paging = @import("paging.zig"); /// How the UART registers are reached: legacy I/O ports or memory-mapped. const Access = enum { port, mmio }; var access: Access = .port; var base: u64 = 0x3F8; // COM1 fn portOut(p: u16, value: u8) void { asm volatile ("outb %[value], %[p]" : : [value] "{al}" (value), [p] "{dx}" (p), ); } fn portIn(p: u16) u8 { return asm volatile ("inb %[p], %[value]" : [value] "={al}" (-> u8), : [p] "{dx}" (p), ); } /// Read UART register `off` through the active access method. fn register(off: u64) u8 { if (access == .mmio) return @as(*volatile u8, @ptrFromInt(base + off)).*; return portIn(@intCast(base + off)); } /// Write UART register `off` through the active access method. fn setRegister(off: u64, value: u8) void { if (access == .mmio) { @as(*volatile u8, @ptrFromInt(base + off)).* = value; } else { portOut(@intCast(base + off), value); } } /// Configure the UART: 38400 baud, 8N1, FIFO on. Safe to call before anything /// else; it has no dependencies, and is a harmless no-op if the port is absent. pub fn init() void { setRegister(1, 0x00); // disable interrupts setRegister(3, 0x80); // enable DLAB (set baud divisor) setRegister(0, 0x03); // divisor low: 38400 baud setRegister(1, 0x00); // divisor high setRegister(3, 0x03); // 8 bits, no parity, one stop bit; DLAB off setRegister(2, 0xC7); // enable + clear FIFO, 14-byte threshold setRegister(4, 0x0B); // RTS/DSR set } /// Point the console at the UART ACPI's SPCR table names (MMIO or I/O port) and /// re-run the UART setup there. Called after discovery when an SPCR entry exists. pub fn reconfigure(is_mmio: bool, address: u64) void { access = if (is_mmio) .mmio else .port; // An MMIO UART is reached through the physmap; an I/O-port UART keeps its // port number unchanged. base = if (is_mmio) paging.mapMmio(address, 0x100, true) else address; init(); } fn writeByte(c: u8) void { // Wait for the transmit-holding register to empty — but bounded, so an absent // UART (whose line-status register reads back as 0x00) can't hang the kernel. var guard: u32 = 0; while (register(5) & 0x20 == 0 and guard < 100_000) : (guard += 1) {} setRegister(0, c); } /// Write bytes, translating LF to CRLF so terminals and logs line up. pub fn write(bytes: []const u8) void { for (bytes) |c| { if (c == '\n') writeByte('\r'); writeByte(c); } }