diff --git a/hal/uart.zig b/hal/uart.zig index f61608d..1a5a97f 100644 --- a/hal/uart.zig +++ b/hal/uart.zig @@ -1,6 +1,7 @@ // Rumpk Layer 0: UART Driver // Minimal serial output for QEMU 'virt' machine // Supports PL011 (ARM64) and 16550A (RISC-V) +// Phase 37.2: Input buffering to prevent character loss const std = @import("std"); const builtin = @import("builtin"); @@ -16,16 +17,25 @@ 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 + +// Input Ring Buffer (256 bytes, power of 2 for fast masking) +const INPUT_BUFFER_SIZE = 256; +var input_buffer: [INPUT_BUFFER_SIZE]u8 = undefined; +var input_head: u32 = 0; // Write position +var input_tail: u32 = 0; // Read position pub fn init() void { + // Initialize buffer pointers + input_head = 0; + input_tail = 0; + 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); @@ -39,6 +49,45 @@ pub fn init_riscv() void { } } +/// 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 next_head = (input_head + 1) % INPUT_BUFFER_SIZE; + if (next_head != input_tail) { + input_buffer[input_head] = byte; + input_head = next_head; + } + // 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 next_head = (input_head + 1) % INPUT_BUFFER_SIZE; + if (next_head != input_tail) { + input_buffer[input_head] = byte; + input_head = next_head; + } + } + }, + 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); @@ -74,31 +123,16 @@ pub fn write_bytes(bytes: []const u8) void { } 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); + // First, poll UART to refill buffer + poll_input(); - 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 => {}, + // Then read from buffer + if (input_tail != input_head) { + const byte = input_buffer[input_tail]; + input_tail = (input_tail + 1) % INPUT_BUFFER_SIZE; + return byte; } + return null; }