sylveos

Toy Operating System
Log | Files | Refs

commit 0f8eb46eba4f1df0d8f5252bd478fd334962780a
parent 34d09bf786776f2e1bdc91cddf3c41aae11439d7
Author: Sylvia Ivory <git@sivory.net>
Date:   Thu,  5 Feb 2026 17:46:07 -0800

Basic GPIO interrupts

Diffstat:
Mpi/devices/gpio.zig | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aprograms/gpio.zig | 35+++++++++++++++++++++++++++++++++++
Mshared/pubsub.zig | 61+++++++++++++++++++++----------------------------------------
3 files changed, 167 insertions(+), 40 deletions(-)

diff --git a/pi/devices/gpio.zig b/pi/devices/gpio.zig @@ -1,6 +1,8 @@ const std = @import("std"); +const shared = @import("shared"); const mem = @import("../mem.zig"); +const interrupts = @import("../interrupts.zig"); pub const Error = error{ PinOutOfRange, @@ -12,6 +14,11 @@ const GPFSEL0: usize = BASE_ADDRESS + 0x0000; const GPSET0: usize = BASE_ADDRESS + 0x001C; const GPCLR0: usize = BASE_ADDRESS + 0x0028; const GPLEV0: usize = BASE_ADDRESS + 0x0034; +const GPEDS0: usize = BASE_ADDRESS + 0x0040; +const GPREN0: usize = BASE_ADDRESS + 0x004C; +const GPFEN0: usize = BASE_ADDRESS + 0x0058; +const GPHEN0: usize = BASE_ADDRESS + 0x0064; +const GPLEN0: usize = BASE_ADDRESS + 0x0070; const GPPUD: usize = BASE_ADDRESS + 0x0094; const GPPUDCLK0: usize = BASE_ADDRESS + 0x0098; @@ -67,6 +74,27 @@ fn addr_bitset(pin: u8, address: *u32) void { mem.put_u32(address, mask); } +fn addr_bitget(pin: u8, address: *u32) bool { + const offset: u5 = @truncate(pin % 32); + const mask = (@as(u32, 1) << offset); + + return (mem.get_u32(address) & mask) != 0; +} + +fn addr_bitor_set(pin: u8, address: *u32) void { + const offset: u5 = @truncate(pin % 32); + const mask = (@as(u32, 1) << offset); + + mem.put_u32(address, mem.get_u32(address) | mask); +} + +fn addr_bitor_unset(pin: u8, address: *u32) void { + const offset: u5 = @truncate(pin % 32); + const mask = (@as(u32, 1) << offset); + + mem.put_u32(address, mem.get_u32(address) | ~mask); +} + // Turn on fn output_set(pin: u8) Error!void { addr_bitset(pin, try get_address(pin, GPSET0, null)); @@ -144,3 +172,86 @@ pub fn set_pull(pin: u8, pull: PullMode) Error!void { mem.put_u32(GPPUDCLK, 0); mem.put_u32(@ptrFromInt(GPPUDCLK0), 0); } + +pub const Event = union(enum) { + PinEvent: u8, + PinRisingEdge: u8, + PinFallingEdge: u8, +}; +var buffer: [1024]u8 = undefined; +var fba: std.heap.FixedBufferAllocator = undefined; +pub var ev: shared.Publisher(Event) = undefined; + +pub fn rising_edge_interrupt(pin: u8, enable: bool) Error!void { + try gpio_check(pin); + + if (enable) { + addr_bitor_set(pin, try get_address(pin, GPREN0, null)); + } else { + addr_bitor_unset(pin, try get_address(pin, GPREN0, null)); + } +} + +pub fn falling_edge_interrupt(pin: u8, enable: bool) Error!void { + try gpio_check(pin); + + if (enable) { + addr_bitor_set(pin, try get_address(pin, GPFEN0, null)); + } else { + addr_bitor_unset(pin, try get_address(pin, GPFEN0, null)); + } +} + +fn has_event(pin: u8) Error!bool { + try gpio_check(pin); + + return addr_bitget(pin, try get_address(pin, GPEDS0, null)); +} + +fn clear_event(pin: u8) Error!void { + try gpio_check(pin); + + addr_bitset(pin, try get_address(pin, GPEDS0, null)); +} + +pub fn enable_interrupts() !void { + mem.barrier(.Write); + + fba = .init(&buffer); + ev = try .init(fba.allocator()); + + interrupts.set_exception_handler(.IRQ, gpio_handler); + interrupts.enable_peripheral_interrupt(.GPIO0); + interrupts.enable_peripheral_interrupt(.GPIO1); + + mem.barrier(.Write); +} + +noinline fn gpio_handler(pc: usize) void { + _ = pc; + mem.barrier(.Write); + defer mem.barrier(.Write); + + // Pins 0-53 + if (!interrupts.pending_peripheral_interrupt(.GPIO0) and !interrupts.pending_peripheral_interrupt(.GPIO1)) { + return; + } + + // Find out which pin had the event + + // A bit rubbish + for (0..(MAX_GPIO + 1)) |n| { + const pin: u8 = @truncate(n); + if (!(has_event(pin) catch false)) continue; + + if (read(pin) catch unreachable) { + // Likely rising + ev.publish(.PinRisingEdge, pin); + } else { + // Likely falling + ev.publish(.PinFallingEdge, pin); + } + + clear_event(pin) catch {}; + } +} diff --git a/programs/gpio.zig b/programs/gpio.zig @@ -0,0 +1,35 @@ +const std = @import("std"); +const shared = @import("shared"); +const pi = @import("pi"); + +const gpio = pi.devices.gpio; +const uart = pi.devices.mini_uart; + +pub fn gpio_rising_edge(pin: *const u8) void { + uart.writer.print("pin {d} is high\n", .{pin.*}) catch {}; +} + +pub fn gpio_falling_edge(pin: *const u8) void { + uart.writer.print("pin {d} is low\n", .{pin.*}) catch {}; +} + +pub fn main() !void { + try gpio.fn_sel(9, .input); + try gpio.fn_sel(10, .output); + try gpio.write(10, true); + + try uart.writer.print("enabling interrupts\n", .{}); + + try gpio.enable_interrupts(); + + try gpio.rising_edge_interrupt(9, true); + try gpio.falling_edge_interrupt(9, true); + + try gpio.ev.listen(.PinRisingEdge, &gpio_rising_edge); + try gpio.ev.listen(.PinFallingEdge, &gpio_falling_edge); + + try uart.writer.print("waiting for interrupts\n", .{}); + + // Wait around so we can listen for interrupts + while (true) {} +} diff --git a/shared/pubsub.zig b/shared/pubsub.zig @@ -1,10 +1,6 @@ const std = @import("std"); const ListenerFn = *const fn (*const anyopaque) void; -const Listener = struct { - callback: ListenerFn, - node: std.SinglyLinkedList.Node, -}; fn validate_listener_fn(comptime ptr: anytype) type { const P = @TypeOf(ptr); @@ -45,7 +41,7 @@ fn validate_listener_fn(comptime ptr: anytype) type { // }; // } -pub fn Publisher(comptime M: type, max_listeners: comptime_int) type { +pub fn Publisher(comptime M: type) type { const msg_info = @typeInfo(M); if (msg_info != .@"union") { @compileError("expected tagged union, found " ++ @typeName(M)); @@ -54,41 +50,34 @@ pub fn Publisher(comptime M: type, max_listeners: comptime_int) type { const msg_union = msg_info.@"union"; const msg_tag_type = msg_union.tag_type orelse @compileError("expected tagged union, found untagged union"); - const Slot = struct { []const u8, std.SinglyLinkedList }; + const Slot = struct { []const u8, usize }; var slots: [msg_union.fields.len]Slot = undefined; comptime for (msg_union.fields, 0..) |field, index| { - slots[index] = .{ field.name, undefined }; + slots[index] = .{ field.name, index }; }; - const event_map: std.StaticStringMap(std.SinglyLinkedList) = .initComptime(slots); + const event_map: std.StaticStringMap(usize) = .initComptime(slots); return struct { const Self = @This(); - listeners: std.StaticStringMap(std.SinglyLinkedList), - buffer: [max_listeners * @sizeOf(Listener)]u8, - fba: std.heap.FixedBufferAllocator, + listeners: [msg_union.fields.len]std.ArrayList(ListenerFn), + gpa: std.mem.Allocator, - pub fn init() Self { - const buffer = undefined; + pub fn init(gpa: std.mem.Allocator) !Self { + var self: Self = undefined; - return .{ - .listeners = event_map, - .buffer = buffer, - .fba = .init(buffer), - }; - } - - pub fn listen(self: *Self, comptime event: msg_tag_type, comptime listener: anytype) !void { - comptime if (max_listeners == 0) @compileError("must use listen_alloc on this Publisher"); + for (0..self.listeners.len) |index| { + self.listeners[index] = try .initCapacity(gpa, 1); + } + self.gpa = gpa; - try self.listen_alloc(self.fba.allocator(), event, listener); + return self; } - // TODO; this leaks - pub fn listen_alloc(self: *Self, gpa: std.mem.Allocator, comptime event: msg_tag_type, comptime listener: anytype) !void { + pub fn listen(self: *Self, comptime event: msg_tag_type, comptime listener: anytype) !void { comptime { const param_type = validate_listener_fn(listener); for (msg_union.fields) |f| { @@ -103,24 +92,16 @@ pub fn Publisher(comptime M: type, max_listeners: comptime_int) type { } } - var node = try gpa.create(Listener); - node.callback = @ptrCast(listener); - - const name = @tagName(event); - var ev = self.listeners.get(name) orelse unreachable; - - ev.prepend(&node.node); + const index = event_map.get(@tagName(event)) orelse unreachable; + try self.listeners[index].append(self.gpa, @ptrCast(listener)); } pub fn publish(self: *Self, comptime event: msg_tag_type, data: anytype) void { - const name = @tagName(event); - const ev = self.listeners.get(name) orelse unreachable; - - var head = ev.first; - while (head) |node| { - const listener: *Listener = @fieldParentPtr("node", node); - listener.callback(@ptrCast(&data)); - head = node.next; + const index = event_map.get(@tagName(event)) orelse unreachable; + const listeners = self.listeners[index]; + + for (listeners.items) |listener| { + listener(@ptrCast(&data)); } } };