309 lines
8.7 KiB
Zig
309 lines
8.7 KiB
Zig
// 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
|
|
pub const PL011_BASE: usize = 0x09000000;
|
|
pub const PL011_DR: usize = 0x00;
|
|
pub const PL011_FR: usize = 0x18;
|
|
pub const PL011_TXFF: u32 = 1 << 5;
|
|
|
|
// RISC-V 16550A Constants
|
|
pub const NS16550A_BASE: usize = 0x10000000;
|
|
pub const NS16550A_THR: usize = 0x00; // Transmitter Holding Register
|
|
pub const NS16550A_LSR: usize = 0x05; // Line Status Register
|
|
pub const NS16550A_THRE: u8 = 1 << 5; // Transmitter Holding Register Empty
|
|
pub const NS16550A_IER: usize = 0x01; // Interrupt Enable Register
|
|
pub const NS16550A_FCR: usize = 0x02; // FIFO Control Register
|
|
pub const NS16550A_LCR: usize = 0x03; // Line Control Register
|
|
|
|
// Input logic moved to uart_input.zig
|
|
|
|
// PL011 Additional Registers
|
|
pub const PL011_IBRD: usize = 0x24; // Integer Baud Rate Divisor
|
|
pub const PL011_FBRD: usize = 0x28; // Fractional Baud Rate Divisor
|
|
pub const PL011_LCR_H: usize = 0x2C; // Line Control
|
|
pub const PL011_CR: usize = 0x30; // Control
|
|
pub const PL011_IMSC: usize = 0x38; // Interrupt Mask Set/Clear
|
|
pub const PL011_ICR: usize = 0x44; // Interrupt Clear
|
|
pub const PL011_RXFE: u32 = 1 << 4; // Receive FIFO Empty
|
|
|
|
pub fn init() void {
|
|
switch (builtin.cpu.arch) {
|
|
.riscv64 => init_riscv(),
|
|
.aarch64 => init_aarch64(),
|
|
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
|
|
// uart_input.poll_input(); // We cannot call this here safely without dep
|
|
}
|
|
|
|
pub fn init_aarch64() void {
|
|
const base = PL011_BASE;
|
|
|
|
// 1. Disable UART during setup
|
|
const cr: *volatile u32 = @ptrFromInt(base + PL011_CR);
|
|
cr.* = 0;
|
|
|
|
// 2. Clear all pending interrupts
|
|
const icr: *volatile u32 = @ptrFromInt(base + PL011_ICR);
|
|
icr.* = 0x7FF;
|
|
|
|
// 3. Set baud rate (115200 @ 24MHz QEMU clock)
|
|
// IBRD = 24000000 / (16 * 115200) = 13
|
|
// FBRD = ((0.0208... * 64) + 0.5) = 1
|
|
const ibrd: *volatile u32 = @ptrFromInt(base + PL011_IBRD);
|
|
const fbrd: *volatile u32 = @ptrFromInt(base + PL011_FBRD);
|
|
ibrd.* = 13;
|
|
fbrd.* = 1;
|
|
|
|
// 4. Line Control: 8N1, FIFO enable
|
|
const lcr_h: *volatile u32 = @ptrFromInt(base + PL011_LCR_H);
|
|
lcr_h.* = (0x3 << 5) | (1 << 4); // WLEN=8bit, FEN=1
|
|
|
|
// 5. Enable receive interrupt
|
|
const imsc: *volatile u32 = @ptrFromInt(base + PL011_IMSC);
|
|
imsc.* = (1 << 4); // RXIM: Receive interrupt mask
|
|
|
|
// 6. Enable UART: TXE + RXE + UARTEN
|
|
cr.* = (1 << 8) | (1 << 9) | (1 << 0); // TXE | RXE | UARTEN
|
|
|
|
// --- LOOPBACK TEST ---
|
|
// PL011 has loopback via CR bit 7 (LBE)
|
|
cr.* = cr.* | (1 << 7); // Enable loopback
|
|
|
|
// Write test byte
|
|
const dr: *volatile u32 = @ptrFromInt(base + PL011_DR);
|
|
const fr: *volatile u32 = @ptrFromInt(base + PL011_FR);
|
|
|
|
// Wait for TX not full
|
|
while ((fr.* & PL011_TXFF) != 0) {}
|
|
dr.* = 0xA5;
|
|
|
|
// Wait for RX not empty
|
|
var timeout: usize = 1000000;
|
|
while ((fr.* & PL011_RXFE) != 0 and timeout > 0) {
|
|
timeout -= 1;
|
|
}
|
|
|
|
var passed = false;
|
|
var reason: []const u8 = "Timeout";
|
|
|
|
if ((fr.* & PL011_RXFE) == 0) {
|
|
const val: u8 = @truncate(dr.*);
|
|
if (val == 0xA5) {
|
|
passed = true;
|
|
} else {
|
|
reason = "Data Mismatch";
|
|
}
|
|
}
|
|
|
|
// Disable loopback
|
|
cr.* = cr.* & ~@as(u32, 1 << 7);
|
|
|
|
if (passed) {
|
|
write_bytes("[UART] Loopback Test: PASS\n");
|
|
} else {
|
|
write_bytes("[UART] Loopback Test: FAIL (");
|
|
write_bytes(reason);
|
|
write_bytes(")\n");
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
// read_byte moved to uart_input.zig
|
|
|
|
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.*;
|
|
}
|
|
},
|
|
.aarch64 => {
|
|
const dr: *volatile u32 = @ptrFromInt(PL011_BASE + PL011_DR);
|
|
const fr: *volatile u32 = @ptrFromInt(PL011_BASE + PL011_FR);
|
|
if ((fr.* & PL011_RXFE) == 0) {
|
|
return @truncate(dr.*);
|
|
}
|
|
},
|
|
else => {},
|
|
}
|
|
return null;
|
|
}
|
|
|
|
pub fn get_lsr() u8 {
|
|
switch (builtin.cpu.arch) {
|
|
.riscv64 => {
|
|
const lsr: *volatile u8 = @ptrFromInt(NS16550A_BASE + NS16550A_LSR);
|
|
return lsr.*;
|
|
},
|
|
.aarch64 => {
|
|
// Return PL011 flags register (low byte)
|
|
const fr: *volatile u32 = @ptrFromInt(PL011_BASE + PL011_FR);
|
|
return @truncate(fr.*);
|
|
},
|
|
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]);
|
|
}
|
|
}
|
|
|
|
pub fn print_hex8(value: u8) void {
|
|
const hex_chars = "0123456789ABCDEF";
|
|
const nibble1 = (value >> 4) & 0xF;
|
|
const nibble2 = value & 0xF;
|
|
write_char(hex_chars[nibble1]);
|
|
write_char(hex_chars[nibble2]);
|
|
}
|