commit 7ebb5bafd8a4a433d75c6d380efdeed9108e3d14
parent 061f53af5e373ac8abe45148cd43c7760b27ae66
Author: Sylvia Ivory <git@sivory.net>
Date: Fri, 6 Feb 2026 17:51:49 -0800
Add new register API
Diffstat:
2 files changed, 354 insertions(+), 0 deletions(-)
diff --git a/pi/register.zig b/pi/register.zig
@@ -0,0 +1,353 @@
+// Referenced based off of Swift MMIO
+const std = @import("std");
+
+const Error = error{OutOfRange};
+
+const Kind = enum {
+ // Only read methods
+ ReadOnly,
+ // Only write methods
+ WriteOnly,
+ // Read, write, mutate methods
+ ReadModifyWrite,
+};
+
+// e.g. clock COUNTER_LOWER
+pub fn Register(comptime T: type, comptime kind: Kind) type {
+ comptime if (@sizeOf(T) != @sizeOf(usize)) @compileError("expected type as large as usize");
+
+ const RegisterBase = struct {
+ const Self = @This();
+
+ inline fn get(addr_ptr: *volatile usize) T {
+ return @bitCast(addr_ptr.*);
+ }
+
+ inline fn set(addr_ptr: *volatile usize, value: T) void {
+ addr_ptr.* = @bitCast(value);
+ }
+ };
+
+ switch (kind) {
+ .ReadOnly => {
+ return struct {
+ address: *volatile usize,
+
+ const Self = @This();
+
+ pub inline fn init(address: usize) Self {
+ return .{ .address = @ptrFromInt(address) };
+ }
+
+ pub inline fn get(self: *const Self) T {
+ return RegisterBase.get(self.address);
+ }
+ };
+ },
+ .WriteOnly => {
+ return struct {
+ address: *volatile usize,
+
+ const Self = @This();
+
+ pub inline fn init(address: usize) Self {
+ return .{ .address = @ptrFromInt(address) };
+ }
+
+ pub inline fn set(self: *const Self, value: T) void {
+ RegisterBase.set(self.address, value);
+ }
+ };
+ },
+ .ReadModifyWrite => {
+ return struct {
+ address: *volatile usize,
+
+ const Self = @This();
+
+ pub inline fn init(address: usize) Self {
+ return .{ .address = @ptrFromInt(address) };
+ }
+
+ pub inline fn get(self: *const Self) T {
+ return RegisterBase.get(self.address);
+ }
+
+ pub inline fn set(self: *const Self, value: T) void {
+ return RegisterBase.set(self.address, value);
+ }
+
+ pub inline fn bor(self: *const Self, value: usize) void {
+ const original = self.address.*;
+
+ self.address.* = original | value;
+ }
+
+ pub inline fn band(self: *const Self, value: usize) void {
+ const original = self.address.*;
+
+ self.address.* = original & value;
+ }
+ };
+ },
+ }
+}
+
+// These tests should be non-issues since Register is simple
+test "Register read" {
+ var value: usize = 617;
+ const value_register: Register(usize, .ReadOnly) = .init(@intFromPtr(&value));
+
+ try std.testing.expectEqual(value_register.get(), value);
+}
+
+test "Register write" {
+ var value: usize = 0;
+ const value_register: Register(usize, .WriteOnly) = .init(@intFromPtr(&value));
+
+ value_register.set(617);
+
+ try std.testing.expectEqual(value, 617);
+}
+
+test "Register bor" {
+ var value: usize = 0b1111_0000;
+ const value_register: Register(usize, .ReadModifyWrite) = .init(@intFromPtr(&value));
+
+ value_register.bor(0b1111);
+
+ try std.testing.expectEqual(value, 0b1111_1111);
+}
+
+test "Register band" {
+ var value: usize = 0b1111_0000;
+ const value_register: Register(usize, .ReadModifyWrite) = .init(@intFromPtr(&value));
+
+ value_register.band(0b1001_1111);
+
+ try std.testing.expectEqual(value, 0b1001_0000);
+}
+
+// e.g. gpio GPFSEL
+pub fn Array(comptime T: type, length: comptime_int, comptime kind: Kind) type {
+ comptime if (@sizeOf(T) > @sizeOf(usize)) @compileError("cannot store elements larger than usize");
+ const elem_mask: usize = comptime (~@as(usize, 0)) >> (@bitSizeOf(usize) - @bitSizeOf(T));
+ const ValueT = switch (@sizeOf(T)) {
+ 0...8 => u8,
+ 9...16 => u16,
+ 17...32 => u32,
+ 33...64 => u64,
+ // maybe you're on a 128 bit platform
+ else => usize,
+ };
+
+ const ArrayBase = struct {
+ const Self = @This();
+
+ inline fn calculate_shift(index: usize) usize {
+ return @bitSizeOf(T) * (index % @bitSizeOf(T));
+ }
+
+ inline fn calculate_address(start: usize, index: usize) *volatile usize {
+ const ptr_offset = @divFloor(index, @bitSizeOf(T)) * @sizeOf(usize);
+ const address: *volatile usize = @ptrFromInt(start + ptr_offset);
+ return address;
+ }
+
+ inline fn calculate_mask(index: usize) usize {
+ return elem_mask << @truncate(Self.calculate_shift(index));
+ }
+
+ inline fn calculate_put_mask(index: usize, value: T) usize {
+ const value_t: ValueT = @bitCast(value);
+ const masked = value_t & elem_mask;
+
+ return masked << @truncate(Self.calculate_shift(index));
+ }
+
+ inline fn get(start: usize, index: usize) !T {
+ if (index > length) return Error.OutOfRange;
+
+ const address = Self.calculate_address(start, index);
+ const mask = Self.calculate_mask(index);
+
+ const masked = (address.*) & mask;
+ const value: ValueT = @truncate(masked >> Self.calculate_shift(index));
+
+ return @bitCast(value);
+ }
+
+ inline fn put(start: usize, index: usize, value: T) !void {
+ if (index > length) return Error.OutOfRange;
+
+ const address = Self.calculate_address(start, index);
+ const masked = Self.calculate_put_mask(index, value);
+
+ address.* = masked;
+ }
+ };
+
+ switch (kind) {
+ .ReadOnly => {
+ return struct {
+ start: usize,
+
+ const Self = @This();
+
+ pub inline fn init(address: usize) Self {
+ return .{ .start = address };
+ }
+
+ pub inline fn get(self: *const Self, index: usize) !T {
+ return ArrayBase.get(self.start, index);
+ }
+ };
+ },
+ .WriteOnly => {
+ return struct {
+ start: usize,
+
+ const Self = @This();
+
+ pub inline fn init(address: usize) Self {
+ return .{ .start = address };
+ }
+
+ pub inline fn put(self: *const Self, index: usize, value: T) !void {
+ return ArrayBase.put(self.start, index, value);
+ }
+ };
+ },
+ .ReadModifyWrite => {
+ return struct {
+ start: usize,
+
+ const Self = @This();
+
+ pub inline fn init(address: usize) Self {
+ return .{ .start = address };
+ }
+
+ pub inline fn get(self: *const Self, index: usize) !T {
+ return ArrayBase.get(self.start, index);
+ }
+
+ pub inline fn put(self: *const Self, index: usize, value: T) !void {
+ return ArrayBase.put(self.start, index, value);
+ }
+
+ pub inline fn insert(self: *const Self, index: usize, value: T) !void {
+ const address = ArrayBase.calculate_address(self.start, index);
+ const original = address.*;
+ const masked = ArrayBase.calculate_put_mask(index, value);
+ const mask = ArrayBase.calculate_mask(index);
+
+ // Remove then insert
+ address.* = (original & ~mask) | masked;
+ }
+
+ pub inline fn remove(self: *const Self, index: usize) !void {
+ self.insert(index, 0);
+ }
+ };
+ },
+ }
+}
+
+test "Array read" {
+ // Note; this will not work on non-64 bit
+ const array: [2]usize = .{
+ 0xFFEEDDCCBBAA9988,
+ 0x7766554433221100,
+ };
+
+ const arr: Array(u8, 2 * 8, .ReadOnly) = .init(@intFromPtr(&array));
+
+ try std.testing.expectEqual(0x88, try arr.get(0));
+ try std.testing.expectEqual(0x99, try arr.get(1));
+ try std.testing.expectEqual(0xAA, try arr.get(2));
+ try std.testing.expectEqual(0xBB, try arr.get(3));
+ try std.testing.expectEqual(0xCC, try arr.get(4));
+ try std.testing.expectEqual(0xDD, try arr.get(5));
+ try std.testing.expectEqual(0xEE, try arr.get(6));
+ try std.testing.expectEqual(0xFF, try arr.get(7));
+ try std.testing.expectEqual(0x00, try arr.get(8));
+ try std.testing.expectEqual(0x11, try arr.get(9));
+ try std.testing.expectEqual(0x22, try arr.get(10));
+ try std.testing.expectEqual(0x33, try arr.get(11));
+ try std.testing.expectEqual(0x44, try arr.get(12));
+ try std.testing.expectEqual(0x55, try arr.get(13));
+ try std.testing.expectEqual(0x66, try arr.get(14));
+ try std.testing.expectEqual(0x77, try arr.get(15));
+}
+
+test "Array write" {
+ var array: [2]usize = .{ 0, 0 };
+
+ const arr: Array(u8, 2 * 8, .WriteOnly) = .init(@intFromPtr(&array));
+
+ try arr.put(0, 0x88);
+ try std.testing.expectEqual(0x88, array[0]);
+ try std.testing.expectEqual(0, array[1]);
+
+ try arr.put(1, 0x99);
+ try std.testing.expectEqual(0x9900, array[0]);
+ try std.testing.expectEqual(0, array[1]);
+
+ try arr.put(2, 0xAA);
+ try std.testing.expectEqual(0xAA0000, array[0]);
+ try std.testing.expectEqual(0, array[1]);
+
+ try arr.put(3, 0xBB);
+ try std.testing.expectEqual(0xBB000000, array[0]);
+ try std.testing.expectEqual(0, array[1]);
+
+ try arr.put(4, 0xCC);
+ try std.testing.expectEqual(0xCC00000000, array[0]);
+ try std.testing.expectEqual(0, array[1]);
+
+ try arr.put(9, 0x11);
+ try std.testing.expectEqual(0xCC00000000, array[0]);
+ try std.testing.expectEqual(0x0000001100, array[1]);
+
+ try arr.put(10, 0x22);
+ try std.testing.expectEqual(0xCC00000000, array[0]);
+ try std.testing.expectEqual(0x0000220000, array[1]);
+}
+
+test "Array insert" {
+ var array: [2]usize = .{ 0, 0 };
+
+ const arr: Array(u8, 2 * 8, .ReadModifyWrite) = .init(@intFromPtr(&array));
+
+ try arr.insert(0, 0x88);
+ try std.testing.expectEqual(0x88, array[0]);
+ try std.testing.expectEqual(0, array[1]);
+
+ try arr.insert(1, 0x99);
+ try std.testing.expectEqual(0x9988, array[0]);
+ try std.testing.expectEqual(0, array[1]);
+
+ try arr.insert(0, 0x99);
+ try std.testing.expectEqual(0x9999, array[0]);
+ try std.testing.expectEqual(0, array[1]);
+
+ try arr.insert(0, 0x88);
+ try std.testing.expectEqual(0x9988, array[0]);
+ try std.testing.expectEqual(0, array[1]);
+
+ try arr.insert(3, 0xBB);
+ try std.testing.expectEqual(0xBB009988, array[0]);
+ try std.testing.expectEqual(0, array[1]);
+
+ try arr.insert(10, 0x22);
+ try std.testing.expectEqual(0x00BB009988, array[0]);
+ try std.testing.expectEqual(0x0000220000, array[1]);
+}
+
+// e.g. gpio GPCLR
+pub fn BitField(start: usize, length: comptime_int, comptime kind: Kind) type {
+ return Array(start, u1, length, kind);
+}
+
+// If Array is correct, then BitField is also correct
diff --git a/pi/root.zig b/pi/root.zig
@@ -2,6 +2,7 @@ pub const Scheduler = @import("scheduler.zig");
pub const PSR = @import("./psr.zig").PSR;
pub const interrupts = @import("./interrupts.zig");
+pub const register = @import("./register.zig");
pub const journal = @import("./journal.zig");
pub const mem = @import("./mem.zig");