commit 1831e7bc39f00ce900135d03aa188bb1cc1afb90
parent a20b8cb3f7361077ee44f182dc3429c04bc37482
Author: Sylvia Ivory <git@sivory.net>
Date: Wed, 28 Jan 2026 22:02:20 -0800
Add Mini-UART RX interrupts
Diffstat:
4 files changed, 214 insertions(+), 39 deletions(-)
diff --git a/pi/interrupts.zig b/pi/interrupts.zig
@@ -51,6 +51,48 @@ pub inline fn pending_basic(i: BasicInterrupt) bool {
return (mem.get_u32_barrier(@ptrFromInt(IRQ_BASIC_PENDING)) & @intFromEnum(i)) != 0;
}
+const PeripheralsInterrupt = enum(u6) {
+ AuxInterrupt = 29,
+ I2CSPISLVInterrupt = 43,
+ PWA0 = 45,
+ PWA1 = 46,
+ SMI = 48,
+ GPIO0 = 49,
+ GPIO1 = 50,
+ GPIO2 = 51,
+ GPIO3 = 52,
+ I2CInt = 53,
+ SPIInt = 54,
+ PCMInt = 55,
+ UARTInt = 57,
+};
+
+// TODO; abstract out GPIO's get_address
+pub inline fn enable_peripheral_interrupt(i: PeripheralsInterrupt) void {
+ const v = @intFromEnum(i);
+ if (v < 32) {
+ mem.put_u32_barrier(@ptrFromInt(IRQ_ENABLE_1), 1 << v);
+ } else {
+ mem.put_u32_barrier(@ptrFromInt(IRQ_ENABLE_2), 1 << (v - 32));
+ }
+}
+pub inline fn disable_peripheral_interrupt(i: PeripheralsInterrupt) void {
+ const v = @intFromEnum(i);
+ if (v < 32) {
+ mem.put_u32_barrier(@ptrFromInt(IRQ_DISABLE_1), 1 << v);
+ } else {
+ mem.put_u32_barrier(@ptrFromInt(IRQ_DISABLE_2), 1 << (v - 32));
+ }
+}
+pub inline fn pending_peripheral_interrupt(i: PeripheralsInterrupt) bool {
+ const v = @intFromEnum(i);
+ if (v < 32) {
+ return mem.get_u32_barrier(@ptrFromInt(IRQ_PENDING_1)) & (1 << v) != 0;
+ } else {
+ return mem.get_u32_barrier(@ptrFromInt(IRQ_PENDING_2)) & (1 << (v - 32)) != 0;
+ }
+}
+
fn empty(lr: u32) void {
_ = lr;
}
@@ -111,6 +153,7 @@ comptime {
);
}
extern fn software_interrupt() void;
+export fn syscall_handler() void {}
// https://leiradel.github.io/2019/02/09/Initialization.html
pub fn setup_exception_vector() void {
diff --git a/src/devices/mini-uart.zig b/src/devices/mini-uart.zig
@@ -1,8 +1,11 @@
const std = @import("std");
-const mem = @import("pi").mem;
+const pi = @import("pi");
const gpio = @import("./gpio.zig");
+const interrupts = pi.interrupts;
+const mem = pi.mem;
+
pub const Error = error{ AlreadyInitialized, NotInitialized, InvalidReadLimit } || gpio.Error;
// Page 8: Auxiliary peripherals Register Map
@@ -34,6 +37,7 @@ const RxPin = enum(u8) {
};
var initialized = false;
+var interrupts_enabled = false;
pub fn initialize(baud: u32, tx_pin: TxPin, rx_pin: RxPin) Error!void {
if (initialized) return Error.AlreadyInitialized;
@@ -65,8 +69,8 @@ pub fn initialize(baud: u32, tx_pin: TxPin, rx_pin: RxPin) Error!void {
mem.put_u32(@ptrFromInt(AUX_ENABLES), 1);
// Disable interrupts
- // Page 13: AUX_MU_IER_REG Register
- // When bits 2:1 is 0, interrupts are disabled
+ // Page 12: AUX_MU_IER_REG Register
+ // When bit 0/1 is 0, interrupts are disabled
mem.put_u32(@ptrFromInt(AUX_MU_IER_REG), 0);
// Disable RX/TX during configuration
@@ -77,8 +81,8 @@ pub fn initialize(baud: u32, tx_pin: TxPin, rx_pin: RxPin) Error!void {
// Set data size to 8 bits
// Page 14: AUX_MU_LCR_REG Register
// When bit 0 is 1, UART is in 8-bit mode, else 7-bit
- // mem.put_u32(@ptrFromInt(AUX_MU_LCR_REG), 1);
- mem.put_u32(@ptrFromInt(AUX_MU_LCR_REG), 3);
+ // Errata: "bit 1 must be set for 8 bit mode"
+ mem.put_u32(@ptrFromInt(AUX_MU_LCR_REG), 0b11);
// Put RTS high (indicate request to send)
// Page 14: AUX_MU_MCR_REG Register
@@ -86,10 +90,9 @@ pub fn initialize(baud: u32, tx_pin: TxPin, rx_pin: RxPin) Error!void {
mem.put_u32(@ptrFromInt(AUX_MU_MCR_REG), 0);
// Clear FIFO
- // Page 12: AUX_MU_IER_REG Register (yes the page is mislabeled)
+ // Page 13: AUX_MU_IER_REG Register
// When bit 1/2 is 1, receive/transmit will be cleared
- mem.put_u32(@ptrFromInt(AUX_MU_IIR_REG), 0x06);
- // mem.put_u32(@ptrFromInt(AUX_MU_IIR_REG), 0xC6);
+ mem.put_u32(@ptrFromInt(AUX_MU_IIR_REG), 0b110);
// Set baud rate
// Page 11: 2.2.1 Mini UART Implementation Details
@@ -99,57 +102,175 @@ pub fn initialize(baud: u32, tx_pin: TxPin, rx_pin: RxPin) Error!void {
// Enable RX/TX again
// Page 17: AUX_MU_CNTL_REG Register
// When bit 0/1 is 1, UART receiver/transmitter is enabled
- mem.put_u32(@ptrFromInt(AUX_MU_CNTL_REG), 0x3);
- // mem.put_u32(@ptrFromInt(AUX_MU_CNTL_REG), 2);
+ mem.put_u32(@ptrFromInt(AUX_MU_CNTL_REG), 0b11);
+
+ mem.barrier(.Write);
initialized = true;
}
+pub fn enable_interrupts() Error!void {
+ if (!initialized) return Error.NotInitialized;
+ if (interrupts_enabled) return Error.AlreadyInitialized;
+
+ mem.barrier(.Write);
+
+ interrupts.set_exception_handler(.IRQ, uart_handler);
+ interrupts.enable_peripheral_interrupt(.AuxInterrupt);
+
+ // Enable RX/TX Interrupts
+ // Page 12: AUX_MU_IIR_REG Register
+ // Bit 0 - TX Interrupt
+ // Bit 1 - RX Interrupt
+ // Errata: "Bits 3:2 are marked as don't care, but
+ // are actually required in order to receive interrupts."
+
+ // TODO; figure out why TX interrupts aren't working
+ // mem.put_u32_barrier(@ptrFromInt(AUX_MU_IER_REG), 0b0111);
+ mem.put_u32_barrier(@ptrFromInt(AUX_MU_IER_REG), 0b0101);
+
+ interrupts_enabled = true;
+}
+
+// var tx_buffer: [1024]u8 = undefined;
+// var tx_list: std.ArrayList(u8) = .initBuffer(&tx_buffer);
+
+var rx_buffer: [1024]u8 = undefined;
+var rx_list: std.ArrayList(u8) = .initBuffer(&rx_buffer);
+
+noinline fn uart_handler(pc: usize) void {
+ _ = pc;
+ mem.barrier(.Write);
+
+ if (!interrupts.pending_peripheral_interrupt(.AuxInterrupt)) return;
+
+ while (true) {
+ const IIR = mem.get_u32(@ptrFromInt(AUX_MU_IIR_REG));
+
+ // UART interrupt pending
+ if ((IIR & 0b1) == 1) return;
+
+ if ((IIR & 0b100) != 0) {
+ rx_list.appendAssumeCapacity(read_byte(true));
+ }
+
+ // TODO; figure out why TX interrupts aren't working
+ // if ((IIR & 0b010) != 0) {
+ // // TX empty
+ // // while (can_write()) {
+ // if (tx_list.items.len >= 1) {
+ // const byte = tx_list.orderedRemove(0);
+ // write_byte(byte, true);
+ // } else {
+ // // break;
+ // }
+ // // }
+ // }
+ }
+
+ mem.barrier(.Write);
+}
+
pub fn is_initialized() bool {
return initialized;
}
-pub fn can_write() bool {
- // Check if FIFO can accept data
- // Page 15: AUX_MU_LSR_REG Register
- // Bit 5 is set when FIFO can accept at least 1 byte
- return (mem.get_u32(@ptrFromInt(AUX_MU_LSR_REG)) & 0x20) != 0;
+const Status = packed struct(u32) {
+ rx_has_symbol: bool,
+ tx_has_space: bool,
+ rx_is_idle: bool,
+ tx_is_idle: bool,
+ rx_overrun: bool,
+ tx_full: bool,
+ rts_status: bool,
+ ctx_status: bool,
+ tx_is_empty: bool,
+ tx_done: bool,
+ _reserved_10_15: u6,
+ rx_fill_level: u4,
+ _reserved_20_23: u4,
+ tx_fill_level: u4,
+ _reserved_28_31: u4,
+};
+
+pub fn status() Status {
+ return @bitCast(mem.get_u32_barrier(@ptrFromInt(AUX_MU_STAT_REG)));
}
pub fn can_read() bool {
// Check if FIFO has data
// Page 15: AUX_MU_LSR_REG Register
// Bit 1 is set when FIFO holds at least 1 byte
- return (mem.get_u32(@ptrFromInt(AUX_MU_LSR_REG)) & 1) == 1;
+ return (mem.get_u32_barrier(@ptrFromInt(AUX_MU_LSR_REG)) & 0x01) == 1;
}
-pub fn write_byte(byte: u8) void {
- // TODO; support timeout
- while (!can_write()) {}
+pub fn read_queued() usize {
+ return rx_list.items.len;
+}
+
+pub fn read_byte(force: bool) u8 {
+ if (force) {
+ // Read from FIFO
+ // Page 11: AUX_MU_IO_REG Register
+ // Bits 7:0 holds data from the FIFO
+ return @truncate(mem.get_u32_barrier(@ptrFromInt(AUX_MU_IO_REG)));
+ }
+
+ if (rx_list.items.len > 0) {
+ // Pop from RX buffer instead
+ // TODO; use FIFO
+ const byte = rx_list.orderedRemove(0);
+ return byte;
+ }
+
+ // TODO; support timeout/async
+ while (!can_read()) {}
- // Write into FIFO
- // Page 11: AUX_MU_IO_REG Register
- // Bits 7:0 holds data from the FIFO
- mem.put_u32(@ptrFromInt(AUX_MU_IO_REG), @as(u32, byte) & 0xFF);
+ return read_byte(true);
+}
+
+pub fn can_write() bool {
+ // Check if FIFO can accept data
+ // Page 15: AUX_MU_LSR_REG Register
+ // Bit 5 is set when FIFO can accept at least 1 byte
+ return (mem.get_u32_barrier(@ptrFromInt(AUX_MU_LSR_REG)) & 0x20) != 0;
+}
+
+// pub fn write_queued() usize {
+// return tx_list.items.len;
+// }
+
+pub fn write_byte(byte: u8, force: bool) void {
+ if (force or can_write()) {
+ // Write into FIFO
+ // Page 11: AUX_MU_IO_REG Register
+ // Bits 7:0 holds data from the FIFO
+ mem.put_u32_barrier(@ptrFromInt(AUX_MU_IO_REG), @as(u32, byte) & 0xFF);
+ return;
+ }
+
+ // if (interrupts_enabled) {
+ // // Throw onto queue, will handle later
+ // tx_list.appendAssumeCapacity(byte);
+ // return;
+ // }
+
+ while (!can_write()) {}
+ write_byte(byte, false);
}
pub fn write_slice(bytes: []const u8) void {
for (bytes) |b| {
- write_byte(b);
+ write_byte(b, false);
}
}
-pub fn read_byte() u8 {
- // TODO; support timeout
- while (!can_read()) {}
-
- // Read from FIFO
- // Page 11: AUX_MU_IO_REG Register
- // Bits 7:0 holds data from the FIFO
- return @truncate(mem.get_u32(AUX_MU_IO_REG));
+pub fn flush() void {
+ // Loop until there's nothing left in TX queue
+ while (status().tx_fill_level != 0) {}
}
-fn drain(io_w: *std.Io.Writer, data: []const []const u8, splat: usize) !usize {
+fn writer_drain(io_w: *std.Io.Writer, data: []const []const u8, splat: usize) !usize {
if (!initialized) return std.Io.Writer.Error.WriteFailed;
// We don't care about getting the "parent"
@@ -157,13 +278,22 @@ fn drain(io_w: *std.Io.Writer, data: []const []const u8, splat: usize) !usize {
_ = splat;
for (data[0]) |b| {
- write_byte(b);
+ write_byte(b, false);
}
return data[0].len;
}
-const writer_vtable: std.Io.Writer.VTable = .{ .drain = drain };
+fn writer_flush(io_w: *std.Io.Writer) !void {
+ if (!initialized) return std.Io.Writer.Error.WriteFailed;
+
+ // We don't care about getting the "parent"
+ _ = io_w;
+
+ flush();
+}
+
+const writer_vtable: std.Io.Writer.VTable = .{ .drain = writer_drain, .flush = writer_flush };
pub var writer = std.Io.Writer{ .buffer = undefined, .vtable = &writer_vtable };
fn stream(io_r: *std.Io.Reader, io_w: *std.Io.Writer, limit: std.Io.Limit) !usize {
@@ -172,7 +302,7 @@ fn stream(io_r: *std.Io.Reader, io_w: *std.Io.Writer, limit: std.Io.Limit) !usiz
if (limit.toInt()) |*max| {
for (0..max) |_| {
- try io_w.writeByte(read_byte());
+ try io_w.writeByte(read_byte(false));
}
return max;
diff --git a/src/main.zig b/src/main.zig
@@ -1,3 +1 @@
-export fn syscall_handler() void {}
-
pub fn main() !void {}
diff --git a/src/root.zig b/src/root.zig
@@ -54,6 +54,10 @@ export fn kmain() void {
uart.writer.print(" 0x{X} = 0x{X}\n", .{ @intFromPtr(dst), dst.* }) catch {};
}
+ uart.write_slice("Enabling Interrupts\n");
+ uart.enable_interrupts() catch {};
+ pi.interrupts.enable_interrupts();
+
uart.write_slice("Calling user main\n");
user_main() catch |e| {
@@ -80,7 +84,7 @@ fn panic_handler(msg: []const u8, trace_addr: ?usize) noreturn {
if (uart.is_initialized()) {
uart.write_slice("kernel panic: ");
uart.write_slice(msg);
- uart.write_byte('\n');
+ uart.write_byte('\n', false);
var it = std.debug.StackIterator.init(trace_addr, null);
var ix: usize = 0;