// SPDX-License-Identifier: LCL-1.0 // Copyright (c) 2026 Markus Maiwald // Stewardship: Self Sovereign Society Foundation // // This file is part of the Nexus Commonwealth. // See legal/LICENSE_COMMONWEALTH.md for license terms. //! Rumpk Layer 0: UART Driver //! //! Minimal serial I/O for QEMU 'virt' machine. //! Supports PL011 (ARM64) and 16550A (RISC-V). //! //! SAFETY: All MMIO accesses use volatile pointers. //! Ring buffer is initialized to undefined for performance; //! head/tail indices ensure only written bytes are read. const std = @import("std"); const builtin = @import("builtin"); // ARM64 PL011 Constants const PL011_BASE: usize = 0x09000000; const PL011_DR: usize = 0x00; const PL011_FR: usize = 0x18; const PL011_TXFF: u32 = 1 << 5; // RISC-V 16550A Constants const NS16550A_BASE: usize = 0x10000000; const NS16550A_THR: usize = 0x00; // Transmitter Holding Register const NS16550A_LSR: usize = 0x05; // Line Status Register const NS16550A_THRE: u8 = 1 << 5; // Transmitter Holding Register Empty const NS16550A_IER: usize = 0x01; // Interrupt Enable Register const NS16550A_FCR: usize = 0x02; // FIFO Control Register const NS16550A_LCR: usize = 0x03; // Line Control Register // Input Ring Buffer (256 bytes, power of 2 for fast masking) const INPUT_BUFFER_SIZE = 256; // SAFETY(RingBuffer): Only accessed via head/tail indices. // SAFETY(RingBuffer): Only accessed via head/tail indices. // Bytes are written before read. No uninitialized reads possible. var input_buffer: [INPUT_BUFFER_SIZE]u8 = undefined; var input_head = std.atomic.Value(u32).init(0); // Write position var input_tail = std.atomic.Value(u32).init(0); // Read position pub fn init() void { // Initialize buffer pointers input_head.store(0, .monotonic); input_tail.store(0, .monotonic); switch (builtin.cpu.arch) { .riscv64 => init_riscv(), else => {}, } } pub fn init_riscv() void { const base = NS16550A_BASE; // 1. Enable Interrupts (Received Data Available) const ier: *volatile u8 = @ptrFromInt(base + NS16550A_IER); ier.* = 0x01; // 0x01 = Data Ready Interrupt. // 2. Disable FIFO (16450 Mode) to ensure immediate non-buffered input visibility const fcr: *volatile u8 = @ptrFromInt(base + NS16550A_FCR); fcr.* = 0x00; // 2b. Enable Modem Control (DTR | RTS | OUT2) // Essential for allowing interrupts and signaling readiness const mcr: *volatile u8 = @ptrFromInt(base + 0x04); // NS16550A_MCR mcr.* = 0x0B; // 3. Set LCR to 8N1 const lcr: *volatile u8 = @ptrFromInt(base + NS16550A_LCR); lcr.* = 0x03; // --- LOOPBACK TEST --- // Enable Loopback Mode (Bit 4 of MCR) mcr.* = 0x1B; // 0x0B | 0x10 // Write a test byte: 0xA5 const thr: *volatile u8 = @ptrFromInt(base + NS16550A_THR); const lsr: *volatile u8 = @ptrFromInt(base + NS16550A_LSR); // Wait for THRE while ((lsr.* & NS16550A_THRE) == 0) {} thr.* = 0xA5; // Wait for Data Ready var timeout: usize = 1000000; while ((lsr.* & 0x01) == 0 and timeout > 0) { timeout -= 1; } var passed = false; var reason: []const u8 = "Timeout"; if ((lsr.* & 0x01) != 0) { // Read RBR const rbr: *volatile u8 = @ptrFromInt(base + 0x00); const val = rbr.*; if (val == 0xA5) { passed = true; } else { reason = "Data Mismatch"; } } // Disable Loopback (Restore MCR) mcr.* = 0x0B; if (passed) { write_bytes("[UART] Loopback Test: PASS\n"); } else { write_bytes("[UART] Loopback Test: FAIL ("); write_bytes(reason); write_bytes(")\n"); } // Capture any data already in hardware FIFO poll_input(); } /// Poll UART hardware and move available bytes into ring buffer /// Should be called periodically (e.g. from scheduler or ISR) pub fn poll_input() void { switch (builtin.cpu.arch) { .riscv64 => { const thr: *volatile u8 = @ptrFromInt(NS16550A_BASE + NS16550A_THR); const lsr: *volatile u8 = @ptrFromInt(NS16550A_BASE + NS16550A_LSR); // Read all available bytes from UART FIFO while ((lsr.* & 0x01) != 0) { // Data Ready const byte = thr.*; // Add to ring buffer if not full const head_val = input_head.load(.monotonic); const tail_val = input_tail.load(.monotonic); const next_head = (head_val + 1) % INPUT_BUFFER_SIZE; if (next_head != tail_val) { input_buffer[head_val] = byte; input_head.store(next_head, .monotonic); } // If full, drop the byte (could log this in debug mode) } }, .aarch64 => { const dr: *volatile u32 = @ptrFromInt(PL011_BASE + PL011_DR); const fr: *volatile u32 = @ptrFromInt(PL011_BASE + PL011_FR); while ((fr.* & (1 << 4)) == 0) { // RXFE (Receive FIFO Empty) is bit 4 const byte: u8 = @truncate(dr.*); const head_val = input_head.load(.monotonic); const tail_val = input_tail.load(.monotonic); const next_head = (head_val + 1) % INPUT_BUFFER_SIZE; if (next_head != tail_val) { input_buffer[head_val] = byte; input_head.store(next_head, .monotonic); } } }, else => {}, } } fn write_char_arm64(c: u8) void { const dr: *volatile u32 = @ptrFromInt(PL011_BASE + PL011_DR); const fr: *volatile u32 = @ptrFromInt(PL011_BASE + PL011_FR); while ((fr.* & PL011_TXFF) != 0) {} dr.* = c; } fn write_char_riscv64(c: u8) void { const thr: *volatile u8 = @ptrFromInt(NS16550A_BASE + NS16550A_THR); const lsr: *volatile u8 = @ptrFromInt(NS16550A_BASE + NS16550A_LSR); // Wait for THRE (Transmitter Holding Register Empty) while ((lsr.* & NS16550A_THRE) == 0) {} thr.* = c; } fn write_char(c: u8) void { switch (builtin.cpu.arch) { .aarch64 => write_char_arm64(c), .riscv64 => write_char_riscv64(c), else => {}, // Do nothing on others } } pub fn write_bytes(bytes: []const u8) void { for (bytes) |b| { if (b == '\n') { write_char('\r'); } write_char(b); } } pub fn read_byte() ?u8 { // First, poll UART to refill buffer poll_input(); // Then read from buffer const head_val = input_head.load(.monotonic); const tail_val = input_tail.load(.monotonic); if (tail_val != head_val) { const byte = input_buffer[tail_val]; input_tail.store((tail_val + 1) % INPUT_BUFFER_SIZE, .monotonic); return byte; } return null; } pub fn read_direct() ?u8 { switch (builtin.cpu.arch) { .riscv64 => { const thr: *volatile u8 = @ptrFromInt(NS16550A_BASE + NS16550A_THR); const lsr: *volatile u8 = @ptrFromInt(NS16550A_BASE + NS16550A_LSR); if ((lsr.* & 0x01) != 0) { return thr.*; } }, else => {}, } return null; } pub fn get_lsr() u8 { switch (builtin.cpu.arch) { .riscv64 => { const lsr: *volatile u8 = @ptrFromInt(NS16550A_BASE + NS16550A_LSR); return lsr.*; }, else => return 0, } } pub fn puts(s: []const u8) void { write_bytes(s); } pub fn putc(c: u8) void { if (c == '\n') { write_char('\r'); } write_char(c); } pub fn print(s: []const u8) void { puts(s); } pub const Writer = struct { pub const Error = error{}; pub fn write(self: Writer, bytes: []const u8) Error!usize { _ = self; write_bytes(bytes); return bytes.len; } pub fn print(self: Writer, comptime fmt: []const u8, args: anytype) Error!void { return std.fmt.format(self, fmt, args); } }; pub fn print_hex(value: usize) void { const hex_chars = "0123456789ABCDEF"; write_bytes("0x"); var i: usize = 0; while (i < 16) : (i += 1) { const shift: u6 = @intCast((15 - i) * 4); const nibble = (value >> shift) & 0xF; write_char(hex_chars[nibble]); } } export fn uart_print_hex(value: u64) void { print_hex(value); }