142 lines
3.8 KiB
Zig
142 lines
3.8 KiB
Zig
// Rumpk Layer 0: UART Driver
|
|
// Minimal serial output for QEMU 'virt' machine
|
|
// Supports PL011 (ARM64) and 16550A (RISC-V)
|
|
|
|
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
|
|
|
|
pub fn init() void {
|
|
switch (builtin.cpu.arch) {
|
|
.riscv64 => init_riscv(),
|
|
else => {},
|
|
}
|
|
}
|
|
|
|
const NS16550A_IER: usize = 0x01; // Interrupt Enable Register
|
|
|
|
pub fn init_riscv() void {
|
|
// Disable Interrupts to rely on Polling (prevents Interrupt Storms if Handler is missing)
|
|
const ier: *volatile u8 = @ptrFromInt(NS16550A_BASE + NS16550A_IER);
|
|
ier.* = 0x00;
|
|
|
|
// Drain FIFO
|
|
const lsr: *volatile u8 = @ptrFromInt(NS16550A_BASE + NS16550A_LSR);
|
|
const rbr: *volatile u8 = @ptrFromInt(NS16550A_BASE + NS16550A_THR);
|
|
while ((lsr.* & 0x01) != 0) {
|
|
_ = rbr.*;
|
|
}
|
|
}
|
|
|
|
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 {
|
|
switch (builtin.cpu.arch) {
|
|
.aarch64 => {
|
|
const dr: *volatile u32 = @ptrFromInt(PL011_BASE + PL011_DR);
|
|
const fr: *volatile u32 = @ptrFromInt(PL011_BASE + PL011_FR);
|
|
if ((fr.* & (1 << 4)) == 0) { // RXFE (Receive FIFO Empty) is bit 4, so if 0, it's NOT empty
|
|
return @truncate(dr.*);
|
|
}
|
|
},
|
|
.riscv64 => {
|
|
const thr: *volatile u8 = @ptrFromInt(NS16550A_BASE + NS16550A_THR);
|
|
const lsr: *volatile u8 = @ptrFromInt(NS16550A_BASE + NS16550A_LSR);
|
|
|
|
const lsr_val = lsr.*;
|
|
|
|
// DIAGNOSTIC: Periodic LSR dump removed
|
|
|
|
if ((lsr_val & 0x01) != 0) { // Data Ready
|
|
const b = thr.*;
|
|
// Signal reception
|
|
// Signal reception removed
|
|
return b;
|
|
}
|
|
},
|
|
else => {},
|
|
}
|
|
return null;
|
|
}
|
|
|
|
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]);
|
|
}
|
|
}
|