sylveos

Toy Operating System
Log | Files | Refs

commit 37fc93e452e0bc8c87a9b7f71e8bef3ce1e0df60
parent 546350ad4560109757ac57b589c307631de8b6ff
Author: Sylvia Ivory <git@sivory.net>
Date:   Sun, 15 Feb 2026 00:48:07 -0800

Update uart

Diffstat:
Mboot/root.zig | 13+++++++------
Mpi/devices/mini-uart.zig | 203++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Mpi/interrupts.zig | 8++++----
Mprograms/debug.zig | 4++--
Mprograms/echo.zig | 10++++------
Mprograms/syscall.zig | 3+--
6 files changed, 157 insertions(+), 84 deletions(-)

diff --git a/boot/root.zig b/boot/root.zig @@ -36,18 +36,19 @@ export fn kmain() void { uart.write_slice("Exception Vector:\n"); for (0..16) |i| { const dst: *allowzero u32 = @ptrFromInt(i * @sizeOf(*u32)); - uart.writer.print(" 0x{X} = 0x{X}\n", .{ @intFromPtr(dst), dst.* }) catch {}; + uart.print(" 0x{X} = 0x{X}\n", .{ @intFromPtr(dst), dst.* }); } uart.write_slice("Enabling Interrupts\n"); - uart.enable_interrupts() catch {}; + uart.set_tx_interrupts(true); + uart.set_rx_interrupts(true) catch {}; pi.interrupts.enable_interrupts(); uart.write_slice("Entering program\n"); uart.flush(); @import("program").main() catch |e| { - uart.writer.print("program returned error: {t}\n", .{e}) catch {}; + uart.print("program returned error: {t}\n", .{e}); }; uart.flush(); @@ -63,15 +64,15 @@ fn panic_handler(msg: []const u8, trace_addr: ?usize) noreturn { @branchHint(.cold); if (uart.is_initialized()) { - uart.writer.print("kernel panic: {s} @ 0x{X}\n", .{ msg, trace_addr orelse 0 }) catch {}; + uart.print("kernel panic: {s}\n", .{msg}); uart.flush(); var it = std.debug.StackIterator.init(trace_addr, null); var ix: usize = 0; while (it.next()) |frame| : (ix += 1) { - // TODO; remove - uart.writer.print("| #{d:0>2}: 0x{X:0>16}\n", .{ ix, frame }) catch {}; + uart.print("| #{d:0>2}: 0x{X:0>16}\n", .{ ix, frame }); + uart.flush(); } } diff --git a/pi/devices/mini-uart.zig b/pi/devices/mini-uart.zig @@ -39,7 +39,9 @@ const RxPin = enum(u8) { }; var initialized = false; -var interrupts_enabled = false; +var rx_interrupts_enabled = false; +var tx_interrupts_enabled = false; +var use_tx_interrupts = false; pub fn initialize(baud: u32, tx_pin: TxPin, rx_pin: RxPin) Error!void { if (initialized) return Error.AlreadyInitialized; @@ -111,47 +113,93 @@ pub fn initialize(baud: u32, tx_pin: TxPin, rx_pin: RxPin) Error!void { initialized = true; } +pub fn set_rx_interrupts(state: bool) Error!void { + if (state) { + try enable_rx_interrupts(); + } else { + try disable_rx_interrupts(); + } +} + +fn interrupt_state(tx: bool, rx: bool) void { // Enable RX/TX Interrupts + // Page 12: AUX_MU_IIR_REG Register + // Bit 0 - RX Interrupt + // Bit 1 - TX Interrupt + // Errata: "Bits 1:0 are swapped. bit 0 is receive + // interrupt and bit 1 is transmit." + // Errata: "Bits 3:2 are marked as don't care, but + // are actually required in order to receive interrupts." + if (tx and rx) { + mem.put_u32_barrier(@ptrFromInt(AUX_MU_IER_REG), 0b1111); + } else if (tx and !rx) { + mem.put_u32_barrier(@ptrFromInt(AUX_MU_IER_REG), 0b1110); + } else if (!tx and rx) { + mem.put_u32_barrier(@ptrFromInt(AUX_MU_IER_REG), 0b0101); + } else if (!tx and !rx) { + mem.put_u32_barrier(@ptrFromInt(AUX_MU_IER_REG), 0b0000); + } +} + // Separate function to allow TX used during early boot -pub fn enable_interrupts() Error!void { +fn enable_rx_interrupts() Error!void { if (!initialized) return Error.NotInitialized; - if (interrupts_enabled) return Error.AlreadyInitialized; + if (rx_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." + interrupt_state(tx_interrupts_enabled, true); + + rx_interrupts_enabled = true; +} + +fn disable_rx_interrupts() Error!void { + if (!initialized) return Error.NotInitialized; + if (!rx_interrupts_enabled) return Error.NotInitialized; + + mem.barrier(.Write); - // 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); + interrupt_state(tx_interrupts_enabled, false); - interrupts_enabled = true; + rx_interrupts_enabled = false; } -// Temporarily disables TX interrupting -pub fn toggle_tx_interrupts() void { - interrupts_enabled = !interrupts_enabled; - if (interrupts_enabled) return; +pub fn set_tx_interrupts(state: bool) void { + use_tx_interrupts = state; - // Flush queue - while (tx_list.pop()) |b| { - write_byte(b) catch {}; + if (state) { + interrupts.set_exception_handler(.IRQ, uart_handler); + interrupts.enable_peripheral_interrupt(.AuxInterrupt); + } else { + drain_write_queue(); } } -fn enable_tx_interrupt() void { - mem.put_u32_barrier(@ptrFromInt(AUX_MU_IER_REG), 0b1111); +fn enable_tx_interrupt() !void { + if (!initialized) return Error.NotInitialized; + + interrupt_state(true, rx_interrupts_enabled); + + tx_interrupts_enabled = true; +} + +fn disable_tx_interrupt() !void { + if (!initialized) return Error.NotInitialized; + + interrupt_state(false, rx_interrupts_enabled); + + tx_interrupts_enabled = false; } -fn disable_tx_interrupt() void { - mem.put_u32_barrier(@ptrFromInt(AUX_MU_IER_REG), 0b1101); +fn drain_write_queue() void { + const cs = mem.enter_critical_section(); + defer cs.exit(); + + while (tx_list.pop()) |b| { + write_byte_sync(b); + } } var tx_list: StackRingBuffer(u8, 128) = .init(); @@ -216,7 +264,7 @@ noinline fn uart_handler(pc: usize, registers: *interrupts.Registers) void { if (tx_list.pop()) |byte| { mem.put_u32_barrier(@ptrFromInt(AUX_MU_IO_REG), @as(u32, byte) & 0xFF); } else { - disable_tx_interrupt(); + disable_tx_interrupt() catch {}; return; } }, @@ -261,8 +309,6 @@ pub fn read_queue_length() usize { pub fn write_queue_length() usize { // Queuing disabled - if (!interrupts_enabled) return 0; - const cs = mem.enter_critical_section(); defer cs.exit(); @@ -273,38 +319,49 @@ 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 rx_list.length() > 0; // or can_read_raw(); + return read_queue_length() > 0 or can_read_raw(); } // // TODO; is this necessary -// inline fn can_read_raw() bool { -// return (mem.get_u32_barrier(@ptrFromInt(AUX_MU_LSR_REG)) & 0x01) == 1; -// } - -pub fn read_byte() ?u8 { - const cs = mem.enter_critical_section(); - defer cs.exit(); - - return rx_list.pop(); +inline fn can_read_raw() bool { + return (mem.get_u32_barrier(@ptrFromInt(AUX_MU_LSR_REG)) & 0x01) == 1; } -pub fn read_byte_blocking(timeout: ?u64) ?u8 { +pub fn read_byte_sync_timeout(timeout: ?u64) !u8 { var wait_until: u64 = std.math.maxInt(u64); + if (timeout) |t| { wait_until = clock.current_count() + t; } while (true) { if (clock.current_count() > wait_until) { - return null; + return Error.Timeout; } - if (read_byte()) |b| { - return b; + if (!can_read()) continue; + + if (read_queue_length() > 0) { + return read_byte_async().?; + } else { + return read_byte_raw(); } + + return; } } +pub fn read_byte_sync() u8 { + return read_byte_sync_timeout(null) catch unreachable; +} + +pub fn read_byte_async() ?u8 { + const cs = mem.enter_critical_section(); + defer cs.exit(); + + return rx_list.pop(); +} + inline fn read_byte_raw() u8 { return @truncate(mem.get_u32_barrier(@ptrFromInt(AUX_MU_IO_REG))); } @@ -320,26 +377,17 @@ pub fn can_write() bool { // return tx_list.items.len; // } -pub fn write_byte(byte: u8) !void { - if (!interrupts_enabled) { - while (!can_write()) {} - write_byte_raw(byte); - return; - } - +pub fn write_byte_async(byte: u8) !void { const cs = mem.enter_critical_section(); defer cs.exit(); - // Let TX start draining try tx_list.push(byte); - enable_tx_interrupt(); -} -pub fn write_byte_blocking(byte: u8, timeout: ?u64) !void { - if (!interrupts_enabled) { - return write_byte(byte); - } + // Let TX start draining + enable_tx_interrupt() catch {}; +} +pub fn write_byte_sync_timeout(byte: u8, timeout: ?u64) !void { var wait_until: u64 = std.math.maxInt(u64); if (timeout) |t| { @@ -351,20 +399,41 @@ pub fn write_byte_blocking(byte: u8, timeout: ?u64) !void { return Error.Timeout; } - write_byte(byte) catch continue; + if (!can_write()) continue; + + write_byte_raw(byte); return; } } -pub inline fn write_byte_raw(byte: u8) void { +pub fn write_byte_sync(byte: u8) void { + write_byte_sync_timeout(byte, null) catch {}; +} + +inline fn write_byte_raw(byte: u8) void { mem.put_u32_barrier(@ptrFromInt(AUX_MU_IO_REG), @as(u32, byte) & 0xFF); } +pub fn write_byte(byte: u8) void { + if (use_tx_interrupts) { + write_byte_async(byte) catch { + write_byte_sync(byte); + }; + } else { + write_byte_sync(byte); + } +} + pub fn write_slice(bytes: []const u8) void { for (bytes) |b| { - // Error only on timeout - write_byte(b) catch {}; + write_byte(b); + } +} + +pub fn write_slice_sync(bytes: []const u8) void { + for (bytes) |b| { + write_byte_sync(b); } } @@ -373,6 +442,10 @@ pub fn flush() void { while (true) { const s = status(); + if (tx_interrupts_enabled == false and write_queue_length() > 0) { + drain_write_queue(); + } + if (s.tx_is_empty and s.tx_is_idle and write_queue_length() == 0) break; } } @@ -384,9 +457,7 @@ fn writer_drain(io_w: *std.Io.Writer, data: []const []const u8, splat: usize) !u _ = io_w; _ = splat; - for (data[0]) |b| { - write_byte(b) catch {}; - } + write_slice(data[0]); return data[0].len; } @@ -400,6 +471,10 @@ fn writer_flush(io_w: *std.Io.Writer) !void { flush(); } +pub fn print(comptime fmt: []const u8, args: anytype) void { + writer.print(fmt, args) catch {}; +} + const writer_vtable: std.Io.Writer.VTable = .{ .drain = writer_drain, .flush = writer_flush }; pub var writer = std.Io.Writer{ .buffer = undefined, .vtable = &writer_vtable }; @@ -408,7 +483,7 @@ fn stream(io_r: *std.Io.Reader, io_w: *std.Io.Writer, limit: std.Io.Limit) !usiz _ = io_r; if (limit.toInt()) |max| { - if (max > rx_list.items.len) { + if (max > rx_list.items.len and rx_interrupts_enabled) { // Use direct writes when there's a large buffer try switch_rx_writer(io_w); @@ -418,7 +493,7 @@ fn stream(io_r: *std.Io.Reader, io_w: *std.Io.Writer, limit: std.Io.Limit) !usiz } else { // Small buffers should just read blocking for (0..max) |_| { - try io_w.writeByte(read_byte_blocking(null).?); + try io_w.writeByte(read_byte_sync()); } } diff --git a/pi/interrupts.zig b/pi/interrupts.zig @@ -170,9 +170,9 @@ export fn data_abort_stub(pc: u32, reg: *Registers) void { } // This interrupt is a bastard -export fn interrupt_stub(pc: u32, reg: *Registers) void { +export fn interrupt() callconv(.{ .arm_interrupt = .{ .type = .irq } }) void { // setup_reg(pc, reg); - interrupt_handler(pc, reg); + interrupt_handler(0, @ptrFromInt(0x8)); } export fn fast_interrupt_stub(pc: u32, reg: *Registers) void { @@ -199,7 +199,7 @@ comptime { asm (create_trampoline("software_interrupt", "4", "movs pc, lr")); asm (create_trampoline("prefetch_abort", "4", "subs pc, lr, #4")); asm (create_trampoline("data_abort", "8", "subs pc, lr, #8")); - asm (create_trampoline("interrupt", "4", "subs pc, lr, #4")); + // asm (create_trampoline("interrupt", "4", "subs pc, lr, #4")); asm (create_trampoline("fast_interrupt", "4", "subs pc, lr, #4")); } extern fn reset() void; @@ -207,7 +207,7 @@ extern fn undefined_instruction() void; extern fn software_interrupt() void; extern fn prefetch_abort() void; extern fn data_abort() void; -extern fn interrupt() void; +// extern fn interrupt() void; extern fn fast_interrupt() void; // https://leiradel.github.io/2019/02/09/Initialization.html diff --git a/programs/debug.zig b/programs/debug.zig @@ -56,7 +56,7 @@ fn syscall_handler(pc: u32, regs: *interrupts.Registers) void { restore_state_privileged(&scheduler_registers); }, 2 => { - uart.write_byte(@truncate(arg0)) catch {}; + uart.write_byte(@truncate(arg0)); }, else => { uart.write_slice("illegal syscall\n"); @@ -162,7 +162,7 @@ extern fn restore_state_privileged(*interrupts.Registers) void; pub fn main() !void { interrupts.disable_interrupts(); - uart.toggle_tx_interrupts(); + uart.set_tx_interrupts(false); const didr = debug.DIDR.get(); try uart.writer.print("Debug ID\n", .{}); diff --git a/programs/echo.zig b/programs/echo.zig @@ -7,20 +7,18 @@ const clock = pi.devices.clock; pub fn main() !void { while (true) { const status = uart.status(); - - uart.writer.print("rx queue: {d}, rx hw: {d}, rx ready: {any}, tx queue: {d}, tx hw: {d}, tx ready: {any}\n", .{ + uart.print("rx queue: {d}, rx hw: {d}, rx ready: {any}, tx queue: {d}, tx hw: {d}, tx ready: {any}\n", .{ uart.read_queue_length(), status.rx_fill_level, uart.can_read(), uart.write_queue_length(), status.tx_fill_level, uart.can_write(), - }) catch {}; + }); while (uart.can_read()) { - var buffer: [1]u8 = .{0}; - uart.reader.readSliceAll(&buffer) catch {}; - uart.writer.print(" read: {s}\n", .{buffer}) catch {}; + const byte = uart.read_byte_sync(); + uart.print(" read: 0x{X}\n", .{byte}); } clock.delay_s(1); diff --git a/programs/syscall.zig b/programs/syscall.zig @@ -29,7 +29,6 @@ comptime { extern fn syscall_test() void; noinline fn software_interrupt_handler(pc: u32, registers: *interrupts.Registers) void { - uart.toggle_tx_interrupts(); const dup = registers.*; uart.writer.print("instruction=0x{X}\n", .{pc}) catch {}; @@ -39,10 +38,10 @@ noinline fn software_interrupt_handler(pc: u32, registers: *interrupts.Registers uart.writer.print("sp = 0x{X}\n", .{dup.sp}) catch {}; uart.writer.print("lr = 0x{X}\n", .{dup.lr}) catch {}; uart.writer.print("pc = 0x{X}\n", .{dup.pc}) catch {}; - uart.toggle_tx_interrupts(); } pub fn main() !void { + uart.set_tx_interrupts(false); interrupts.set_exception_handler(.SoftwareInterrupt, software_interrupt_handler); try uart.writer.print("testing syscall\n", .{});