commit 37fc93e452e0bc8c87a9b7f71e8bef3ce1e0df60
parent 546350ad4560109757ac57b589c307631de8b6ff
Author: Sylvia Ivory <git@sivory.net>
Date: Sun, 15 Feb 2026 00:48:07 -0800
Update uart
Diffstat:
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", .{});