commit 0f8eb46eba4f1df0d8f5252bd478fd334962780a
parent 34d09bf786776f2e1bdc91cddf3c41aae11439d7
Author: Sylvia Ivory <git@sivory.net>
Date: Thu, 5 Feb 2026 17:46:07 -0800
Basic GPIO interrupts
Diffstat:
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));
}
}
};