sylveos

Toy Operating System
Log | Files | Refs

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:
Mpi/interrupts.zig | 43+++++++++++++++++++++++++++++++++++++++++++
Msrc/devices/mini-uart.zig | 202+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Msrc/main.zig | 2--
Msrc/root.zig | 6+++++-
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;