From 4e3451bcab9b2d164287deb6b4c9dc1bc5440d16 Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Tue, 27 May 2025 14:05:58 -0300 Subject: [PATCH 01/41] [kernel][lib][arch] Refactor arch as real dependency --- slipstream/kernel/arch/x86/spin_lock.zig | 2 +- slipstream/kernel/build.zig | 1 + slipstream/kernel/build.zig.zon | 3 +++ slipstream/kernel/lib/arch/build.zig | 25 +++++++++++++++++++ slipstream/kernel/lib/arch/build.zig.zon | 6 +++++ slipstream/kernel/lib/arch/src/root.zig | 11 ++++++++ .../kernel/lib/arch/{ => src}/x86/intrin.zig | 0 7 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 slipstream/kernel/lib/arch/build.zig create mode 100644 slipstream/kernel/lib/arch/build.zig.zon create mode 100644 slipstream/kernel/lib/arch/src/root.zig rename slipstream/kernel/lib/arch/{ => src}/x86/intrin.zig (100%) diff --git a/slipstream/kernel/arch/x86/spin_lock.zig b/slipstream/kernel/arch/x86/spin_lock.zig index 7ddb270..5ea0970 100644 --- a/slipstream/kernel/arch/x86/spin_lock.zig +++ b/slipstream/kernel/arch/x86/spin_lock.zig @@ -5,7 +5,7 @@ const std = @import("std"); const assert = @import("../../kernel/assert.zig"); const mp = @import("mp.zig"); -const arch = @import("../../lib/arch/x86/intrin.zig"); +const arch = @import("arch").intrin; const ArchSpinLock = @import("../../kernel/arch/SpinLock.zig"); inline fn archSpinLockCore(lock: *ArchSpinLock, val: u32) void { diff --git a/slipstream/kernel/build.zig b/slipstream/kernel/build.zig index 7aa5dff..2629fc3 100644 --- a/slipstream/kernel/build.zig +++ b/slipstream/kernel/build.zig @@ -41,6 +41,7 @@ pub fn build(b: *Build) !void { standalone(build_config, kernel); const deps = [_]struct { name: []const u8, dep_name: []const u8, module_name: []const u8 }{ + .{ .name = "arch", .dep_name = "arch", .module_name = "arch" }, .{ .name = "dbl", .dep_name = "dbl", .module_name = "dbl" }, .{ .name = "lazy_init", .dep_name = "lazy_init", .module_name = "lazy_init" }, .{ .name = "lockdep", .dep_name = "lockdep", .module_name = "lockdep" }, diff --git a/slipstream/kernel/build.zig.zon b/slipstream/kernel/build.zig.zon index 82b8890..dad160a 100644 --- a/slipstream/kernel/build.zig.zon +++ b/slipstream/kernel/build.zig.zon @@ -16,5 +16,8 @@ .lockdep = .{ .path = "../system/ulib/lockdep", }, + .arch = .{ + .path = "lib/arch", + }, }, } diff --git a/slipstream/kernel/lib/arch/build.zig b/slipstream/kernel/lib/arch/build.zig new file mode 100644 index 0000000..ab5eb94 --- /dev/null +++ b/slipstream/kernel/lib/arch/build.zig @@ -0,0 +1,25 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. + +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const mod = b.addModule("arch", .{ + .root_source_file = b.path("src/root.zig"), + .target = target, + .optimize = optimize, + }); + + const host_test_step = b.step("test", "Run unit tests"); + const host_unit_tests = b.addTest(.{ + .root_module = mod, + .target = b.graph.host, + }); + + const run_host_unit_tests = b.addRunArtifact(host_unit_tests); + host_test_step.dependOn(&run_host_unit_tests.step); +} diff --git a/slipstream/kernel/lib/arch/build.zig.zon b/slipstream/kernel/lib/arch/build.zig.zon new file mode 100644 index 0000000..5388140 --- /dev/null +++ b/slipstream/kernel/lib/arch/build.zig.zon @@ -0,0 +1,6 @@ +.{ + .name = .arch, + .fingerprint = 0xf812224abd18ee41, + .version = "0.0.1", + .paths = .{""}, +} diff --git a/slipstream/kernel/lib/arch/src/root.zig b/slipstream/kernel/lib/arch/src/root.zig new file mode 100644 index 0000000..e4e8917 --- /dev/null +++ b/slipstream/kernel/lib/arch/src/root.zig @@ -0,0 +1,11 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. + +const std = @import("std"); +const builtin = @import("builtin"); + +pub const intrin = if (builtin.cpu.arch == .x86_64) + @import("x86/intrin.zig") +else + @compileError("Unsupported architecture: " ++ @tagName(builtin.cpu.arch)); diff --git a/slipstream/kernel/lib/arch/x86/intrin.zig b/slipstream/kernel/lib/arch/src/x86/intrin.zig similarity index 100% rename from slipstream/kernel/lib/arch/x86/intrin.zig rename to slipstream/kernel/lib/arch/src/x86/intrin.zig From 27ed0b2fdfa76b31b2ae0a1f0c420f2374e04e09 Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Tue, 27 May 2025 17:02:07 -0300 Subject: [PATCH 02/41] [kernel][assert] Fix zig name convention --- slipstream/kernel/arch/x86/mp.zig | 12 +++++----- slipstream/kernel/arch/x86/start.S | 2 +- slipstream/kernel/kernel/Thread.zig | 2 +- slipstream/kernel/kernel/assert.zig | 26 +++++++++++--------- slipstream/kernel/top/debug.zig | 37 ++++++++++++++++------------- 5 files changed, 44 insertions(+), 35 deletions(-) diff --git a/slipstream/kernel/arch/x86/mp.zig b/slipstream/kernel/arch/x86/mp.zig index 89b6d10..7f81ad5 100644 --- a/slipstream/kernel/arch/x86/mp.zig +++ b/slipstream/kernel/arch/x86/mp.zig @@ -181,12 +181,12 @@ inline fn percpuFor(cpu_num: CpuNum) *X86PerCpu { /// Called from assembly. export fn initPercpu(cpu_num: CpuNum) callconv(.C) void { const percpu = percpuFor(cpu_num); - assert.debug_assert(@src(), percpu.cpu_num == cpu_num); - assert.debug_assert(@src(), percpu.direct == percpu); + assert.debugAssert(@src(), percpu.cpu_num == cpu_num, "percpu.cpu_num == cpu_num"); + assert.debugAssert(@src(), percpu.direct == percpu, "percpu.direct == percpu"); // Assembly code has already set up %gs.base so that this function's own code can use it // implicitly for stack-protector or safe-stack. - assert.debug_assert(@src(), x86.readMsr(registers.X86_MSR_IA32_GS_BASE) == @intFromPtr(percpu)); + assert.debugAssert(@src(), x86.readMsr(registers.X86_MSR_IA32_GS_BASE) == @intFromPtr(percpu), "x86.readMsr(registers.X86_MSR_IA32_GS_BASE) == @intFromPtr(percpu)"); // Set the KERNEL_GS_BASE MSR to 0 // When we enter user space, this will be populated via a swapgs @@ -273,9 +273,9 @@ pub fn forceHaltAllButLocalAndBsp() void { pub fn setupPercpu(cpu_num: CpuNum, percpu: *PerCpu) void { const arch_percpu = percpuFor(cpu_num); - //assert.debug_assert(@src(), arch_percpu != null); - assert.debug_assert(@src(), arch_percpu.high_level_percpu == null or - arch_percpu.high_level_percpu == percpu); + //assert.debugAssert(@src(), arch_percpu != null, "arch_percpu != null"); + assert.debugAssert(@src(), arch_percpu.high_level_percpu == null or + arch_percpu.high_level_percpu == percpu, "arch_percpu.high_level_percpu == null or arch_percpu.high_level_percpu == percpu"); arch_percpu.high_level_percpu = percpu; } diff --git a/slipstream/kernel/arch/x86/start.S b/slipstream/kernel/arch/x86/start.S index c36ff4a..0d0fc8e 100644 --- a/slipstream/kernel/arch/x86/start.S +++ b/slipstream/kernel/arch/x86/start.S @@ -232,7 +232,7 @@ highaddr: // call would make it eligible for stack-guard checking itself. But // %gs is not set up yet in the prologue of the function, so it would // crash if it tried to use the stack-guard. - call choose_stack_guard + call chooseStackGuard /* call the main module */ call lk_main diff --git a/slipstream/kernel/kernel/Thread.zig b/slipstream/kernel/kernel/Thread.zig index 734c352..18393b0 100644 --- a/slipstream/kernel/kernel/Thread.zig +++ b/slipstream/kernel/kernel/Thread.zig @@ -26,7 +26,7 @@ pub fn getListLock() *SpinLock { /// /// This function is called once, from kmain() pub fn threadInitEarly() void { - assert.debug_assert(@src(), arch.currCpuNum() == 0); + assert.debugAssert(@src(), arch.currCpuNum() == 0, "arch.currCpuNum() == 0"); // Initialize the thread list. This needs to be done manually now, since initial thread code // manipulates the list before global constructors are run. diff --git a/slipstream/kernel/kernel/assert.zig b/slipstream/kernel/kernel/assert.zig index bd7340d..1d36160 100644 --- a/slipstream/kernel/kernel/assert.zig +++ b/slipstream/kernel/kernel/assert.zig @@ -4,19 +4,21 @@ const std = @import("std"); const debug = @import("../top/debug.zig"); -const build_options = @import("build_options"); +const builtin = @import("builtin"); /// Assert that x is true, else panic -pub fn assert(comptime src: std.builtin.SourceLocation, x: bool) void { +pub fn assert(comptime src: std.builtin.SourceLocation, x: bool, comptime expression: []const u8) void { if (!x) { - debug.assert_fail(src.file, src.line); + @branchHint(.cold); + debug.assertFail(src.file, src.line, expression); } } /// Assert that x is true, else panic with the given message -pub fn assert_msg(comptime src: std.builtin.SourceLocation, x: bool, comptime fmt: []const u8, args: anytype) void { +pub fn assertMsg(comptime src: std.builtin.SourceLocation, x: bool, comptime expression: []const u8, fmt: []const u8, args: anytype) void { if (!x) { - debug.assert_fail_msg(src.file, src.line, fmt, args); + @branchHint(.cold); + debug.assertFailMsg(src.file, src.line, expression, fmt, args); } } @@ -24,10 +26,11 @@ pub fn assert_msg(comptime src: std.builtin.SourceLocation, x: bool, comptime fm /// /// Depending on build arguments, DEBUG_ASSERT may or may not be enabled. When disabled, |x| will not /// be evaluated. -pub fn debug_assert(comptime src: std.builtin.SourceLocation, x: bool) void { - if (build_options.DEBUG_ASSERT_IMPLEMENTED) { +pub fn debugAssert(comptime src: std.builtin.SourceLocation, x: bool, comptime expression: []const u8) void { + if (comptime builtin.mode == .Debug) { if (!x) { - debug.assert_fail(src.file, src.line); + @branchHint(.cold); + debug.assertFail(src.file, src.line, expression); } } } @@ -36,10 +39,11 @@ pub fn debug_assert(comptime src: std.builtin.SourceLocation, x: bool) void { /// /// Depending on build arguments, DEBUG_ASSERT_MSG may or may not be enabled. When disabled, |x| will /// not be evaluated. -pub fn debug_assert_msg(comptime src: std.builtin.SourceLocation, x: bool, comptime fmt: []const u8, args: anytype) void { - if (build_options.DEBUG_ASSERT_IMPLEMENTED) { +pub fn debugAssertMsg(comptime src: std.builtin.SourceLocation, x: bool, comptime expression: []const u8, fmt: []const u8, args: anytype) void { + if (comptime builtin.mode == .Debug) { if (!x) { - debug.assert_fail_msg(src.file, src.line, fmt, args); + @branchHint(.cold); + debug.assertFailMsg(src.file, src.line, expression, fmt, args); } } } diff --git a/slipstream/kernel/top/debug.zig b/slipstream/kernel/top/debug.zig index b8d4033..69650d7 100644 --- a/slipstream/kernel/top/debug.zig +++ b/slipstream/kernel/top/debug.zig @@ -9,8 +9,8 @@ // * Calling "printf" with the reason for the panic, followed by // a newline. // -// * A call to "PanicFinish". -inline fn PanicStart(_: ?*const anyopaque, _: ?*const anyopaque) void { +// * A call to "panicFinish". +inline fn panicStart(_: ?*const anyopaque, _: ?*const anyopaque) void { // TODO: Implement platform_panic_start() // platform_panic_start(); @@ -21,7 +21,7 @@ inline fn PanicStart(_: ?*const anyopaque, _: ?*const anyopaque) void { // // This function will not return, but will perform an action such as // rebooting the system or dropping the system into a debug shell. -inline fn PanicFinish() noreturn { +inline fn panicFinish() noreturn { // Add a newline between the panic message and the stack trace. //std.debug.print("\n", .{}); @@ -35,22 +35,22 @@ inline fn PanicFinish() noreturn { } // Determine if the given string ends with the given character. -fn EndsWith(str: []const u8, x: u8) bool { +fn endsWith(str: []const u8, x: u8) bool { return str.len > 0 and str[str.len - 1] == x; } fn vpanic(pc: ?*const anyopaque, frame: ?*const anyopaque, fmt: []const u8, _: anytype) noreturn { - PanicStart(pc, frame); + panicStart(pc, frame); // Print the user message. //std.debug.print(fmt, args); // Add a newline to the end of the panic message if it was missing. - if (!EndsWith(fmt, '\n')) { + if (!endsWith(fmt, '\n')) { //std.debug.print("\n", .{}); } - PanicFinish(); + panicFinish(); } pub fn panic(comptime fmt: []const u8, args: anytype) noreturn { @@ -60,31 +60,36 @@ pub fn panic(comptime fmt: []const u8, args: anytype) noreturn { vpanic(pc, frame, fmt, args); } -pub fn assert_fail_msg(_: []const u8, _: c_int, _: []const u8, comptime fmt: []const u8, _: anytype) noreturn { - PanicStart(null, null); +pub fn assertFailMsg(comptime file: []const u8, comptime line: c_int, comptime expression: []const u8, comptime fmt: []const u8, args: anytype) noreturn { + panicStart(null, null); + + _ = file; + _ = line; + _ = expression; + _ = args; // Print the user message. //std.debug.print("ASSERT FAILED at ({s}:{d}): {s}\n", .{ file, line, expression }); //std.debug.print(fmt, args); // Add a newline to the end of the panic message if it was missing. - if (!EndsWith(fmt, '\n')) { + if (!endsWith(fmt, '\n')) { //std.debug.print("\n", .{}); } - PanicFinish(); + panicFinish(); } -pub fn assert_fail(comptime file: []const u8, comptime line: c_int) noreturn { - PanicStart(null, null); +pub fn assertFail(comptime file: []const u8, comptime line: c_int, comptime expression: []const u8) noreturn { + panicStart(null, null); _ = file; _ = line; - //_ = expression; + _ = expression; //std.debug.print("ASSERT FAILED at ({s}:{d}): {s}\n", .{ file, line, expression }); - PanicFinish(); + panicFinish(); } -pub export fn choose_stack_guard() callconv(.C) usize { +pub export fn chooseStackGuard() callconv(.C) usize { var guard: usize = undefined; // TODO: Implement hw_rng_get_entropy() // if (hw_rng_get_entropy(&guard, @sizeOf(guard)) != @sizeOf(guard)) { From 963532c8fb57933b25c1c7fde53d1ba7d31bc54a Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Wed, 28 May 2025 08:12:17 -0300 Subject: [PATCH 03/41] [ulib][affine] Initial version --- slipstream/system/ulib/affine/build.zig | 25 ++ slipstream/system/ulib/affine/build.zig.zon | 6 + slipstream/system/ulib/affine/src/assert.zig | 19 ++ slipstream/system/ulib/affine/src/ratio.zig | 266 ++++++++++++++++++ .../system/ulib/affine/src/ratio_test.zig | 109 +++++++ slipstream/system/ulib/affine/src/root.zig | 10 + 6 files changed, 435 insertions(+) create mode 100644 slipstream/system/ulib/affine/build.zig create mode 100644 slipstream/system/ulib/affine/build.zig.zon create mode 100644 slipstream/system/ulib/affine/src/assert.zig create mode 100644 slipstream/system/ulib/affine/src/ratio.zig create mode 100644 slipstream/system/ulib/affine/src/ratio_test.zig create mode 100644 slipstream/system/ulib/affine/src/root.zig diff --git a/slipstream/system/ulib/affine/build.zig b/slipstream/system/ulib/affine/build.zig new file mode 100644 index 0000000..36432f9 --- /dev/null +++ b/slipstream/system/ulib/affine/build.zig @@ -0,0 +1,25 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. + +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const mod = b.addModule("affine", .{ + .root_source_file = b.path("src/root.zig"), + .target = target, + .optimize = optimize, + }); + + const host_test_step = b.step("test", "Run unit tests"); + const host_unit_tests = b.addTest(.{ + .root_module = mod, + .target = b.graph.host, + }); + + const run_host_unit_tests = b.addRunArtifact(host_unit_tests); + host_test_step.dependOn(&run_host_unit_tests.step); +} diff --git a/slipstream/system/ulib/affine/build.zig.zon b/slipstream/system/ulib/affine/build.zig.zon new file mode 100644 index 0000000..bff136d --- /dev/null +++ b/slipstream/system/ulib/affine/build.zig.zon @@ -0,0 +1,6 @@ +.{ + .name = .affine, + .fingerprint = 0xfbf5743f550a372f, + .version = "0.0.1", + .paths = .{""}, +} diff --git a/slipstream/system/ulib/affine/src/assert.zig b/slipstream/system/ulib/affine/src/assert.zig new file mode 100644 index 0000000..34317f6 --- /dev/null +++ b/slipstream/system/ulib/affine/src/assert.zig @@ -0,0 +1,19 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. + +const builtin = @import("builtin"); + +pub inline fn assert(predicate: bool) void { + if (!predicate) { + @trap(); + } +} + +pub inline fn debugAssert(predicate: bool) void { + if (comptime builtin.mode == .Debug) { + if (!predicate) { + assert(predicate); + } + } +} diff --git a/slipstream/system/ulib/affine/src/ratio.zig b/slipstream/system/ulib/affine/src/ratio.zig new file mode 100644 index 0000000..7981cd6 --- /dev/null +++ b/slipstream/system/ulib/affine/src/ratio.zig @@ -0,0 +1,266 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. + +const std = @import("std"); +const internal = @import("assert.zig"); +const Ratio = @This(); + +/// A ratio represents a rational number as a numerator and denominator. +numerator: u32 = 1, +denominator: u32 = 1, + +pub const Exact = enum { + no, + yes, +}; + +/// Rounding behaviors used when scaling. +/// +/// val | N | D | Down | Up | TowardsZero | AwayFromZero +/// -----+---+---+------+----+-------------+-------------- +/// 7 | 1 | 2 | 3 | 4 | 3 | 4 +/// -7 | 1 | 2 | -4 | -3 | -3 | -4 +/// +pub const Round = enum { + down, + up, + towards_zero, + away_from_zero, +}; + +/// Used to indicate overflow/underflow of scaling operations. +pub const overflow = std.math.maxInt(i64); +pub const underflow = std.math.minInt(i64); + +/// Reduces the ratio of N/D +pub fn reduceGeneric(comptime T: type, numerator: *T, denominator: *T) void { + comptime { + if (!(T == u32 or T == u64)) { + @compileError("Reduce is only defined for u32 and u64"); + } + } + + internal.assert(denominator.* != 0); + + if (numerator.* == 0) { + denominator.* = 1; + return; + } + + const gcd = std.math.gcd(numerator.*, denominator.*); + internal.debugAssert(gcd != 0); + + if (gcd == 1) { + return; + } + + numerator.* = numerator.* / gcd; + denominator.* = denominator.* / gcd; +} + +/// Produces the product of two 32 bit ratios. If exact is true, asserts on loss +/// of precision. +pub fn productStatic(a_numerator: u32, a_denominator: u32, b_numerator: u32, b_denominator: u32, product_numerator: *u32, product_denominator: *u32, exact: Exact) void { + var numerator: u64 = @as(u64, a_numerator) * @as(u64, b_numerator); + var denominator: u64 = @as(u64, a_denominator) * @as(u64, b_denominator); + + reduceGeneric(u64, &numerator, &denominator); + + if (numerator > std.math.maxInt(u32) or denominator > std.math.maxInt(u32)) { + internal.assert(exact == .no); + + // Try to find the best approximation of the ratio that we can. Our + // approach is as follows. Figure out the number of bits to the right + // we need to shift the numerator and denominator, rounding up or down + // in the process, such that the result can be reduced to fit into 32 + // bits. + // + // This approach tends to beat out a just-shift-until-it-fits approach, + // as well as an always-shift-then-reduce approach, but _none_ of these + // approaches always finds the best solution. + // + // TODO(Herrera): figure out if it is reasonable to actually compute + // the best solution. Alternatively, consider implementing a "just + // shift until it fits" solution if the approximate results are good + // enough. + // + var i: u32 = 1; + while (i <= 32) : (i += 1) { + // Produce a version of the numerator and denominator which have + // each been divided by 2^i, rounding up/down as appropriate + // (instead of truncating). + var rounded_numerator: u64 = (numerator + (@as(u64, 1) << @intCast(i - 1))) >> @intCast(i); + var rounded_denominator: u64 = (denominator + (@as(u64, 1) << @intCast(i - 1))) >> @intCast(i); + + if (rounded_denominator == 0) { + // Product is larger than we can represent. Return the largest value we + // can represent. + product_numerator.* = std.math.maxInt(u32); + product_denominator.* = 1; + return; + } + + if (rounded_numerator == 0) { + // Product is smaller than we can represent. Return 0. + product_numerator.* = 0; + product_denominator.* = 1; + return; + } + + reduceGeneric(u64, &rounded_numerator, &rounded_denominator); + if (fitsIn32Bits(rounded_numerator, rounded_denominator)) { + product_numerator.* = @intCast(rounded_numerator); + product_denominator.* = @intCast(rounded_denominator); + return; + } + } + } + + product_numerator.* = @intCast(numerator); + product_denominator.* = @intCast(denominator); +} + +/// Produces the product of a 32 bit ratio and the int64_t as an int64_t. Returns +/// a saturated value (either overflow or underflow) on overflow/underflow. +pub fn scaleGeneric(comptime round: Round, value: i64, numerator: u32, denominator: u32) i64 { + internal.assert(denominator != 0); + + if (value >= 0) { + // limit == 0x7FFFFFFFFFFFFFFF + const limit: u64 = std.math.maxInt(i64); + const ratio_round: Round = if (round == .down or round == .towards_zero) .down else .up; + + return @intCast(scaleU64(ratio_round, limit, @intCast(value), numerator, denominator)); + } else { + // LIMIT == 0x8000000000000000 + // + // Note: We are attempting to pass the unsigned distance from zero into + // our ScaleUInt64 function. In the case of negative numbers, we pass + // the twos compliment into the scale function, and then flip the sign + // again on the way out. + // + // We are taking the advantage of the fact that the twos compliment of + // MIN is itself for any signed integer type, and that casting this + // value to an unsigned integer of the same size properly produces the + // original value's distance from zero. Clamping the limit to the + // distance of MIN from zero means that saturated results will likewise + // get properly flipped back to MIN during the return. + // + const limit: u64 = comptime @bitCast(@as(i64, std.math.minInt(i64))); + const ratio_round: Round = if (round == .down or round == .away_from_zero) .up else .down; + + return @bitCast(0 -% scaleU64(ratio_round, limit, 0 -% @as(u64, @bitCast(value)), numerator, denominator)); + } +} + +pub fn init(numerator: u32, denominator: u32) Ratio { + internal.debugAssert(denominator != 0); + return .{ + .numerator = numerator, + .denominator = denominator, + }; +} + +pub fn reduce(self: *Ratio) void { + reduceGeneric(u32, &self.numerator, &self.denominator); +} + +pub fn invertible(self: Ratio) bool { + return self.numerator != 0; +} + +pub fn inverse(self: Ratio) Ratio { + internal.debugAssert(self.invertible()); + return Ratio.init(self.denominator, self.numerator); +} + +pub fn scale(self: Ratio, value: i64) i64 { + return scaleGeneric(.down, value, self.numerator, self.denominator); +} + +pub fn product(a: Ratio, b: Ratio, exact: Exact) Ratio { + var result_numerator: u32 = undefined; + var result_denominator: u32 = undefined; + productStatic(a.numerator, a.denominator, b.numerator, b.denominator, &result_numerator, &result_denominator, exact); + return Ratio.init(result_numerator, result_denominator); +} + +/// Returns the ratio of the two ratios. +pub inline fn div(a: Ratio, b: Ratio) Ratio { + return product(a, b.inverse(), .yes); +} + +/// Returns the product of the two ratios. +pub inline fn mul(a: Ratio, b: Ratio) Ratio { + return Ratio.product(a, b, .yes); +} + +/// Returns the product of the rate and the int64_t. +pub fn mulRatioInt(a: Ratio, b: i64) i64 { + return a.scale(b, .down); +} + +/// Returns the product of the rate and the int64_t. +pub fn mulIntRatio(a: i64, b: Ratio) i64 { + return b.scale(a, .down); +} + +/// Returns the the int64_t divided by the rate. +pub fn divIntRatio(a: i64, b: Ratio) i64 { + return b.inverse().scale(a, .down); +} + +/// Scales a u64 value by the ratio of two u32 values. +/// If round is .up, the result is rounded up rather than down. +/// Returns the result, or overflow_limit_64 if overflow occurs. +fn scaleU64(comptime round: Round, comptime overflow_limit_64: u64, value: u64, numerator: u32, denominator: u32) u64 { + comptime { + if (round != .down and round != .up) { + @compileError("round must be .down or .up"); + } + } + + const low_32_bits: u64 = 0xffffffff; + + // high and low are the product of the numerator and the high and low halves + // (respectively) of value. + const high = @as(u64, numerator) * (value >> 32); + const low = @as(u64, numerator) * (value & low_32_bits); + + // Move the high end of low into the low end of high. + const high_with_carry = high + (low >> 32); + const low_remainder = low & low_32_bits; + + // Compute the divmod of high/D + const high_q = high_with_carry / denominator; + const high_r = high_with_carry % denominator; + + // If high_q is larger than the overflow limit, then we can just get out now. + const overflow_limit_32 = overflow_limit_64 >> 32; + if (high_q > overflow_limit_32) { + return overflow_limit_64; + } + + // The remainder of high/D are the high bits of low. Or them in, and do the + // divmod for the low portion + const low_with_remainder = low_remainder | (high_r << 32); + const low_q = low_with_remainder / denominator; + const low_r = low_with_remainder % denominator; + const result = (high_q << 32) | low_q; + + if (result >= overflow_limit_64) { + return overflow_limit_64; + } + + if (round == .up and low_r != 0) { + return result + 1; + } + + return result; +} + +/// Returns true if both numerator and denominator fit in 32 bits +fn fitsIn32Bits(numerator: u64, denominator: u64) bool { + return numerator <= std.math.maxInt(u32) and denominator <= std.math.maxInt(u32); +} diff --git a/slipstream/system/ulib/affine/src/ratio_test.zig b/slipstream/system/ulib/affine/src/ratio_test.zig new file mode 100644 index 0000000..e8554c3 --- /dev/null +++ b/slipstream/system/ulib/affine/src/ratio_test.zig @@ -0,0 +1,109 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. + +const Ratio = @import("ratio.zig"); + +const std = @import("std"); +const testing = std.testing; + +test "Ratio construction" { + // Test default construction + var ratio = Ratio{}; + try testing.expectEqual(@as(u32, 1), ratio.numerator); + try testing.expectEqual(@as(u32, 1), ratio.denominator); + + // Test explicit construction + ratio = Ratio{ .numerator = 3, .denominator = 4 }; + try testing.expectEqual(@as(u32, 3), ratio.numerator); + try testing.expectEqual(@as(u32, 4), ratio.denominator); + + // Test that reduction is not automatically performed + ratio = Ratio{ .numerator = 9, .denominator = 21 }; + try testing.expectEqual(@as(u32, 9), ratio.numerator); + try testing.expectEqual(@as(u32, 21), ratio.denominator); +} + +test "Ratio reduction" { + const TestCase = struct { + n: u32, + d: u32, + expected_n: u32, + expected_d: u32, + }; + + const test_cases = [_]TestCase{ + .{ .n = 1, .d = 1, .expected_n = 1, .expected_d = 1 }, + .{ .n = 10, .d = 10, .expected_n = 1, .expected_d = 1 }, + .{ .n = 10, .d = 2, .expected_n = 5, .expected_d = 1 }, + .{ .n = 0, .d = 1, .expected_n = 0, .expected_d = 1 }, + .{ .n = 0, .d = 500, .expected_n = 0, .expected_d = 1 }, + .{ .n = 48000, .d = 44100, .expected_n = 160, .expected_d = 147 }, + .{ .n = 44100, .d = 48000, .expected_n = 147, .expected_d = 160 }, + }; + + for (test_cases) |tc| { + var n = tc.n; + var d = tc.d; + Ratio.reduceGeneric(u32, &n, &d); + try testing.expectEqual(tc.expected_n, n); + try testing.expectEqual(tc.expected_d, d); + } +} + +test "Ratio product" { + const TestCase = struct { + a_n: u32, + a_d: u32, + b_n: u32, + b_d: u32, + expected_n: u32, + expected_d: u32, + exact: Ratio.Exact, + }; + + const test_cases = [_]TestCase{ + .{ .a_n = 1, .a_d = 1, .b_n = 1, .b_d = 1, .expected_n = 1, .expected_d = 1, .exact = .yes }, + .{ .a_n = 0, .a_d = 1, .b_n = 1, .b_d = 1, .expected_n = 0, .expected_d = 1, .exact = .yes }, + .{ .a_n = 0, .a_d = 500, .b_n = 1, .b_d = 1, .expected_n = 0, .expected_d = 1, .exact = .yes }, + .{ .a_n = 3, .a_d = 4, .b_n = 5, .b_d = 9, .expected_n = 5, .expected_d = 12, .exact = .yes }, + .{ .a_n = 48000, .a_d = 44100, .b_n = 1000007, .b_d = 1000000, .expected_n = 1000007, .expected_d = 918750, .exact = .yes }, + }; + + for (test_cases) |tc| { + var n: u32 = undefined; + var d: u32 = undefined; + Ratio.productStatic(tc.a_n, tc.a_d, tc.b_n, tc.b_d, &n, &d, tc.exact); + try testing.expectEqual(tc.expected_n, n); + try testing.expectEqual(tc.expected_d, d); + } +} + +test "Ratio scale" { + const TestCase = struct { + val: i64, + n: u32, + d: u32, + expected: i64, + round: Ratio.Round, + }; + + const test_cases = [_]TestCase{ + .{ .val = 0, .n = 0, .d = 1, .expected = 0, .round = .down }, + .{ .val = 1234567890, .n = 0, .d = 1, .expected = 0, .round = .down }, + .{ .val = 0, .n = 1, .d = 1, .expected = 0, .round = .down }, + .{ .val = 1234567890, .n = 1, .d = 1, .expected = 1234567890, .round = .down }, + .{ .val = 198, .n = 48000, .d = 44100, .expected = 215, .round = .down }, + .{ .val = -198, .n = 48000, .d = 44100, .expected = -216, .round = .down }, + .{ .val = -(49 * 198), .n = 48000, .d = 44100, .expected = -10560, .round = .down }, + .{ .val = (49 * 198) + 1, .n = 48000, .d = 44100, .expected = 10561, .round = .down }, + .{ .val = -((49 * 198) + 1), .n = 48000, .d = 44100, .expected = -10562, .round = .down }, + .{ .val = 0x1517ffffeae80, .n = 0x0bebc200, .d = 0x33333333, .expected = 0x4e94914f0000, .round = .down }, + .{ .val = -0x1517ffffeae80, .n = 0x0bebc200, .d = 0x33333333, .expected = -0x4e94914f0000, .round = .down }, + }; + + inline for (test_cases) |tc| { + const result = Ratio.scaleGeneric(tc.round, tc.val, tc.n, tc.d); + try testing.expectEqual(tc.expected, result); + } +} diff --git a/slipstream/system/ulib/affine/src/root.zig b/slipstream/system/ulib/affine/src/root.zig new file mode 100644 index 0000000..93cbb2c --- /dev/null +++ b/slipstream/system/ulib/affine/src/root.zig @@ -0,0 +1,10 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. + +pub const Ratio = @import("ratio.zig"); + +comptime { + _ = Ratio; + _ = @import("ratio_test.zig"); +} From 9355bfce1125a1870b0b75ac552457962cbb00a5 Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Wed, 28 May 2025 09:09:54 -0300 Subject: [PATCH 04/41] [kernel][x86] Set TLS stackguard --- slipstream/kernel/arch/x86/start.S | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/slipstream/kernel/arch/x86/start.S b/slipstream/kernel/arch/x86/start.S index 0d0fc8e..3a931cf 100644 --- a/slipstream/kernel/arch/x86/start.S +++ b/slipstream/kernel/arch/x86/start.S @@ -12,6 +12,7 @@ #include #include #include +#include #include "multiboot.h" #define MSR_EFER 0xc0000080 @@ -234,6 +235,11 @@ highaddr: // crash if it tried to use the stack-guard. call chooseStackGuard + // Move it into place. + mov %rax, %gs:SX_TLS_STACK_GUARD_OFFSET + // Don't leak that value to other code. + xor %eax, %eax + /* call the main module */ call lk_main From 562f150663452dd8afe0ae48b915a7fdc966ee04 Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Wed, 28 May 2025 09:11:54 -0300 Subject: [PATCH 05/41] [kernel][x86] Track early boot timestamps --- slipstream/kernel/arch/x86/start.S | 16 +++++++++++++++ .../kernel/platform/boot_timestamps.zig | 10 ++++++++++ slipstream/kernel/lib/arch/src/root.zig | 2 ++ .../kernel/lib/arch/src/x86/early_ticks.zig | 20 +++++++++++++++++++ slipstream/kernel/root.zig | 4 +++- 5 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 slipstream/kernel/kernel/platform/boot_timestamps.zig create mode 100644 slipstream/kernel/lib/arch/src/x86/early_ticks.zig diff --git a/slipstream/kernel/arch/x86/start.S b/slipstream/kernel/arch/x86/start.S index 3a931cf..5811a50 100644 --- a/slipstream/kernel/arch/x86/start.S +++ b/slipstream/kernel/arch/x86/start.S @@ -22,6 +22,14 @@ #define PHYS_ADDR_DELTA (KERNEL_BASE + KERNEL_LOAD_OFFSET - PHYS_LOAD_ADDRESS) #define PHYS(x) ((x) - PHYS_ADDR_DELTA) +// Clobbers %rax, %rdx. +.macro sample_ticks out + rdtsc + shl $32, %rdx + or %rdx, %rax + mov %rax, \out +.endm + .section ".text.boot" .code32 .global _start @@ -198,9 +206,14 @@ farjump64: jmp *%rax highaddr: + // As early as possible collect the time stamp. + sample_ticks %r15 + /* load the high kernel stack */ mov $(_kstack + 4096), %rsp + mov %r15, kernel_entry_ticks(%rip) + /* reload the gdtr */ lgdt _gdtr @@ -240,6 +253,9 @@ highaddr: // Don't leak that value to other code. xor %eax, %eax + // Collect the time stamp of entering "normal" Zig code in virtual space. + sample_ticks kernel_virtual_entry_ticks(%rip) + /* call the main module */ call lk_main diff --git a/slipstream/kernel/kernel/platform/boot_timestamps.zig b/slipstream/kernel/kernel/platform/boot_timestamps.zig new file mode 100644 index 0000000..24d10b5 --- /dev/null +++ b/slipstream/kernel/kernel/platform/boot_timestamps.zig @@ -0,0 +1,10 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. + +const arch = @import("arch"); + +// Samples taken at the first instruction in the kernel. +pub export var kernel_entry_ticks: arch.EarlyTicks = undefined; +// ... and at the entry to normal virtual-space kernel code. +pub export var kernel_virtual_entry_ticks: arch.EarlyTicks = undefined; diff --git a/slipstream/kernel/lib/arch/src/root.zig b/slipstream/kernel/lib/arch/src/root.zig index e4e8917..c95fcd0 100644 --- a/slipstream/kernel/lib/arch/src/root.zig +++ b/slipstream/kernel/lib/arch/src/root.zig @@ -9,3 +9,5 @@ pub const intrin = if (builtin.cpu.arch == .x86_64) @import("x86/intrin.zig") else @compileError("Unsupported architecture: " ++ @tagName(builtin.cpu.arch)); + +pub const EarlyTicks = if (builtin.cpu.arch == .x86_64) @import("x86/early_ticks.zig").EarlyTicks else @compileError("Unsupported architecture: " ++ @tagName(builtin.cpu.arch)); diff --git a/slipstream/kernel/lib/arch/src/x86/early_ticks.zig b/slipstream/kernel/lib/arch/src/x86/early_ticks.zig new file mode 100644 index 0000000..2c5c5aa --- /dev/null +++ b/slipstream/kernel/lib/arch/src/x86/early_ticks.zig @@ -0,0 +1,20 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. + +const intrin = @import("intrin.zig"); + +pub const EarlyTicks = packed struct { + /// A structure representing early tick counts from the TSC. + tsc: u64, + + /// Get the current TSC value. + pub inline fn get() EarlyTicks { + return .{ .tsc = intrin.cycles() }; + } + + /// Return a zeroed EarlyTicks structure. + pub inline fn zero() EarlyTicks { + return .{ .tsc = 0 }; + } +}; diff --git a/slipstream/kernel/root.zig b/slipstream/kernel/root.zig index df04f0b..3df682b 100644 --- a/slipstream/kernel/root.zig +++ b/slipstream/kernel/root.zig @@ -6,7 +6,8 @@ const top = @import("top/main.zig"); const arch = @import("arch/x86/arch.zig"); const mmu = @import("arch/x86/mmu.zig"); const faults = @import("arch/x86/faults.zig"); -const mp = @import("arch/x86/mp.zig"); +const mp = @import("kernel/arch/mp.zig"); +const platform = @import("kernel/platform/boot_timestamps.zig"); comptime { _ = arch; @@ -14,4 +15,5 @@ comptime { _ = faults; _ = top; _ = mp; + _ = platform; } From 5beb427daf45c99d77ed23561df8c180d1584ca4 Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Thu, 29 May 2025 14:33:28 -0300 Subject: [PATCH 06/41] [system][public] Initial version --- slipstream/kernel/build.zig | 2 +- slipstream/kernel/root.zig | 2 + slipstream/system/public/build.zig | 40 +++++++++++++++++++ slipstream/system/public/build.zig.zon | 9 +++++ .../system/public/slipstream/assert.zig | 16 ++++++++ .../system/public/slipstream/internal.zig | 9 +++++ slipstream/system/public/slipstream/root.zig | 9 +++++ 7 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 slipstream/system/public/build.zig create mode 100644 slipstream/system/public/build.zig.zon create mode 100644 slipstream/system/public/slipstream/assert.zig create mode 100644 slipstream/system/public/slipstream/internal.zig create mode 100644 slipstream/system/public/slipstream/root.zig diff --git a/slipstream/kernel/build.zig b/slipstream/kernel/build.zig index 2629fc3..43f7e5d 100644 --- a/slipstream/kernel/build.zig +++ b/slipstream/kernel/build.zig @@ -16,7 +16,7 @@ pub fn build(b: *Build) !void { options.addOption(u32, "SMP_MAX_CPUS", 1); options.addOption(bool, "DEBUG_ASSERT_IMPLEMENTED", true); - const kernel = b.createModule(.{ + const kernel = b.addModule("kernel", .{ .root_source_file = b.path("root.zig"), .optimize = .Debug, }); diff --git a/slipstream/kernel/root.zig b/slipstream/kernel/root.zig index 3df682b..fd3ba59 100644 --- a/slipstream/kernel/root.zig +++ b/slipstream/kernel/root.zig @@ -2,6 +2,8 @@ //! Use of this source code is governed by a BSD-style license that can be //! found in the LICENSE file. +pub const assert = @import("kernel/assert.zig"); + const top = @import("top/main.zig"); const arch = @import("arch/x86/arch.zig"); const mmu = @import("arch/x86/mmu.zig"); diff --git a/slipstream/system/public/build.zig b/slipstream/system/public/build.zig new file mode 100644 index 0000000..71ccf62 --- /dev/null +++ b/slipstream/system/public/build.zig @@ -0,0 +1,40 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. + +const std = @import("std"); + +pub fn isKernel(query: std.Target.Query) bool { + return query.abi == .none and query.os_tag == .freestanding; +} + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const mod = b.addModule("public", .{ + .root_source_file = b.path("slipstream/root.zig"), + .target = target, + .optimize = optimize, + }); + + if (isKernel(target.query)) { + const deps = [_]struct { name: []const u8, dep_name: []const u8, module_name: []const u8 }{ + .{ .name = "kernel", .dep_name = "kernel", .module_name = "kernel" }, + }; + + for (deps) |dep| { + const dep_module = b.dependency(dep.dep_name, .{}); + mod.addImport(dep.name, dep_module.module(dep.module_name)); + } + } + + //const host_test_step = b.step("test", "Run unit tests"); + //const host_unit_tests = b.addTest(.{ + // .root_module = mod, + // .target = b.graph.host, + //}); + // + //const run_host_unit_tests = b.addRunArtifact(host_unit_tests); + //host_test_step.dependOn(&run_host_unit_tests.step); +} diff --git a/slipstream/system/public/build.zig.zon b/slipstream/system/public/build.zig.zon new file mode 100644 index 0000000..3c01fd3 --- /dev/null +++ b/slipstream/system/public/build.zig.zon @@ -0,0 +1,9 @@ +.{ + .name = .public, + .fingerprint = 0x3bb42e1dd601ba4b, + .version = "0.0.1", + .paths = .{""}, + .dependencies = .{ .kernel = .{ + .path = "../../kernel", + } }, +} diff --git a/slipstream/system/public/slipstream/assert.zig b/slipstream/system/public/slipstream/assert.zig new file mode 100644 index 0000000..a79f454 --- /dev/null +++ b/slipstream/system/public/slipstream/assert.zig @@ -0,0 +1,16 @@ +//! Copyright 2018 The Fuchsia Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. + +const builtin = @import("builtin"); +const std = @import("std"); + +pub fn isKernel(comptime target: std.Target) bool { + return target.abi == .none and target.os.tag == .freestanding; +} + +const Impl = if (isKernel(builtin.target)) @import("kernel").assert else @import("internal.zig"); + +pub fn slipstreamAssert(comptime src: std.builtin.SourceLocation, x: bool, comptime expression: []const u8) void { + Impl.assert(src, x, expression); +} diff --git a/slipstream/system/public/slipstream/internal.zig b/slipstream/system/public/slipstream/internal.zig new file mode 100644 index 0000000..67a758f --- /dev/null +++ b/slipstream/system/public/slipstream/internal.zig @@ -0,0 +1,9 @@ +//! Copyright 2018 The Fuchsia Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. + +const std = @import("std"); + +pub fn assert(comptime _: std.builtin.SourceLocation, x: bool, comptime _: []const u8) void { + std.debug.assert(x); +} diff --git a/slipstream/system/public/slipstream/root.zig b/slipstream/system/public/slipstream/root.zig new file mode 100644 index 0000000..fc97cfe --- /dev/null +++ b/slipstream/system/public/slipstream/root.zig @@ -0,0 +1,9 @@ +//! Copyright 2018 The Fuchsia Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. + +pub const assert = @import("assert.zig"); + +comptime { + _ = assert; +} From 30c72315ace83beca247a97fc70f28eb8b89b672 Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Thu, 29 May 2025 14:36:19 -0300 Subject: [PATCH 07/41] [ulib][hwreg] Initial version --- slipstream/system/ulib/hwreg/build.zig | 34 + slipstream/system/ulib/hwreg/build.zig.zon | 11 + .../system/ulib/hwreg/src/bitfields.zig | 770 ++++++++++++++++++ slipstream/system/ulib/hwreg/src/root.zig | 9 + 4 files changed, 824 insertions(+) create mode 100644 slipstream/system/ulib/hwreg/build.zig create mode 100644 slipstream/system/ulib/hwreg/build.zig.zon create mode 100644 slipstream/system/ulib/hwreg/src/bitfields.zig create mode 100644 slipstream/system/ulib/hwreg/src/root.zig diff --git a/slipstream/system/ulib/hwreg/build.zig b/slipstream/system/ulib/hwreg/build.zig new file mode 100644 index 0000000..9ac6dec --- /dev/null +++ b/slipstream/system/ulib/hwreg/build.zig @@ -0,0 +1,34 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. + +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const mod = b.addModule("hwreg", .{ + .root_source_file = b.path("src/root.zig"), + .target = target, + .optimize = optimize, + }); + + const deps = [_]struct { name: []const u8, dep_name: []const u8, module_name: []const u8 }{ + .{ .name = "public", .dep_name = "public", .module_name = "public" }, + }; + + for (deps) |dep| { + const dep_module = b.dependency(dep.dep_name, .{}); + mod.addImport(dep.name, dep_module.module(dep.module_name)); + } + + const test_step = b.step("test", "Run unit tests"); + const unit_tests = b.addTest(.{ + .root_module = mod, + .target = b.graph.host, + }); + + const run_unit_tests = b.addRunArtifact(unit_tests); + test_step.dependOn(&run_unit_tests.step); +} diff --git a/slipstream/system/ulib/hwreg/build.zig.zon b/slipstream/system/ulib/hwreg/build.zig.zon new file mode 100644 index 0000000..fff1abd --- /dev/null +++ b/slipstream/system/ulib/hwreg/build.zig.zon @@ -0,0 +1,11 @@ +.{ + .name = .hwreg, + .fingerprint = 0x5a18493e51a3d0f, + .version = "0.0.1", + .paths = .{""}, + .dependencies = .{ + .public = .{ + .path = "../../public", + }, + }, +} diff --git a/slipstream/system/ulib/hwreg/src/bitfields.zig b/slipstream/system/ulib/hwreg/src/bitfields.zig new file mode 100644 index 0000000..1a141e1 --- /dev/null +++ b/slipstream/system/ulib/hwreg/src/bitfields.zig @@ -0,0 +1,770 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. + +const std = @import("std"); +const assert = @import("public").assert; + +/// Hardware register library for Zig +/// Provides type-safe bitfield manipulation for hardware registers +pub const hwreg = struct { + /// Tag to enable pretty-printing interfaces on a register + pub const EnablePrinter = struct {}; + + /// Supported integer types for register access + pub fn IsSupportedInt(comptime T: type) bool { + return T == u8 or T == u16 or T == u32 or T == u64; + } + + /// Compute a bit mask with the specified number of bits + pub fn computeMask(comptime IntType: type, num_bits: u32) IntType { + if (num_bits == 0) return 0; + if (num_bits >= @bitSizeOf(IntType)) return ~@as(IntType, 0); + return (@as(IntType, 1) << @intCast(num_bits)) - 1; + } + + /// Field information for printing support + pub const FieldInfo = struct { + name: []const u8, + bit_high: u32, + bit_low: u32, + + pub fn bitHighIncl(self: @This()) u32 { + return self.bit_high; + } + + pub fn bitLow(self: @This()) u32 { + return self.bit_low; + } + }; + + /// Parameters for register fields + pub fn FieldParameters(comptime has_printer: bool, comptime ValueType: type) type { + return struct { + fields_mask: ValueType = 0, + rsvdz_mask: ValueType = 0, + printer: if (has_printer) PrinterInfo else void = if (has_printer) PrinterInfo{} else {}, + + const PrinterInfo = struct { + fields: [32]FieldInfo = [_]FieldInfo{FieldInfo{ .name = "", .bit_high = 0, .bit_low = 0 }} ** 32, + num_fields: u32 = 0, + }; + }; + } + + /// Base class for hardware registers + pub fn RegisterBase(comptime DerivedType: type, comptime IntType: type, comptime PrinterState: type) type { + if (!IsSupportedInt(IntType)) { + @compileError("Unsupported register access width"); + } + + const has_printer = PrinterState == EnablePrinter; + + return struct { + const Self = @This(); + pub const SelfType = DerivedType; + pub const ValueType = IntType; + pub const PrinterEnabled = has_printer; + + params_: FieldParameters(has_printer, IntType) = .{}, + reg_value_: ValueType = 0, + reg_addr_: u32 = 0, + + pub fn regAddr(self: *const Self) u32 { + return self.reg_addr_; + } + + pub fn setRegAddr(self: *Self, addr: u32) void { + self.reg_addr_ = addr; + } + + pub fn regValue(self: *const Self) ValueType { + return self.reg_value_; + } + + pub fn regValuePtr(self: *Self) *ValueType { + return &self.reg_value_; + } + + pub fn regValuePtrConst(self: *const Self) *const ValueType { + return &self.reg_value_; + } + + pub fn setRegValue(self: *Self, value: IntType) *DerivedType { + self.reg_value_ = value; + return @ptrCast(self); + } + + pub fn readFrom(self: *Self, reg_io: anytype) *DerivedType { + self.reg_value_ = reg_io.read(ValueType, self.reg_addr_); + return @ptrCast(self); + } + + pub fn writeTo(self: *Self, reg_io: anytype) *DerivedType { + const masked_value = self.reg_value_ & ~self.params_.rsvdz_mask; + reg_io.write(masked_value, self.reg_addr_); + return @ptrCast(self); + } + + /// Print register information (requires PrinterEnabled) + pub fn printReg(self: *const Self, print_fn: anytype) void { + if (!has_printer) { + @compileError("Pass hwreg.EnablePrinter to RegisterBase to enable printing"); + } + printRegister(print_fn, &self.params_.printer.fields, self.params_.printer.num_fields, self.reg_value_, self.params_.fields_mask, @sizeOf(ValueType)); + } + + /// Print register to stdout (requires PrinterEnabled) + pub fn print(self: *const Self) void { + if (!has_printer) { + @compileError("Pass hwreg.EnablePrinter to RegisterBase to enable printing"); + } + self.printReg(struct { + fn printFn(arg: []const u8) void { + std.debug.print("{s}\n", .{arg}); + } + }.printFn); + } + + /// Iterate over each field with a callback (requires PrinterEnabled) + pub fn forEachField(self: *const Self, callback: anytype) void { + if (!has_printer) { + @compileError("Pass hwreg.EnablePrinter to RegisterBase to enable field iteration"); + } + var i: u32 = 0; + while (i < self.params_.printer.num_fields) : (i += 1) { + const field = self.params_.printer.fields[i]; + const mask = computeMask(ValueType, field.bit_high - field.bit_low + 1) << @intCast(field.bit_low); + const value = (self.reg_value_ & mask) >> @intCast(field.bit_low); + const is_rsvdz = (mask & self.rsvdzMask()) == mask; + callback(if (is_rsvdz) null else field.name, value, field.bit_high, field.bit_low); + } + } + + pub fn fieldsMask(self: *const Self) IntType { + return self.params_.fields_mask; + } + + pub fn rsvdzMask(self: *const Self) IntType { + return self.params_.rsvdz_mask; + } + + pub fn params(self: *Self) *FieldParameters(has_printer, ValueType) { + return &self.params_; + } + }; + } + + /// Typed register address + pub fn RegisterAddr(comptime RegType: type) type { + return struct { + const Self = @This(); + reg_addr_: u32, + + pub fn init(reg_addr: u32) Self { + return Self{ .reg_addr_ = reg_addr }; + } + + /// Read register value from MMIO + pub fn readFrom(self: Self, reg_io: anytype) RegType { + var reg = RegType{}; + reg.setRegAddr(self.reg_addr_); + _ = reg.readFrom(reg_io); + return reg; + } + + /// Create register instance with given value + pub fn fromValue(self: Self, value: RegType.ValueType) RegType { + var reg = RegType{}; + reg.setRegAddr(self.reg_addr_); + _ = reg.setRegValue(value); + return reg; + } + + pub fn addr(self: Self) u32 { + return self.reg_addr_; + } + }; + } + + /// Reference to a bitfield within a register + pub fn BitfieldRef(comptime IntType: type) type { + return struct { + const Self = @This(); + value_ptr_: *IntType, + shift_: u32, + mask_: IntType, + + pub fn init(value_ptr: *IntType, bit_high_incl: u32, bit_low: u32) Self { + return Self{ + .value_ptr_ = value_ptr, + .shift_ = bit_low, + .mask_ = computeMask(IntType, bit_high_incl - bit_low + 1), + }; + } + + pub fn initUnshifted(value_ptr: *IntType, bit_high_incl: u32, bit_low: u32) Self { + return Self{ + .value_ptr_ = value_ptr, + .shift_ = 0, + .mask_ = computeMask(IntType, bit_high_incl - bit_low + 1) << @intCast(bit_low), + }; + } + + pub fn get(self: Self) IntType { + return @intCast((self.value_ptr_.* >> @intCast(self.shift_)) & self.mask_); + } + + pub fn set(self: Self, field_val: IntType) void { + assert.slipstreamAssert(@src(), (field_val & ~self.mask_) == 0, "field_val & ~self.mask_ == 0"); + const masked = self.value_ptr_.* & ~(self.mask_ << @intCast(self.shift_)); + self.value_ptr_.* = masked | (field_val << @intCast(self.shift_)); + } + }; + } + + /// Print register information helper + fn printRegister(print_fn: anytype, fields: []const FieldInfo, num_fields: u32, reg_value: anytype, fields_mask: anytype, value_size: usize) void { + var i: u32 = 0; + while (i < num_fields) : (i += 1) { + const field = fields[i]; + const mask = computeMask(@TypeOf(reg_value), field.bit_high - field.bit_low + 1) << @intCast(field.bit_low); + const value = (reg_value & mask) >> @intCast(field.bit_low); + + var buf: [256]u8 = undefined; + const field_str = std.fmt.bufPrint(&buf, "{s}[{d}:{d}]: 0x{X:0>{d}} ({d})", .{ field.name, field.bit_high, field.bit_low, value, value_size * 2, value }) catch "field format error"; + + print_fn(field_str); + } + + // Print unknown bits if any + const unknown_bits = reg_value & ~fields_mask; + if (unknown_bits != 0) { + var buf: [256]u8 = undefined; + const unknown_str = std.fmt.bufPrint(&buf, "unknown set bits: 0x{X:0>{d}}", .{ unknown_bits, value_size * 2 }) catch "unknown bits format error"; + print_fn(unknown_str); + } + } +}; + +/// Example usage and helper macros implemented as functions +pub const examples = struct { + /// Example register definition + pub const AuxControl = struct { + const Self = @This(); + + // Inherit from RegisterBase + base: hwreg.RegisterBase(Self, u32, void) = .{}, + + // Expose ValueType for RegisterAddr + pub const ValueType = u32; + + pub fn get() hwreg.RegisterAddr(Self) { + return hwreg.RegisterAddr(Self).init(0x64010); + } + + // Equivalent of DEF_BIT(31, enabled) + pub fn enabled(self: *const Self) u32 { + return hwreg.BitfieldRef(u32).init(@constCast(self.base.regValuePtrConst()), 31, 31).get(); + } + + pub fn setEnabled(self: *Self, val: u32) *Self { + hwreg.BitfieldRef(u32).init(self.base.regValuePtr(), 31, 31).set(val); + return self; + } + + // Equivalent of DEF_FIELD(24, 20, message_size) + pub fn messageSize(self: *const Self) u32 { + return hwreg.BitfieldRef(u32).init(@constCast(self.base.regValuePtrConst()), 24, 20).get(); + } + + pub fn setMessageSize(self: *Self, val: u32) *Self { + hwreg.BitfieldRef(u32).init(self.base.regValuePtr(), 24, 20).set(val); + return self; + } + + // Delegate common methods to base + pub fn regAddr(self: *const Self) u32 { + return self.base.regAddr(); + } + + pub fn setRegAddr(self: *Self, addr: u32) void { + self.base.setRegAddr(addr); + } + + pub fn regValue(self: *const Self) u32 { + return self.base.regValue(); + } + + pub fn setRegValue(self: *Self, value: u32) *Self { + _ = self.base.setRegValue(value); + return self; + } + + pub fn readFrom(self: *Self, reg_io: anytype) *Self { + _ = self.base.readFrom(reg_io); + return self; + } + + pub fn writeTo(self: *Self, reg_io: anytype) *Self { + _ = self.base.writeTo(reg_io); + return self; + } + }; + + /// Mock register I/O for testing + pub const MockRegisterIo = struct { + const Self = @This(); + + pub fn read(self: *Self, comptime T: type, addr: u32) T { + _ = self; + _ = addr; + return 0; // Return zero for mock + } + + pub fn write(self: *Self, value: anytype, addr: u32) void { + _ = self; + std.debug.print("Writing 0x{X} to address 0x{X}\n", .{ value, addr }); + } + }; + + /// Example usage functions + pub fn example1() void { + var reg_io = MockRegisterIo{}; + + // Read the register's value from MMIO + var reg = AuxControl.get().readFrom(®_io); + + // Read this register's "message_size" field + const size = reg.messageSize(); + std.debug.print("Message size: {}\n", .{size}); + + // Change this field's value + _ = reg.setMessageSize(1234); + + // Write the modified register value to MMIO + _ = reg.writeTo(®_io); + } + + pub fn example2() void { + var reg_io = MockRegisterIo{}; + + // Read, modify, and write in a fluent style + _ = AuxControl.get().readFrom(®_io).setMessageSize(1234).setEnabled(1).writeTo(®_io); + } + + pub fn example3() void { + var reg_io = MockRegisterIo{}; + + // Start with a zero-initialized register + var reg = AuxControl.get().fromValue(0); + + // Fill out fields + _ = reg.setMessageSize(2345); + + // Write the register value to MMIO + _ = reg.writeTo(®_io); + } +}; + +// Compile-time tests +test "hwreg basic functionality" { + const expect = std.testing.expect; + + // Test mask computation + try expect(hwreg.computeMask(u32, 0) == 0); + try expect(hwreg.computeMask(u32, 1) == 1); + try expect(hwreg.computeMask(u32, 8) == 0xFF); + try expect(hwreg.computeMask(u32, 32) == 0xFFFFFFFF); + + // Test bitfield reference + var value: u32 = 0; + var bitfield = hwreg.BitfieldRef(u32).init(&value, 7, 4); + bitfield.set(0xA); + try expect(value == 0xA0); + try expect(bitfield.get() == 0xA); +} + +test "hwreg register example" { + var reg_io = examples.MockRegisterIo{}; + var reg = examples.AuxControl.get().fromValue(0); + + _ = reg.setMessageSize(15); + _ = reg.setEnabled(1); + + const size = reg.messageSize(); + const enabled = reg.enabled(); + + try std.testing.expect(size == 15); + try std.testing.expect(enabled == 1); + + _ = reg.writeTo(®_io); +} + +test "hwreg sub-bit test" { + const expect = std.testing.expect; + + // Test with u8 + { + var value: u8 = 0; + var reg = hwreg.BitfieldRef(u8).init(&value, 7, 0); + try expect(reg.get() == 0); + + reg.set(1); + try expect(value == 1); + try expect(reg.get() == 1); + reg.set(0); + + reg.set(2); + try expect(value == 2); + try expect(reg.get() == 2); + reg.set(0); + + reg.set(128); + try expect(value == 128); + try expect(reg.get() == 128); + reg.set(0); + } + + // Test with u16 + { + var value: u16 = 0; + var reg = hwreg.BitfieldRef(u16).init(&value, 15, 0); + try expect(reg.get() == 0); + + reg.set(1); + try expect(value == 1); + try expect(reg.get() == 1); + reg.set(0); + + reg.set(2); + try expect(value == 2); + try expect(reg.get() == 2); + reg.set(0); + + reg.set(32768); + try expect(value == 32768); + try expect(reg.get() == 32768); + reg.set(0); + } + + // Test with u32 + { + var value: u32 = 0; + var reg = hwreg.BitfieldRef(u32).init(&value, 31, 0); + try expect(reg.get() == 0); + + reg.set(1); + try expect(value == 1); + try expect(reg.get() == 1); + reg.set(0); + + reg.set(2); + try expect(value == 2); + try expect(reg.get() == 2); + reg.set(0); + + reg.set(2147483648); + try expect(value == 2147483648); + try expect(reg.get() == 2147483648); + reg.set(0); + } + + // Test with u64 + { + var value: u64 = 0; + var reg = hwreg.BitfieldRef(u64).init(&value, 63, 0); + try expect(reg.get() == 0); + + reg.set(1); + try expect(value == 1); + try expect(reg.get() == 1); + reg.set(0); + + reg.set(2); + try expect(value == 2); + try expect(reg.get() == 2); + reg.set(0); + + reg.set(9223372036854775808); + try expect(value == 9223372036854775808); + try expect(reg.get() == 9223372036854775808); + reg.set(0); + } +} + +test "struct sub field test" { + const expect = std.testing.expect; + + const StructSubFieldTestReg = struct { + field1: u32, + field2: u32, + field3: u32, + + pub fn wholeLength(self: *const @This()) u32 { + return hwreg.BitfieldRef(u32).init(@constCast(&self.field1), 31, 0).get(); + } + + pub fn setWholeLength(self: *@This(), val: u32) void { + hwreg.BitfieldRef(u32).init(&self.field1, 31, 0).set(val); + } + + pub fn singleBit(self: *const @This()) u32 { + return hwreg.BitfieldRef(u32).init(@constCast(&self.field2), 2, 2).get(); + } + + pub fn setSingleBit(self: *@This(), val: u32) void { + hwreg.BitfieldRef(u32).init(&self.field2, 2, 2).set(val); + } + + pub fn range1(self: *const @This()) u32 { + return hwreg.BitfieldRef(u32).init(@constCast(&self.field3), 2, 1).get(); + } + + pub fn setRange1(self: *@This(), val: u32) void { + hwreg.BitfieldRef(u32).init(&self.field3, 2, 1).set(val); + } + + pub fn range2(self: *const @This()) u32 { + return hwreg.BitfieldRef(u32).init(@constCast(&self.field3), 5, 3).get(); + } + + pub fn setRange2(self: *@This(), val: u32) void { + hwreg.BitfieldRef(u32).init(&self.field3, 5, 3).set(val); + } + }; + + var val = StructSubFieldTestReg{ + .field1 = 0, + .field2 = 0, + .field3 = 0, + }; + + // Test whole length field + try expect(val.wholeLength() == 0); + val.setWholeLength(std.math.maxInt(u32)); + try expect(val.wholeLength() == std.math.maxInt(u32)); + try expect(val.field1 == std.math.maxInt(u32)); + val.setWholeLength(0); + try expect(val.wholeLength() == 0); + try expect(val.field1 == 0); + + // Test single bit field + try expect(val.singleBit() == 0); + val.setSingleBit(1); + try expect(val.singleBit() == 1); + try expect(val.field2 == 4); + val.setSingleBit(0); + try expect(val.singleBit() == 0); + try expect(val.field2 == 0); + + // Test adjacent fields + try expect(val.range1() == 0); + try expect(val.range2() == 0); + val.setRange1(3); + try expect(val.range1() == 3); + try expect(val.range2() == 0); + try expect(val.field3 == 3 << 1); + val.setRange2(1); + try expect(val.range1() == 3); + try expect(val.range2() == 1); + try expect(val.field3 == (3 << 1) | (1 << 3)); + val.setRange2(2); + try expect(val.range1() == 3); + try expect(val.range2() == 2); + try expect(val.field3 == (3 << 1) | (2 << 3)); + val.setRange1(0); + try expect(val.range1() == 0); + try expect(val.range2() == 2); + try expect(val.field3 == (2 << 3)); +} + +test "hwreg enum subfield test" { + const expect = std.testing.expect; + + const StructEnumSubFieldTestReg = struct { + const Self = @This(); + + const EnumWholeRange = enum(u32) { + kZero = 0, + kOne = 1, + kMax = std.math.maxInt(u32), + }; + + const EnumBit = enum(u8) { + kZero = 0, + kOne = 1, + }; + + const EnumRange = enum(u32) { + kZero = 0, + kOne = 1, + kTwo = 2, + kThree = 3, + }; + + field1: u32, + field2: u32, + field3: u32, + + pub fn wholeLength(self: *const Self) EnumWholeRange { + return @enumFromInt(hwreg.BitfieldRef(u32).init(@constCast(&self.field1), 31, 0).get()); + } + + pub fn setWholeLength(self: *Self, val: EnumWholeRange) void { + hwreg.BitfieldRef(u32).init(&self.field1, 31, 0).set(@intFromEnum(val)); + } + + pub fn singleBit(self: *const Self) EnumBit { + return @enumFromInt(hwreg.BitfieldRef(u32).init(@constCast(&self.field2), 2, 2).get()); + } + + pub fn setSingleBit(self: *Self, val: EnumBit) void { + hwreg.BitfieldRef(u32).init(&self.field2, 2, 2).set(@intFromEnum(val)); + } + + pub fn range1(self: *const Self) EnumRange { + return @enumFromInt(hwreg.BitfieldRef(u32).init(@constCast(&self.field3), 2, 1).get()); + } + + pub fn setRange1(self: *Self, val: EnumRange) void { + hwreg.BitfieldRef(u32).init(&self.field3, 2, 1).set(@intFromEnum(val)); + } + + pub fn range2(self: *const Self) EnumRange { + return @enumFromInt(hwreg.BitfieldRef(u32).init(@constCast(&self.field3), 5, 3).get()); + } + + pub fn setRange2(self: *Self, val: EnumRange) void { + hwreg.BitfieldRef(u32).init(&self.field3, 5, 3).set(@intFromEnum(val)); + } + }; + + var val = StructEnumSubFieldTestReg{ + .field1 = 0, + .field2 = 0, + .field3 = 0, + }; + + // Test whole length field + try expect(val.wholeLength() == .kZero); + val.setWholeLength(.kMax); + try expect(val.wholeLength() == .kMax); + try expect(val.field1 == std.math.maxInt(u32)); + val.setWholeLength(.kZero); + try expect(val.wholeLength() == .kZero); + try expect(val.field1 == 0); + + // Test single bit field + try expect(val.singleBit() == .kZero); + val.setSingleBit(.kOne); + try expect(val.singleBit() == .kOne); + try expect(val.field2 == 4); + val.setSingleBit(.kZero); + try expect(val.singleBit() == .kZero); + try expect(val.field2 == 0); + + // Test adjacent fields + try expect(val.range1() == .kZero); + try expect(val.range2() == .kZero); + val.setRange1(.kThree); + try expect(val.range1() == .kThree); + try expect(val.range2() == .kZero); + try expect(val.field3 == 3 << 1); + val.setRange2(.kOne); + try expect(val.range1() == .kThree); + try expect(val.range2() == .kOne); + try expect(val.field3 == (3 << 1) | (1 << 3)); + val.setRange2(.kTwo); + try expect(val.range1() == .kThree); + try expect(val.range2() == .kTwo); + try expect(val.field3 == (3 << 1) | (2 << 3)); + val.setRange1(.kZero); + try expect(val.range1() == .kZero); + try expect(val.range2() == .kTwo); + try expect(val.field3 == (2 << 3)); +} + +test "hwreg unshifted fields" { + const expect = std.testing.expect; + + const UnshiftedFieldTestReg = struct { + const Self = @This(); + + data: u16, + + pub fn field1(self: *const Self) u16 { + return hwreg.BitfieldRef(u16).initUnshifted(@constCast(&self.data), 15, 12).get(); + } + + pub fn setField1(self: *Self, val: u16) void { + hwreg.BitfieldRef(u16).initUnshifted(&self.data, 15, 12).set(val); + } + + pub fn field2(self: *const Self) u16 { + return hwreg.BitfieldRef(u16).initUnshifted(@constCast(&self.data), 11, 8).get(); + } + + pub fn setField2(self: *Self, val: u16) void { + hwreg.BitfieldRef(u16).initUnshifted(&self.data, 11, 8).set(val); + } + + pub fn field3(self: *const Self) u16 { + return hwreg.BitfieldRef(u16).initUnshifted(@constCast(&self.data), 7, 4).get(); + } + + pub fn setField3(self: *Self, val: u16) void { + hwreg.BitfieldRef(u16).initUnshifted(&self.data, 7, 4).set(val); + } + + pub fn field4(self: *const Self) u16 { + return hwreg.BitfieldRef(u16).initUnshifted(@constCast(&self.data), 3, 0).get(); + } + + pub fn setField4(self: *Self, val: u16) void { + hwreg.BitfieldRef(u16).initUnshifted(&self.data, 3, 0).set(val); + } + }; + + // Test simple field isolation + { + var test_reg = UnshiftedFieldTestReg{ .data = 0xffff }; + try expect(test_reg.field1() == 0xf000); + try expect(test_reg.field2() == 0x0f00); + try expect(test_reg.field3() == 0x00f0); + try expect(test_reg.field4() == 0x000f); + } + + // Test assignment + { + var test_reg = UnshiftedFieldTestReg{ .data = 0x0 }; + try expect(test_reg.field1() == 0); + try expect(test_reg.field2() == 0); + try expect(test_reg.field3() == 0); + try expect(test_reg.field4() == 0); + + test_reg.setField1(0xf000); + try expect(test_reg.field1() == 0xf000); + try expect(test_reg.field2() == 0); + try expect(test_reg.field3() == 0); + try expect(test_reg.field4() == 0); + + test_reg.setField2(0xf00); + try expect(test_reg.field1() == 0xf000); + try expect(test_reg.field2() == 0xf00); + try expect(test_reg.field3() == 0); + try expect(test_reg.field4() == 0); + + test_reg.setField3(0xf0); + try expect(test_reg.field1() == 0xf000); + try expect(test_reg.field2() == 0xf00); + try expect(test_reg.field3() == 0xf0); + try expect(test_reg.field4() == 0); + + test_reg.setField4(0xf); + try expect(test_reg.field1() == 0xf000); + try expect(test_reg.field2() == 0xf00); + try expect(test_reg.field3() == 0xf0); + try expect(test_reg.field4() == 0xf); + } +} diff --git a/slipstream/system/ulib/hwreg/src/root.zig b/slipstream/system/ulib/hwreg/src/root.zig new file mode 100644 index 0000000..115b778 --- /dev/null +++ b/slipstream/system/ulib/hwreg/src/root.zig @@ -0,0 +1,9 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. + +pub const bitfields = @import("bitfields.zig"); + +comptime { + _ = bitfields; +} From 5607bfdb980b7972d2267e465fb7ada02f33ff80 Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Tue, 3 Jun 2025 17:01:57 -0300 Subject: [PATCH 08/41] [ulib][mock_function] Initial version --- .../system/ulib/mock_function/build.zig | 25 ++ .../system/ulib/mock_function/build.zig.zon | 6 + .../ulib/mock_function/src/mock_function.zig | 336 ++++++++++++++++++ 3 files changed, 367 insertions(+) create mode 100644 slipstream/system/ulib/mock_function/build.zig create mode 100644 slipstream/system/ulib/mock_function/build.zig.zon create mode 100644 slipstream/system/ulib/mock_function/src/mock_function.zig diff --git a/slipstream/system/ulib/mock_function/build.zig b/slipstream/system/ulib/mock_function/build.zig new file mode 100644 index 0000000..833f97c --- /dev/null +++ b/slipstream/system/ulib/mock_function/build.zig @@ -0,0 +1,25 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. + +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const mod = b.addModule("mock_function", .{ + .root_source_file = b.path("src/mock_function.zig"), + .target = target, + .optimize = optimize, + }); + + const test_step = b.step("test", "Run unit tests"); + const unit_tests = b.addTest(.{ + .root_module = mod, + .target = b.graph.host, + }); + + const run_unit_tests = b.addRunArtifact(unit_tests); + test_step.dependOn(&run_unit_tests.step); +} diff --git a/slipstream/system/ulib/mock_function/build.zig.zon b/slipstream/system/ulib/mock_function/build.zig.zon new file mode 100644 index 0000000..f123e41 --- /dev/null +++ b/slipstream/system/ulib/mock_function/build.zig.zon @@ -0,0 +1,6 @@ +.{ + .name = .mock_function, + .fingerprint = 0xfe3b4796b62a5a77, + .version = "0.0.1", + .paths = .{""}, +} diff --git a/slipstream/system/ulib/mock_function/src/mock_function.zig b/slipstream/system/ulib/mock_function/src/mock_function.zig new file mode 100644 index 0000000..ce90d3c --- /dev/null +++ b/slipstream/system/ulib/mock_function/src/mock_function.zig @@ -0,0 +1,336 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. + +const std = @import("std"); + +const testing = std.testing; + +/// This struct mocks a single function. The expect_*() functions are used by the test to set +/// expectations, and call() is used by the code under test. There are three variants: +/// +/// * expectCall(return_value, args) sets the expectation that the call will be made with +/// arguments `args`, each compared using std.meta.eql. `return_value` will be returned +/// unconditionally, or is omitted if the function returns void. +/// * expectCallWithMatcher(matcher) uses a `matcher` to validate the arguments. The matcher will be +/// called with the arguments to the mocked function call, and the call will return the matcher's +/// return value. +/// * expectNoCall() expects that the function will not be called. +/// +/// Example: +/// +/// ```zig +/// const SomeClassTest = struct { +/// mock_some_method: MockFunction(i32, .{ u32, u32 }), +/// allocator: Allocator, +/// +/// const Self = @This(); +/// +/// pub fn init(allocator: Allocator) Self { +/// return Self{ +/// .mock_some_method = MockFunction(i32, .{ u32, u32 }).init(allocator), +/// .allocator = allocator, +/// }; +/// } +/// +/// pub fn deinit(self: *Self) void { +/// self.mock_some_method.deinit(); +/// } +/// +/// pub fn callsSomeMethod(self: *Self) i32 { +/// return self.someMethod(100, 30); +/// } +/// +/// fn someMethod(self: *Self, a: u32, b: u32) i32 { +/// return self.mock_some_method.call(.{ a, b }); +/// } +/// }; +/// +/// test "some test" { +/// var test_instance = SomeClassTest.init(testing.allocator); +/// defer test_instance.deinit(); +/// +/// try test_instance.mock_some_method.expectCall(42, .{ 100, 30 }); +/// try test_instance.mock_some_method.expectCallWithMatcher(struct { +/// fn matcher(args: struct { u32, u32 }) i32 { +/// try testing.expect(args[0] == 200); +/// try testing.expect(args[1] == 60); +/// return 42; +/// } +/// }.matcher); +/// +/// try testing.expect(test_instance.callsSomeMethod() == 42); +/// try testing.expect(test_instance.someMethod(200, 60) == 42); +/// +/// try test_instance.mock_some_method.verifyAndClear(); +/// } +/// ``` +pub fn MockFunction(comptime ReturnType: type, comptime arg_types: []const type) type { + // Create a tuple type from the argument types + const ArgsTuple = blk: { + var fields: [arg_types.len]std.builtin.Type.StructField = undefined; + inline for (arg_types, 0..) |arg_type, i| { + fields[i] = std.builtin.Type.StructField{ + .name = std.fmt.comptimePrint("{d}", .{i}), + .type = arg_type, + .default_value_ptr = null, + .is_comptime = false, + .alignment = @alignOf(arg_type), + }; + } + + break :blk @Type(std.builtin.Type{ + .@"struct" = std.builtin.Type.Struct{ + .layout = .auto, + .fields = &fields, + .decls = &[_]std.builtin.Type.Declaration{}, + .is_tuple = true, + }, + }); + }; + + return struct { + const Self = @This(); + + const Expectation = union(enum) { + exact_match: struct { + expected_args: ArgsTuple, + return_value: if (ReturnType == void) void else ReturnType, + }, + matcher: struct { + matcher_fn: *const fn (ArgsTuple) (if (ReturnType == void) void else ReturnType), + }, + }; + + expectations: std.ArrayList(Expectation), + expectation_index: usize, + has_expectations: bool, + + pub fn init() Self { + return Self{ + .expectations = std.ArrayList(Expectation).init(std.testing.allocator), + .expectation_index = 0, + .has_expectations = false, + }; + } + + pub fn deinit(self: *Self) void { + self.expectations.deinit(); + } + + /// Sets expectation for a call with specific arguments and return value + pub fn expectCall(self: *Self, return_value: if (ReturnType == void) void else ReturnType, expected_args: anytype) *Self { + // Convert the arguments to our ArgsTuple type + const converted_args = convertArgsToTuple(expected_args); + + const expectation = if (ReturnType == void) + Expectation{ .exact_match = .{ .expected_args = converted_args, .return_value = {} } } + else + Expectation{ .exact_match = .{ .expected_args = converted_args, .return_value = return_value } }; + + self.expectations.append(expectation) catch unreachable; + self.has_expectations = true; + return self; + } + + /// Sets expectation using a custom matcher function + pub fn expectCallWithMatcher(self: *Self, matcher_fn: *const fn (ArgsTuple) (if (ReturnType == void) void else ReturnType)) *Self { + const expectation = Expectation{ .matcher = .{ .matcher_fn = matcher_fn } }; + self.expectations.append(expectation) catch unreachable; + self.has_expectations = true; + return self; + } + + /// Sets expectation that no call will be made + pub fn expectNoCall(self: *Self) *Self { + self.has_expectations = true; + return self; + } + + /// Call the mocked function with the provided arguments + pub fn call(self: *Self, args: anytype) if (ReturnType == void) void else ReturnType { + const converted_args = convertArgsToTuple(args); + const expectation = self.callHelper(); + + switch (expectation) { + .exact_match => |exact| { + // Compare arguments using comptime-generated comparison + if (!argsEqual(converted_args, exact.expected_args)) { + std.debug.print("\nMock function called with unexpected arguments:\n", .{}); + std.debug.print("Expected: {any}\n", .{exact.expected_args}); + std.debug.print("Actual: {any}\n", .{converted_args}); + @panic("Mock function called with unexpected arguments"); + } + + if (ReturnType == void) { + return; + } else { + return exact.return_value; + } + }, + .matcher => |matcher| { + if (ReturnType == void) { + matcher.matcher_fn(converted_args); + return; + } else { + return matcher.matcher_fn(converted_args); + } + }, + } + } + + pub fn hasExpectations(self: *const Self) bool { + return self.has_expectations; + } + + /// Verify that all expectations were met and clear the mock + pub fn verifyAndClear(self: *Self) void { + testing.expect(self.expectation_index == self.expectations.items.len) catch {}; + self.expectations.clearRetainingCapacity(); + self.expectation_index = 0; + } + + fn callHelper(self: *Self) Expectation { + const enough_expectations_were_set = self.expectation_index < self.expectations.items.len; + //TODO (Herrera): Use slipstream assert. + if (!enough_expectations_were_set) { + std.debug.panic("Mock function called more times than expected: ({d} < {d})", .{ self.expectation_index, self.expectations.items.len }); + } + + const expectation = self.expectations.items[self.expectation_index]; + self.expectation_index += 1; + return expectation; + } + + /// Comptime-generated function to compare argument tuples + fn argsEqual(a: ArgsTuple, b: ArgsTuple) bool { + inline for (0..arg_types.len) |i| { + const field_name = std.fmt.comptimePrint("{d}", .{i}); + const a_val = @field(a, field_name); + const b_val = @field(b, field_name); + + // Use std.meta.eql for deep comparison + if (!std.meta.eql(a_val, b_val)) { + return false; + } + } + return true; + } + + /// Convert input arguments to our internal ArgsTuple type + fn convertArgsToTuple(args: anytype) ArgsTuple { + const input_info = @typeInfo(@TypeOf(args)); + if (input_info != .@"struct" or !input_info.@"struct".is_tuple) { + @compileError("Arguments must be passed as a tuple"); + } + + var result: ArgsTuple = undefined; + inline for (0..arg_types.len) |i| { + const field_name = std.fmt.comptimePrint("{d}", .{i}); + @field(result, field_name) = args[i]; + } + return result; + } + }; +} + +// Helper function to create a mock function with void return type +pub fn MockVoidFunction(comptime arg_types: []const type) type { + return MockFunction(void, arg_types); +} + +// Move-only class for testing +const MoveOnlyClass = struct { + key: u32, + + const Self = @This(); + + pub fn init() Self { + return Self{ .key = 0 }; + } + + pub fn initWithKey(key: u32) Self { + return Self{ .key = key }; + } + + pub fn getKey(self: *const Self) u32 { + return self.key; + } + + pub fn eql(self: *const Self, other: *const Self) bool { + return self.key == other.key; + } +}; + +test "move argument" { + var mock_function = MockVoidFunction(&[_]type{ MoveOnlyClass, i32 }).init(); + defer mock_function.deinit(); + + const arg1 = MoveOnlyClass.initWithKey(10); + const arg2 = MoveOnlyClass.initWithKey(10); + + _ = mock_function.expectCall({}, .{ arg1, 25 }); + mock_function.call(.{ arg2, 25 }); + + mock_function.verifyAndClear(); +} + +test "move return value" { + var mock_function = MockFunction(MoveOnlyClass, &[_]type{ i32, i32 }).init(); + defer mock_function.deinit(); + + const arg1 = MoveOnlyClass.initWithKey(50); + + _ = mock_function.expectCall(arg1, .{ 100, 200 }); + const ret = mock_function.call(.{ 100, 200 }); + + mock_function.verifyAndClear(); + try testing.expect(ret.getKey() == 50); +} + +test "move tuple return value" { + const TupleType = struct { i32, MoveOnlyClass }; + var mock_function = MockFunction(TupleType, &[_]type{i32}).init(); + defer mock_function.deinit(); + + const arg1 = MoveOnlyClass.initWithKey(30); + const expected_return = TupleType{ 80, arg1 }; + + _ = mock_function.expectCall(expected_return, .{5000}); + const tup = mock_function.call(.{5000}); + const ret = tup[1]; + + mock_function.verifyAndClear(); + try testing.expect(tup[0] == 80); + try testing.expect(ret.getKey() == 30); +} + +test "with matcher" { + var mock_function_int = MockFunction(i32, &[_]type{i32}).init(); + defer mock_function_int.deinit(); + + var mock_function_void = MockVoidFunction(&[_]type{i32}).init(); + defer mock_function_void.deinit(); + + const IntMatcher = struct { + fn matcher(args: struct { i32 }) i32 { + testing.expect(args[0] == 138) catch unreachable; + return 42; + } + }; + + const VoidMatcher = struct { + fn matcher(args: struct { i32 }) void { + testing.expect(args[0] == 159) catch unreachable; + } + }; + + _ = mock_function_int.expectCallWithMatcher(IntMatcher.matcher); + _ = mock_function_void.expectCallWithMatcher(VoidMatcher.matcher); + + try testing.expect(mock_function_int.call(.{138}) == 42); + mock_function_void.call(.{159}); + + mock_function_int.verifyAndClear(); + mock_function_void.verifyAndClear(); +} From 6a8b15b257de9eeaf706ba112cf6a5b62b754155 Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Fri, 30 May 2025 09:45:58 -0300 Subject: [PATCH 09/41] [ulib][hwreg] Add visitor pattern and Reserved-zero --- slipstream/system/ulib/hwreg/build.zig | 3 + slipstream/system/ulib/hwreg/build.zig.zon | 3 + .../system/ulib/hwreg/src/bitfields.zig | 1250 +++++++++++++---- slipstream/system/ulib/hwreg/src/internal.zig | 252 ++++ slipstream/system/ulib/hwreg/src/mock.zig | 160 +++ slipstream/system/ulib/hwreg/src/root.zig | 1 + 6 files changed, 1361 insertions(+), 308 deletions(-) create mode 100644 slipstream/system/ulib/hwreg/src/internal.zig create mode 100644 slipstream/system/ulib/hwreg/src/mock.zig diff --git a/slipstream/system/ulib/hwreg/build.zig b/slipstream/system/ulib/hwreg/build.zig index 9ac6dec..2a192a1 100644 --- a/slipstream/system/ulib/hwreg/build.zig +++ b/slipstream/system/ulib/hwreg/build.zig @@ -16,6 +16,7 @@ pub fn build(b: *std.Build) void { const deps = [_]struct { name: []const u8, dep_name: []const u8, module_name: []const u8 }{ .{ .name = "public", .dep_name = "public", .module_name = "public" }, + .{ .name = "mock_function", .dep_name = "mock_function", .module_name = "mock_function" }, }; for (deps) |dep| { @@ -23,10 +24,12 @@ pub fn build(b: *std.Build) void { mod.addImport(dep.name, dep_module.module(dep.module_name)); } + const test_filters = b.option([]const []const u8, "test-filter", "Skip tests that do not match any filter") orelse &[0][]const u8{}; const test_step = b.step("test", "Run unit tests"); const unit_tests = b.addTest(.{ .root_module = mod, .target = b.graph.host, + .filters = test_filters, }); const run_unit_tests = b.addRunArtifact(unit_tests); diff --git a/slipstream/system/ulib/hwreg/build.zig.zon b/slipstream/system/ulib/hwreg/build.zig.zon index fff1abd..2f52ab5 100644 --- a/slipstream/system/ulib/hwreg/build.zig.zon +++ b/slipstream/system/ulib/hwreg/build.zig.zon @@ -7,5 +7,8 @@ .public = .{ .path = "../../public", }, + .mock_function = .{ + .path = "../mock_function", + }, }, } diff --git a/slipstream/system/ulib/hwreg/src/bitfields.zig b/slipstream/system/ulib/hwreg/src/bitfields.zig index 1a141e1..37615fc 100644 --- a/slipstream/system/ulib/hwreg/src/bitfields.zig +++ b/slipstream/system/ulib/hwreg/src/bitfields.zig @@ -3,283 +3,284 @@ //! found in the LICENSE file. const std = @import("std"); -const assert = @import("public").assert; +const internal = @import("internal.zig"); + +/// Tag that can be passed as the third template parameter for RegisterBase to enable +/// the pretty-printing interfaces on a register. +pub const EnablePrinter = struct {}; + +/// An instance of RegisterBase represents a staging copy of a register, +/// which can be written to the register itself. It knows the register's +/// address and stores a value for the register. +/// +/// Normal usage is to create types that embed RegisterBase and +/// provide methods for accessing bitfields of the register. RegisterBase +/// does not provide a constructor because constructors are not inherited by +/// derived classes by default, and we don't want the derived classes to +/// have to declare constructors. +/// +/// Any bits not declared using field definitions will be automatically +/// preserved across RMW operations. +pub fn RegisterBase(comptime DerivedType: type, comptime IntType: type, comptime PrinterState: type) type { + comptime { + if (!internal.isSupportedInt(IntType)) { + @compileError("Unsupported register access width"); + } + if (PrinterState != void) { + if (PrinterState != EnablePrinter) { + @compileError("unsupported printer state"); + } + } + } -/// Hardware register library for Zig -/// Provides type-safe bitfield manipulation for hardware registers -pub const hwreg = struct { - /// Tag to enable pretty-printing interfaces on a register - pub const EnablePrinter = struct {}; + const has_printer = PrinterState == EnablePrinter; - /// Supported integer types for register access - pub fn IsSupportedInt(comptime T: type) bool { - return T == u8 or T == u16 or T == u32 or T == u64; - } + return struct { + const Self = @This(); - /// Compute a bit mask with the specified number of bits - pub fn computeMask(comptime IntType: type, num_bits: u32) IntType { - if (num_bits == 0) return 0; - if (num_bits >= @bitSizeOf(IntType)) return ~@as(IntType, 0); - return (@as(IntType, 1) << @intCast(num_bits)) - 1; - } + pub const SelfType = DerivedType; + pub const ValueType = IntType; + pub const PrinterEnabled = has_printer; - /// Field information for printing support - pub const FieldInfo = struct { - name: []const u8, - bit_high: u32, - bit_low: u32, + params: internal.FieldParameters(has_printer, IntType) = .{}, + reg_value: ValueType = 0, + reg_addr: u32 = 0, - pub fn bitHighIncl(self: @This()) u32 { - return self.bit_high; + pub fn regAddr(self: *const Self) u32 { + return self.reg_addr; } - pub fn bitLow(self: @This()) u32 { - return self.bit_low; + pub fn setRegAddr(self: *Self, addr: u32) void { + self.reg_addr = addr; } - }; - - /// Parameters for register fields - pub fn FieldParameters(comptime has_printer: bool, comptime ValueType: type) type { - return struct { - fields_mask: ValueType = 0, - rsvdz_mask: ValueType = 0, - printer: if (has_printer) PrinterInfo else void = if (has_printer) PrinterInfo{} else {}, - - const PrinterInfo = struct { - fields: [32]FieldInfo = [_]FieldInfo{FieldInfo{ .name = "", .bit_high = 0, .bit_low = 0 }} ** 32, - num_fields: u32 = 0, - }; - }; - } - /// Base class for hardware registers - pub fn RegisterBase(comptime DerivedType: type, comptime IntType: type, comptime PrinterState: type) type { - if (!IsSupportedInt(IntType)) { - @compileError("Unsupported register access width"); + pub fn regValue(self: *const Self) ValueType { + return self.reg_value; } - const has_printer = PrinterState == EnablePrinter; - - return struct { - const Self = @This(); - pub const SelfType = DerivedType; - pub const ValueType = IntType; - pub const PrinterEnabled = has_printer; - - params_: FieldParameters(has_printer, IntType) = .{}, - reg_value_: ValueType = 0, - reg_addr_: u32 = 0, - - pub fn regAddr(self: *const Self) u32 { - return self.reg_addr_; - } - - pub fn setRegAddr(self: *Self, addr: u32) void { - self.reg_addr_ = addr; - } + pub fn regValuePtr(self: *Self) *ValueType { + //std.debug.print("RegisterBase::regValuePtr {}\n", .{&self.reg_value}); + return &self.reg_value; + } - pub fn regValue(self: *const Self) ValueType { - return self.reg_value_; - } + pub fn regValuePtrConst(self: *const Self) *const ValueType { + return &self.reg_value; + } - pub fn regValuePtr(self: *Self) *ValueType { - return &self.reg_value_; - } + pub fn setRegValue(self: *Self, value: IntType) *SelfType { + self.reg_value = value; + return @ptrCast(self); + } - pub fn regValuePtrConst(self: *const Self) *const ValueType { - return &self.reg_value_; - } + pub fn readFrom(self: *Self, reg_io: anytype) *SelfType { + internal.visit(struct { + fn readFn(io: anytype, self_ptr: *Self) void { + self_ptr.reg_value = io.read(ValueType, self_ptr.reg_addr); + } + }.readFn, reg_io.*, .{self}); + return @ptrCast(self); + } - pub fn setRegValue(self: *Self, value: IntType) *DerivedType { - self.reg_value_ = value; - return @ptrCast(self); - } + pub fn writeTo(self: *Self, reg_io: anytype) *SelfType { + internal.visit(struct { + fn writeFn(io: anytype, self_ptr: *Self) void { + const masked_value = self_ptr.reg_value & ~self_ptr.params.rsvdz_mask; + var mutable_io = io; + mutable_io.write(ValueType, masked_value, self_ptr.reg_addr); + } + }.writeFn, reg_io.*, .{self}); + return @ptrCast(self); + } - pub fn readFrom(self: *Self, reg_io: anytype) *DerivedType { - self.reg_value_ = reg_io.read(ValueType, self.reg_addr_); - return @ptrCast(self); + /// Invokes print_fn once for each field, including each + /// RsvdZ field, and one extra time if there are any undefined bits set. + /// The callback argument must not be accessed after the callback + /// returns. The callback will be called once for each field with a + /// null-terminated string describing the name and contents of the field. + /// + /// Printed fields will look like: "field_name[26:8]: 0x00123 (291)" + /// The undefined bits message will look like: "unknown set bits: 0x00301000" + /// + /// WARNING: This will substantially increase code size and stack usage at the + /// call site. + /// + /// Example use: + /// reg.print(struct { fn printFn(arg: []const u8) void { std.debug.print("{s}\n", .{arg}); } }.printFn); + pub fn print(self: *Self, print_fn: anytype) void { + if (!has_printer) { + @compileError("Pass EnablePrinter to RegisterBase to enable printing"); } + internal.printRegister(print_fn, &self.params.printer.fields, self.params.printer.num_fields, self.reg_value, self.params.fields_mask, @sizeOf(ValueType)); + } - pub fn writeTo(self: *Self, reg_io: anytype) *DerivedType { - const masked_value = self.reg_value_ & ~self.params_.rsvdz_mask; - reg_io.write(masked_value, self.reg_addr_); - return @ptrCast(self); + /// Equivalent to print([](const char* arg) { printf("%s\n", arg); }); + pub fn printStdout(self: *Self) void { + if (!has_printer) { + @compileError("Pass EnablePrinter to RegisterBase to enable printing"); } + internal.printRegisterPrintf(&self.params.printer.fields, self.params.printer.num_fields, self.reg_value, self.params.fields_mask, @sizeOf(ValueType)); + } - /// Print register information (requires PrinterEnabled) - pub fn printReg(self: *const Self, print_fn: anytype) void { - if (!has_printer) { - @compileError("Pass hwreg.EnablePrinter to RegisterBase to enable printing"); - } - printRegister(print_fn, &self.params_.printer.fields, self.params_.printer.num_fields, self.reg_value_, self.params_.fields_mask, @sizeOf(ValueType)); + pub fn forEachField(self: *Self, comptime callback: anytype) void { + if (!has_printer) { + @compileError("Pass EnablePrinter to RegisterBase to enable field iteration"); } - - /// Print register to stdout (requires PrinterEnabled) - pub fn print(self: *const Self) void { - if (!has_printer) { - @compileError("Pass hwreg.EnablePrinter to RegisterBase to enable printing"); - } - self.printReg(struct { - fn printFn(arg: []const u8) void { - std.debug.print("{s}\n", .{arg}); - } - }.printFn); + var i: u32 = 0; + while (i < self.params.printer.num_fields) : (i += 1) { + const field = self.params.printer.fields[i]; + const mask = internal.computeMask(ValueType, field.bitHighIncl() - field.bitLow() + 1) << @intCast(field.bitLow()); + const value = (self.reg_value & mask) >> @intCast(field.bitLow()); + const is_rsvdz = (mask & self.rsvdzMask()) == mask; + callback(if (is_rsvdz) null else field.name, value, field.bitHighIncl(), field.bitLow()); } + } - /// Iterate over each field with a callback (requires PrinterEnabled) - pub fn forEachField(self: *const Self, callback: anytype) void { - if (!has_printer) { - @compileError("Pass hwreg.EnablePrinter to RegisterBase to enable field iteration"); - } - var i: u32 = 0; - while (i < self.params_.printer.num_fields) : (i += 1) { - const field = self.params_.printer.fields[i]; - const mask = computeMask(ValueType, field.bit_high - field.bit_low + 1) << @intCast(field.bit_low); - const value = (self.reg_value_ & mask) >> @intCast(field.bit_low); - const is_rsvdz = (mask & self.rsvdzMask()) == mask; - callback(if (is_rsvdz) null else field.name, value, field.bit_high, field.bit_low); - } - } + pub fn fieldsMask(self: *const Self) IntType { + return self.params.fields_mask; + } - pub fn fieldsMask(self: *const Self) IntType { - return self.params_.fields_mask; - } + pub fn rsvdzMask(self: *const Self) IntType { + return self.params.rsvdz_mask; + } - pub fn rsvdzMask(self: *const Self) IntType { - return self.params_.rsvdz_mask; - } + pub fn getParams(self: *Self) *internal.FieldParameters(has_printer, ValueType) { + return &self.params; + } + }; +} - pub fn params(self: *Self) *FieldParameters(has_printer, ValueType) { - return &self.params_; - } - }; - } +/// An instance of RegisterAddr represents a typed register address: It +/// knows the address of the register (within the MMIO address space) and +/// the type of its contents, RegType. RegType represents the register's +/// bitfields. RegType should embed RegisterBase. +pub fn RegisterAddr(comptime RegType: type) type { + return struct { + const Self = @This(); - /// Typed register address - pub fn RegisterAddr(comptime RegType: type) type { - return struct { - const Self = @This(); - reg_addr_: u32, + reg_addr: u32, - pub fn init(reg_addr: u32) Self { - return Self{ .reg_addr_ = reg_addr }; - } + pub fn init(reg_addr: u32) Self { + return Self{ .reg_addr = reg_addr }; + } - /// Read register value from MMIO - pub fn readFrom(self: Self, reg_io: anytype) RegType { - var reg = RegType{}; - reg.setRegAddr(self.reg_addr_); - _ = reg.readFrom(reg_io); - return reg; - } + /// Instantiate a RegisterBase using the value of the register read from MMIO. + pub fn readFrom(self: Self, reg_io: anytype) RegType { + var reg: RegType = RegType.init(); + reg.setRegAddr(self.reg_addr); + _ = reg.readFrom(reg_io); + return reg; + } - /// Create register instance with given value - pub fn fromValue(self: Self, value: RegType.ValueType) RegType { - var reg = RegType{}; - reg.setRegAddr(self.reg_addr_); - _ = reg.setRegValue(value); - return reg; - } + /// Instantiate a RegisterBase using the given value for the register. + pub fn fromValue(self: Self, value: RegType.ValueType) RegType { + var reg: RegType = RegType.init(); + reg.setRegAddr(self.reg_addr); + _ = reg.setRegValue(value); + return reg; + } - pub fn addr(self: Self) u32 { - return self.reg_addr_; - } - }; - } + pub fn addr(self: Self) u32 { + return self.reg_addr; + } + }; +} - /// Reference to a bitfield within a register - pub fn BitfieldRef(comptime IntType: type) type { - return struct { - const Self = @This(); - value_ptr_: *IntType, - shift_: u32, - mask_: IntType, - - pub fn init(value_ptr: *IntType, bit_high_incl: u32, bit_low: u32) Self { - return Self{ - .value_ptr_ = value_ptr, - .shift_ = bit_low, - .mask_ = computeMask(IntType, bit_high_incl - bit_low + 1), - }; - } +pub fn BitfieldRef(comptime IntType: type) type { + return struct { + const Self = @This(); - pub fn initUnshifted(value_ptr: *IntType, bit_high_incl: u32, bit_low: u32) Self { - return Self{ - .value_ptr_ = value_ptr, - .shift_ = 0, - .mask_ = computeMask(IntType, bit_high_incl - bit_low + 1) << @intCast(bit_low), - }; - } + value_ptr: *IntType, + shift: u32, + mask: IntType, - pub fn get(self: Self) IntType { - return @intCast((self.value_ptr_.* >> @intCast(self.shift_)) & self.mask_); + pub fn init(value_ptr: *IntType, comptime bit_high_incl: u32, comptime bit_low: u32) Self { + comptime { + if (bit_high_incl < bit_low) { + @compileError("bit_high_incl must be >= bit_low"); + } + if (bit_high_incl >= @bitSizeOf(IntType)) { + @compileError("bit_high_incl must be < @bitSizeOf(IntType)"); + } } + return Self{ + .value_ptr = value_ptr, + .shift = bit_low, + .mask = internal.computeMask(IntType, bit_high_incl - bit_low + 1), + }; + } - pub fn set(self: Self, field_val: IntType) void { - assert.slipstreamAssert(@src(), (field_val & ~self.mask_) == 0, "field_val & ~self.mask_ == 0"); - const masked = self.value_ptr_.* & ~(self.mask_ << @intCast(self.shift_)); - self.value_ptr_.* = masked | (field_val << @intCast(self.shift_)); + pub fn initUnshifted(value_ptr: *IntType, comptime bit_high_incl: u32, comptime bit_low: u32) Self { + comptime { + if (bit_high_incl < bit_low) { + @compileError("bit_high_incl must be >= bit_low"); + } + if (bit_high_incl >= @bitSizeOf(IntType)) { + @compileError("bit_high_incl must be < @bitSizeOf(IntType)"); + } } - }; - } - - /// Print register information helper - fn printRegister(print_fn: anytype, fields: []const FieldInfo, num_fields: u32, reg_value: anytype, fields_mask: anytype, value_size: usize) void { - var i: u32 = 0; - while (i < num_fields) : (i += 1) { - const field = fields[i]; - const mask = computeMask(@TypeOf(reg_value), field.bit_high - field.bit_low + 1) << @intCast(field.bit_low); - const value = (reg_value & mask) >> @intCast(field.bit_low); - - var buf: [256]u8 = undefined; - const field_str = std.fmt.bufPrint(&buf, "{s}[{d}:{d}]: 0x{X:0>{d}} ({d})", .{ field.name, field.bit_high, field.bit_low, value, value_size * 2, value }) catch "field format error"; + return Self{ + .value_ptr = value_ptr, + .shift = 0, + .mask = @as(IntType, internal.computeMask(IntType, bit_high_incl - bit_low + 1)) << @intCast(bit_low), + }; + } - print_fn(field_str); + pub fn get(self: Self) IntType { + return @intCast((self.value_ptr.* >> @intCast(self.shift)) & self.mask); } - // Print unknown bits if any - const unknown_bits = reg_value & ~fields_mask; - if (unknown_bits != 0) { - var buf: [256]u8 = undefined; - const unknown_str = std.fmt.bufPrint(&buf, "unknown set bits: 0x{X:0>{d}}", .{ unknown_bits, value_size * 2 }) catch "unknown bits format error"; - print_fn(unknown_str); + pub fn set(self: Self, field_val: IntType) void { + std.debug.assert((field_val & ~self.mask) == 0); + const masked = self.value_ptr.* & ~(self.mask << @intCast(self.shift)); + self.value_ptr.* = masked | (field_val << @intCast(self.shift)); } - } -}; + }; +} -/// Example usage and helper macros implemented as functions +/// Example register definition for documentation purposes pub const examples = struct { - /// Example register definition + /// Define bitfields for an "AuxControl" 32-bit register. pub const AuxControl = struct { const Self = @This(); - // Inherit from RegisterBase - base: hwreg.RegisterBase(Self, u32, void) = .{}, + // Embed RegisterBase + base: RegisterBase(Self, u32, void) = .{}, - // Expose ValueType for RegisterAddr + // Required for RegisterAddr pub const ValueType = u32; - pub fn get() hwreg.RegisterAddr(Self) { - return hwreg.RegisterAddr(Self).init(0x64010); + pub fn init() Self { + return Self{ + .base = .{}, + }; + } + + /// Returns an object representing the register's type and address. + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(0x64010); } - // Equivalent of DEF_BIT(31, enabled) + // Bits [30:25] and [19:0] are automatically preserved across RMW cycles. + + // Define a single-bit field. + // DEF_BIT(31, enabled); pub fn enabled(self: *const Self) u32 { - return hwreg.BitfieldRef(u32).init(@constCast(self.base.regValuePtrConst()), 31, 31).get(); + return BitfieldRef(u32).init(@constCast(self.base.regValuePtrConst()), 31, 31).get(); } pub fn setEnabled(self: *Self, val: u32) *Self { - hwreg.BitfieldRef(u32).init(self.base.regValuePtr(), 31, 31).set(val); + BitfieldRef(u32).init(self.base.regValuePtr(), 31, 31).set(val); return self; } - // Equivalent of DEF_FIELD(24, 20, message_size) + // Define a 5-bit field, from bits 20-24 (inclusive). + // DEF_FIELD(24, 20, message_size); pub fn messageSize(self: *const Self) u32 { - return hwreg.BitfieldRef(u32).init(@constCast(self.base.regValuePtrConst()), 24, 20).get(); + return BitfieldRef(u32).init(@constCast(self.base.regValuePtrConst()), 24, 20).get(); } pub fn setMessageSize(self: *Self, val: u32) *Self { - hwreg.BitfieldRef(u32).init(self.base.regValuePtr(), 24, 20).set(val); + BitfieldRef(u32).init(self.base.regValuePtr(), 24, 20).set(val); return self; } @@ -316,76 +317,79 @@ pub const examples = struct { pub const MockRegisterIo = struct { const Self = @This(); - pub fn read(self: *Self, comptime T: type, addr: u32) T { + pub fn read(self: *const Self, comptime T: type, addr: u32) T { _ = self; _ = addr; return 0; // Return zero for mock } - pub fn write(self: *Self, value: anytype, addr: u32) void { + pub fn write(self: *Self, comptime T: type, value: T, addr: u32) void { _ = self; std.debug.print("Writing 0x{X} to address 0x{X}\n", .{ value, addr }); } }; - /// Example usage functions pub fn example1() void { var reg_io = MockRegisterIo{}; - // Read the register's value from MMIO + // Read the register's value from MMIO. "reg" is a snapshot of the + // register's value which also knows the register's address. var reg = AuxControl.get().readFrom(®_io); - // Read this register's "message_size" field + // Read this register's "message_size" field. const size = reg.messageSize(); std.debug.print("Message size: {}\n", .{size}); - // Change this field's value + // Change this field's value. This modifies the snapshot. _ = reg.setMessageSize(1234); - // Write the modified register value to MMIO + // Write the modified register value to MMIO. _ = reg.writeTo(®_io); } + // Fields may also be set in a fluent style pub fn example2() void { var reg_io = MockRegisterIo{}; - // Read, modify, and write in a fluent style + // Read the register's value from MMIO, updates the message size and + // enabled bit, then writes the value back to MMIO _ = AuxControl.get().readFrom(®_io).setMessageSize(1234).setEnabled(1).writeTo(®_io); } + // It is also possible to write a register without having to read it first: pub fn example3() void { var reg_io = MockRegisterIo{}; - // Start with a zero-initialized register + // Start off with a value that is initialized to zero. var reg = AuxControl.get().fromValue(0); - - // Fill out fields + // Fill out fields. _ = reg.setMessageSize(2345); - - // Write the register value to MMIO + // Write the register value to MMIO. _ = reg.writeTo(®_io); } }; // Compile-time tests -test "hwreg basic functionality" { - const expect = std.testing.expect; +const expect = std.testing.expect; +const Mock = @import("mock.zig"); + +test "basic functionality" { // Test mask computation - try expect(hwreg.computeMask(u32, 0) == 0); - try expect(hwreg.computeMask(u32, 1) == 1); - try expect(hwreg.computeMask(u32, 8) == 0xFF); - try expect(hwreg.computeMask(u32, 32) == 0xFFFFFFFF); + try expect(internal.computeMask(u32, 0) == 0); + try expect(internal.computeMask(u32, 1) == 1); + try expect(internal.computeMask(u32, 8) == 0xFF); + try expect(internal.computeMask(u32, 32) == 0xFFFFFFFF); // Test bitfield reference var value: u32 = 0; - var bitfield = hwreg.BitfieldRef(u32).init(&value, 7, 4); + var bitfield = BitfieldRef(u32).init(&value, 7, 4); bitfield.set(0xA); try expect(value == 0xA0); try expect(bitfield.get() == 0xA); } -test "hwreg register example" { +test "register example" { var reg_io = examples.MockRegisterIo{}; var reg = examples.AuxControl.get().fromValue(0); @@ -401,13 +405,11 @@ test "hwreg register example" { _ = reg.writeTo(®_io); } -test "hwreg sub-bit test" { - const expect = std.testing.expect; - +test "sub-bit test" { // Test with u8 { var value: u8 = 0; - var reg = hwreg.BitfieldRef(u8).init(&value, 7, 0); + var reg = BitfieldRef(u8).init(&value, 7, 0); try expect(reg.get() == 0); reg.set(1); @@ -429,7 +431,7 @@ test "hwreg sub-bit test" { // Test with u16 { var value: u16 = 0; - var reg = hwreg.BitfieldRef(u16).init(&value, 15, 0); + var reg = BitfieldRef(u16).init(&value, 15, 0); try expect(reg.get() == 0); reg.set(1); @@ -451,7 +453,7 @@ test "hwreg sub-bit test" { // Test with u32 { var value: u32 = 0; - var reg = hwreg.BitfieldRef(u32).init(&value, 31, 0); + var reg = BitfieldRef(u32).init(&value, 31, 0); try expect(reg.get() == 0); reg.set(1); @@ -473,7 +475,7 @@ test "hwreg sub-bit test" { // Test with u64 { var value: u64 = 0; - var reg = hwreg.BitfieldRef(u64).init(&value, 63, 0); + var reg = BitfieldRef(u64).init(&value, 63, 0); try expect(reg.get() == 0); reg.set(1); @@ -494,43 +496,41 @@ test "hwreg sub-bit test" { } test "struct sub field test" { - const expect = std.testing.expect; - const StructSubFieldTestReg = struct { field1: u32, field2: u32, field3: u32, pub fn wholeLength(self: *const @This()) u32 { - return hwreg.BitfieldRef(u32).init(@constCast(&self.field1), 31, 0).get(); + return BitfieldRef(u32).init(@constCast(&self.field1), 31, 0).get(); } pub fn setWholeLength(self: *@This(), val: u32) void { - hwreg.BitfieldRef(u32).init(&self.field1, 31, 0).set(val); + BitfieldRef(u32).init(&self.field1, 31, 0).set(val); } pub fn singleBit(self: *const @This()) u32 { - return hwreg.BitfieldRef(u32).init(@constCast(&self.field2), 2, 2).get(); + return BitfieldRef(u32).init(@constCast(&self.field2), 2, 2).get(); } pub fn setSingleBit(self: *@This(), val: u32) void { - hwreg.BitfieldRef(u32).init(&self.field2, 2, 2).set(val); + BitfieldRef(u32).init(&self.field2, 2, 2).set(val); } pub fn range1(self: *const @This()) u32 { - return hwreg.BitfieldRef(u32).init(@constCast(&self.field3), 2, 1).get(); + return BitfieldRef(u32).init(@constCast(&self.field3), 2, 1).get(); } pub fn setRange1(self: *@This(), val: u32) void { - hwreg.BitfieldRef(u32).init(&self.field3, 2, 1).set(val); + BitfieldRef(u32).init(&self.field3, 2, 1).set(val); } pub fn range2(self: *const @This()) u32 { - return hwreg.BitfieldRef(u32).init(@constCast(&self.field3), 5, 3).get(); + return BitfieldRef(u32).init(@constCast(&self.field3), 5, 3).get(); } pub fn setRange2(self: *@This(), val: u32) void { - hwreg.BitfieldRef(u32).init(&self.field3, 5, 3).set(val); + BitfieldRef(u32).init(&self.field3, 5, 3).set(val); } }; @@ -579,28 +579,26 @@ test "struct sub field test" { try expect(val.field3 == (2 << 3)); } -test "hwreg enum subfield test" { - const expect = std.testing.expect; - +test "enum subfield test" { const StructEnumSubFieldTestReg = struct { const Self = @This(); const EnumWholeRange = enum(u32) { - kZero = 0, - kOne = 1, - kMax = std.math.maxInt(u32), + zero = 0, + one = 1, + max = std.math.maxInt(u32), }; const EnumBit = enum(u8) { - kZero = 0, - kOne = 1, + zero = 0, + one = 1, }; const EnumRange = enum(u32) { - kZero = 0, - kOne = 1, - kTwo = 2, - kThree = 3, + zero = 0, + one = 1, + two = 2, + three = 3, }; field1: u32, @@ -608,35 +606,35 @@ test "hwreg enum subfield test" { field3: u32, pub fn wholeLength(self: *const Self) EnumWholeRange { - return @enumFromInt(hwreg.BitfieldRef(u32).init(@constCast(&self.field1), 31, 0).get()); + return @enumFromInt(BitfieldRef(u32).init(@constCast(&self.field1), 31, 0).get()); } pub fn setWholeLength(self: *Self, val: EnumWholeRange) void { - hwreg.BitfieldRef(u32).init(&self.field1, 31, 0).set(@intFromEnum(val)); + BitfieldRef(u32).init(&self.field1, 31, 0).set(@intFromEnum(val)); } pub fn singleBit(self: *const Self) EnumBit { - return @enumFromInt(hwreg.BitfieldRef(u32).init(@constCast(&self.field2), 2, 2).get()); + return @enumFromInt(BitfieldRef(u32).init(@constCast(&self.field2), 2, 2).get()); } pub fn setSingleBit(self: *Self, val: EnumBit) void { - hwreg.BitfieldRef(u32).init(&self.field2, 2, 2).set(@intFromEnum(val)); + BitfieldRef(u32).init(&self.field2, 2, 2).set(@intFromEnum(val)); } pub fn range1(self: *const Self) EnumRange { - return @enumFromInt(hwreg.BitfieldRef(u32).init(@constCast(&self.field3), 2, 1).get()); + return @enumFromInt(BitfieldRef(u32).init(@constCast(&self.field3), 2, 1).get()); } pub fn setRange1(self: *Self, val: EnumRange) void { - hwreg.BitfieldRef(u32).init(&self.field3, 2, 1).set(@intFromEnum(val)); + BitfieldRef(u32).init(&self.field3, 2, 1).set(@intFromEnum(val)); } pub fn range2(self: *const Self) EnumRange { - return @enumFromInt(hwreg.BitfieldRef(u32).init(@constCast(&self.field3), 5, 3).get()); + return @enumFromInt(BitfieldRef(u32).init(@constCast(&self.field3), 5, 3).get()); } pub fn setRange2(self: *Self, val: EnumRange) void { - hwreg.BitfieldRef(u32).init(&self.field3, 5, 3).set(@intFromEnum(val)); + BitfieldRef(u32).init(&self.field3, 5, 3).set(@intFromEnum(val)); } }; @@ -647,82 +645,80 @@ test "hwreg enum subfield test" { }; // Test whole length field - try expect(val.wholeLength() == .kZero); - val.setWholeLength(.kMax); - try expect(val.wholeLength() == .kMax); + try expect(val.wholeLength() == .zero); + val.setWholeLength(.max); + try expect(val.wholeLength() == .max); try expect(val.field1 == std.math.maxInt(u32)); - val.setWholeLength(.kZero); - try expect(val.wholeLength() == .kZero); + val.setWholeLength(.zero); + try expect(val.wholeLength() == .zero); try expect(val.field1 == 0); // Test single bit field - try expect(val.singleBit() == .kZero); - val.setSingleBit(.kOne); - try expect(val.singleBit() == .kOne); + try expect(val.singleBit() == .zero); + val.setSingleBit(.one); + try expect(val.singleBit() == .one); try expect(val.field2 == 4); - val.setSingleBit(.kZero); - try expect(val.singleBit() == .kZero); + val.setSingleBit(.zero); + try expect(val.singleBit() == .zero); try expect(val.field2 == 0); // Test adjacent fields - try expect(val.range1() == .kZero); - try expect(val.range2() == .kZero); - val.setRange1(.kThree); - try expect(val.range1() == .kThree); - try expect(val.range2() == .kZero); + try expect(val.range1() == .zero); + try expect(val.range2() == .zero); + val.setRange1(.three); + try expect(val.range1() == .three); + try expect(val.range2() == .zero); try expect(val.field3 == 3 << 1); - val.setRange2(.kOne); - try expect(val.range1() == .kThree); - try expect(val.range2() == .kOne); + val.setRange2(.one); + try expect(val.range1() == .three); + try expect(val.range2() == .one); try expect(val.field3 == (3 << 1) | (1 << 3)); - val.setRange2(.kTwo); - try expect(val.range1() == .kThree); - try expect(val.range2() == .kTwo); + val.setRange2(.two); + try expect(val.range1() == .three); + try expect(val.range2() == .two); try expect(val.field3 == (3 << 1) | (2 << 3)); - val.setRange1(.kZero); - try expect(val.range1() == .kZero); - try expect(val.range2() == .kTwo); + val.setRange1(.zero); + try expect(val.range1() == .zero); + try expect(val.range2() == .two); try expect(val.field3 == (2 << 3)); } -test "hwreg unshifted fields" { - const expect = std.testing.expect; - +test "unshifted fields" { const UnshiftedFieldTestReg = struct { const Self = @This(); data: u16, pub fn field1(self: *const Self) u16 { - return hwreg.BitfieldRef(u16).initUnshifted(@constCast(&self.data), 15, 12).get(); + return BitfieldRef(u16).initUnshifted(@constCast(&self.data), 15, 12).get(); } pub fn setField1(self: *Self, val: u16) void { - hwreg.BitfieldRef(u16).initUnshifted(&self.data, 15, 12).set(val); + BitfieldRef(u16).initUnshifted(&self.data, 15, 12).set(val); } pub fn field2(self: *const Self) u16 { - return hwreg.BitfieldRef(u16).initUnshifted(@constCast(&self.data), 11, 8).get(); + return BitfieldRef(u16).initUnshifted(@constCast(&self.data), 11, 8).get(); } pub fn setField2(self: *Self, val: u16) void { - hwreg.BitfieldRef(u16).initUnshifted(&self.data, 11, 8).set(val); + BitfieldRef(u16).initUnshifted(&self.data, 11, 8).set(val); } pub fn field3(self: *const Self) u16 { - return hwreg.BitfieldRef(u16).initUnshifted(@constCast(&self.data), 7, 4).get(); + return BitfieldRef(u16).initUnshifted(@constCast(&self.data), 7, 4).get(); } pub fn setField3(self: *Self, val: u16) void { - hwreg.BitfieldRef(u16).initUnshifted(&self.data, 7, 4).set(val); + BitfieldRef(u16).initUnshifted(&self.data, 7, 4).set(val); } pub fn field4(self: *const Self) u16 { - return hwreg.BitfieldRef(u16).initUnshifted(@constCast(&self.data), 3, 0).get(); + return BitfieldRef(u16).initUnshifted(@constCast(&self.data), 3, 0).get(); } pub fn setField4(self: *Self, val: u16) void { - hwreg.BitfieldRef(u16).initUnshifted(&self.data, 3, 0).set(val); + BitfieldRef(u16).initUnshifted(&self.data, 3, 0).set(val); } }; @@ -768,3 +764,641 @@ test "hwreg unshifted fields" { try expect(test_reg.field4() == 0xf); } } + +test "RsvdZ partial test" { + const RsvdZPartialTestReg8 = struct { + const Self = @This(); + + data: u8 = 0, + + pub fn rsvdZField(self: *const Self) u8 { + return BitfieldRef(u8).init(@constCast(&self.data), 7, 3).get(); + } + + pub fn setRsvdZField(self: *Self, val: u8) void { + BitfieldRef(u8).init(&self.data, 7, 3).set(val); + } + + pub fn writeMasked(self: *Self) void { + // Mask off the RsvdZ bits (set them to 0) + self.data &= 0x07; // Keep only bits 2:0 + } + }; + + const RsvdZPartialTestReg16 = struct { + const Self = @This(); + + data: u16 = 0, + + pub fn rsvdZField(self: *const Self) u16 { + return BitfieldRef(u16).init(@constCast(&self.data), 14, 1).get(); + } + + pub fn setRsvdZField(self: *Self, val: u16) void { + BitfieldRef(u16).init(&self.data, 14, 1).set(val); + } + + pub fn writeMasked(self: *Self) void { + // Mask off the RsvdZ bits (set them to 0) + self.data &= 0x8001; // Keep only bits 15 and 0 + } + }; + + const RsvdZPartialTestReg32 = struct { + const Self = @This(); + + data: u32 = 0, + + pub fn rsvdZField1(self: *const Self) u32 { + return BitfieldRef(u32).init(@constCast(&self.data), 31, 12).get(); + } + + pub fn setRsvdZField1(self: *Self, val: u32) void { + BitfieldRef(u32).init(&self.data, 31, 12).set(val); + } + + pub fn rsvdZField2(self: *const Self) u32 { + return BitfieldRef(u32).init(@constCast(&self.data), 10, 5).get(); + } + + pub fn setRsvdZField2(self: *Self, val: u32) void { + BitfieldRef(u32).init(&self.data, 10, 5).set(val); + } + + pub fn rsvdZBit(self: *const Self) u32 { + return BitfieldRef(u32).init(@constCast(&self.data), 3, 3).get(); + } + + pub fn setRsvdZBit(self: *Self, val: u32) void { + BitfieldRef(u32).init(&self.data, 3, 3).set(val); + } + + pub fn writeMasked(self: *Self) void { + // Mask off the RsvdZ bits (set them to 0) + self.data &= (1 << 11) | 0x17; // Keep only bits 11 and 4:0 + } + }; + + const RsvdZPartialTestReg64 = struct { + const Self = @This(); + + data: u64 = 0, + + pub fn rsvdZField1(self: *const Self) u64 { + return BitfieldRef(u64).init(@constCast(&self.data), 63, 18).get(); + } + + pub fn setRsvdZField1(self: *Self, val: u64) void { + BitfieldRef(u64).init(&self.data, 63, 18).set(val); + } + + pub fn rsvdZField2(self: *const Self) u64 { + return BitfieldRef(u64).init(@constCast(&self.data), 10, 0).get(); + } + + pub fn setRsvdZField2(self: *Self, val: u64) void { + BitfieldRef(u64).init(&self.data, 10, 0).set(val); + } + + pub fn writeMasked(self: *Self) void { + // Mask off the RsvdZ bits (set them to 0) + self.data &= 0x7f << 11; // Keep only bits 17:11 + } + }; + + // Test 8-bit register + { + var reg = RsvdZPartialTestReg8{ .data = std.math.maxInt(u8) }; + try expect(reg.data == std.math.maxInt(u8)); + reg.writeMasked(); + try expect(reg.data == 0x7); + } + + // Test 16-bit register + { + var reg = RsvdZPartialTestReg16{ .data = std.math.maxInt(u16) }; + try expect(reg.data == std.math.maxInt(u16)); + reg.writeMasked(); + try expect(reg.data == 0x8001); + } + + // Test 32-bit register + { + var reg = RsvdZPartialTestReg32{ .data = std.math.maxInt(u32) }; + try expect(reg.data == std.math.maxInt(u32)); + reg.writeMasked(); + try expect(reg.data == (1 << 11) | 0x17); + } + + // Test 64-bit register + { + var reg = RsvdZPartialTestReg64{ .data = std.math.maxInt(u64) }; + try expect(reg.data == std.math.maxInt(u64)); + reg.writeMasked(); + try expect(reg.data == 0x7f << 11); + } +} + +test "RsvdZ full test" { + const RsvdZFullTestReg8 = struct { + const Self = @This(); + + data: u8 = 0, + + pub fn rsvdZField(self: *const Self) u8 { + return BitfieldRef(u8).init(@constCast(&self.data), 7, 0).get(); + } + + pub fn setRsvdZField(self: *Self, val: u8) void { + BitfieldRef(u8).init(&self.data, 7, 0).set(val); + } + + pub fn writeMasked(self: *Self) void { + // Mask off all bits (set them to 0) + self.data = 0; + } + }; + + const RsvdZFullTestReg16 = struct { + const Self = @This(); + + data: u16 = 0, + + pub fn rsvdZField(self: *const Self) u16 { + return BitfieldRef(u16).init(@constCast(&self.data), 15, 0).get(); + } + + pub fn setRsvdZField(self: *Self, val: u16) void { + BitfieldRef(u16).init(&self.data, 15, 0).set(val); + } + + pub fn writeMasked(self: *Self) void { + // Mask off all bits (set them to 0) + self.data = 0; + } + }; + + const RsvdZFullTestReg32 = struct { + const Self = @This(); + + data: u32 = 0, + + pub fn rsvdZField(self: *const Self) u32 { + return BitfieldRef(u32).init(@constCast(&self.data), 31, 0).get(); + } + + pub fn setRsvdZField(self: *Self, val: u32) void { + BitfieldRef(u32).init(&self.data, 31, 0).set(val); + } + + pub fn writeMasked(self: *Self) void { + // Mask off all bits (set them to 0) + self.data = 0; + } + }; + + const RsvdZFullTestReg64 = struct { + const Self = @This(); + + data: u64 = 0, + + pub fn rsvdZField(self: *const Self) u64 { + return BitfieldRef(u64).init(@constCast(&self.data), 63, 0).get(); + } + + pub fn setRsvdZField(self: *Self, val: u64) void { + BitfieldRef(u64).init(&self.data, 63, 0).set(val); + } + + pub fn writeMasked(self: *Self) void { + // Mask off all bits (set them to 0) + self.data = 0; + } + }; + + // Test 8-bit register + { + var reg = RsvdZFullTestReg8{ .data = std.math.maxInt(u8) }; + try expect(reg.data == std.math.maxInt(u8)); + reg.writeMasked(); + try expect(reg.data == 0); + } + + // Test 16-bit register + { + var reg = RsvdZFullTestReg16{ .data = std.math.maxInt(u16) }; + try expect(reg.data == std.math.maxInt(u16)); + reg.writeMasked(); + try expect(reg.data == 0); + } + + // Test 32-bit register + { + var reg = RsvdZFullTestReg32{ .data = std.math.maxInt(u32) }; + try expect(reg.data == std.math.maxInt(u32)); + reg.writeMasked(); + try expect(reg.data == 0); + } + + // Test 64-bit register + { + var reg = RsvdZFullTestReg64{ .data = std.math.maxInt(u64) }; + try expect(reg.data == std.math.maxInt(u64)); + reg.writeMasked(); + try expect(reg.data == 0); + } +} + +fn TemplatedReg(comptime N: u32) type { + comptime { + std.debug.assert(N < 32); + } + + return struct { + const Self = @This(); + + base: RegisterBase(Self, u32, void) = .{}, + + pub const ValueType = u32; + + pub fn init() Self { + return Self{ + .base = .{}, + }; + } + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(0); + } + + pub fn head(self: *const Self) u32 { + return BitfieldRef(u32).init(@constCast(self.base.regValuePtrConst()), N, 0).get(); + } + + pub fn setHead(self: *Self, val: u32) *Self { + BitfieldRef(u32).init(self.base.regValuePtr(), N, 0).set(val); + return self; + } + + pub fn readFrom(self: *Self, reg_io: anytype) *Self { + _ = self.base.readFrom(reg_io); + return self; + } + + pub fn writeTo(self: *Self, reg_io: anytype) *Self { + _ = self.base.writeTo(reg_io); + return self; + } + + pub fn setRegAddr(self: *Self, addr: u32) void { + self.base.setRegAddr(addr); + } + + pub fn regValue(self: *const Self) u32 { + return self.base.regValue(); + } + + pub fn setRegValue(self: *Self, value: u32) *Self { + _ = self.base.setRegValue(value); + return self; + } + }; +} + +test "Templated register" { + const TestMmio = struct { + fake_reg: u32, + + pub fn read(self: *const @This(), comptime T: type, addr: u32) T { + _ = addr; + return @intCast(self.fake_reg); + } + + pub fn write(self: *@This(), comptime T: type, value: T, addr: u32) void { + _ = addr; + self.fake_reg = @intCast(value); + } + }; + + var mmio = TestMmio{ .fake_reg = 0xffff_ffff }; + + { + var reg = TemplatedReg(0).get().readFrom(&mmio); + try expect(reg.head() == 0b1); + } + + { + var reg = TemplatedReg(4).get().readFrom(&mmio); + try expect(reg.head() == 0b11111); + } + + { + var reg = TemplatedReg(31).get().readFrom(&mmio); + try expect(reg.head() == 0xffff_ffff); + } + + { + var reg = TemplatedReg(1).get().readFrom(&mmio); + _ = reg.setHead(0); + _ = reg.writeTo(&mmio); + try expect(reg.head() == 0); + } +} + +const PrintableTestReg = struct { + const Self = @This(); + const RegBase = RegisterBase(Self, u32, EnablePrinter); + pub const ValueType = u32; + pub const printer_enabled: bool = RegBase.PrinterEnabled; + + base: RegBase = .{}, + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(0); + } + + pub fn init() Self { + var self = Self{ .base = .{} }; + + // Register fields for printing and overlap checking + const params = self.base.getParams(); + internal.RsvdZField(Self, void, true).init(params, 31, 31); + internal.Field(Self, void, true).init(params, "field1", 30, 21); + internal.Field(Self, void, true).init(params, "field2", 20, 12); + internal.RsvdZField(Self, void, true).init(params, 11, 0); + + return self; + } + + pub fn field1(self: *const Self) u32 { + return BitfieldRef(u32).init(self.base.regValuePtr(), 30, 21).get(); + } + + pub fn setField1(self: *Self, value: u32) *Self { + BitfieldRef(u32).init(self.base.regValuePtr(), 30, 21).set(value); + return self; + } + + pub fn field2(self: *const Self) u32 { + return BitfieldRef(u32).init(self.base.regValuePtr(), 20, 12).get(); + } + + pub fn setField2(self: *Self, value: u32) *Self { + BitfieldRef(u32).init(self.base.regValuePtr(), 20, 12).set(value); + return self; + } + + pub fn readFrom(self: *Self, reg_io: anytype) *Self { + _ = self.base.readFrom(reg_io); + return self; + } + + pub fn writeTo(self: *Self, reg_io: anytype) *Self { + _ = self.base.writeTo(reg_io); + return self; + } + + pub fn setRegAddr(self: *Self, addr: u32) void { + self.base.setRegAddr(addr); + } + + pub fn regValue(self: *const Self) u32 { + return self.base.regValue(); + } + + pub fn setRegValue(self: *Self, value: u32) *Self { + _ = self.base.setRegValue(value); + return self; + } + + pub fn print(self: *Self, print_fn: anytype) void { + self.base.print(print_fn); + } + + pub fn printStdout(self: *Self) void { + self.base.printStdout(); + } +}; + +const PrintableTestReg2 = struct { + const Self = @This(); + const RegBase = RegisterBase(Self, u32, EnablePrinter); + pub const ValueType = u32; + pub const printer_enabled: bool = RegBase.PrinterEnabled; + + base: RegBase = .{}, + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(0); + } + + pub fn init() Self { + var self = Self{}; + + // Register fields for printing and overlap checking + const params = self.base.getParams(); + internal.Field(Self, void, true).init(params, "field1", 30, 21); + internal.Field(Self, void, true).init(params, "field2", 20, 12); + + return self; + } + + pub fn field1(self: *const Self) u32 { + return BitfieldRef(u32).init(self.base.regValuePtr(), 30, 21).get(); + } + + pub fn setField1(self: *Self, value: u32) *Self { + BitfieldRef(u32).init(self.base.regValuePtr(), 30, 21).set(value); + return self; + } + + pub fn field2(self: *const Self) u32 { + return BitfieldRef(u32).init(self.base.regValuePtr(), 20, 12).get(); + } + + pub fn setField2(self: *Self, value: u32) *Self { + BitfieldRef(u32).init(self.base.regValuePtr(), 20, 12).set(value); + return self; + } + + pub fn readFrom(self: *Self, reg_io: anytype) *Self { + _ = self.base.readFrom(reg_io); + return self; + } + + pub fn writeTo(self: *Self, reg_io: anytype) *Self { + _ = self.base.writeTo(reg_io); + return self; + } + + pub fn setRegAddr(self: *Self, addr: u32) void { + self.base.setRegAddr(addr); + } + + pub fn regValue(self: *const Self) u32 { + return self.base.regValue(); + } + + pub fn setRegValue(self: *Self, value: u32) *Self { + _ = self.base.setRegValue(value); + return self; + } + + pub fn print(self: *Self, print_fn: anytype) void { + self.base.print(print_fn); + } + + pub fn printStdout(self: *Self) void { + self.base.printStdout(); + } +}; + +test "Print" { + const TestMmio = struct { + fake_reg: u64, + + pub fn read(self: *const @This(), comptime T: type, addr: u32) T { + _ = addr; + return @intCast(self.fake_reg); + } + + pub fn write(self: *@This(), comptime T: type, value: T, addr: u32) void { + _ = addr; + self.fake_reg = @intCast(value); + } + }; + + const kInitVal: u32 = 0xe9872fff; + var mmio = TestMmio{ .fake_reg = kInitVal }; + + { + var reg = PrintableTestReg.get().readFrom(&mmio); + //var call_count: u32 = 0; + const expected = [_][]const u8{ + "RsvdZ[31:31]: 0x1 (1)", + "field1[30:21]: 0x34c (844)", + "field2[20:12]: 0x072 (114)", + "RsvdZ[11:0]: 0xfff (4095)", + }; + + reg.print(struct { + fn printFn(arg: []const u8) void { + // In a real test, we would check against expected[call_count] + std.debug.print("PrintableTestReg: {s}\n", .{arg}); + } + }.printFn); + + //_ = call_count; + _ = expected; + } + + { + var reg = PrintableTestReg2.get().readFrom(&mmio); + //var call_count: u32 = 0; + const expected = [_][]const u8{ + "field1[30:21]: 0x34c (844)", + "field2[20:12]: 0x072 (114)", + "unknown set bits: 0x80000fff", + }; + + reg.print(struct { + fn printFn(arg: []const u8) void { + // In a real test, we would check against expected[call_count] + std.debug.print("PrintableTestReg2: {s}\n", .{arg}); + } + }.printFn); + + // _ = call_count; + _ = expected; + } +} + +test "Register with variant IO" { + const FakeIo = struct { + pub fn write(self: @This(), comptime IntType: type, value: IntType, offset: u32) void { + _ = self; + _ = offset; + std.debug.assert(value == 17); + } + + pub fn read(self: @This(), comptime IntType: type, offset: u32) IntType { + _ = self; + _ = offset; + return 23; + } + }; + + const TestRegForVariantIo = struct { + const Self = @This(); + const RegBase = RegisterBase(Self, u64, void); + pub const ValueType = u64; + pub const printer_enabled: bool = RegBase.PrinterEnabled; + + reg_base: RegBase, + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(0); + } + + pub fn init() Self { + var self = Self{ .reg_base = .{} }; + internal.Field(Self, void, true).init(self.reg_base.getParams(), "value", 63, 0); + return self; + } + + pub fn valueGet(self: *Self) u64 { + return BitfieldRef(u64).init(self.reg_base.regValuePtr(), 63, 0).get(); + } + + pub fn valueSet(self: *Self, value: u64) *Self { + BitfieldRef(u64).init(self.reg_base.regValuePtr(), 63, 0).set(value); + return self; + } + + pub fn readFrom(self: *Self, reg_io: anytype) *Self { + return self.reg_base.readFrom(reg_io); + } + + pub fn writeTo(self: *Self, reg_io: anytype) *Self { + return self.reg_base.writeTo(reg_io); + } + + pub fn setRegAddr(self: *Self, addr: u32) void { + self.reg_base.setRegAddr(addr); + } + + pub fn setRegValue(self: *Self, value: u64) *Self { + return self.reg_base.setRegValue(value); + } + }; + + const MyIo = union(enum) { + fake: FakeIo, + mock: *Mock.RegisterIo, + }; + + var io = MyIo{ .fake = FakeIo{} }; + // Test with FakeIo + { + var reg = TestRegForVariantIo.get().readFrom(&io); + try expect(reg.valueGet() == 23); + _ = reg.valueSet(17); + _ = reg.writeTo(&io); + } + + // Test with Mock + { + var mock = Mock.init(); + defer mock.deinit(); + + io = MyIo{ .mock = mock.io() }; + + _ = mock.expectRead(u64, 17, 0).expectWrite(u64, 23, 1); + var reg = TestRegForVariantIo.get().readFrom(&io); + try expect(reg.valueGet() == 17); + reg.setRegAddr(1); + _ = reg.valueSet(23); + _ = reg.writeTo(&io); + mock.verifyAndClear(); + } +} diff --git a/slipstream/system/ulib/hwreg/src/internal.zig b/slipstream/system/ulib/hwreg/src/internal.zig new file mode 100644 index 0000000..a03b6ed --- /dev/null +++ b/slipstream/system/ulib/hwreg/src/internal.zig @@ -0,0 +1,252 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. + +const std = @import("std"); + +/// Check if a type is a supported integer type for hwreg operations +pub fn isSupportedInt(comptime T: type) bool { + return switch (T) { + u8, u16, u32, u64 => true, + else => false, + }; +} + +/// Compute a bitmask with the specified number of bits set +pub fn computeMask(comptime IntType: type, num_bits: u32) IntType { + if (num_bits == @bitSizeOf(IntType)) { + return ~@as(IntType, 0); + } + return (@as(IntType, 1) << @intCast(num_bits)) - 1; +} + +/// Generate a unique predicate for conditional fields +pub fn unexpandedPred(comptime pred: bool, comptime id: comptime_int) bool { + _ = id; + return pred; +} + +/// Field printer for debug output +pub const FieldPrinter = struct { + name: ?[]const u8, + bit_high_incl: u32, + bit_low: u32, + + pub fn init() FieldPrinter { + return FieldPrinter{ + .name = null, + .bit_high_incl = 0, + .bit_low = 0, + }; + } + + pub fn initWithParams(name: []const u8, bit_high_incl: u32, bit_low: u32) FieldPrinter { + return FieldPrinter{ + .name = name, + .bit_high_incl = bit_high_incl, + .bit_low = bit_low, + }; + } + + /// Print the field name and extracted value in hex format + pub fn print(self: FieldPrinter, value: u64, buf: []u8) []u8 { + if (self.name) |name| { + const field_value = (value >> @intCast(self.bit_low)) & computeMask(u64, self.bit_high_incl - self.bit_low + 1); + + //if (is_kernel) { + // Kernel version without padding + // _ = std.fmt.bufPrint(buf, "{s}[{d}:{d}]: 0x{x} ({d})", .{ name_, bit_high_incl_, bit_low_, val, val }) catch unreachable; + //} else { + // User space version with padding + const pad_len = (self.bit_high_incl - self.bit_low + 3) / 4; + return std.fmt.bufPrint(buf, "{s}[{d}:{d}]: 0x{x:0>[4]} ({[3]d})", .{ name, self.bit_high_incl, self.bit_low, field_value, pad_len }) catch unreachable; + //} + } + return buf; + } +}; + +/// Field printer list - conditional storage based on whether printing is enabled +pub fn FieldPrinterList(comptime enabled: bool, comptime IntType: type) type { + if (!enabled) { + return struct { + pub fn appendField(self: *@This(), name: []const u8, bit_high_incl: u32, bit_low: u32) void { + _ = self; + _ = name; + _ = bit_high_incl; + _ = bit_low; + } + }; + } else { + return struct { + fields: [@bitSizeOf(IntType)]FieldPrinter = [_]FieldPrinter{FieldPrinter.init()} ** @bitSizeOf(IntType), + num_fields: u32 = 0, + + pub fn appendField(self: *@This(), name: []const u8, bit_high_incl: u32, bit_low: u32) void { + std.debug.assert(self.num_fields < self.fields.len); + self.fields[self.num_fields] = FieldPrinter.initWithParams(name, bit_high_incl, bit_low); + self.num_fields += 1; + } + }; + } +} + +/// Field parameters for register metadata +pub fn FieldParameters(comptime printer_enabled: bool, comptime ValueType: type) type { + return struct { + rsvdz_mask: ValueType = 0, + fields_mask: ValueType = 0, + printer: FieldPrinterList(printer_enabled, ValueType) = .{}, + }; +} + +/// Field registration for overlap checking and pretty-printing +pub fn Field(comptime RegType: type, comptime UnusedMarker: type, comptime enabled: bool) type { + _ = UnusedMarker; + return struct { + const IntType = RegType.ValueType; + pub fn init(reg: *FieldParameters(RegType.printer_enabled, IntType), name: []const u8, bit_high_incl: u32, bit_low: u32) void { + if (enabled) { + const mask: IntType = computeMask(IntType, bit_high_incl - bit_low + 1) << @intCast(bit_low); + // Check for overlapping bit ranges + std.debug.assert((reg.fields_mask & mask) == 0); + reg.fields_mask |= mask; + + reg.printer.appendField(name, bit_high_incl, bit_low); + } + } + }; +} + +/// Reserved-zero field registration +pub fn RsvdZField(comptime RegType: type, comptime UnusedMarker: type, comptime enabled: bool) type { + return struct { + const IntType = RegType.ValueType; + pub fn init(reg: *FieldParameters(RegType.printer_enabled, IntType), bit_high_incl: u32, bit_low: u32) void { + Field(RegType, UnusedMarker, enabled).init(reg, "RsvdZ", bit_high_incl, bit_low); + + if (enabled) { + const mask: IntType = computeMask(IntType, bit_high_incl - bit_low + 1) << @intCast(bit_low); + reg.rsvdz_mask |= mask; + } + } + }; +} + +/// Print register fields for debugging +pub fn printRegister(print_fn: anytype, fields: []FieldPrinter, num_fields: usize, reg_value: u64, fields_mask: u64, register_width_bytes: i32) void { + var buf: [128]u8 = undefined; + + for (0..num_fields) |i| { + const fmt_buf = fields[i].print(reg_value, &buf); + print_fn(fmt_buf); + } + + // Check for unknown bits + const val = reg_value & ~fields_mask; + if (val != 0) { + const pad_len: usize = @intCast(@divExact(register_width_bytes * 8, 4)); + const fmt_buf = std.fmt.bufPrint(&buf, "unknown set bits: 0x{X:0>[1]}", .{ val, pad_len }) catch unreachable; + print_fn(fmt_buf); + } +} + +/// Utility for printf-style register printing +pub fn printRegisterPrintf(fields: []FieldPrinter, num_fields: usize, reg_value: u64, fields_mask: u64, register_width_bytes: i32) void { + const print_fn = struct { + fn print(arg: []const u8) void { + std.debug.print("{s}\n", .{arg}); + } + }.print; + + printRegister(print_fn, fields, num_fields, reg_value, fields_mask, register_width_bytes); +} + +fn isVariant(comptime T: type) bool { + return switch (@typeInfo(T)) { + .@"union" => |union_info| union_info.tag_type != null, + else => false, + }; +} + +/// Forward declaration helper for visitEach +fn visitEach(comptime f: anytype, v: anytype, args: anytype, comptime indices: []const usize) void { + const V = @TypeOf(v); + comptime std.debug.assert(indices.len == @typeInfo(V).@"union".fields.len); + + inline for (indices) |i| { + const field = @typeInfo(V).@"union".fields[i]; + if (@as(usize, @intFromEnum(v)) == i) { + const selected = @field(v, field.name); + @call(.auto, visit, .{ f, selected, args }); + return; + } + } + + unreachable; +} + +/// Main visit function that handles both variant and non-variant types +pub fn visit(comptime f: anytype, v: anytype, args: anytype) void { + const V = @TypeOf(v); + + if (comptime isVariant(V)) { + const union_info = @typeInfo(V).@"union"; + const field_count = union_info.fields.len; + + // Create compile-time array of indices + const indices = comptime blk: { + var arr: [field_count]usize = undefined; + var i: usize = 0; + while (i < field_count) : (i += 1) { + arr[i] = i; + } + break :blk arr; + }; + + visitEach(f, v, args, &indices); + } else { + @call(.auto, f, .{v} ++ args); + } +} + +/// Atomic array I/O reference for thread-safe operations +pub fn AtomicArrayIoRef(comptime ElementType: type, comptime memory_order: std.builtin.AtomicOrder) type { + return struct { + const Self = @This(); + + ref: *ElementType, + + //pub fn init(ref: *ElementType) Self { + // return Self{ .ref = ref }; + //} + + pub fn store(self: Self, value: ElementType) void { + @atomicStore(ElementType, self.ref, value, memory_order); + } + + pub fn load(self: Self) ElementType { + return @atomicLoad(ElementType, self.ref, memory_order); + } + + pub fn fetchAdd(self: Self, n: ElementType) ElementType { + return @atomicRmw(ElementType, self.ref, .Add, n, memory_order); + } + + pub fn fetchSub(self: Self, n: ElementType) ElementType { + return @atomicRmw(ElementType, self.ref, .Sub, n, memory_order); + } + + pub fn fetchAnd(self: Self, bits: ElementType) ElementType { + return @atomicRmw(ElementType, self.ref, .And, bits, memory_order); + } + + pub fn fetchOr(self: Self, bits: ElementType) ElementType { + return @atomicRmw(ElementType, self.ref, .Or, bits, memory_order); + } + + pub fn fetchXor(self: Self, bits: ElementType) ElementType { + return @atomicRmw(ElementType, self.ref, .Xor, bits, memory_order); + } + }; +} diff --git a/slipstream/system/ulib/hwreg/src/mock.zig b/slipstream/system/ulib/hwreg/src/mock.zig new file mode 100644 index 0000000..be6404f --- /dev/null +++ b/slipstream/system/ulib/hwreg/src/mock.zig @@ -0,0 +1,160 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. + +const std = @import("std"); +const internal = @import("internal.zig"); +const mock_function = @import("mock_function"); + +const Mock = @This(); + +// The io() pointer can be passed to ReadFrom and WriteTo methods. The +// Mock should first be primed with expectRead() and expectWrite() calls. + +mock: mock_function.MockFunction(u64, &[_]type{ ExpectedIo, u32 }), +io_instance: RegisterIo, + +const ExpectedWrite = struct { + size: usize, + value: u64, + + pub fn eql(self: ExpectedWrite, other: ExpectedWrite) bool { + return self.size == other.size and self.value == other.value; + } +}; + +const ExpectedRead = struct { + size: usize, + + pub fn eql(self: ExpectedRead, other: ExpectedRead) bool { + return self.size == other.size; + } +}; + +const ExpectedIo = union(enum) { + write: ExpectedWrite, + read: ExpectedRead, +}; + +const MockRegisterIo = struct { + mock: *Mock, + + pub fn write(self: MockRegisterIo, comptime IntType: type, value: IntType, offset: u32) void { + comptime { + if (!internal.isSupportedInt(IntType)) { + @compileError("unsupported register access width"); + } + } + const expected = ExpectedIo{ .write = ExpectedWrite{ + .size = @sizeOf(IntType), + .value = @intCast(value), + } }; + _ = self.mock.mock.call(.{ expected, offset }); + } + + pub fn read(self: MockRegisterIo, comptime IntType: type, offset: u32) IntType { + comptime { + if (!internal.isSupportedInt(IntType)) { + @compileError("unsupported register access width"); + } + } + const expected = ExpectedIo{ .read = ExpectedRead{ + .size = @sizeOf(IntType), + } }; + const result = self.mock.mock.call(.{ expected, offset }); + return @intCast(result); + } +}; + +const DummyIo = struct { + pub fn write(_: DummyIo, comptime IntType: type, _: IntType, _: u32) void { + comptime { + if (!internal.isSupportedInt(IntType)) { + @compileError("unsupported register access width"); + } + } + std.debug.panic("hwreg Mock RegisterIo used in default-constructed state", .{}); + } + + pub fn read(_: DummyIo, comptime IntType: type, _: u32) IntType { + comptime { + if (!internal.isSupportedInt(IntType)) { + @compileError("unsupported register access width"); + } + } + std.debug.panic("hwreg Mock RegisterIo used in default-constructed state", .{}); + return 0; + } +}; + +pub const RegisterIo = union(enum) { + dummy: DummyIo, + mock: MockRegisterIo, + + pub fn write(self: RegisterIo, comptime IntType: type, value: IntType, offset: u32) void { + switch (self) { + .dummy => |dummy| dummy.write(IntType, value, offset), + .mock => |mock_io| mock_io.write(IntType, value, offset), + } + } + + pub fn read(self: RegisterIo, comptime IntType: type, offset: u32) IntType { + return switch (self) { + .dummy => |dummy| dummy.read(IntType, offset), + .mock => |mock_io| mock_io.read(IntType, offset), + }; + } +}; + +pub fn init() Mock { + return Mock{ + .mock = mock_function.MockFunction(u64, &.{ ExpectedIo, u32 }).init(), + .io_instance = undefined, + }; +} + +pub fn deinit(self: *Mock) void { + self.mock.deinit(); +} + +pub fn expectWrite(self: *Mock, comptime IntType: type, value: IntType, offset: u32) *Mock { + comptime { + std.debug.assert(internal.isSupportedInt(IntType)); + } + _ = self.mock.expectCall(0, .{ + ExpectedIo{ .write = ExpectedWrite{ + .size = @sizeOf(IntType), + .value = value, + } }, + offset, + }); + return self; +} + +pub fn expectRead(self: *Mock, comptime IntType: type, value: IntType, offset: u32) *Mock { + comptime { + std.debug.assert(internal.isSupportedInt(IntType)); + } + + _ = self.mock.expectCall(value, .{ + ExpectedIo{ .read = ExpectedRead{ + .size = @sizeOf(IntType), + } }, + offset, + }); + return self; +} + +pub fn expectNoIo(self: *Mock) *Mock { + self.mock.expectNoCall(); + return self; +} + +pub fn verifyAndClear(self: *Mock) void { + self.mock.verifyAndClear(); +} + +pub fn io(self: *Mock) *RegisterIo { + self.io_instance = RegisterIo{ .mock = MockRegisterIo{ .mock = self } }; + return &self.io_instance; +} diff --git a/slipstream/system/ulib/hwreg/src/root.zig b/slipstream/system/ulib/hwreg/src/root.zig index 115b778..c11a9d3 100644 --- a/slipstream/system/ulib/hwreg/src/root.zig +++ b/slipstream/system/ulib/hwreg/src/root.zig @@ -3,6 +3,7 @@ //! found in the LICENSE file. pub const bitfields = @import("bitfields.zig"); +pub const Mock = @import("mock.zig"); comptime { _ = bitfields; From d5d52dad0d92e2450f23e6e22076fc6d9918d7c9 Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Mon, 9 Jun 2025 06:48:52 -0300 Subject: [PATCH 10/41] [ulib][mmio-ptr] Initial version --- slipstream/system/ulib/mmio-ptr/build.zig | 34 ++ slipstream/system/ulib/mmio-ptr/build.zig.zon | 6 + slipstream/system/ulib/mmio-ptr/src/fake.zig | 39 ++ .../system/ulib/mmio-ptr/src/mmio-ptr.zig | 423 ++++++++++++++++++ 4 files changed, 502 insertions(+) create mode 100644 slipstream/system/ulib/mmio-ptr/build.zig create mode 100644 slipstream/system/ulib/mmio-ptr/build.zig.zon create mode 100644 slipstream/system/ulib/mmio-ptr/src/fake.zig create mode 100644 slipstream/system/ulib/mmio-ptr/src/mmio-ptr.zig diff --git a/slipstream/system/ulib/mmio-ptr/build.zig b/slipstream/system/ulib/mmio-ptr/build.zig new file mode 100644 index 0000000..08ee900 --- /dev/null +++ b/slipstream/system/ulib/mmio-ptr/build.zig @@ -0,0 +1,34 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. + +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const mod = b.addModule("mmio-ptr", .{ + .root_source_file = b.path("src/mmio-ptr.zig"), + .target = target, + .optimize = optimize, + }); + + const deps = [_]struct { name: []const u8, dep_name: []const u8, module_name: []const u8 }{}; + + for (deps) |dep| { + const dep_module = b.dependency(dep.dep_name, .{}); + mod.addImport(dep.name, dep_module.module(dep.module_name)); + } + + const test_filters = b.option([]const []const u8, "test-filter", "Skip tests that do not match any filter") orelse &[0][]const u8{}; + const test_step = b.step("test", "Run unit tests"); + const unit_tests = b.addTest(.{ + .root_module = mod, + .target = b.graph.host, + .filters = test_filters, + }); + + const run_unit_tests = b.addRunArtifact(unit_tests); + test_step.dependOn(&run_unit_tests.step); +} diff --git a/slipstream/system/ulib/mmio-ptr/build.zig.zon b/slipstream/system/ulib/mmio-ptr/build.zig.zon new file mode 100644 index 0000000..f2931e1 --- /dev/null +++ b/slipstream/system/ulib/mmio-ptr/build.zig.zon @@ -0,0 +1,6 @@ +.{ + .name = .mmio_ptr, + .fingerprint = 0xc2f3226ebbd2821c, + .version = "0.0.1", + .paths = .{""}, +} diff --git a/slipstream/system/ulib/mmio-ptr/src/fake.zig b/slipstream/system/ulib/mmio-ptr/src/fake.zig new file mode 100644 index 0000000..bd92b82 --- /dev/null +++ b/slipstream/system/ulib/mmio-ptr/src/fake.zig @@ -0,0 +1,39 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. +//! Fake MMIO pointers for testing. + +/// Create a fake MMIO pointer from a regular pointer. Mock tests for drivers should +/// use this to implicitly specify that they are handling MMIO pointers. +/// Example usage: +/// +/// fn checkBuffer(buffer: []u8) void { +/// const value_ptr = fakeMmioPtr(buffer.ptr); +/// +/// // Perform reads/writes with the fake MMIO pointer +/// const val = mmioRead8(&value_ptr[2]); +/// // ... +/// } +pub fn fakeMmioPtr(ptr: anytype) switch (@typeInfo(@TypeOf(ptr))) { + .pointer => |info| switch (info.is_const) { + true => *const volatile info.child, + false => *volatile info.child, + }, + else => @compileError("fakeMmioPtr expects a pointer type"), +} { + const PtrType = @TypeOf(ptr); + const ptr_info = @typeInfo(PtrType); + + if (ptr_info != .pointer) { + @compileError("fakeMmioPtr expects a pointer type"); + } + + const ChildType = ptr_info.pointer.child; + const is_const = ptr_info.pointer.is_const; + + if (is_const) { + return @as(*const volatile ChildType, ptr); + } else { + return @as(*volatile ChildType, ptr); + } +} diff --git a/slipstream/system/ulib/mmio-ptr/src/mmio-ptr.zig b/slipstream/system/ulib/mmio-ptr/src/mmio-ptr.zig new file mode 100644 index 0000000..ec1adb5 --- /dev/null +++ b/slipstream/system/ulib/mmio-ptr/src/mmio-ptr.zig @@ -0,0 +1,423 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. +//! Low level API for reading and writing to Memory-Mapped I/O buffers. +//! +//! This module provides platform-specific implementations for MMIO operations +//! that avoid problematic instructions on certain hypervisors. + +const std = @import("std"); +const builtin = @import("builtin"); + +const fake = @import("fake.zig"); + +/// Write an 8-bit value to MMIO +pub fn mmioWrite8(data: u8, buffer: *volatile u8) void { + switch (builtin.cpu.arch) { + .aarch64 => { + // Use inline assembly to ensure non-writeback load/store instructions + // for ARM64 KVM hypervisor compatibility + asm volatile ("strb %w[data], [%[buffer]]" + : + : [buffer] "r" (buffer), + [data] "r" (data), + : "memory" + ); + }, + .x86_64, .x86 => { + // Use inline assembly to ensure simple integer mov instructions + // for x86 KVM hypervisor compatibility + asm volatile ("movb %[data], (%[buffer])" + : + : [buffer] "r" (buffer), + [data] "ir" (data), + ); + }, + .riscv64, .riscv32 => { + // RISC-V doesn't have fancy load/store variants + buffer.* = data; + }, + else => @compileError("No MMIO access implementation for this arch."), + } +} + +/// Write a 16-bit value to MMIO +pub fn mmioWrite16(data: u16, buffer: *volatile u16) void { + switch (builtin.cpu.arch) { + .aarch64 => { + asm volatile ("strh %w[data], [%[buffer]]" + : + : [buffer] "r" (buffer), + [data] "r" (data), + : "memory" + ); + }, + .x86_64, .x86 => { + asm volatile ("movw %[data], (%[buffer])" + : + : [buffer] "r" (buffer), + [data] "ir" (data), + ); + }, + .riscv64, .riscv32 => { + buffer.* = data; + }, + else => @compileError("No MMIO access implementation for this arch."), + } +} + +/// Write a 32-bit value to MMIO +pub fn mmioWrite32(data: u32, buffer: *volatile u32) void { + switch (builtin.cpu.arch) { + .aarch64 => { + asm volatile ("str %w[data], [%[buffer]]" + : + : [buffer] "r" (buffer), + [data] "r" (data), + : "memory" + ); + }, + .x86_64, .x86 => { + asm volatile ("movl %[data], (%[buffer])" + : + : [buffer] "r" (buffer), + [data] "ir" (data), + ); + }, + .riscv64, .riscv32 => { + buffer.* = data; + }, + else => @compileError("No MMIO access implementation for this arch."), + } +} + +/// Write a 64-bit value to MMIO +pub fn mmioWrite64(data: u64, buffer: *volatile u64) void { + switch (builtin.cpu.arch) { + .aarch64 => { + asm volatile ("str %[data], [%[buffer]]" + : + : [buffer] "r" (buffer), + [data] "r" (data), + : "memory" + ); + }, + .x86_64 => { + asm volatile ("movq %[data], (%[buffer])" + : + : [buffer] "r" (buffer), + [data] "er" (data), + ); + }, + .x86 => @compileError("64-bit MMIO not supported on 32-bit x86"), + .riscv64, .riscv32 => { + buffer.* = data; + }, + else => @compileError("No MMIO access implementation for this arch."), + } +} + +/// Read an 8-bit value from MMIO +pub fn mmioRead8(buffer: *const volatile u8) u8 { + switch (builtin.cpu.arch) { + .aarch64 => { + var data: u8 = undefined; + asm volatile ("ldrb %w[data], [%[buffer]]" + : [data] "=r" (data), + : [buffer] "r" (buffer), + : "memory" + ); + return data; + }, + .x86_64, .x86 => { + var data: u8 = undefined; + asm volatile ("movb (%[buffer]), %[data]" + : [data] "=r" (data), + : [buffer] "r" (buffer), + ); + return data; + }, + .riscv64, .riscv32 => { + return buffer.*; + }, + else => @compileError("No MMIO access implementation for this arch."), + } +} + +/// Read a 16-bit value from MMIO +pub fn mmioRead16(buffer: *const volatile u16) u16 { + switch (builtin.cpu.arch) { + .aarch64 => { + var data: u16 = undefined; + asm volatile ("ldrh %w[data], [%[buffer]]" + : [data] "=r" (data), + : [buffer] "r" (buffer), + : "memory" + ); + return data; + }, + .x86_64, .x86 => { + var data: u16 = undefined; + asm volatile ("movw (%[buffer]), %[data]" + : [data] "=r" (data), + : [buffer] "r" (buffer), + ); + return data; + }, + .riscv64, .riscv32 => { + return buffer.*; + }, + else => @compileError("No MMIO access implementation for this arch."), + } +} + +/// Read a 32-bit value from MMIO +pub fn mmioRead32(buffer: *const volatile u32) u32 { + switch (builtin.cpu.arch) { + .aarch64 => { + var data: u32 = undefined; + asm volatile ("ldr %w[data], [%[buffer]]" + : [data] "=r" (data), + : [buffer] "r" (buffer), + : "memory" + ); + return data; + }, + .x86_64, .x86 => { + var data: u32 = undefined; + asm volatile ("movl (%[buffer]), %[data]" + : [data] "=r" (data), + : [buffer] "r" (buffer), + ); + return data; + }, + .riscv64, .riscv32 => { + return buffer.*; + }, + else => @compileError("No MMIO access implementation for this arch."), + } +} + +/// Read a 64-bit value from MMIO +pub fn mmioRead64(buffer: *const volatile u64) u64 { + switch (builtin.cpu.arch) { + .aarch64 => { + var data: u64 = undefined; + asm volatile ("ldr %[data], [%[buffer]]" + : [data] "=r" (data), + : [buffer] "r" (buffer), + : "memory" + ); + return data; + }, + .x86_64 => { + var data: u64 = undefined; + asm volatile ("movq (%[buffer]), %[data]" + : [data] "=r" (data), + : [buffer] "r" (buffer), + ); + return data; + }, + .x86 => @compileError("64-bit MMIO not supported on 32-bit x86"), + .riscv64, .riscv32 => { + return buffer.*; + }, + else => @compileError("No MMIO access implementation for this arch."), + } +} + +// mmioReadBuffer/mmioWriteBuffer provide methods for doing bulk/memcpy style transfers to/from +// device memory. These methods to not provide any access width guarantees and as such should only +// be used in situations where this is acceptable. +// +// For example, do _not_ use these functions to dump a bank of 32-bit MMIO registers, but rather +// use it only for accessing the small banks of RAM or ROM which might exist inside of a device +pub fn mmioWriteBuffer(mmio: [*]volatile u8, source: []const u8) void { + var mmio_offset: usize = 0; + var source_offset: usize = 0; + const size = source.len; + + // Write byte by byte for simplicity and correctness + while (source_offset < size) { + mmioWrite8(source[source_offset], &mmio[mmio_offset]); + mmio_offset += 1; + source_offset += 1; + } +} + +pub fn mmioReadBuffer(dest: []u8, mmio: [*]const volatile u8) void { + var dest_offset: usize = 0; + var mmio_offset: usize = 0; + const size = dest.len; + + // Read byte by byte for simplicity and correctness + while (dest_offset < size) { + dest[dest_offset] = mmioRead8(&mmio[mmio_offset]); + dest_offset += 1; + mmio_offset += 1; + } +} + +pub fn mmioWrite(comptime T: type, data: T, buffer: *volatile T) void { + switch (T) { + u8 => mmioWrite8(data, buffer), + u16 => mmioWrite16(data, buffer), + u32 => mmioWrite32(data, buffer), + u64 => mmioWrite64(data, buffer), + else => @compileError("Unsupported MMIO write type: " ++ @typeName(T)), + } +} + +/// Generic read function that dispatches based on type +pub fn mmioRead(comptime T: type, buffer: *const volatile T) T { + return switch (T) { + u8 => mmioRead8(buffer), + u16 => mmioRead16(buffer), + u32 => mmioRead32(buffer), + u64 => mmioRead64(buffer), + else => @compileError("Unsupported MMIO read type: " ++ @typeName(T)), + }; +} + +test "low level API writes" { + var value8: u8 = undefined; + var value16: u16 = undefined; + var value32: u32 = undefined; + var value64: u64 = undefined; + + const value8_ptr = fake.fakeMmioPtr(&value8); + const value16_ptr = fake.fakeMmioPtr(&value16); + const value32_ptr = fake.fakeMmioPtr(&value32); + const value64_ptr = fake.fakeMmioPtr(&value64); + + mmioWrite8(10, value8_ptr); + mmioWrite16(11, value16_ptr); + mmioWrite32(12, value32_ptr); + mmioWrite64(13, value64_ptr); + + try std.testing.expect(value8 == 10); + try std.testing.expect(value16 == 11); + try std.testing.expect(value32 == 12); + try std.testing.expect(value64 == 13); +} + +test "low level API reads" { + var value8: u8 = 10; + var value16: u16 = 11; + var value32: u32 = 12; + var value64: u64 = 13; + const const_value8: u8 = 14; + const const_value16: u16 = 15; + const const_value32: u32 = 16; + const const_value64: u64 = 17; + + const value8_ptr = fake.fakeMmioPtr(&value8); + const value16_ptr = fake.fakeMmioPtr(&value16); + const value32_ptr = fake.fakeMmioPtr(&value32); + const value64_ptr = fake.fakeMmioPtr(&value64); + const const_value8_ptr = fake.fakeMmioPtr(&const_value8); + const const_value16_ptr = fake.fakeMmioPtr(&const_value16); + const const_value32_ptr = fake.fakeMmioPtr(&const_value32); + const const_value64_ptr = fake.fakeMmioPtr(&const_value64); + + try std.testing.expect(mmioRead8(value8_ptr) == 10); + try std.testing.expect(mmioRead16(value16_ptr) == 11); + try std.testing.expect(mmioRead32(value32_ptr) == 12); + try std.testing.expect(mmioRead64(value64_ptr) == 13); + try std.testing.expect(mmioRead8(const_value8_ptr) == 14); + try std.testing.expect(mmioRead16(const_value16_ptr) == 15); + try std.testing.expect(mmioRead32(const_value32_ptr) == 16); + try std.testing.expect(mmioRead64(const_value64_ptr) == 17); +} + +test "generic writes" { + var value8: u8 = undefined; + var value16: u16 = undefined; + var value32: u32 = undefined; + var value64: u64 = undefined; + + const value8_ptr = fake.fakeMmioPtr(&value8); + const value16_ptr = fake.fakeMmioPtr(&value16); + const value32_ptr = fake.fakeMmioPtr(&value32); + const value64_ptr = fake.fakeMmioPtr(&value64); + + mmioWrite(u8, 10, value8_ptr); + mmioWrite(u16, 11, value16_ptr); + mmioWrite(u32, 12, value32_ptr); + mmioWrite(u64, 13, value64_ptr); + + try std.testing.expect(value8 == 10); + try std.testing.expect(value16 == 11); + try std.testing.expect(value32 == 12); + try std.testing.expect(value64 == 13); +} + +test "generic reads" { + var value8: u8 = 10; + var value16: u16 = 11; + var value32: u32 = 12; + var value64: u64 = 13; + const const_value8: u8 = 14; + const const_value16: u16 = 15; + const const_value32: u32 = 16; + const const_value64: u64 = 17; + + const value8_ptr = fake.fakeMmioPtr(&value8); + const value16_ptr = fake.fakeMmioPtr(&value16); + const value32_ptr = fake.fakeMmioPtr(&value32); + const value64_ptr = fake.fakeMmioPtr(&value64); + const const_value8_ptr = fake.fakeMmioPtr(&const_value8); + const const_value16_ptr = fake.fakeMmioPtr(&const_value16); + const const_value32_ptr = fake.fakeMmioPtr(&const_value32); + const const_value64_ptr = fake.fakeMmioPtr(&const_value64); + + try std.testing.expect(mmioRead(u8, value8_ptr) == 10); + try std.testing.expect(mmioRead(u16, value16_ptr) == 11); + try std.testing.expect(mmioRead(u32, value32_ptr) == 12); + try std.testing.expect(mmioRead(u64, value64_ptr) == 13); + try std.testing.expect(mmioRead(u8, const_value8_ptr) == 14); + try std.testing.expect(mmioRead(u16, const_value16_ptr) == 15); + try std.testing.expect(mmioRead(u32, const_value32_ptr) == 16); + try std.testing.expect(mmioRead(u64, const_value64_ptr) == 17); +} + +test "read buffer" { + const array = blk: { + var result: [256]u8 = undefined; + for (result, 0..) |_, i| { + result[i] = @intCast(i); + } + break :blk result; + }; + + // Read all but the first and last byte. + var result: [254]u8 = std.mem.zeroes([254]u8); + var mutable_array = array; // Make a mutable copy + const value_ptr = fake.fakeMmioPtr(&mutable_array[1]); + mmioReadBuffer(result[0..], @ptrCast(value_ptr)); + + for (result, 1..) |byte, i| { + try std.testing.expect(byte == i); + } +} + +test "write buffer" { + const source_array = blk: { + var result: [256]u8 = undefined; + for (result, 0..) |_, i| { + result[i] = @intCast(i); + } + break :blk result; + }; + + // Write all but the first and last byte. + var mmio_buffer: [256]u8 = std.mem.zeroes([256]u8); + const mmio_ptr = fake.fakeMmioPtr(&mmio_buffer[1]); + mmioWriteBuffer(@ptrCast(mmio_ptr), source_array[1..255]); + + try std.testing.expect(mmio_buffer[0] == 0); + for (mmio_buffer[1..255], 1..) |byte, i| { + try std.testing.expect(byte == i); + } + try std.testing.expect(mmio_buffer[255] == 0); +} From cfa1365c1cf1cc14b3a307c9bb3d8068ed44d3de Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Wed, 4 Jun 2025 14:39:06 -0300 Subject: [PATCH 11/41] [ulib][hwreg] Add mmio support --- slipstream/system/ulib/hwreg/build.zig | 1 + slipstream/system/ulib/hwreg/build.zig.zon | 3 + slipstream/system/ulib/hwreg/src/mmio.zig | 78 ++++++++++++++++++++++ 3 files changed, 82 insertions(+) create mode 100644 slipstream/system/ulib/hwreg/src/mmio.zig diff --git a/slipstream/system/ulib/hwreg/build.zig b/slipstream/system/ulib/hwreg/build.zig index 2a192a1..70b0aef 100644 --- a/slipstream/system/ulib/hwreg/build.zig +++ b/slipstream/system/ulib/hwreg/build.zig @@ -17,6 +17,7 @@ pub fn build(b: *std.Build) void { const deps = [_]struct { name: []const u8, dep_name: []const u8, module_name: []const u8 }{ .{ .name = "public", .dep_name = "public", .module_name = "public" }, .{ .name = "mock_function", .dep_name = "mock_function", .module_name = "mock_function" }, + .{ .name = "mmio-ptr", .dep_name = "mmio-ptr", .module_name = "mmio-ptr" }, }; for (deps) |dep| { diff --git a/slipstream/system/ulib/hwreg/build.zig.zon b/slipstream/system/ulib/hwreg/build.zig.zon index 2f52ab5..c67e3ae 100644 --- a/slipstream/system/ulib/hwreg/build.zig.zon +++ b/slipstream/system/ulib/hwreg/build.zig.zon @@ -10,5 +10,8 @@ .mock_function = .{ .path = "../mock_function", }, + .@"mmio-ptr" = .{ + .path = "../mmio-ptr", + }, }, } diff --git a/slipstream/system/ulib/hwreg/src/mmio.zig b/slipstream/system/ulib/hwreg/src/mmio.zig new file mode 100644 index 0000000..9e3169c --- /dev/null +++ b/slipstream/system/ulib/hwreg/src/mmio.zig @@ -0,0 +1,78 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. + +const std = @import("std"); +const internal = @import("internal.zig"); +const mmio_ptr = @import("mmio-ptr"); + +/// This can be passed to readFrom and writeTo methods. The RegisterAddr object holds an offset from +/// an MMIO base address stored in this object. +/// +/// The template parameter defines a type meant to be used for MMIO operations (read/write). +/// This implies that values are casted to and from |ForcedAccessType|. This affects register +/// offsets as well, since offsets will be scaled by |@sizeOf(ForcedAccessType)|. +/// +/// |ForcedAccessType = void| is a special case where unscaled and unconstrained MMIO operations are +/// performed; that is no casting is performed between types and no scaling is applied to the +/// offsets. +pub fn RegisterMmioScaled(comptime ForcedAccessType: type) type { + comptime { + if (ForcedAccessType != void and !internal.isSupportedInt(ForcedAccessType)) { + @compileError("Unsupported type."); + } + } + + return struct { + const Self = @This(); + + mmio: [*]volatile u8, + + pub fn init(mmio: *volatile anyopaque) Self { + return Self{ .mmio = initPtr(mmio) }; + } + + fn IoType(comptime IntType: type) type { + return if (ForcedAccessType == void) IntType else ForcedAccessType; + } + + const kScale = @sizeOf(if (ForcedAccessType == void) u8 else ForcedAccessType); + + fn initPtr(mmio: *volatile anyopaque) [*]volatile u8 { + const addr = @intFromPtr(mmio); + return @ptrFromInt(addr); + } + + /// Write |val| to the |@sizeOf(IntType)| byte field located |offset| bytes from + /// |base()|. + pub fn write(self: Self, comptime IntType: type, val: IntType, offset: u32) void { + const IoTypeForInt = IoType(IntType); + comptime std.debug.assert(@sizeOf(IntType) <= @sizeOf(IoTypeForInt)); + mmio_ptr.mmioWrite(IoTypeForInt, @as(IoTypeForInt, @intCast(val)), self.mmioPtr(IoTypeForInt, offset)); + } + + /// Read the value of the |@sizeOf(IntType)| byte field located |offset| bytes from + /// |base()|. + pub fn read(self: Self, comptime IntType: type, offset: u32) IntType { + const IoTypeForInt = IoType(IntType); + comptime std.debug.assert(@sizeOf(IntType) <= @sizeOf(IoTypeForInt)); + return @as(IntType, @intCast(mmio_ptr.mmioRead(IoTypeForInt, self.mmioPtr(IoTypeForInt, offset)))); + } + + pub fn base(self: Self) usize { + return @intFromPtr(self.mmio); + } + + fn mmioPtr(self: Self, comptime U: type, offset: u32) *volatile U { + const IntType = switch (@typeInfo(U)) { + .pointer => |ptr_info| ptr_info.child, + else => U, + }; + comptime std.debug.assert(internal.isSupportedInt(IntType)); + const addr = self.mmio + (offset * kScale); + return @ptrCast(@alignCast(addr)); + } + }; +} + +pub const RegisterMmio = RegisterMmioScaled(void); From 5ab6bfd1710a51e5506ba919e10cb409495e3464 Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Thu, 5 Jun 2025 14:55:19 -0300 Subject: [PATCH 12/41] [ulib][hwreg] Fix unaligned mmio access --- slipstream/system/ulib/hwreg/src/mmio.zig | 67 +++++++++++++++-------- 1 file changed, 45 insertions(+), 22 deletions(-) diff --git a/slipstream/system/ulib/hwreg/src/mmio.zig b/slipstream/system/ulib/hwreg/src/mmio.zig index 9e3169c..7ce6f44 100644 --- a/slipstream/system/ulib/hwreg/src/mmio.zig +++ b/slipstream/system/ulib/hwreg/src/mmio.zig @@ -26,29 +26,34 @@ pub fn RegisterMmioScaled(comptime ForcedAccessType: type) type { return struct { const Self = @This(); - mmio: [*]volatile u8, + mmio: [*]volatile u8 = undefined, pub fn init(mmio: *volatile anyopaque) Self { return Self{ .mmio = initPtr(mmio) }; } - fn IoType(comptime IntType: type) type { - return if (ForcedAccessType == void) IntType else ForcedAccessType; - } - - const kScale = @sizeOf(if (ForcedAccessType == void) u8 else ForcedAccessType); - - fn initPtr(mmio: *volatile anyopaque) [*]volatile u8 { - const addr = @intFromPtr(mmio); - return @ptrFromInt(addr); - } - /// Write |val| to the |@sizeOf(IntType)| byte field located |offset| bytes from /// |base()|. pub fn write(self: Self, comptime IntType: type, val: IntType, offset: u32) void { const IoTypeForInt = IoType(IntType); comptime std.debug.assert(@sizeOf(IntType) <= @sizeOf(IoTypeForInt)); - mmio_ptr.mmioWrite(IoTypeForInt, @as(IoTypeForInt, @intCast(val)), self.mmioPtr(IoTypeForInt, offset)); + + const target_addr = @intFromPtr(self.mmio) + (offset * scale); + const alignment = @alignOf(IoTypeForInt); + + if (target_addr % alignment == 0) { + // Fast path: aligned access + const ptr: *volatile IoTypeForInt = @ptrFromInt(target_addr); + mmio_ptr.mmioWrite(IoTypeForInt, @as(IoTypeForInt, @intCast(val)), ptr); + } else { + // Slow path: unaligned access - use byte-wise operations + const casted_val = @as(IoTypeForInt, @intCast(val)); + const bytes = std.mem.toBytes(casted_val); + for (bytes, 0..) |byte, i| { + const byte_ptr: *volatile u8 = @ptrFromInt(target_addr + i); + mmio_ptr.mmioWrite(u8, byte, byte_ptr); + } + } } /// Read the value of the |@sizeOf(IntType)| byte field located |offset| bytes from @@ -56,21 +61,39 @@ pub fn RegisterMmioScaled(comptime ForcedAccessType: type) type { pub fn read(self: Self, comptime IntType: type, offset: u32) IntType { const IoTypeForInt = IoType(IntType); comptime std.debug.assert(@sizeOf(IntType) <= @sizeOf(IoTypeForInt)); - return @as(IntType, @intCast(mmio_ptr.mmioRead(IoTypeForInt, self.mmioPtr(IoTypeForInt, offset)))); + + const target_addr = @intFromPtr(self.mmio) + (offset * scale); + const alignment = @alignOf(IoTypeForInt); + + if (target_addr % alignment == 0) { + // Fast path: aligned access + const ptr: *const volatile IoTypeForInt = @ptrFromInt(target_addr); + return @as(IntType, @intCast(mmio_ptr.mmioRead(IoTypeForInt, ptr))); + } else { + // Slow path: unaligned access - use byte-wise operations + var bytes: [@sizeOf(IoTypeForInt)]u8 = undefined; + for (&bytes, 0..) |*byte, i| { + const byte_ptr: *const volatile u8 = @ptrFromInt(target_addr + i); + byte.* = mmio_ptr.mmioRead(u8, byte_ptr); + } + const result = std.mem.bytesToValue(IoTypeForInt, &bytes); + return @as(IntType, @intCast(result)); + } } pub fn base(self: Self) usize { return @intFromPtr(self.mmio); } - fn mmioPtr(self: Self, comptime U: type, offset: u32) *volatile U { - const IntType = switch (@typeInfo(U)) { - .pointer => |ptr_info| ptr_info.child, - else => U, - }; - comptime std.debug.assert(internal.isSupportedInt(IntType)); - const addr = self.mmio + (offset * kScale); - return @ptrCast(@alignCast(addr)); + fn IoType(comptime IntType: type) type { + return if (ForcedAccessType == void) IntType else ForcedAccessType; + } + + const scale = @sizeOf(if (ForcedAccessType == void) u8 else ForcedAccessType); + + fn initPtr(mmio: *volatile anyopaque) [*]volatile u8 { + const addr = @intFromPtr(mmio); + return @ptrFromInt(addr); } }; } From c2fe696a8854e59c53b714b60a9b6ba02470d102 Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Wed, 4 Jun 2025 14:39:57 -0300 Subject: [PATCH 13/41] [ulib][hwreg] Refactor bitfields --- .../system/ulib/hwreg/src/bitfields.zig | 2755 +++++++++++++---- slipstream/system/ulib/hwreg/src/internal.zig | 15 + 2 files changed, 2140 insertions(+), 630 deletions(-) diff --git a/slipstream/system/ulib/hwreg/src/bitfields.zig b/slipstream/system/ulib/hwreg/src/bitfields.zig index 37615fc..947a893 100644 --- a/slipstream/system/ulib/hwreg/src/bitfields.zig +++ b/slipstream/system/ulib/hwreg/src/bitfields.zig @@ -4,6 +4,10 @@ const std = @import("std"); const internal = @import("internal.zig"); +const mmio = @import("mmio.zig"); + +const testing = std.testing; +const Mock = @import("mock.zig"); /// Tag that can be passed as the third template parameter for RegisterBase to enable /// the pretty-printing interfaces on a register. @@ -59,7 +63,6 @@ pub fn RegisterBase(comptime DerivedType: type, comptime IntType: type, comptime } pub fn regValuePtr(self: *Self) *ValueType { - //std.debug.print("RegisterBase::regValuePtr {}\n", .{&self.reg_value}); return &self.reg_value; } @@ -67,21 +70,21 @@ pub fn RegisterBase(comptime DerivedType: type, comptime IntType: type, comptime return &self.reg_value; } - pub fn setRegValue(self: *Self, value: IntType) *SelfType { + pub fn setRegValue(self: *Self, value: IntType) SelfType { self.reg_value = value; - return @ptrCast(self); + return SelfType{ .base = self.* }; } - pub fn readFrom(self: *Self, reg_io: anytype) *SelfType { + pub fn readFrom(self: *Self, reg_io: anytype) SelfType { internal.visit(struct { fn readFn(io: anytype, self_ptr: *Self) void { self_ptr.reg_value = io.read(ValueType, self_ptr.reg_addr); } }.readFn, reg_io.*, .{self}); - return @ptrCast(self); + return SelfType{ .base = self.* }; } - pub fn writeTo(self: *Self, reg_io: anytype) *SelfType { + pub fn writeTo(self: *Self, reg_io: anytype) SelfType { internal.visit(struct { fn writeFn(io: anytype, self_ptr: *Self) void { const masked_value = self_ptr.reg_value & ~self_ptr.params.rsvdz_mask; @@ -89,7 +92,7 @@ pub fn RegisterBase(comptime DerivedType: type, comptime IntType: type, comptime mutable_io.write(ValueType, masked_value, self_ptr.reg_addr); } }.writeFn, reg_io.*, .{self}); - return @ptrCast(self); + return SelfType{ .base = self.* }; } /// Invokes print_fn once for each field, including each @@ -166,16 +169,16 @@ pub fn RegisterAddr(comptime RegType: type) type { /// Instantiate a RegisterBase using the value of the register read from MMIO. pub fn readFrom(self: Self, reg_io: anytype) RegType { var reg: RegType = RegType.init(); - reg.setRegAddr(self.reg_addr); - _ = reg.readFrom(reg_io); + reg.base.setRegAddr(self.reg_addr); + _ = reg.base.readFrom(reg_io); return reg; } /// Instantiate a RegisterBase using the given value for the register. pub fn fromValue(self: Self, value: RegType.ValueType) RegType { var reg: RegType = RegType.init(); - reg.setRegAddr(self.reg_addr); - _ = reg.setRegValue(value); + reg.base.setRegAddr(self.reg_addr); + _ = reg.base.setRegValue(value); return reg; } @@ -194,14 +197,6 @@ pub fn BitfieldRef(comptime IntType: type) type { mask: IntType, pub fn init(value_ptr: *IntType, comptime bit_high_incl: u32, comptime bit_low: u32) Self { - comptime { - if (bit_high_incl < bit_low) { - @compileError("bit_high_incl must be >= bit_low"); - } - if (bit_high_incl >= @bitSizeOf(IntType)) { - @compileError("bit_high_incl must be < @bitSizeOf(IntType)"); - } - } return Self{ .value_ptr = value_ptr, .shift = bit_low, @@ -210,14 +205,6 @@ pub fn BitfieldRef(comptime IntType: type) type { } pub fn initUnshifted(value_ptr: *IntType, comptime bit_high_incl: u32, comptime bit_low: u32) Self { - comptime { - if (bit_high_incl < bit_low) { - @compileError("bit_high_incl must be >= bit_low"); - } - if (bit_high_incl >= @bitSizeOf(IntType)) { - @compileError("bit_high_incl must be < @bitSizeOf(IntType)"); - } - } return Self{ .value_ptr = value_ptr, .shift = 0, @@ -237,775 +224,2289 @@ pub fn BitfieldRef(comptime IntType: type) type { }; } -/// Example register definition for documentation purposes -pub const examples = struct { - /// Define bitfields for an "AuxControl" 32-bit register. - pub const AuxControl = struct { +pub fn DefField(comptime ParentType: type, comptime bit_high: u32, comptime bit_low: u32, comptime name: []const u8) type { + return DefCondField(ParentType, bit_high, bit_low, name, true); +} + +pub fn DefCondField(comptime ParentType: type, comptime bit_high: u32, comptime bit_low: u32, comptime name: []const u8, comptime cond: bool) type { + if (cond) { + return Field(ParentType, bit_high, bit_low, name, cond, false); + } else { + return MarkerField(ParentType, bit_high, bit_low, name, cond); + } +} + +pub fn DefUnshiftedField(comptime ParentType: type, comptime bit_high: u32, comptime bit_low: u32, comptime name: []const u8) type { + return DefCondUnshiftedField(ParentType, bit_high, bit_low, name, true); +} + +pub fn DefCondUnshiftedField(comptime ParentType: type, comptime bit_high: u32, comptime bit_low: u32, comptime name: []const u8, comptime cond: bool) type { + if (cond) { + return Field(ParentType, bit_high, bit_low, name, cond, true); + } else { + return MarkerField(ParentType, bit_high, bit_low, name, cond); + } +} + +fn MarkerField(comptime ParentType: type, comptime bit_high: u32, comptime bit_low: u32, comptime name: []const u8, comptime cond: bool) type { + return struct { + inline fn Marker() type { + return struct {}; + } + const InternalField = internal.Field(ParentType, Marker(), cond); + + pub fn init(parent: *ParentType) void { + InternalField.init(parent.base.getParams(), name, bit_high, bit_low); + } + }; +} + +fn Field(comptime ParentType: type, comptime bit_high: u32, comptime bit_low: u32, comptime name: []const u8, comptime cond: bool, comptime unshifted: bool) type { + comptime { + if (bit_high < bit_low) { + @compileError("bit_high_incl must be >= bit_low"); + } + if (bit_low >= @bitSizeOf(ParentType.ValueType)) { + @compileError("bit_high_incl must be < @bitSizeOf(IntType)"); + } + } + + return struct { + const InternalField = internal.Field(ParentType, Marker(), cond); + + inline fn Marker() type { + return struct {}; + } + + pub fn init(parent: *ParentType) void { + InternalField.init(parent.base.getParams(), name, bit_high, bit_low); + } + + pub fn get(parent: *ParentType) ParentType.ValueType { + if (unshifted) { + return BitfieldRef(ParentType.ValueType).initUnshifted(parent.base.regValuePtr(), bit_high, bit_low).get(); + } else { + return BitfieldRef(ParentType.ValueType).init(parent.base.regValuePtr(), bit_high, bit_low).get(); + } + } + + pub fn set(parent: *ParentType, value: ParentType.ValueType) void { + if (unshifted) { + BitfieldRef(ParentType.ValueType).initUnshifted(parent.base.regValuePtr(), bit_high, bit_low).set(value); + } else { + BitfieldRef(ParentType.ValueType).init(parent.base.regValuePtr(), bit_high, bit_low).set(value); + } + } + }; +} + +pub fn DefEnumField(comptime ParentType: type, comptime EnumType: type, comptime bit_high: u32, comptime bit_low: u32, comptime name: []const u8) type { + return DefCondEnumField(ParentType, EnumType, bit_high, bit_low, name, true); +} + +pub fn DefCondEnumField(comptime ParentType: type, comptime EnumType: type, comptime bit_high: u32, comptime bit_low: u32, comptime name: []const u8, comptime cond: bool) type { + if (cond) { + return EnumField(ParentType, EnumType, bit_high, bit_low, name, cond); + } else { + return struct {}; + } +} + +fn EnumField(comptime ParentType: type, comptime EnumType: type, comptime bit_high: u32, comptime bit_low: u32, comptime name: []const u8, comptime cond: bool) type { + comptime { + if (bit_high < bit_low) { + @compileError("bit_high must be >= bit_low"); + } + if (bit_high >= @bitSizeOf(ParentType.ValueType)) { + @compileError("bit_high must be < @bitSizeOf(ValueType)"); + } + if (@typeInfo(EnumType) != .@"enum") { + @compileError("EnumType must be an enum"); + } + } + + return struct { + const InternalField = internal.Field(ParentType, Marker(), cond); + + inline fn Marker() type { + return struct {}; + } + + pub fn init(parent: *ParentType) void { + InternalField.init(parent.base.getParams(), name, bit_high, bit_low); + } + + pub fn get(parent: *ParentType) EnumType { + const raw_value = BitfieldRef(ParentType.ValueType).init(parent.base.regValuePtr(), bit_high, bit_low).get(); + return @enumFromInt(raw_value); + } + + pub fn set(parent: *ParentType, value: EnumType) void { + const raw_value = @intFromEnum(value); + BitfieldRef(ParentType.ValueType).init(parent.base.regValuePtr(), bit_high, bit_low).set(raw_value); + } + }; +} + +pub fn DefBit(comptime ParentType: type, comptime bit: u32, comptime name: []const u8) type { + return DefCondBit(ParentType, bit, name, true); +} + +pub fn DefCondBit(comptime ParentType: type, comptime bit: u32, comptime name: []const u8, comptime cond: bool) type { + return DefCondField(ParentType, bit, bit, name, cond); +} + +pub fn DefRsvdzField(comptime ParentType: type, comptime bit_high: u32, comptime bit_low: u32) type { + return DefCondRsvdzField(ParentType, bit_high, bit_low, true); +} + +pub fn DefCondRsvdzField(comptime ParentType: type, comptime bit_high: u32, comptime bit_low: u32, comptime cond: bool) type { + comptime { + if (bit_high < bit_low) { + @compileError("bit_high must be >= bit_low"); + } + if (bit_high >= @bitSizeOf(ParentType.ValueType)) { + @compileError("bit_high must be < @bitSizeOf(ValueType)"); + } + } + + return struct { + inline fn RsvdZMarker() type { + return struct {}; + } + + pub fn init(parent: *ParentType) void { + internal.RsvdZField(ParentType, RsvdZMarker(), cond).init(parent.base.getParams(), bit_high, bit_low); + } + }; +} + +// Declares single-bit reserved-zero fields in a derived class of RegisterBase. +// This will ensure that on RegisterBase::WriteTo(), reserved-zero bits are +// automatically zeroed. +pub fn DefRsvdzBit(comptime ParentType: type, comptime bit: u32) type { + return DefCondRsvdzBit(ParentType, bit, true); +} + +pub fn DefCondRsvdzBit(comptime ParentType: type, comptime bit: u32, comptime cond: bool) type { + return DefCondRsvdzField(ParentType, bit, bit, cond); +} + +// Declares "decltype(FIELD) NAME() const" and "void set_NAME(decltype(FIELD))" that +// reads/modifies the declared bitrange. Both bit indices are inclusive. +pub fn DefSubfield(comptime ParentType: type, comptime bit_high: u32, comptime bit_low: u32, comptime name: []const u8) type { + const FieldType = @FieldType(ParentType, name); + + comptime { + internal.subFieldCheck(FieldType, bit_high, bit_low); + } + + return Subfield(ParentType, bit_high, bit_low, name, false); +} + +pub fn DefCondSubfield(comptime ParentType: type, comptime bit_high: u32, comptime bit_low: u32, comptime name: []const u8, comptime cond: bool) type { + const FieldType = @FieldType(ParentType, name); + + comptime { + internal.subFieldCheck(FieldType, bit_high, bit_low); + } + + if (cond) { + return Subfield(ParentType, bit_high, bit_low, name, false); + } else { + return struct {}; + } +} + +fn Subfield(comptime ParentType: type, comptime bit_high: u32, comptime bit_low: u32, comptime name: []const u8, comptime unshifted: bool) type { + const FieldType = @FieldType(ParentType, name); + + comptime { + internal.subFieldCheck(FieldType, bit_high, bit_low); + } + + return struct { + pub fn get(parent: *ParentType) FieldType { + if (unshifted) { + return BitfieldRef(FieldType).initUnshifted(&@field(parent, name), bit_high, bit_low).get(); + } else { + return BitfieldRef(FieldType).init(&@field(parent, name), bit_high, bit_low).get(); + } + } + + pub fn set(parent: *ParentType, value: FieldType) void { + if (unshifted) { + BitfieldRef(FieldType).initUnshifted(&@field(parent, name), bit_high, bit_low).set(value); + } else { + BitfieldRef(FieldType).init(&@field(parent, name), bit_high, bit_low).set(value); + } + } + }; +} + +// Declares "decltype(FIELD) NAME() const" and "void set_NAME(decltype(FIELD))" that +// reads/modifies the declared bit. +pub fn DefSubbit(comptime ParentType: type, comptime bit: u32, comptime name: []const u8) type { + return DefSubfield(ParentType, bit, bit, name); +} + +pub fn DefCondSubbit(comptime ParentType: type, comptime bit: u32, comptime name: []const u8, comptime cond: bool) type { + return DefCondSubfield(ParentType, bit, bit, name, cond); +} + +pub fn DefUnshiftedSubfield(comptime ParentType: type, comptime bit_high: u32, comptime bit_low: u32, comptime name: []const u8) type { + return DefCondUnshiftedSubfield(ParentType, bit_high, bit_low, name, true); +} + +pub fn DefCondUnshiftedSubfield(comptime ParentType: type, comptime bit_high: u32, comptime bit_low: u32, comptime name: []const u8, comptime cond: bool) type { + if (cond) { + return Subfield(ParentType, bit_high, bit_low, name, true); + } else { + return struct {}; + } +} + +// Declares "TYPE NAME() const" and "void set_NAME(TYPE)" that +// reads/modifies the declared bitrange. Both bit indices are inclusive. +pub fn DefEnumSubfield(comptime ParentType: type, comptime EnumType: type, comptime bit_high: u32, comptime bit_low: u32, comptime name: []const u8) type { + return DefCondEnumSubfield(ParentType, EnumType, bit_high, bit_low, name, true); +} + +pub fn DefCondEnumSubfield(comptime ParentType: type, comptime EnumType: type, comptime bit_high: u32, comptime bit_low: u32, comptime name: []const u8, comptime cond: bool) type { + if (cond) { + return EnumSubfield(ParentType, EnumType, bit_high, bit_low, name, cond); + } else { + return struct {}; + } +} + +fn EnumSubfield(comptime ParentType: type, comptime EnumType: type, comptime bit_high: u32, comptime bit_low: u32, comptime name: []const u8, comptime cond: bool) type { + const FieldType = @FieldType(ParentType, name); + + comptime { + internal.subFieldCheck(FieldType, bit_high, bit_low); + if (@typeInfo(EnumType) != .@"enum") { + @compileError("EnumType must be an enum"); + } + } + + return struct { + const InternalField = internal.Field(ParentType, Marker(), cond); + + inline fn Marker() type { + return struct {}; + } + + pub fn init(parent: *ParentType) void { + InternalField.init(parent.base.getParams(), name, bit_high, bit_low); + } + + pub fn get(parent: *ParentType) EnumType { + const raw_value = BitfieldRef(FieldType).init(&@field(parent, name), bit_high, bit_low).get(); + return @enumFromInt(raw_value); + } + + pub fn set(parent: *ParentType, value: EnumType) void { + const raw_value = @intFromEnum(value); + BitfieldRef(FieldType).init(&@field(parent, name), bit_high, bit_low).set(@intCast(raw_value)); + } + }; +} + +fn StructSubBitTest(comptime IntType: type) type { + return struct { const Self = @This(); + pub const ValueType = IntType; + pub const printer_enabled: bool = false; - // Embed RegisterBase - base: RegisterBase(Self, u32, void) = .{}, + field: IntType = 0, - // Required for RegisterAddr - pub const ValueType = u32; + // Comptime field definitions + pub const FirstBit = DefSubbit(Self, 0, "field"); + pub const MidBit = DefSubbit(Self, 1, "field"); + pub const LastBit = DefSubbit(Self, @bitSizeOf(IntType) - 1, "field"); - pub fn init() Self { - return Self{ - .base = .{}, - }; + pub fn firstBit(self: *Self) IntType { + return FirstBit.get(self); } - /// Returns an object representing the register's type and address. - pub fn get() RegisterAddr(Self) { - return RegisterAddr(Self).init(0x64010); + pub fn setFirstBit(self: *Self, value: IntType) *Self { + FirstBit.set(self, value); + return self; + } + + pub fn midBit(self: *Self) IntType { + return MidBit.get(self); + } + + pub fn setMidBit(self: *Self, value: IntType) *Self { + MidBit.set(self, value); + return self; + } + + pub fn lastBit(self: *Self) IntType { + return LastBit.get(self); + } + + pub fn setLastBit(self: *Self, value: IntType) *Self { + LastBit.set(self, value); + return self; } + }; +} + +fn structSubBitTest(comptime IntType: type) !void { + const TestReg = StructSubBitTest(IntType); + var val = TestReg{}; + + try testing.expect(0 == val.firstBit()); + try testing.expect(0 == val.midBit()); + try testing.expect(0 == val.lastBit()); + + _ = val.setFirstBit(1); + try testing.expect(1 == val.field); + try testing.expect(1 == val.firstBit()); + try testing.expect(0 == val.midBit()); + try testing.expect(0 == val.lastBit()); + _ = val.setFirstBit(0); + + _ = val.setMidBit(1); + try testing.expect(2 == val.field); + try testing.expect(0 == val.firstBit()); + try testing.expect(1 == val.midBit()); + try testing.expect(0 == val.lastBit()); + _ = val.setMidBit(0); + + _ = val.setLastBit(1); + try testing.expect(@as(IntType, 1) << (@bitSizeOf(IntType) - 1) == val.field); + try testing.expect(0 == val.firstBit()); + try testing.expect(0 == val.midBit()); + try testing.expect(1 == val.lastBit()); + _ = val.setLastBit(0); +} + +test "StructSubBit" { + try structSubBitTest(u8); + try structSubBitTest(u16); + try structSubBitTest(u32); + try structSubBitTest(u64); +} + +fn StructSubFieldTest(comptime IntType: type) type { + return struct { + const Self = @This(); + + field1: IntType = 0, + field2: IntType = 0, + field3: IntType = 0, - // Bits [30:25] and [19:0] are automatically preserved across RMW cycles. + // Comptime field definitions + pub const WholeLength = DefSubfield(Self, @bitSizeOf(IntType) - 1, 0, "field1"); + pub const SingleBit = DefSubfield(Self, 2, 2, "field2"); + pub const Range1 = DefSubfield(Self, 2, 1, "field3"); + pub const Range2 = DefSubfield(Self, 5, 3, "field3"); - // Define a single-bit field. - // DEF_BIT(31, enabled); - pub fn enabled(self: *const Self) u32 { - return BitfieldRef(u32).init(@constCast(self.base.regValuePtrConst()), 31, 31).get(); + pub fn wholeLength(self: *Self) IntType { + return WholeLength.get(self); } - pub fn setEnabled(self: *Self, val: u32) *Self { - BitfieldRef(u32).init(self.base.regValuePtr(), 31, 31).set(val); + pub fn setWholeLength(self: *Self, value: IntType) *Self { + WholeLength.set(self, value); return self; } - // Define a 5-bit field, from bits 20-24 (inclusive). - // DEF_FIELD(24, 20, message_size); - pub fn messageSize(self: *const Self) u32 { - return BitfieldRef(u32).init(@constCast(self.base.regValuePtrConst()), 24, 20).get(); + pub fn singleBit(self: *Self) IntType { + return SingleBit.get(self); } - pub fn setMessageSize(self: *Self, val: u32) *Self { - BitfieldRef(u32).init(self.base.regValuePtr(), 24, 20).set(val); + pub fn setSingleBit(self: *Self, value: IntType) *Self { + SingleBit.set(self, value); return self; } - // Delegate common methods to base - pub fn regAddr(self: *const Self) u32 { - return self.base.regAddr(); + pub fn range1(self: *Self) IntType { + return Range1.get(self); } - pub fn setRegAddr(self: *Self, addr: u32) void { - self.base.setRegAddr(addr); + pub fn setRange1(self: *Self, value: IntType) *Self { + Range1.set(self, value); + return self; } - pub fn regValue(self: *const Self) u32 { - return self.base.regValue(); + pub fn range2(self: *Self) IntType { + return Range2.get(self); } - pub fn setRegValue(self: *Self, value: u32) *Self { - _ = self.base.setRegValue(value); + pub fn setRange2(self: *Self, value: IntType) *Self { + Range2.set(self, value); return self; } + }; +} - pub fn readFrom(self: *Self, reg_io: anytype) *Self { - _ = self.base.readFrom(reg_io); +fn structSubFieldTest(comptime IntType: type) !void { + const TestReg = StructSubFieldTest(IntType); + var val = TestReg{}; + + // Ensure writing to a whole length field affects all bits + const kMax = std.math.maxInt(IntType); + try testing.expect(0 == val.wholeLength()); + _ = val.setWholeLength(kMax); + try testing.expect(kMax == val.wholeLength()); + try testing.expect(kMax == val.field1); + _ = val.setWholeLength(0); + try testing.expect(0 == val.wholeLength()); + try testing.expect(0 == val.field1); + + // Ensure writing to a single bit only affects that bit + try testing.expect(0 == val.singleBit()); + _ = val.setSingleBit(1); + try testing.expect(1 == val.singleBit()); + try testing.expect(4 == val.field2); + _ = val.setSingleBit(0); + try testing.expect(0 == val.singleBit()); + try testing.expect(0 == val.field2); + + // Ensure writing to adjacent fields does not bleed across + try testing.expect(0 == val.range1()); + try testing.expect(0 == val.range2()); + _ = val.setRange1(3); + try testing.expect(3 == val.range1()); + try testing.expect(0 == val.range2()); + try testing.expect(@as(IntType, 3) << 1 == val.field3); + _ = val.setRange2(1); + try testing.expect(3 == val.range1()); + try testing.expect(1 == val.range2()); + try testing.expect((@as(IntType, 3) << 1) | (@as(IntType, 1) << 3) == val.field3); + _ = val.setRange2(2); + try testing.expect(3 == val.range1()); + try testing.expect(2 == val.range2()); + try testing.expect((@as(IntType, 3) << 1) | (@as(IntType, 2) << 3) == val.field3); + _ = val.setRange1(0); + try testing.expect(0 == val.range1()); + try testing.expect(2 == val.range2()); + try testing.expect(@as(IntType, 2) << 3 == val.field3); +} + +test "StructSubField" { + try structSubFieldTest(u8); + try structSubFieldTest(u16); + try structSubFieldTest(u32); + try structSubFieldTest(u64); +} + +fn StructEnumSubFieldTest(comptime IntType: type) type { + return struct { + const Self = @This(); + + const EnumWholeRange = enum(IntType) { + kZero = 0, + kOne = 1, + kMax = std.math.maxInt(IntType), + }; + + const EnumBit = enum(u8) { + kZero = 0, + kOne = 1, + }; + + const EnumRange = enum(u64) { + kZero = 0, + kOne = 1, + kTwo = 2, + kThree = 3, + }; + + field1: IntType = 0, + field2: IntType = 0, + field3: IntType = 0, + + pub const WholeLength = DefEnumSubfield(Self, EnumWholeRange, @bitSizeOf(IntType) - 1, 0, "field1"); + pub const SingleBit = DefEnumSubfield(Self, EnumBit, 2, 2, "field2"); + pub const Range1 = DefEnumSubfield(Self, EnumRange, 2, 1, "field3"); + pub const Range2 = DefEnumSubfield(Self, EnumRange, 5, 3, "field3"); + + pub fn wholeLength(self: *Self) EnumWholeRange { + return WholeLength.get(self); + } + + pub fn setWholeLength(self: *Self, value: EnumWholeRange) *Self { + WholeLength.set(self, value); + return self; + } + + pub fn singleBit(self: *Self) EnumBit { + return SingleBit.get(self); + } + + pub fn setSingleBit(self: *Self, value: EnumBit) *Self { + SingleBit.set(self, value); + return self; + } + + pub fn range1(self: *Self) EnumRange { + return Range1.get(self); + } + + pub fn setRange1(self: *Self, value: EnumRange) *Self { + Range1.set(self, value); + return self; + } + + pub fn range2(self: *Self) EnumRange { + return Range2.get(self); + } + + pub fn setRange2(self: *Self, value: EnumRange) *Self { + Range2.set(self, value); + return self; + } + }; +} + +fn structEnumSubFieldTest(comptime IntType: type) !void { + const TestReg = StructEnumSubFieldTest(IntType); + const EnumWholeRange = TestReg.EnumWholeRange; + const EnumRange = TestReg.EnumRange; + const EnumBit = TestReg.EnumBit; + + var val = TestReg{}; + + // Ensure writing to a whole length field affects all bits + const kMax = std.math.maxInt(IntType); + try testing.expect(EnumWholeRange.kZero == val.wholeLength()); + _ = val.setWholeLength(EnumWholeRange.kMax); + try testing.expect(EnumWholeRange.kMax == val.wholeLength()); + try testing.expect(kMax == val.field1); + _ = val.setWholeLength(EnumWholeRange.kZero); + try testing.expect(EnumWholeRange.kZero == val.wholeLength()); + try testing.expect(0 == val.field1); + + // Ensure writing to a single bit only affects that bit + try testing.expect(EnumBit.kZero == val.singleBit()); + _ = val.setSingleBit(EnumBit.kOne); + try testing.expect(EnumBit.kOne == val.singleBit()); + try testing.expect(4 == val.field2); + _ = val.setSingleBit(EnumBit.kZero); + try testing.expect(EnumBit.kZero == val.singleBit()); + try testing.expect(0 == val.field2); + + // Ensure writing to adjacent fields does not bleed across + try testing.expect(EnumRange.kZero == val.range1()); + try testing.expect(EnumRange.kZero == val.range2()); + _ = val.setRange1(EnumRange.kThree); + try testing.expect(EnumRange.kThree == val.range1()); + try testing.expect(EnumRange.kZero == val.range2()); + try testing.expect(@as(IntType, 3) << 1 == val.field3); + _ = val.setRange2(EnumRange.kOne); + try testing.expect(EnumRange.kThree == val.range1()); + try testing.expect(EnumRange.kOne == val.range2()); + try testing.expect((@as(IntType, 3) << 1) | (@as(IntType, 1) << 3) == val.field3); + _ = val.setRange2(EnumRange.kTwo); + try testing.expect(EnumRange.kThree == val.range1()); + try testing.expect(EnumRange.kTwo == val.range2()); + try testing.expect((@as(IntType, 3) << 1) | (@as(IntType, 2) << 3) == val.field3); + _ = val.setRange1(EnumRange.kZero); + try testing.expect(EnumRange.kZero == val.range1()); + try testing.expect(EnumRange.kTwo == val.range2()); + try testing.expect(@as(IntType, 2) << 3 == val.field3); +} + +test "StructEnumSubFieldU8" { + try structEnumSubFieldTest(u8); +} +test "StructEnumSubFieldU16" { + try structEnumSubFieldTest(u16); +} + +test "StructEnumSubFieldU32" { + try structEnumSubFieldTest(u32); +} + +test "StructEnumSubFieldU64" { + try structEnumSubFieldTest(u64); +} + +const ConditionalSubfieldTestRegEnum = enum { + kA, + kB, + kC, +}; + +fn ConditionalSubfieldTestReg(comptime condition: ConditionalSubfieldTestRegEnum) type { + const kA = condition == ConditionalSubfieldTestRegEnum.kA; + const kB = condition == ConditionalSubfieldTestRegEnum.kB; + const kC = condition == ConditionalSubfieldTestRegEnum.kC; + + return struct { + const Self = @This(); + pub const ValueType = u16; + pub const printer_enabled: bool = false; + + field: u16 = 0, + + // Conditional kA fields + pub const Same = DefCondSubfield(Self, 15, 8, "field", kA); + pub const A7 = DefCondSubbit(Self, 7, "field", kA); + pub const A64 = DefCondSubfield(Self, 6, 4, "field", kA); + pub const A3 = DefCondSubbit(Self, 3, "field", kA); + + // Conditional kB fields + pub const SameB = DefCondSubfield(Self, 12, 10, "field", kB); + pub const B75 = DefCondUnshiftedSubfield(Self, 7, 5, "field", kB); + pub const B43 = DefCondSubfield(Self, 4, 3, "field", kB); + + // Conditional kC fields + const Rsvp = enum(u4) { kNo = 0, kYes = 0b1111 }; + pub const SameC = DefCondSubfield(Self, 13, 12, "field", kC); + pub const C74 = DefCondEnumSubfield(Self, Rsvp, 7, 4, "field", kC); + pub const C3 = DefCondSubbit(Self, 3, "field", kC); + + // Unconditional, common field + pub const Common = DefSubfield(Self, 2, 0, "field"); + + pub fn same(self: *Self) u16 { + if (kA) return Same.get(self); + if (kB) return SameB.get(self); + if (kC) return SameC.get(self); + unreachable; + } + + pub fn setSame(self: *Self, value: u16) *Self { + if (kA) Same.set(self, value); + if (kB) SameB.set(self, value); + if (kC) SameC.set(self, value); return self; } - pub fn writeTo(self: *Self, reg_io: anytype) *Self { - _ = self.base.writeTo(reg_io); - return self; - } - }; + pub fn a7(self: *Self) u16 { + return A7.get(self); + } + + pub fn setA7(self: *Self, value: u16) *Self { + A7.set(self, value); + return self; + } + + pub fn a64(self: *Self) u16 { + return A64.get(self); + } + + pub fn setA64(self: *Self, value: u16) *Self { + A64.set(self, value); + return self; + } + + pub fn a3(self: *Self) u16 { + return A3.get(self); + } + + pub fn setA3(self: *Self, value: u16) *Self { + A3.set(self, value); + return self; + } + + pub fn b75(self: *Self) u16 { + return B75.get(self); + } + + pub fn setB75(self: *Self, value: u16) *Self { + B75.set(self, value); + return self; + } + + pub fn b43(self: *Self) u16 { + return B43.get(self); + } + + pub fn setB43(self: *Self, value: u16) *Self { + B43.set(self, value); + return self; + } + + pub fn c74(self: *Self) Rsvp { + return C74.get(self); + } + + pub fn setC74(self: *Self, value: Rsvp) *Self { + C74.set(self, value); + return self; + } + + pub fn c3(self: *Self) u16 { + return C3.get(self); + } + + pub fn setC3(self: *Self, value: u16) *Self { + C3.set(self, value); + return self; + } + + pub fn common(self: *Self) u16 { + return Common.get(self); + } + + pub fn setCommon(self: *Self, value: u16) *Self { + Common.set(self, value); + return self; + } + }; +} + +test "ConditionalSubfields" { + { + const RegA = ConditionalSubfieldTestReg(ConditionalSubfieldTestRegEnum.kA); + var reg = RegA{ .field = 0xffff }; + try testing.expect(0xff == reg.same()); + _ = reg.setSame(0); + try testing.expect(0b1 == reg.a7()); + _ = reg.setA7(0); + try testing.expect(0b111 == reg.a64()); + _ = reg.setA64(0); + try testing.expect(0b1 == reg.a3()); + _ = reg.setA3(0); + try testing.expect(0b111 == reg.common()); + _ = reg.setCommon(0); + try testing.expect(0 == reg.field); + } + + { + const RegB = ConditionalSubfieldTestReg(ConditionalSubfieldTestRegEnum.kB); + var reg = RegB{ .field = 0xffff }; + try testing.expect(0b111 == reg.same()); + _ = reg.setSame(0); + try testing.expect(0b11100000 == reg.b75()); + _ = reg.setB75(0); + try testing.expect(0b11 == reg.b43()); + _ = reg.setB43(0); + try testing.expect(0b111 == reg.common()); + _ = reg.setCommon(0); + try testing.expect(0xe300 == reg.field); + } + + { + const RegC = ConditionalSubfieldTestReg(ConditionalSubfieldTestRegEnum.kC); + const Rsvp = RegC.Rsvp; + var reg = RegC{ .field = 0xffff }; + try testing.expect(0b11 == reg.same()); + _ = reg.setSame(0); + try testing.expect(Rsvp.kYes == reg.c74()); + _ = reg.setC74(Rsvp.kNo); + try testing.expect(0b1 == reg.c3()); + _ = reg.setC3(0); + try testing.expect(0b111 == reg.common()); + _ = reg.setCommon(0); + try testing.expect(0xcf00 == reg.field); + } +} + +test "UnshifedFields" { + const UnshiftedFieldTestReg = struct { + const Self = @This(); + pub const ValueType = u16; + + data: u16 = 0, + + // Comptime field definitions + pub const Field1 = DefUnshiftedSubfield(Self, 15, 12, "data"); + pub const Field2 = DefUnshiftedSubfield(Self, 11, 8, "data"); + pub const Field3 = DefUnshiftedSubfield(Self, 7, 4, "data"); + pub const Field4 = DefUnshiftedSubfield(Self, 3, 0, "data"); + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(0); + } + + pub fn field1(self: *Self) ValueType { + return Field1.get(self); + } + + pub fn setField1(self: *Self, value: ValueType) *Self { + Field1.set(self, value); + return self; + } + + pub fn field2(self: *Self) ValueType { + return Field2.get(self); + } + + pub fn setField2(self: *Self, value: ValueType) *Self { + Field2.set(self, value); + return self; + } + + pub fn field3(self: *Self) ValueType { + return Field3.get(self); + } + + pub fn setField3(self: *Self, value: ValueType) *Self { + Field3.set(self, value); + return self; + } + + pub fn field4(self: *Self) ValueType { + return Field4.get(self); + } + + pub fn setField4(self: *Self, value: ValueType) *Self { + Field4.set(self, value); + return self; + } + }; + + // Test simple field isolation + { + var test_reg = UnshiftedFieldTestReg{}; + test_reg.data = 0xffff; + try testing.expect(test_reg.field1() == 0xf000); + try testing.expect(test_reg.field2() == 0x0f00); + try testing.expect(test_reg.field3() == 0x00f0); + try testing.expect(test_reg.field4() == 0x000f); + } + + // Test assignment + { + var test_reg = UnshiftedFieldTestReg{}; + test_reg.data = 0x0; + try testing.expect(test_reg.field1() == 0); + try testing.expect(test_reg.field2() == 0); + try testing.expect(test_reg.field3() == 0); + try testing.expect(test_reg.field4() == 0); + + _ = test_reg.setField1(0xf000); + try testing.expect(test_reg.field1() == 0xf000); + try testing.expect(test_reg.field2() == 0); + try testing.expect(test_reg.field3() == 0); + try testing.expect(test_reg.field4() == 0); + + _ = test_reg.setField2(0xf00); + try testing.expect(test_reg.field1() == 0xf000); + try testing.expect(test_reg.field2() == 0xf00); + try testing.expect(test_reg.field3() == 0); + try testing.expect(test_reg.field4() == 0); + + _ = test_reg.setField3(0xf0); + try testing.expect(test_reg.field1() == 0xf000); + try testing.expect(test_reg.field2() == 0xf00); + try testing.expect(test_reg.field3() == 0xf0); + try testing.expect(test_reg.field4() == 0); + + _ = test_reg.setField4(0xf); + try testing.expect(test_reg.field1() == 0xf000); + try testing.expect(test_reg.field2() == 0xf00); + try testing.expect(test_reg.field3() == 0xf0); + try testing.expect(test_reg.field4() == 0xf); + } +} + +test "RsvdzPartial" { + const RsvdZPartialTestReg8 = struct { + const Self = @This(); + pub const ValueType = u8; + const ParentType = RegisterBase(Self, ValueType, void); + pub const printer_enabled: bool = ParentType.PrinterEnabled; + + base: ParentType = undefined, + + // Comptime field definitions + pub const RsvdZField = DefRsvdzField(Self, 7, 3); + + pub fn init() Self { + var self = Self{ .base = .{} }; + RsvdZField.init(&self); + return self; + } + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(0); + } + + pub fn rsvdZField(self: *Self) ValueType { + return RsvdZField.get(self); + } + + pub fn setRsvdZField(self: *Self, value: ValueType) *Self { + RsvdZField.set(self, value); + return self; + } + }; + + const RsvdZPartialTestReg16 = struct { + const Self = @This(); + pub const ValueType = u16; + const ParentType = RegisterBase(Self, ValueType, void); + pub const printer_enabled: bool = ParentType.PrinterEnabled; + + base: ParentType = undefined, + + // Comptime field definitions + pub const RsvdZField = DefRsvdzField(Self, 14, 1); + + pub fn init() Self { + var self = Self{ .base = .{} }; + RsvdZField.init(&self); + return self; + } + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(0); + } + + pub fn rsvdZField(self: *Self) ValueType { + return RsvdZField.get(self); + } + + pub fn setRsvdZField(self: *Self, value: ValueType) *Self { + RsvdZField.set(self, value); + return self; + } + }; + + const RsvdZPartialTestReg32 = struct { + const Self = @This(); + pub const ValueType = u32; + const ParentType = RegisterBase(Self, ValueType, void); + pub const printer_enabled: bool = ParentType.PrinterEnabled; + + base: ParentType = undefined, + + // Comptime field definitions + pub const RsvdZField1 = DefRsvdzField(Self, 31, 12); + pub const RsvdZField2 = DefRsvdzField(Self, 10, 5); + pub const RsvdZBit = DefRsvdzBit(Self, 3); + + pub fn init() Self { + var self = Self{ .base = .{} }; + RsvdZField1.init(&self); + RsvdZField2.init(&self); + RsvdZBit.init(&self); + return self; + } + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(0); + } + }; + + const RsvdZPartialTestReg64 = struct { + const Self = @This(); + pub const ValueType = u64; + const ParentType = RegisterBase(Self, ValueType, void); + pub const printer_enabled: bool = ParentType.PrinterEnabled; + + base: ParentType = undefined, + + // Comptime field definitions + pub const RsvdZField1 = DefRsvdzField(Self, 63, 18); + pub const RsvdZField2 = DefRsvdzField(Self, 10, 0); + + pub fn init() Self { + var self = Self{ .base = .{} }; + RsvdZField1.init(&self); + RsvdZField2.init(&self); + return self; + } + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(0); + } + }; + + var fake_reg: u64 = undefined; + var mmio_reg = mmio.RegisterMmio.init(&fake_reg); + + // Ensure we mask off the RsvdZ bits when we write them back, regardless of + // what we read them as. + { + const allones = std.math.maxInt(u8); + var mock: Mock = Mock.init(std.testing.allocator); + defer mock.deinit(); + mock.initRegisterIo(); + _ = mock.expectRead(u8, allones, 0).expectWrite(u8, 0x7, 0); + var reg = RsvdZPartialTestReg8.get().readFrom(mock.io()); + try testing.expect(reg.base.regValue() == allones); + _ = reg.base.writeTo(mock.io()); + mock.verifyAndClear(); + } + { + fake_reg = std.math.maxInt(u16); + var reg = RsvdZPartialTestReg16.get().readFrom(&mmio_reg); + try testing.expect(reg.base.regValue() == std.math.maxInt(u16)); + _ = reg.base.writeTo(&mmio_reg); + const reg_value: u64 = fake_reg; + try testing.expect(reg_value == 0x8001); + } + { + fake_reg = std.math.maxInt(u32); + var reg = RsvdZPartialTestReg32.get().readFrom(&mmio_reg); + try testing.expect(reg.base.regValue() == std.math.maxInt(u32)); + _ = reg.base.writeTo(&mmio_reg); + const reg_value: u64 = fake_reg; + try testing.expect(reg_value == (1 << 11) | 0x17); + } + { + fake_reg = std.math.maxInt(u64); + var reg = RsvdZPartialTestReg64.get().readFrom(&mmio_reg); + try testing.expect(reg.base.regValue() == std.math.maxInt(u64)); + _ = reg.base.writeTo(&mmio_reg); + const reg_value: u64 = fake_reg; + try testing.expect(reg_value == 0x7f << 11); + } +} + +test "RsvdzFull" { + const RsvdZFullTestReg8 = struct { + const Self = @This(); + pub const ValueType = u8; + const ParentType = RegisterBase(Self, ValueType, void); + pub const printer_enabled: bool = ParentType.PrinterEnabled; + + base: ParentType = undefined, + + // Comptime field definitions + pub const RsvdZField = DefRsvdzField(Self, 7, 0); + + pub fn init() Self { + var self = Self{ .base = .{} }; + RsvdZField.init(&self); + return self; + } + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(0); + } + + pub fn rsvdZField(self: *Self) ValueType { + return RsvdZField.get(self); + } + + pub fn setRsvdZField(self: *Self, value: ValueType) *Self { + RsvdZField.set(self, value); + return self; + } + }; + + const RsvdZFullTestReg16 = struct { + const Self = @This(); + pub const ValueType = u16; + const ParentType = RegisterBase(Self, ValueType, void); + pub const printer_enabled: bool = ParentType.PrinterEnabled; + + base: ParentType = undefined, + + // Comptime field definitions + pub const RsvdZField = DefRsvdzField(Self, 15, 0); + + pub fn init() Self { + var self = Self{ .base = .{} }; + RsvdZField.init(&self); + return self; + } + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(0); + } + }; + + const RsvdZFullTestReg32 = struct { + const Self = @This(); + pub const ValueType = u32; + const ParentType = RegisterBase(Self, ValueType, void); + pub const printer_enabled: bool = ParentType.PrinterEnabled; + + base: ParentType = undefined, + + // Comptime field definitions + pub const RsvdZField = DefRsvdzField(Self, 31, 0); + + pub fn init() Self { + var self = Self{ .base = .{} }; + RsvdZField.init(&self); + return self; + } + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(0); + } + }; + + const RsvdZFullTestReg64 = struct { + const Self = @This(); + pub const ValueType = u64; + const ParentType = RegisterBase(Self, ValueType, void); + pub const printer_enabled: bool = ParentType.PrinterEnabled; + + base: ParentType = undefined, + + // Comptime field definitions + pub const RsvdZField = DefRsvdzField(Self, 63, 0); + + pub fn init() Self { + var self = Self{ .base = .{} }; + RsvdZField.init(&self); + return self; + } + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(0); + } + }; + + var fake_reg: u64 = undefined; + var mmio_reg = mmio.RegisterMmio.init(&fake_reg); + + { + fake_reg = std.math.maxInt(u8); + var reg = RsvdZFullTestReg8.get().readFrom(&mmio_reg); + try testing.expect(reg.base.regValue() == std.math.maxInt(u8)); + _ = reg.base.writeTo(&mmio_reg); + const reg_value: u64 = fake_reg; + try testing.expect(reg_value == 0); + } + + { + fake_reg = std.math.maxInt(u16); + var reg = RsvdZFullTestReg16.get().readFrom(&mmio_reg); + try testing.expect(reg.base.regValue() == std.math.maxInt(u16)); + _ = reg.base.writeTo(&mmio_reg); + const reg_value: u64 = fake_reg; + try testing.expect(reg_value == 0); + } + + { + fake_reg = std.math.maxInt(u32); + var reg = RsvdZFullTestReg32.get().readFrom(&mmio_reg); + try testing.expect(reg.base.regValue() == std.math.maxInt(u32)); + _ = reg.base.writeTo(&mmio_reg); + const reg_value: u64 = fake_reg; + try testing.expect(reg_value == 0); + } + + { + fake_reg = std.math.maxInt(u64); + var reg = RsvdZFullTestReg64.get().readFrom(&mmio_reg); + try testing.expect(reg.base.regValue() == std.math.maxInt(u64)); + _ = reg.base.writeTo(&mmio_reg); + const reg_value: u64 = fake_reg; + try testing.expect(reg_value == 0); + } +} + +test "Field" { + const FieldTestReg8 = struct { + const Self = @This(); + pub const ValueType = u8; + const ParentType = RegisterBase(Self, ValueType, void); + pub const printer_enabled: bool = ParentType.PrinterEnabled; + + base: ParentType = undefined, + + // Comptime field definitions + pub const Field1 = DefField(Self, 7, 3, "field1"); + pub const Field2 = DefField(Self, 2, 0, "field2"); + + pub fn init() Self { + var self = Self{ .base = .{} }; + Field1.init(&self); + Field2.init(&self); + return self; + } + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(0); + } + + pub fn field1(self: *Self) ValueType { + return Field1.get(self); + } + + pub fn setField1(self: *Self, value: ValueType) *Self { + Field1.set(self, value); + return self; + } + + pub fn field2(self: *Self) ValueType { + return Field2.get(self); + } + + pub fn setField2(self: *Self, value: ValueType) *Self { + Field2.set(self, value); + return self; + } + }; + + const FieldTestReg16 = struct { + const Self = @This(); + pub const ValueType = u16; + const ParentType = RegisterBase(Self, ValueType, void); + pub const printer_enabled: bool = ParentType.PrinterEnabled; + + base: ParentType = undefined, + + // Comptime field definitions + pub const Field1 = DefField(Self, 13, 3, "field1"); + pub const Field2 = DefField(Self, 2, 1, "field2"); + pub const Field3 = DefBit(Self, 0, "field3"); + + pub fn init() Self { + var self = Self{ .base = .{} }; + Field1.init(&self); + Field2.init(&self); + Field3.init(&self); + return self; + } + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(0); + } + + pub fn field1(self: *Self) ValueType { + return Field1.get(self); + } + + pub fn setField1(self: *Self, value: ValueType) *Self { + Field1.set(self, value); + return self; + } + + pub fn field2(self: *Self) ValueType { + return Field2.get(self); + } + + pub fn setField2(self: *Self, value: ValueType) *Self { + Field2.set(self, value); + return self; + } + + pub fn field3(self: *Self) ValueType { + return Field3.get(self); + } + + pub fn setField3(self: *Self, value: ValueType) *Self { + Field3.set(self, value); + return self; + } + }; + + const FieldTestReg32 = struct { + const Self = @This(); + pub const ValueType = u32; + const ParentType = RegisterBase(Self, ValueType, void); + pub const printer_enabled: bool = ParentType.PrinterEnabled; + + base: ParentType = undefined, + + // Comptime field definitions + pub const Field1 = DefField(Self, 30, 21, "field1"); + pub const Field2 = DefField(Self, 20, 12, "field2"); + pub const Field3 = DefRsvdzField(Self, 11, 0); + + pub fn init() Self { + var self = Self{ .base = .{} }; + Field1.init(&self); + Field2.init(&self); + Field3.init(&self); + return self; + } + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(0); + } + + pub fn field1(self: *Self) ValueType { + return Field1.get(self); + } + + pub fn setField1(self: *Self, value: ValueType) *Self { + Field1.set(self, value); + return self; + } + + pub fn field2(self: *Self) ValueType { + return Field2.get(self); + } + + pub fn setField2(self: *Self, value: ValueType) *Self { + Field2.set(self, value); + return self; + } + }; + + const FieldTestReg64 = struct { + const Self = @This(); + pub const ValueType = u64; + const ParentType = RegisterBase(Self, ValueType, void); + pub const printer_enabled: bool = ParentType.PrinterEnabled; + + base: ParentType = undefined, + + // Comptime field definitions + pub const Field1 = DefField(Self, 60, 20, "field1"); + pub const Field2 = DefField(Self, 10, 0, "field2"); + + pub fn init() Self { + var self = Self{ .base = .{} }; + Field1.init(&self); + Field2.init(&self); + return self; + } + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(0); + } + + pub fn field1(self: *Self) ValueType { + return Field1.get(self); + } + + pub fn setField1(self: *Self, value: ValueType) *Self { + Field1.set(self, value); + return self; + } + + pub fn field2(self: *Self) ValueType { + return Field2.get(self); + } + + pub fn setField2(self: *Self, value: ValueType) *Self { + Field2.set(self, value); + return self; + } + }; + + var fake_reg: u64 = undefined; + var mmio_reg = mmio.RegisterMmio.init(&fake_reg); + + // Ensure modified fields go to the right place, and unspecified bits are + // preserved. + { + const kInitVal: u8 = 0x42; + fake_reg = kInitVal; + var reg = FieldTestReg8.get().readFrom(&mmio_reg); + try std.testing.expect(kInitVal == reg.base.regValue()); + try std.testing.expect((kInitVal >> 3) == reg.field1()); + try std.testing.expect(0x2 == reg.field2()); + _ = reg.setField1(0x1f); + _ = reg.setField2(0x1); + try std.testing.expect(0x1f == reg.field1()); + try std.testing.expect(0x1 == reg.field2()); + + _ = reg.base.writeTo(&mmio_reg); + const reg_value: u64 = fake_reg; + try std.testing.expect(((0x1f << 3) | 1) == reg_value); + } + + { + const kInitVal: u16 = 0b1010_1111_0101_0000; + fake_reg = kInitVal; + var reg = FieldTestReg16.get().readFrom(&mmio_reg); + try std.testing.expect(kInitVal == reg.base.regValue()); + try std.testing.expect(((kInitVal >> 3) & ((1 << 11) - 1)) == reg.field1()); + try std.testing.expect(((kInitVal >> 1) & 0x3) == reg.field2()); + try std.testing.expect((kInitVal & 1) == reg.field3()); + _ = reg.setField1(42); + _ = reg.setField2(2); + _ = reg.setField3(1); + try std.testing.expect(42 == reg.field1()); + try std.testing.expect(2 == reg.field2()); + try std.testing.expect(1 == reg.field3()); + _ = reg.base.writeTo(&mmio_reg); + const reg_value: u64 = fake_reg; + try std.testing.expect(((0b10 << 14) | (42 << 3) | (2 << 1) | 1) == reg_value); + } + + { + const kInitVal: u32 = 0xe987_2fff; + fake_reg = kInitVal; + var reg = FieldTestReg32.get().readFrom(&mmio_reg); + try std.testing.expect(kInitVal == reg.base.regValue()); + try std.testing.expect(((kInitVal >> 21) & ((1 << 10) - 1)) == reg.field1()); + try std.testing.expect(((kInitVal >> 12) & ((1 << 9) - 1)) == reg.field2()); + _ = reg.setField1(0x3a7); + _ = reg.setField2(0x8f); + try std.testing.expect(0x3a7 == reg.field1()); + try std.testing.expect(0x8f == reg.field2()); + _ = reg.base.writeTo(&mmio_reg); + const reg_value: u64 = fake_reg; + try std.testing.expect(((0b1 << 31) | (0x3a7 << 21) | (0x8f << 12)) == reg_value); + } + + { + const kInitVal: u64 = 0xfedc_ba98_7654_3210; + fake_reg = kInitVal; + var reg = FieldTestReg64.get().readFrom(&mmio_reg); + try std.testing.expect(kInitVal == reg.base.regValue()); + try std.testing.expect(((kInitVal >> 20) & ((1 << 41) - 1)) == reg.field1()); + try std.testing.expect((kInitVal & ((1 << 11) - 1)) == reg.field2()); + _ = reg.setField1(0x1a2_3456_789a); + _ = reg.setField2(0x78c); + try std.testing.expect(0x1a2_3456_789a == reg.field1()); + try std.testing.expect(0x78c == reg.field2()); + _ = reg.base.writeTo(&mmio_reg); + const reg_value: u64 = fake_reg; + try std.testing.expect(((0b111 << 61) | (0x1a2_3456_789a << 20) | (0x86 << 11) | 0x78c) == reg_value); + } +} + +const EnumFieldTestReg8 = struct { + const Self = @This(); + pub const ValueType = u8; + const ParentType = RegisterBase(Self, ValueType, void); + pub const printer_enabled: bool = ParentType.PrinterEnabled; + + base: ParentType = .{}, + + const MyEnum = enum(u8) { + Test0 = 0, + Test1 = 1, + Test2 = 2, + Test3 = 3, + }; + + pub const TestField = DefEnumField(Self, MyEnum, 3, 2, "TestField"); + + pub fn init() Self { + return Self{ + .base = .{}, + }; + } + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(0); + } + + pub fn fromValue(value: ValueType) Self { + var self = Self.init(); + self.base.setRegValue(value); + return self; + } + + pub fn testField(self: *Self) MyEnum { + return TestField.get(self); + } + + pub fn setTestField(self: *Self, value: MyEnum) *Self { + TestField.set(self, value); + return self; + } + + pub fn regValue(self: *Self) ValueType { + return self.base.regValue(); + } +}; + +const EnumFieldTestReg8WithEnumClass = struct { + const Self = @This(); + pub const ValueType = u8; + const ParentType = RegisterBase(Self, ValueType, void); + pub const printer_enabled: bool = ParentType.PrinterEnabled; + + base: ParentType = .{}, + + const MyEnum = enum(u8) { + Test0 = 0, + Test1 = 1, + Test2 = 2, + Test3 = 3, + }; + + pub const TestField = DefEnumField(Self, MyEnum, 3, 2, "TestField"); + + pub fn init() Self { + return Self{ + .base = .{}, + }; + } + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(0); + } + + pub fn fromValue(value: ValueType) Self { + var self = Self.init(); + self.base.setRegValue(value); + return self; + } + + pub fn testField(self: *Self) MyEnum { + return TestField.get(self); + } + + pub fn setTestField(self: *Self, value: MyEnum) *Self { + TestField.set(self, value); + return self; + } + + pub fn regValue(self: *Self) ValueType { + return self.base.regValue(); + } +}; + +test "EnumField" { + { + const result = blk: { + var reg = EnumFieldTestReg8WithEnumClass.get().fromValue(255); + _ = reg.setTestField(EnumFieldTestReg8WithEnumClass.MyEnum.Test0); + break :blk reg.regValue(); + }; + const mask: u8 = 0xF3; + try testing.expect(result == mask); + var test_reg = EnumFieldTestReg8WithEnumClass.get().fromValue(result); + try testing.expect(test_reg.testField() == EnumFieldTestReg8WithEnumClass.MyEnum.Test0); + } + { + const result = blk: { + var reg = EnumFieldTestReg8.get().fromValue(255); + _ = reg.setTestField(EnumFieldTestReg8.MyEnum.Test1); + break :blk reg.regValue(); + }; + const mask: u8 = 0xF3; + try testing.expect(result == (mask | (1 << 2))); + var test_reg = EnumFieldTestReg8.get().fromValue(result); + try testing.expect(test_reg.testField() == EnumFieldTestReg8.MyEnum.Test1); + } + { + const result = blk: { + var reg = EnumFieldTestReg8.get().fromValue(255); + _ = reg.setTestField(EnumFieldTestReg8.MyEnum.Test2); + break :blk reg.regValue(); + }; + const mask: u8 = 0xF3; + try testing.expect(result == (mask | (2 << 2))); + var test_reg = EnumFieldTestReg8.get().fromValue(result); + try testing.expect(test_reg.testField() == EnumFieldTestReg8.MyEnum.Test2); + } + { + const result = blk: { + var reg = EnumFieldTestReg8.get().fromValue(255); + _ = reg.setTestField(EnumFieldTestReg8.MyEnum.Test3); + break :blk reg.regValue(); + }; + const mask: u8 = 0xF3; + try testing.expect(result == (mask | (3 << 2))); + var test_reg = EnumFieldTestReg8.get().fromValue(result); + try testing.expect(test_reg.testField() == EnumFieldTestReg8.MyEnum.Test3); + } +} + +const UnshiftedTestReg16 = struct { + const Self = @This(); + pub const ValueType = u16; + + base: RegisterBase(Self, u16, void) = .{}, + + // Comptime field definitions + pub const Field1 = DefUnshiftedField(Self, 15, 12, "field1"); + pub const Field2 = DefUnshiftedField(Self, 11, 8, "field2"); + pub const Field3 = DefUnshiftedField(Self, 7, 4, "field3"); + pub const Field4 = DefUnshiftedField(Self, 3, 0, "field4"); + + pub fn init() Self { + return Self{ + .base = .{}, + }; + } + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(0); + } + + pub fn field1(self: *const Self) u16 { + return Field1.get(@constCast(self)); + } + + pub fn setField1(self: *Self, value: u16) *Self { + Field1.set(self, value); + return self; + } + + pub fn field2(self: *const Self) u16 { + return Field2.get(@constCast(self)); + } + + pub fn setField2(self: *Self, value: u16) *Self { + Field2.set(self, value); + return self; + } + + pub fn field3(self: *const Self) u16 { + return Field3.get(@constCast(self)); + } + + pub fn setField3(self: *Self, value: u16) *Self { + Field3.set(self, value); + return self; + } + + pub fn field4(self: *const Self) u16 { + return Field4.get(@constCast(self)); + } + + pub fn setField4(self: *Self, value: u16) *Self { + Field4.set(self, value); + return self; + } + + pub fn readFrom(self: *Self, reg_io: anytype) *Self { + _ = self.base.readFrom(reg_io); + return self; + } + + pub fn writeTo(self: *Self, reg_io: anytype) *Self { + _ = self.base.writeTo(reg_io); + return self; + } + + pub fn setRegAddr(self: *Self, addr: u32) void { + self.base.setRegAddr(addr); + } + + pub fn regValue(self: *const Self) u16 { + return self.base.regValue(); + } +}; + +const TestPciBar32 = struct { + const Self = @This(); + pub const ValueType = u32; + + base: RegisterBase(Self, u32, void) = .{}, + + // Comptime field definitions + pub const Address = DefUnshiftedField(Self, 31, 4, "address"); + pub const IsPrefetchable = DefBit(Self, 3, "is_prefetchable"); + pub const RsvdZBit2 = DefRsvdzBit(Self, 2); + pub const Is64Bit = DefBit(Self, 1, "is_64bit"); + pub const IsIoSpace = DefBit(Self, 0, "is_io_space"); + + pub fn init() Self { + return Self{ + .base = .{}, + }; + } + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(0); + } - /// Mock register I/O for testing - pub const MockRegisterIo = struct { - const Self = @This(); + pub fn address(self: *const Self) u32 { + return Address.get(@constCast(self)); + } - pub fn read(self: *const Self, comptime T: type, addr: u32) T { - _ = self; - _ = addr; - return 0; // Return zero for mock - } + pub fn setAddress(self: *Self, value: u32) *Self { + Address.set(self, value); + return self; + } - pub fn write(self: *Self, comptime T: type, value: T, addr: u32) void { - _ = self; - std.debug.print("Writing 0x{X} to address 0x{X}\n", .{ value, addr }); - } - }; + pub fn isPrefetchable(self: *const Self) u32 { + return IsPrefetchable.get(@constCast(self)); + } - pub fn example1() void { - var reg_io = MockRegisterIo{}; + pub fn setIsPrefetchable(self: *Self, value: u32) *Self { + IsPrefetchable.set(self, value); + return self; + } - // Read the register's value from MMIO. "reg" is a snapshot of the - // register's value which also knows the register's address. - var reg = AuxControl.get().readFrom(®_io); + pub fn is64Bit(self: *const Self) u32 { + return Is64Bit.get(@constCast(self)); + } - // Read this register's "message_size" field. - const size = reg.messageSize(); - std.debug.print("Message size: {}\n", .{size}); + pub fn setIs64Bit(self: *Self, value: u32) *Self { + Is64Bit.set(self, value); + return self; + } - // Change this field's value. This modifies the snapshot. - _ = reg.setMessageSize(1234); + pub fn isIoSpace(self: *const Self) u32 { + return IsIoSpace.get(@constCast(self)); + } - // Write the modified register value to MMIO. - _ = reg.writeTo(®_io); + pub fn setIsIoSpace(self: *Self, value: u32) *Self { + IsIoSpace.set(self, value); + return self; } - // Fields may also be set in a fluent style - pub fn example2() void { - var reg_io = MockRegisterIo{}; + pub fn readFrom(self: *Self, reg_io: anytype) *Self { + _ = self.base.readFrom(reg_io); + return self; + } - // Read the register's value from MMIO, updates the message size and - // enabled bit, then writes the value back to MMIO - _ = AuxControl.get().readFrom(®_io).setMessageSize(1234).setEnabled(1).writeTo(®_io); + pub fn writeTo(self: *Self, reg_io: anytype) *Self { + _ = self.base.writeTo(reg_io); + return self; } - // It is also possible to write a register without having to read it first: - pub fn example3() void { - var reg_io = MockRegisterIo{}; + pub fn setRegAddr(self: *Self, addr: u32) void { + self.base.setRegAddr(addr); + } - // Start off with a value that is initialized to zero. - var reg = AuxControl.get().fromValue(0); - // Fill out fields. - _ = reg.setMessageSize(2345); - // Write the register value to MMIO. - _ = reg.writeTo(®_io); + pub fn regValue(self: *const Self) u32 { + return self.base.regValue(); } }; -// Compile-time tests +test "UnshiftedField" { + // Tests simple field isolation + { + const fake_reg: u16 = 0xffff; + var test_reg = UnshiftedTestReg16.get().fromValue(fake_reg); + try testing.expect(test_reg.field1() == 0xf000); + try testing.expect(test_reg.field2() == 0x0f00); + try testing.expect(test_reg.field3() == 0x00f0); + try testing.expect(test_reg.field4() == 0x000f); + } -const expect = std.testing.expect; -const Mock = @import("mock.zig"); + // Test assignment + { + const fake_reg: u16 = 0x0000; + var test_reg = UnshiftedTestReg16.get().fromValue(fake_reg); + try testing.expect(test_reg.field1() == 0); + try testing.expect(test_reg.field2() == 0); + try testing.expect(test_reg.field3() == 0); + try testing.expect(test_reg.field4() == 0); + + _ = test_reg.setField1(0xf000); + try testing.expect(test_reg.field1() == 0xf000); + try testing.expect(test_reg.field2() == 0); + try testing.expect(test_reg.field3() == 0); + try testing.expect(test_reg.field4() == 0); + + _ = test_reg.setField2(0xf00); + try testing.expect(test_reg.field1() == 0xf000); + try testing.expect(test_reg.field2() == 0xf00); + try testing.expect(test_reg.field3() == 0); + try testing.expect(test_reg.field4() == 0); + + _ = test_reg.setField3(0xf0); + try testing.expect(test_reg.field1() == 0xf000); + try testing.expect(test_reg.field2() == 0xf00); + try testing.expect(test_reg.field3() == 0xf0); + try testing.expect(test_reg.field4() == 0); + + _ = test_reg.setField4(0xf); + try testing.expect(test_reg.field1() == 0xf000); + try testing.expect(test_reg.field2() == 0xf00); + try testing.expect(test_reg.field3() == 0xf0); + try testing.expect(test_reg.field4() == 0xf); + } -test "basic functionality" { - // Test mask computation - try expect(internal.computeMask(u32, 0) == 0); - try expect(internal.computeMask(u32, 1) == 1); - try expect(internal.computeMask(u32, 8) == 0xFF); - try expect(internal.computeMask(u32, 32) == 0xFFFFFFFF); - - // Test bitfield reference - var value: u32 = 0; - var bitfield = BitfieldRef(u32).init(&value, 7, 4); - bitfield.set(0xA); - try expect(value == 0xA0); - try expect(bitfield.get() == 0xA); + // Test Writing a Bar size to an address field ala PCI + { + const fake_reg: u32 = 1 << 20; // A 1 MB size BAR + var test_reg = TestPciBar32.get().fromValue(fake_reg); + + try testing.expect(test_reg.address() == (1 << 20)); + _ = test_reg.setIsPrefetchable(1); + _ = test_reg.setIs64Bit(1); + _ = test_reg.setIsIoSpace(1); + try testing.expect(test_reg.address() == (1 << 20)); + try testing.expect(test_reg.isPrefetchable() == 1); + try testing.expect(test_reg.is64Bit() == 1); + try testing.expect(test_reg.isIoSpace() == 1); + } } -test "register example" { - var reg_io = examples.MockRegisterIo{}; - var reg = examples.AuxControl.get().fromValue(0); - - _ = reg.setMessageSize(15); - _ = reg.setEnabled(1); +const ConstexprArithmeticTestReg = struct { + const Self = @This(); + pub const ValueType = u32; - const size = reg.messageSize(); - const enabled = reg.enabled(); + base: RegisterBase(Self, u32, void) = .{}, - try std.testing.expect(size == 15); - try std.testing.expect(enabled == 1); + const kTen: u32 = 10; - _ = reg.writeTo(®_io); -} + // Comptime field definitions + pub const Field2 = DefField(Self, kTen + 2 * kTen, kTen, "field2"); + pub const RsvdzField = DefRsvdzField(Self, kTen - 1, 2); + pub const Field1 = DefBit(Self, 2 + 3 - 4, "field1"); -test "sub-bit test" { - // Test with u8 - { - var value: u8 = 0; - var reg = BitfieldRef(u8).init(&value, 7, 0); - try expect(reg.get() == 0); + pub fn init() Self { + return Self{ + .base = .{}, + }; + } - reg.set(1); - try expect(value == 1); - try expect(reg.get() == 1); - reg.set(0); + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(0); + } - reg.set(2); - try expect(value == 2); - try expect(reg.get() == 2); - reg.set(0); + pub fn field1(self: *const Self) u32 { + return Field1.get(@constCast(self)); + } - reg.set(128); - try expect(value == 128); - try expect(reg.get() == 128); - reg.set(0); + pub fn setField1(self: *Self, value: u32) *Self { + Field1.set(self, value); + return self; } - // Test with u16 - { - var value: u16 = 0; - var reg = BitfieldRef(u16).init(&value, 15, 0); - try expect(reg.get() == 0); + pub fn field2(self: *const Self) u32 { + return Field2.get(@constCast(self)); + } - reg.set(1); - try expect(value == 1); - try expect(reg.get() == 1); - reg.set(0); + pub fn setField2(self: *Self, value: u32) *Self { + Field2.set(self, value); + return self; + } - reg.set(2); - try expect(value == 2); - try expect(reg.get() == 2); - reg.set(0); + pub fn readFrom(self: *Self, reg_io: anytype) *Self { + _ = self.base.readFrom(reg_io); + return self; + } - reg.set(32768); - try expect(value == 32768); - try expect(reg.get() == 32768); - reg.set(0); + pub fn writeTo(self: *Self, reg_io: anytype) *Self { + _ = self.base.writeTo(reg_io); + return self; } +}; - // Test with u32 - { - var value: u32 = 0; - var reg = BitfieldRef(u32).init(&value, 31, 0); - try expect(reg.get() == 0); +test "BitsAsConstexprArithmeticExpressions" { + var fake_reg: u32 = 1 << 31; + var mmio_reg = mmio.RegisterMmio.init(&fake_reg); - reg.set(1); - try expect(value == 1); - try expect(reg.get() == 1); - reg.set(0); + var reg = ConstexprArithmeticTestReg.get().readFrom(&mmio_reg); + _ = reg.setField1(1); + _ = reg.setField2(0xabcd); + _ = reg.writeTo(&mmio_reg); +} - reg.set(2); - try expect(value == 2); - try expect(reg.get() == 2); - reg.set(0); +const ConditionalFieldTestRegEnum = enum { + kA, + kB, + kC, +}; - reg.set(2147483648); - try expect(value == 2147483648); - try expect(reg.get() == 2147483648); - reg.set(0); - } +fn ConditionalFieldTestReg(comptime condition: ConditionalFieldTestRegEnum) type { + return struct { + const Self = @This(); + pub const ValueType = u8; + const ParentType = RegisterBase(Self, ValueType, void); + pub const printer_enabled: bool = ParentType.PrinterEnabled; - // Test with u64 - { - var value: u64 = 0; - var reg = BitfieldRef(u64).init(&value, 63, 0); - try expect(reg.get() == 0); + base: ParentType = .{}, - reg.set(1); - try expect(value == 1); - try expect(reg.get() == 1); - reg.set(0); + const kA = condition == ConditionalFieldTestRegEnum.kA; + const kB = condition == ConditionalFieldTestRegEnum.kB; + const kC = condition == ConditionalFieldTestRegEnum.kC; - reg.set(2); - try expect(value == 2); - try expect(reg.get() == 2); - reg.set(0); + // Conditional kA fields + pub const A7 = if (kA) DefBit(Self, 7, "a_7") else void; + pub const A6_4 = if (kA) DefField(Self, 6, 4, "a_6_4") else void; + pub const ARsvdzBit3 = if (kA) DefRsvdzBit(Self, 3) else void; - reg.set(9223372036854775808); - try expect(value == 9223372036854775808); - try expect(reg.get() == 9223372036854775808); - reg.set(0); - } -} + // Conditional kB fields + pub const B7_5 = if (kB) DefUnshiftedField(Self, 7, 5, "b_7_5") else void; + pub const BRsvdzField4_3 = if (kB) DefRsvdzField(Self, 4, 3) else void; -test "struct sub field test" { - const StructSubFieldTestReg = struct { - field1: u32, - field2: u32, - field3: u32, + // Conditional kC fields + const Rsvp = enum(u8) { + kNo = 0, + kYes = 0b1111, + }; + pub const C7_4 = if (kC) DefEnumField(Self, Rsvp, 7, 4, "c_7_4") else void; + pub const C3 = if (kC) DefBit(Self, 3, "c_3") else void; - pub fn wholeLength(self: *const @This()) u32 { - return BitfieldRef(u32).init(@constCast(&self.field1), 31, 0).get(); - } + // Unconditional, common field + pub const Common = DefField(Self, 2, 0, "common"); - pub fn setWholeLength(self: *@This(), val: u32) void { - BitfieldRef(u32).init(&self.field1, 31, 0).set(val); + pub fn init() Self { + var self = Self{ .base = .{} }; + if (kA) { + A7.init(&self); + A6_4.init(&self); + ARsvdzBit3.init(&self); + } + if (kB) { + B7_5.init(&self); + BRsvdzField4_3.init(&self); + } + if (kC) { + C7_4.init(&self); + C3.init(&self); + } + Common.init(&self); + return self; } - pub fn singleBit(self: *const @This()) u32 { - return BitfieldRef(u32).init(@constCast(&self.field2), 2, 2).get(); + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(0); } - pub fn setSingleBit(self: *@This(), val: u32) void { - BitfieldRef(u32).init(&self.field2, 2, 2).set(val); + pub fn a7(self: *Self) ValueType { + comptime std.debug.assert(kA); + return A7.get(self); } - pub fn range1(self: *const @This()) u32 { - return BitfieldRef(u32).init(@constCast(&self.field3), 2, 1).get(); + pub fn setA7(self: *Self, value: ValueType) *Self { + comptime std.debug.assert(kA); + A7.set(self, value); + return self; } - pub fn setRange1(self: *@This(), val: u32) void { - BitfieldRef(u32).init(&self.field3, 2, 1).set(val); + pub fn a6_4(self: *Self) ValueType { + comptime std.debug.assert(kA); + return A6_4.get(self); } - pub fn range2(self: *const @This()) u32 { - return BitfieldRef(u32).init(@constCast(&self.field3), 5, 3).get(); + pub fn setA6_4(self: *Self, value: ValueType) *Self { + comptime std.debug.assert(kA); + A6_4.set(self, value); + return self; } - pub fn setRange2(self: *@This(), val: u32) void { - BitfieldRef(u32).init(&self.field3, 5, 3).set(val); + pub fn b7_5(self: *Self) ValueType { + comptime std.debug.assert(kB); + return B7_5.get(self); } - }; - - var val = StructSubFieldTestReg{ - .field1 = 0, - .field2 = 0, - .field3 = 0, - }; - - // Test whole length field - try expect(val.wholeLength() == 0); - val.setWholeLength(std.math.maxInt(u32)); - try expect(val.wholeLength() == std.math.maxInt(u32)); - try expect(val.field1 == std.math.maxInt(u32)); - val.setWholeLength(0); - try expect(val.wholeLength() == 0); - try expect(val.field1 == 0); - - // Test single bit field - try expect(val.singleBit() == 0); - val.setSingleBit(1); - try expect(val.singleBit() == 1); - try expect(val.field2 == 4); - val.setSingleBit(0); - try expect(val.singleBit() == 0); - try expect(val.field2 == 0); - - // Test adjacent fields - try expect(val.range1() == 0); - try expect(val.range2() == 0); - val.setRange1(3); - try expect(val.range1() == 3); - try expect(val.range2() == 0); - try expect(val.field3 == 3 << 1); - val.setRange2(1); - try expect(val.range1() == 3); - try expect(val.range2() == 1); - try expect(val.field3 == (3 << 1) | (1 << 3)); - val.setRange2(2); - try expect(val.range1() == 3); - try expect(val.range2() == 2); - try expect(val.field3 == (3 << 1) | (2 << 3)); - val.setRange1(0); - try expect(val.range1() == 0); - try expect(val.range2() == 2); - try expect(val.field3 == (2 << 3)); -} - -test "enum subfield test" { - const StructEnumSubFieldTestReg = struct { - const Self = @This(); - - const EnumWholeRange = enum(u32) { - zero = 0, - one = 1, - max = std.math.maxInt(u32), - }; - - const EnumBit = enum(u8) { - zero = 0, - one = 1, - }; - const EnumRange = enum(u32) { - zero = 0, - one = 1, - two = 2, - three = 3, - }; - - field1: u32, - field2: u32, - field3: u32, + pub fn setB7_5(self: *Self, value: ValueType) *Self { + comptime std.debug.assert(kB); + B7_5.set(self, value); + return self; + } - pub fn wholeLength(self: *const Self) EnumWholeRange { - return @enumFromInt(BitfieldRef(u32).init(@constCast(&self.field1), 31, 0).get()); + pub fn c7_4(self: *Self) Rsvp { + comptime std.debug.assert(kC); + return C7_4.get(self); } - pub fn setWholeLength(self: *Self, val: EnumWholeRange) void { - BitfieldRef(u32).init(&self.field1, 31, 0).set(@intFromEnum(val)); + pub fn setC7_4(self: *Self, value: Rsvp) *Self { + comptime std.debug.assert(kC); + C7_4.set(self, value); + return self; } - pub fn singleBit(self: *const Self) EnumBit { - return @enumFromInt(BitfieldRef(u32).init(@constCast(&self.field2), 2, 2).get()); + pub fn c3(self: *Self) ValueType { + comptime std.debug.assert(kC); + return C3.get(self); } - pub fn setSingleBit(self: *Self, val: EnumBit) void { - BitfieldRef(u32).init(&self.field2, 2, 2).set(@intFromEnum(val)); + pub fn setC3(self: *Self, value: ValueType) *Self { + comptime std.debug.assert(kC); + C3.set(self, value); + return self; } - pub fn range1(self: *const Self) EnumRange { - return @enumFromInt(BitfieldRef(u32).init(@constCast(&self.field3), 2, 1).get()); + pub fn common(self: *Self) ValueType { + return Common.get(self); } - pub fn setRange1(self: *Self, val: EnumRange) void { - BitfieldRef(u32).init(&self.field3, 2, 1).set(@intFromEnum(val)); + pub fn setCommon(self: *Self, value: ValueType) *Self { + Common.set(self, value); + return self; } - pub fn range2(self: *const Self) EnumRange { - return @enumFromInt(BitfieldRef(u32).init(@constCast(&self.field3), 5, 3).get()); + pub fn readFrom(self: *Self, reg_io: anytype) *Self { + _ = self.base.readFrom(reg_io); + return self; } - pub fn setRange2(self: *Self, val: EnumRange) void { - BitfieldRef(u32).init(&self.field3, 5, 3).set(@intFromEnum(val)); + pub fn writeTo(self: *Self, reg_io: anytype) *Self { + _ = self.base.writeTo(reg_io); + return self; } - }; - var val = StructEnumSubFieldTestReg{ - .field1 = 0, - .field2 = 0, - .field3 = 0, + pub fn regValue(self: *const Self) ValueType { + return self.base.regValue(); + } }; - - // Test whole length field - try expect(val.wholeLength() == .zero); - val.setWholeLength(.max); - try expect(val.wholeLength() == .max); - try expect(val.field1 == std.math.maxInt(u32)); - val.setWholeLength(.zero); - try expect(val.wholeLength() == .zero); - try expect(val.field1 == 0); - - // Test single bit field - try expect(val.singleBit() == .zero); - val.setSingleBit(.one); - try expect(val.singleBit() == .one); - try expect(val.field2 == 4); - val.setSingleBit(.zero); - try expect(val.singleBit() == .zero); - try expect(val.field2 == 0); - - // Test adjacent fields - try expect(val.range1() == .zero); - try expect(val.range2() == .zero); - val.setRange1(.three); - try expect(val.range1() == .three); - try expect(val.range2() == .zero); - try expect(val.field3 == 3 << 1); - val.setRange2(.one); - try expect(val.range1() == .three); - try expect(val.range2() == .one); - try expect(val.field3 == (3 << 1) | (1 << 3)); - val.setRange2(.two); - try expect(val.range1() == .three); - try expect(val.range2() == .two); - try expect(val.field3 == (3 << 1) | (2 << 3)); - val.setRange1(.zero); - try expect(val.range1() == .zero); - try expect(val.range2() == .two); - try expect(val.field3 == (2 << 3)); } -test "unshifted fields" { - const UnshiftedFieldTestReg = struct { +fn ConditionalFieldsWithSameNameTestReg(comptime condition: bool) type { + return struct { const Self = @This(); + pub const ValueType = u16; + const ParentType = RegisterBase(Self, ValueType, void); + pub const printer_enabled: bool = ParentType.PrinterEnabled; - data: u16, + base: ParentType = .{}, - pub fn field1(self: *const Self) u16 { - return BitfieldRef(u16).initUnshifted(@constCast(&self.data), 15, 12).get(); - } + const Enum = enum(u8) { + kA = 0b00, + kB = 0b11, + }; - pub fn setField1(self: *Self, val: u16) void { - BitfieldRef(u16).initUnshifted(&self.data, 15, 12).set(val); - } + pub const A = if (condition) DefBit(Self, 15, "a") else DefBit(Self, 0, "a"); + pub const B = if (condition) DefField(Self, 14, 12, "b") else DefField(Self, 3, 1, "b"); + pub const C = if (condition) DefUnshiftedField(Self, 11, 10, "c") else DefUnshiftedField(Self, 5, 4, "c"); + pub const D = if (condition) DefEnumField(Self, Enum, 9, 8, "d") else DefEnumField(Self, Enum, 7, 6, "d"); - pub fn field2(self: *const Self) u16 { - return BitfieldRef(u16).initUnshifted(@constCast(&self.data), 11, 8).get(); + pub fn init() Self { + var self = Self{ .base = .{} }; + A.init(&self); + B.init(&self); + C.init(&self); + D.init(&self); + return self; } - pub fn setField2(self: *Self, val: u16) void { - BitfieldRef(u16).initUnshifted(&self.data, 11, 8).set(val); + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(0); } - pub fn field3(self: *const Self) u16 { - return BitfieldRef(u16).initUnshifted(@constCast(&self.data), 7, 4).get(); + pub fn a(self: *Self) ValueType { + return A.get(self); } - pub fn setField3(self: *Self, val: u16) void { - BitfieldRef(u16).initUnshifted(&self.data, 7, 4).set(val); + pub fn setA(self: *Self, value: ValueType) *Self { + A.set(self, value); + return self; } - pub fn field4(self: *const Self) u16 { - return BitfieldRef(u16).initUnshifted(@constCast(&self.data), 3, 0).get(); + pub fn b(self: *Self) ValueType { + return B.get(self); } - pub fn setField4(self: *Self, val: u16) void { - BitfieldRef(u16).initUnshifted(&self.data, 3, 0).set(val); + pub fn setB(self: *Self, value: ValueType) *Self { + B.set(self, value); + return self; } - }; - - // Test simple field isolation - { - var test_reg = UnshiftedFieldTestReg{ .data = 0xffff }; - try expect(test_reg.field1() == 0xf000); - try expect(test_reg.field2() == 0x0f00); - try expect(test_reg.field3() == 0x00f0); - try expect(test_reg.field4() == 0x000f); - } - - // Test assignment - { - var test_reg = UnshiftedFieldTestReg{ .data = 0x0 }; - try expect(test_reg.field1() == 0); - try expect(test_reg.field2() == 0); - try expect(test_reg.field3() == 0); - try expect(test_reg.field4() == 0); - - test_reg.setField1(0xf000); - try expect(test_reg.field1() == 0xf000); - try expect(test_reg.field2() == 0); - try expect(test_reg.field3() == 0); - try expect(test_reg.field4() == 0); - - test_reg.setField2(0xf00); - try expect(test_reg.field1() == 0xf000); - try expect(test_reg.field2() == 0xf00); - try expect(test_reg.field3() == 0); - try expect(test_reg.field4() == 0); - - test_reg.setField3(0xf0); - try expect(test_reg.field1() == 0xf000); - try expect(test_reg.field2() == 0xf00); - try expect(test_reg.field3() == 0xf0); - try expect(test_reg.field4() == 0); - - test_reg.setField4(0xf); - try expect(test_reg.field1() == 0xf000); - try expect(test_reg.field2() == 0xf00); - try expect(test_reg.field3() == 0xf0); - try expect(test_reg.field4() == 0xf); - } -} - -test "RsvdZ partial test" { - const RsvdZPartialTestReg8 = struct { - const Self = @This(); - data: u8 = 0, - - pub fn rsvdZField(self: *const Self) u8 { - return BitfieldRef(u8).init(@constCast(&self.data), 7, 3).get(); + pub fn c(self: *Self) ValueType { + return C.get(self); } - pub fn setRsvdZField(self: *Self, val: u8) void { - BitfieldRef(u8).init(&self.data, 7, 3).set(val); + pub fn setC(self: *Self, value: ValueType) *Self { + C.set(self, value); + return self; } - pub fn writeMasked(self: *Self) void { - // Mask off the RsvdZ bits (set them to 0) - self.data &= 0x07; // Keep only bits 2:0 + pub fn d(self: *Self) Enum { + return D.get(self); } - }; - - const RsvdZPartialTestReg16 = struct { - const Self = @This(); - data: u16 = 0, + pub fn setD(self: *Self, value: Enum) *Self { + D.set(self, value); + return self; + } - pub fn rsvdZField(self: *const Self) u16 { - return BitfieldRef(u16).init(@constCast(&self.data), 14, 1).get(); + pub fn readFrom(self: *Self, reg_io: anytype) *Self { + _ = self.base.readFrom(reg_io); + return self; } - pub fn setRsvdZField(self: *Self, val: u16) void { - BitfieldRef(u16).init(&self.data, 14, 1).set(val); + pub fn writeTo(self: *Self, reg_io: anytype) *Self { + _ = self.base.writeTo(reg_io); + return self; } - pub fn writeMasked(self: *Self) void { - // Mask off the RsvdZ bits (set them to 0) - self.data &= 0x8001; // Keep only bits 15 and 0 + pub fn regValue(self: *const Self) ValueType { + return self.base.regValue(); } }; +} - const RsvdZPartialTestReg32 = struct { +fn RegisterWithConditional(comptime variant: u32) type { + comptime std.debug.assert(variant < 15 and variant > 10); + + return struct { const Self = @This(); + pub const ValueType = u32; + const ParentType = RegisterBase(Self, ValueType, void); + pub const printer_enabled: bool = ParentType.PrinterEnabled; - data: u32 = 0, + base: ParentType = .{}, - pub fn rsvdZField1(self: *const Self) u32 { - return BitfieldRef(u32).init(@constCast(&self.data), 31, 12).get(); - } + //pub const Xmit = if (variant == 14) DefBit(Self, 14, "xmit") else if (variant == 13) DefBit(Self, 13, "xmit") else if (variant == 12) DefBit(Self, 12, "xmit") else if (variant == 11) DefBit(Self, 11, "xmit") else void; + pub const Xmit = DefCondBit(Self, variant, "xmit", true); + pub const RsvdzField = DefRsvdzField(Self, 31, variant + 1); + pub const A = DefBit(Self, 4, "a"); + pub const B = DefBit(Self, 3, "b"); + pub const C = DefBit(Self, 2, "c"); + pub const D = DefBit(Self, 1, "d"); + pub const E = DefBit(Self, 0, "e"); - pub fn setRsvdZField1(self: *Self, val: u32) void { - BitfieldRef(u32).init(&self.data, 31, 12).set(val); + pub fn init() Self { + var self = Self{ .base = .{} }; + if (variant >= 11 and variant <= 14) { + Xmit.init(&self); + } + RsvdzField.init(&self); + A.init(&self); + B.init(&self); + C.init(&self); + D.init(&self); + E.init(&self); + return self; } - pub fn rsvdZField2(self: *const Self) u32 { - return BitfieldRef(u32).init(@constCast(&self.data), 10, 5).get(); + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(0); } - pub fn setRsvdZField2(self: *Self, val: u32) void { - BitfieldRef(u32).init(&self.data, 10, 5).set(val); + pub fn fromValue(value: ValueType) Self { + var self = Self.init(); + self.base.setRegValue(value); + return self; } - pub fn rsvdZBit(self: *const Self) u32 { - return BitfieldRef(u32).init(@constCast(&self.data), 3, 3).get(); + pub fn xmit(self: *Self) ValueType { + return Xmit.get(self); } - pub fn setRsvdZBit(self: *Self, val: u32) void { - BitfieldRef(u32).init(&self.data, 3, 3).set(val); + pub fn setXmit(self: *Self, value: ValueType) *Self { + Xmit.set(self, value); + return self; } - pub fn writeMasked(self: *Self) void { - // Mask off the RsvdZ bits (set them to 0) - self.data &= (1 << 11) | 0x17; // Keep only bits 11 and 4:0 + pub fn a(self: *Self) ValueType { + return A.get(self); } - }; - - const RsvdZPartialTestReg64 = struct { - const Self = @This(); - data: u64 = 0, - - pub fn rsvdZField1(self: *const Self) u64 { - return BitfieldRef(u64).init(@constCast(&self.data), 63, 18).get(); + pub fn setA(self: *Self, value: ValueType) *Self { + A.set(self, value); + return self; } - pub fn setRsvdZField1(self: *Self, val: u64) void { - BitfieldRef(u64).init(&self.data, 63, 18).set(val); + pub fn b(self: *Self) ValueType { + return B.get(self); } - pub fn rsvdZField2(self: *const Self) u64 { - return BitfieldRef(u64).init(@constCast(&self.data), 10, 0).get(); + pub fn setB(self: *Self, value: ValueType) *Self { + B.set(self, value); + return self; } - pub fn setRsvdZField2(self: *Self, val: u64) void { - BitfieldRef(u64).init(&self.data, 10, 0).set(val); + pub fn c(self: *Self) ValueType { + return C.get(self); } - pub fn writeMasked(self: *Self) void { - // Mask off the RsvdZ bits (set them to 0) - self.data &= 0x7f << 11; // Keep only bits 17:11 + pub fn setC(self: *Self, value: ValueType) *Self { + C.set(self, value); + return self; } - }; - - // Test 8-bit register - { - var reg = RsvdZPartialTestReg8{ .data = std.math.maxInt(u8) }; - try expect(reg.data == std.math.maxInt(u8)); - reg.writeMasked(); - try expect(reg.data == 0x7); - } - // Test 16-bit register - { - var reg = RsvdZPartialTestReg16{ .data = std.math.maxInt(u16) }; - try expect(reg.data == std.math.maxInt(u16)); - reg.writeMasked(); - try expect(reg.data == 0x8001); - } - - // Test 32-bit register - { - var reg = RsvdZPartialTestReg32{ .data = std.math.maxInt(u32) }; - try expect(reg.data == std.math.maxInt(u32)); - reg.writeMasked(); - try expect(reg.data == (1 << 11) | 0x17); - } + pub fn d(self: *Self) ValueType { + return D.get(self); + } - // Test 64-bit register - { - var reg = RsvdZPartialTestReg64{ .data = std.math.maxInt(u64) }; - try expect(reg.data == std.math.maxInt(u64)); - reg.writeMasked(); - try expect(reg.data == 0x7f << 11); - } -} + pub fn setD(self: *Self, value: ValueType) *Self { + D.set(self, value); + return self; + } -test "RsvdZ full test" { - const RsvdZFullTestReg8 = struct { - const Self = @This(); + pub fn e(self: *Self) ValueType { + return E.get(self); + } - data: u8 = 0, + pub fn setE(self: *Self, value: ValueType) *Self { + E.set(self, value); + return self; + } - pub fn rsvdZField(self: *const Self) u8 { - return BitfieldRef(u8).init(@constCast(&self.data), 7, 0).get(); + pub fn readFrom(self: *Self, reg_io: anytype) *Self { + _ = self.base.readFrom(reg_io); + return self; } - pub fn setRsvdZField(self: *Self, val: u8) void { - BitfieldRef(u8).init(&self.data, 7, 0).set(val); + pub fn writeTo(self: *Self, reg_io: anytype) *Self { + _ = self.base.writeTo(reg_io); + return self; } - pub fn writeMasked(self: *Self) void { - // Mask off all bits (set them to 0) - self.data = 0; + pub fn regValue(self: *const Self) ValueType { + return self.base.regValue(); } }; +} - const RsvdZFullTestReg16 = struct { - const Self = @This(); +test "ConditionalFields" { + { + const RegA = ConditionalFieldTestReg(ConditionalFieldTestRegEnum.kA); - data: u16 = 0, + var fake_reg: u8 = 0xff; + var mmio_reg = mmio.RegisterMmio.init(&fake_reg); - pub fn rsvdZField(self: *const Self) u16 { - return BitfieldRef(u16).init(@constCast(&self.data), 15, 0).get(); - } + var reg = RegA.get().readFrom(&mmio_reg); + try testing.expect(0b1 == reg.a7()); + try testing.expect(0b111 == reg.a6_4()); + try testing.expect(0b111 == reg.common()); + _ = reg.writeTo(&mmio_reg).readFrom(&mmio_reg); + try testing.expect(0b11110111 == reg.regValue()); + } - pub fn setRsvdZField(self: *Self, val: u16) void { - BitfieldRef(u16).init(&self.data, 15, 0).set(val); - } + { + const RegB = ConditionalFieldTestReg(ConditionalFieldTestRegEnum.kB); - pub fn writeMasked(self: *Self) void { - // Mask off all bits (set them to 0) - self.data = 0; - } - }; + var fake_reg: u8 = 0xff; + var mmio_reg = mmio.RegisterMmio.init(&fake_reg); - const RsvdZFullTestReg32 = struct { - const Self = @This(); + var reg = RegB.get().readFrom(&mmio_reg); + try testing.expect(0b11100000 == reg.b7_5()); + try testing.expect(0b111 == reg.common()); + _ = reg.writeTo(&mmio_reg).readFrom(&mmio_reg); + try testing.expect(0b11100111 == reg.regValue()); + } - data: u32 = 0, + { + const RegC = ConditionalFieldTestReg(ConditionalFieldTestRegEnum.kC); - pub fn rsvdZField(self: *const Self) u32 { - return BitfieldRef(u32).init(@constCast(&self.data), 31, 0).get(); - } + var fake_reg: u16 = 0xffff; + var mmio_reg = mmio.RegisterMmio.init(&fake_reg); - pub fn setRsvdZField(self: *Self, val: u32) void { - BitfieldRef(u32).init(&self.data, 31, 0).set(val); - } + var reg = RegC.get().readFrom(&mmio_reg); + try testing.expect(RegC.Rsvp.kYes == reg.c7_4()); + try testing.expect(0b1 == reg.c3()); + try testing.expect(0b111 == reg.common()); + _ = reg.writeTo(&mmio_reg).readFrom(&mmio_reg); + try testing.expect(0xff == reg.regValue()); + } - pub fn writeMasked(self: *Self) void { - // Mask off all bits (set them to 0) - self.data = 0; - } - }; + { + const Reg = ConditionalFieldsWithSameNameTestReg(true); - const RsvdZFullTestReg64 = struct { - const Self = @This(); + var fake_reg: u16 = 0xffff; + var mmio_reg = mmio.RegisterMmio.init(&fake_reg); - data: u64 = 0, + var reg = Reg.get().readFrom(&mmio_reg); + _ = reg.setA(0).setB(0).setC(0x0c00).setD(Reg.Enum.kA).writeTo(&mmio_reg); + const expected_reg: u16 = 0x0cff; + try testing.expect(expected_reg == fake_reg); + } - pub fn rsvdZField(self: *const Self) u64 { - return BitfieldRef(u64).init(@constCast(&self.data), 63, 0).get(); - } + { + const Reg = ConditionalFieldsWithSameNameTestReg(false); - pub fn setRsvdZField(self: *Self, val: u64) void { - BitfieldRef(u64).init(&self.data, 63, 0).set(val); - } + var fake_reg: u16 = 0xffff; + var mmio_reg = mmio.RegisterMmio.init(&fake_reg); - pub fn writeMasked(self: *Self) void { - // Mask off all bits (set them to 0) - self.data = 0; - } - }; + var reg = Reg.get().readFrom(&mmio_reg); + _ = reg.setA(0).setB(0).setC(0x0030).setD(Reg.Enum.kA).writeTo(&mmio_reg); + const expected_reg: u16 = 0xff30; + try testing.expect(fake_reg == expected_reg); + } - // Test 8-bit register + // Check conditionals work as expected, depending on the condition, the right + // bit should be set. + // 14 -> false + // 12 -> true { - var reg = RsvdZFullTestReg8{ .data = std.math.maxInt(u8) }; - try expect(reg.data == std.math.maxInt(u8)); - reg.writeMasked(); - try expect(reg.data == 0); + var fake_reg: u16 = 0; + var mmio_reg = mmio.RegisterMmio.init(&fake_reg); + var reg = RegisterWithConditional(14).get().fromValue(0); + _ = reg.setXmit(1).writeTo(&mmio_reg); + try testing.expect((fake_reg & (@as(u16, 1) << 14)) != 0); } - // Test 16-bit register { - var reg = RsvdZFullTestReg16{ .data = std.math.maxInt(u16) }; - try expect(reg.data == std.math.maxInt(u16)); - reg.writeMasked(); - try expect(reg.data == 0); + var fake_reg: u16 = 0; + var mmio_reg = mmio.RegisterMmio.init(&fake_reg); + var reg = RegisterWithConditional(13).get().fromValue(0); + _ = reg.setXmit(1).writeTo(&mmio_reg); + try testing.expect((fake_reg & (@as(u16, 1) << 13)) != 0); } - // Test 32-bit register { - var reg = RsvdZFullTestReg32{ .data = std.math.maxInt(u32) }; - try expect(reg.data == std.math.maxInt(u32)); - reg.writeMasked(); - try expect(reg.data == 0); + var fake_reg: u16 = 0; + var mmio_reg = mmio.RegisterMmio.init(&fake_reg); + var reg = RegisterWithConditional(12).get().fromValue(0); + _ = reg.setXmit(1).writeTo(&mmio_reg); + try testing.expect((fake_reg & (@as(u16, 1) << 12)) != 0); } - // Test 64-bit register { - var reg = RsvdZFullTestReg64{ .data = std.math.maxInt(u64) }; - try expect(reg.data == std.math.maxInt(u64)); - reg.writeMasked(); - try expect(reg.data == 0); + var fake_reg: u16 = 0; + var mmio_reg = mmio.RegisterMmio.init(&fake_reg); + var reg = RegisterWithConditional(11).get().fromValue(0); + _ = reg.setXmit(1).writeTo(&mmio_reg); + try testing.expect((fake_reg & (@as(u16, 1) << 11)) != 0); } } @@ -1065,7 +2566,7 @@ fn TemplatedReg(comptime N: u32) type { }; } -test "Templated register" { +test "Templated" { const TestMmio = struct { fake_reg: u32, @@ -1080,28 +2581,28 @@ test "Templated register" { } }; - var mmio = TestMmio{ .fake_reg = 0xffff_ffff }; + var mmio_io = TestMmio{ .fake_reg = 0xffff_ffff }; { - var reg = TemplatedReg(0).get().readFrom(&mmio); - try expect(reg.head() == 0b1); + var reg = TemplatedReg(0).get().readFrom(&mmio_io); + try testing.expect(reg.head() == 0b1); } { - var reg = TemplatedReg(4).get().readFrom(&mmio); - try expect(reg.head() == 0b11111); + var reg = TemplatedReg(4).get().readFrom(&mmio_io); + try testing.expect(reg.head() == 0b11111); } { - var reg = TemplatedReg(31).get().readFrom(&mmio); - try expect(reg.head() == 0xffff_ffff); + var reg = TemplatedReg(31).get().readFrom(&mmio_io); + try testing.expect(reg.head() == 0xffff_ffff); } { - var reg = TemplatedReg(1).get().readFrom(&mmio); + var reg = TemplatedReg(1).get().readFrom(&mmio_io); _ = reg.setHead(0); - _ = reg.writeTo(&mmio); - try expect(reg.head() == 0); + _ = reg.writeTo(&mmio_io); + try testing.expect(reg.head() == 0); } } @@ -1269,10 +2770,10 @@ test "Print" { }; const kInitVal: u32 = 0xe9872fff; - var mmio = TestMmio{ .fake_reg = kInitVal }; + var mmio_io = TestMmio{ .fake_reg = kInitVal }; { - var reg = PrintableTestReg.get().readFrom(&mmio); + var reg = PrintableTestReg.get().readFrom(&mmio_io); //var call_count: u32 = 0; const expected = [_][]const u8{ "RsvdZ[31:31]: 0x1 (1)", @@ -1293,7 +2794,7 @@ test "Print" { } { - var reg = PrintableTestReg2.get().readFrom(&mmio); + var reg = PrintableTestReg2.get().readFrom(&mmio_io); //var call_count: u32 = 0; const expected = [_][]const u8{ "field1[30:21]: 0x34c (844)", @@ -1313,7 +2814,7 @@ test "Print" { } } -test "Register with variant IO" { +test "Variant" { const FakeIo = struct { pub fn write(self: @This(), comptime IntType: type, value: IntType, offset: u32) void { _ = self; @@ -1330,45 +2831,39 @@ test "Register with variant IO" { const TestRegForVariantIo = struct { const Self = @This(); - const RegBase = RegisterBase(Self, u64, void); pub const ValueType = u64; - pub const printer_enabled: bool = RegBase.PrinterEnabled; + const ParentType = RegisterBase(Self, ValueType, void); + pub const printer_enabled: bool = ParentType.PrinterEnabled; - reg_base: RegBase, + base: ParentType, + const ValueField = DefField(Self, 63, 0, "value"); pub fn get() RegisterAddr(Self) { return RegisterAddr(Self).init(0); } pub fn init() Self { - var self = Self{ .reg_base = .{} }; - internal.Field(Self, void, true).init(self.reg_base.getParams(), "value", 63, 0); - return self; - } - - pub fn valueGet(self: *Self) u64 { - return BitfieldRef(u64).init(self.reg_base.regValuePtr(), 63, 0).get(); - } - - pub fn valueSet(self: *Self, value: u64) *Self { - BitfieldRef(u64).init(self.reg_base.regValuePtr(), 63, 0).set(value); + var self = Self{ .base = .{} }; + ValueField.init(&self); return self; } pub fn readFrom(self: *Self, reg_io: anytype) *Self { - return self.reg_base.readFrom(reg_io); + _ = self.base.readFrom(reg_io); + return self; } pub fn writeTo(self: *Self, reg_io: anytype) *Self { - return self.reg_base.writeTo(reg_io); + _ = self.base.writeTo(reg_io); + return self; } pub fn setRegAddr(self: *Self, addr: u32) void { - self.reg_base.setRegAddr(addr); + self.base.setRegAddr(addr); } pub fn setRegValue(self: *Self, value: u64) *Self { - return self.reg_base.setRegValue(value); + return self.base.setRegValue(value); } }; @@ -1381,8 +2876,8 @@ test "Register with variant IO" { // Test with FakeIo { var reg = TestRegForVariantIo.get().readFrom(&io); - try expect(reg.valueGet() == 23); - _ = reg.valueSet(17); + try std.testing.expect(TestRegForVariantIo.ValueField.get(®) == 23); + _ = TestRegForVariantIo.ValueField.set(®, 17); _ = reg.writeTo(&io); } @@ -1395,9 +2890,9 @@ test "Register with variant IO" { _ = mock.expectRead(u64, 17, 0).expectWrite(u64, 23, 1); var reg = TestRegForVariantIo.get().readFrom(&io); - try expect(reg.valueGet() == 17); + try std.testing.expect(TestRegForVariantIo.ValueField.get(®) == 17); reg.setRegAddr(1); - _ = reg.valueSet(23); + TestRegForVariantIo.ValueField.set(®, 23); _ = reg.writeTo(&io); mock.verifyAndClear(); } diff --git a/slipstream/system/ulib/hwreg/src/internal.zig b/slipstream/system/ulib/hwreg/src/internal.zig index a03b6ed..a3136d3 100644 --- a/slipstream/system/ulib/hwreg/src/internal.zig +++ b/slipstream/system/ulib/hwreg/src/internal.zig @@ -4,6 +4,21 @@ const std = @import("std"); +// Internal fn for checking the type and range of a subfield definition. +pub fn subFieldCheck(comptime FieldType: type, comptime bit_high: u32, comptime bit_low: u32) void { + comptime { + if (!isSupportedInt(FieldType)) { + @compileError("Unsupported field type: " ++ @typeName(FieldType)); + } + if (bit_high < bit_low) { + @compileError("Upper bit goes before lower bit"); + } + if (bit_high >= @bitSizeOf(FieldType)) { + @compileError("Upper bit is out of range"); + } + } +} + /// Check if a type is a supported integer type for hwreg operations pub fn isSupportedInt(comptime T: type) bool { return switch (T) { From 53fbffea603a7bba67dfe87dd296bb1002891b6c Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Thu, 5 Jun 2025 14:56:43 -0300 Subject: [PATCH 14/41] [ulib][hwreg] Export internal impl --- slipstream/system/ulib/hwreg/src/root.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/slipstream/system/ulib/hwreg/src/root.zig b/slipstream/system/ulib/hwreg/src/root.zig index c11a9d3..a1c15aa 100644 --- a/slipstream/system/ulib/hwreg/src/root.zig +++ b/slipstream/system/ulib/hwreg/src/root.zig @@ -3,6 +3,7 @@ //! found in the LICENSE file. pub const bitfields = @import("bitfields.zig"); +pub const internal = @import("internal.zig"); pub const Mock = @import("mock.zig"); comptime { From a75ec6932e13eda19463594141766b9b33f8bae9 Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Fri, 6 Jun 2025 16:13:37 -0300 Subject: [PATCH 15/41] [ulib][hwreg] Small refactor --- .../system/ulib/hwreg/src/bitfields.zig | 88 ++++++++----------- slipstream/system/ulib/hwreg/src/mock.zig | 10 ++- 2 files changed, 41 insertions(+), 57 deletions(-) diff --git a/slipstream/system/ulib/hwreg/src/bitfields.zig b/slipstream/system/ulib/hwreg/src/bitfields.zig index 947a893..e4aba0b 100644 --- a/slipstream/system/ulib/hwreg/src/bitfields.zig +++ b/slipstream/system/ulib/hwreg/src/bitfields.zig @@ -1100,7 +1100,7 @@ test "UnshifedFields" { } test "RsvdzPartial" { - const RsvdZPartialTestReg8 = struct { + const RsvdzPartialTestReg8 = struct { const Self = @This(); pub const ValueType = u8; const ParentType = RegisterBase(Self, ValueType, void); @@ -1109,11 +1109,11 @@ test "RsvdzPartial" { base: ParentType = undefined, // Comptime field definitions - pub const RsvdZField = DefRsvdzField(Self, 7, 3); + pub const RsvdzField = DefRsvdzField(Self, 7, 3); pub fn init() Self { var self = Self{ .base = .{} }; - RsvdZField.init(&self); + RsvdzField.init(&self); return self; } @@ -1122,16 +1122,16 @@ test "RsvdzPartial" { } pub fn rsvdZField(self: *Self) ValueType { - return RsvdZField.get(self); + return RsvdzField.get(self); } pub fn setRsvdZField(self: *Self, value: ValueType) *Self { - RsvdZField.set(self, value); + RsvdzField.set(self, value); return self; } }; - const RsvdZPartialTestReg16 = struct { + const RsvdzPartialTestReg16 = struct { const Self = @This(); pub const ValueType = u16; const ParentType = RegisterBase(Self, ValueType, void); @@ -1140,29 +1140,20 @@ test "RsvdzPartial" { base: ParentType = undefined, // Comptime field definitions - pub const RsvdZField = DefRsvdzField(Self, 14, 1); + pub const RsvdzField = DefRsvdzField(Self, 14, 1); pub fn init() Self { var self = Self{ .base = .{} }; - RsvdZField.init(&self); + RsvdzField.init(&self); return self; } pub fn get() RegisterAddr(Self) { return RegisterAddr(Self).init(0); } - - pub fn rsvdZField(self: *Self) ValueType { - return RsvdZField.get(self); - } - - pub fn setRsvdZField(self: *Self, value: ValueType) *Self { - RsvdZField.set(self, value); - return self; - } }; - const RsvdZPartialTestReg32 = struct { + const RsvdzPartialTestReg32 = struct { const Self = @This(); pub const ValueType = u32; const ParentType = RegisterBase(Self, ValueType, void); @@ -1171,15 +1162,15 @@ test "RsvdzPartial" { base: ParentType = undefined, // Comptime field definitions - pub const RsvdZField1 = DefRsvdzField(Self, 31, 12); - pub const RsvdZField2 = DefRsvdzField(Self, 10, 5); - pub const RsvdZBit = DefRsvdzBit(Self, 3); + pub const RsvdzField1 = DefRsvdzField(Self, 31, 12); + pub const RsvdzField2 = DefRsvdzField(Self, 10, 5); + pub const RsvdzBit = DefRsvdzBit(Self, 3); pub fn init() Self { var self = Self{ .base = .{} }; - RsvdZField1.init(&self); - RsvdZField2.init(&self); - RsvdZBit.init(&self); + RsvdzField1.init(&self); + RsvdzField2.init(&self); + RsvdzBit.init(&self); return self; } @@ -1188,7 +1179,7 @@ test "RsvdzPartial" { } }; - const RsvdZPartialTestReg64 = struct { + const RsvdzPartialTestReg64 = struct { const Self = @This(); pub const ValueType = u64; const ParentType = RegisterBase(Self, ValueType, void); @@ -1219,18 +1210,18 @@ test "RsvdzPartial" { // what we read them as. { const allones = std.math.maxInt(u8); - var mock: Mock = Mock.init(std.testing.allocator); + var mock: Mock = Mock.init(); defer mock.deinit(); - mock.initRegisterIo(); + _ = mock.expectRead(u8, allones, 0).expectWrite(u8, 0x7, 0); - var reg = RsvdZPartialTestReg8.get().readFrom(mock.io()); + var reg = RsvdzPartialTestReg8.get().readFrom(mock.io()); try testing.expect(reg.base.regValue() == allones); _ = reg.base.writeTo(mock.io()); mock.verifyAndClear(); } { fake_reg = std.math.maxInt(u16); - var reg = RsvdZPartialTestReg16.get().readFrom(&mmio_reg); + var reg = RsvdzPartialTestReg16.get().readFrom(&mmio_reg); try testing.expect(reg.base.regValue() == std.math.maxInt(u16)); _ = reg.base.writeTo(&mmio_reg); const reg_value: u64 = fake_reg; @@ -1238,7 +1229,7 @@ test "RsvdzPartial" { } { fake_reg = std.math.maxInt(u32); - var reg = RsvdZPartialTestReg32.get().readFrom(&mmio_reg); + var reg = RsvdzPartialTestReg32.get().readFrom(&mmio_reg); try testing.expect(reg.base.regValue() == std.math.maxInt(u32)); _ = reg.base.writeTo(&mmio_reg); const reg_value: u64 = fake_reg; @@ -1246,7 +1237,7 @@ test "RsvdzPartial" { } { fake_reg = std.math.maxInt(u64); - var reg = RsvdZPartialTestReg64.get().readFrom(&mmio_reg); + var reg = RsvdzPartialTestReg64.get().readFrom(&mmio_reg); try testing.expect(reg.base.regValue() == std.math.maxInt(u64)); _ = reg.base.writeTo(&mmio_reg); const reg_value: u64 = fake_reg; @@ -1275,18 +1266,9 @@ test "RsvdzFull" { pub fn get() RegisterAddr(Self) { return RegisterAddr(Self).init(0); } - - pub fn rsvdZField(self: *Self) ValueType { - return RsvdZField.get(self); - } - - pub fn setRsvdZField(self: *Self, value: ValueType) *Self { - RsvdZField.set(self, value); - return self; - } }; - const RsvdZFullTestReg16 = struct { + const RsvdzFullTestReg16 = struct { const Self = @This(); pub const ValueType = u16; const ParentType = RegisterBase(Self, ValueType, void); @@ -1295,11 +1277,11 @@ test "RsvdzFull" { base: ParentType = undefined, // Comptime field definitions - pub const RsvdZField = DefRsvdzField(Self, 15, 0); + pub const RsvdzField = DefRsvdzField(Self, 15, 0); pub fn init() Self { var self = Self{ .base = .{} }; - RsvdZField.init(&self); + RsvdzField.init(&self); return self; } @@ -1308,7 +1290,7 @@ test "RsvdzFull" { } }; - const RsvdZFullTestReg32 = struct { + const RsvdzFullTestReg32 = struct { const Self = @This(); pub const ValueType = u32; const ParentType = RegisterBase(Self, ValueType, void); @@ -1317,11 +1299,11 @@ test "RsvdzFull" { base: ParentType = undefined, // Comptime field definitions - pub const RsvdZField = DefRsvdzField(Self, 31, 0); + pub const RsvdzField = DefRsvdzField(Self, 31, 0); pub fn init() Self { var self = Self{ .base = .{} }; - RsvdZField.init(&self); + RsvdzField.init(&self); return self; } @@ -1330,7 +1312,7 @@ test "RsvdzFull" { } }; - const RsvdZFullTestReg64 = struct { + const RsvdzFullTestReg64 = struct { const Self = @This(); pub const ValueType = u64; const ParentType = RegisterBase(Self, ValueType, void); @@ -1339,11 +1321,11 @@ test "RsvdzFull" { base: ParentType = undefined, // Comptime field definitions - pub const RsvdZField = DefRsvdzField(Self, 63, 0); + pub const RsvdzField = DefRsvdzField(Self, 63, 0); pub fn init() Self { var self = Self{ .base = .{} }; - RsvdZField.init(&self); + RsvdzField.init(&self); return self; } @@ -1366,7 +1348,7 @@ test "RsvdzFull" { { fake_reg = std.math.maxInt(u16); - var reg = RsvdZFullTestReg16.get().readFrom(&mmio_reg); + var reg = RsvdzFullTestReg16.get().readFrom(&mmio_reg); try testing.expect(reg.base.regValue() == std.math.maxInt(u16)); _ = reg.base.writeTo(&mmio_reg); const reg_value: u64 = fake_reg; @@ -1375,7 +1357,7 @@ test "RsvdzFull" { { fake_reg = std.math.maxInt(u32); - var reg = RsvdZFullTestReg32.get().readFrom(&mmio_reg); + var reg = RsvdzFullTestReg32.get().readFrom(&mmio_reg); try testing.expect(reg.base.regValue() == std.math.maxInt(u32)); _ = reg.base.writeTo(&mmio_reg); const reg_value: u64 = fake_reg; @@ -1384,7 +1366,7 @@ test "RsvdzFull" { { fake_reg = std.math.maxInt(u64); - var reg = RsvdZFullTestReg64.get().readFrom(&mmio_reg); + var reg = RsvdzFullTestReg64.get().readFrom(&mmio_reg); try testing.expect(reg.base.regValue() == std.math.maxInt(u64)); _ = reg.base.writeTo(&mmio_reg); const reg_value: u64 = fake_reg; @@ -1875,7 +1857,7 @@ const TestPciBar32 = struct { // Comptime field definitions pub const Address = DefUnshiftedField(Self, 31, 4, "address"); pub const IsPrefetchable = DefBit(Self, 3, "is_prefetchable"); - pub const RsvdZBit2 = DefRsvdzBit(Self, 2); + pub const RsvdzBit2 = DefRsvdzBit(Self, 2); pub const Is64Bit = DefBit(Self, 1, "is_64bit"); pub const IsIoSpace = DefBit(Self, 0, "is_io_space"); diff --git a/slipstream/system/ulib/hwreg/src/mock.zig b/slipstream/system/ulib/hwreg/src/mock.zig index be6404f..4c4eca0 100644 --- a/slipstream/system/ulib/hwreg/src/mock.zig +++ b/slipstream/system/ulib/hwreg/src/mock.zig @@ -37,9 +37,10 @@ const ExpectedIo = union(enum) { }; const MockRegisterIo = struct { + const Self = @This(); mock: *Mock, - pub fn write(self: MockRegisterIo, comptime IntType: type, value: IntType, offset: u32) void { + pub fn write(self: *const Self, comptime IntType: type, value: IntType, offset: u32) void { comptime { if (!internal.isSupportedInt(IntType)) { @compileError("unsupported register access width"); @@ -52,7 +53,7 @@ const MockRegisterIo = struct { _ = self.mock.mock.call(.{ expected, offset }); } - pub fn read(self: MockRegisterIo, comptime IntType: type, offset: u32) IntType { + pub fn read(self: *const Self, comptime IntType: type, offset: u32) IntType { comptime { if (!internal.isSupportedInt(IntType)) { @compileError("unsupported register access width"); @@ -67,7 +68,8 @@ const MockRegisterIo = struct { }; const DummyIo = struct { - pub fn write(_: DummyIo, comptime IntType: type, _: IntType, _: u32) void { + const Self = @This(); + pub fn write(_: *const Self, comptime IntType: type, _: IntType, _: u32) void { comptime { if (!internal.isSupportedInt(IntType)) { @compileError("unsupported register access width"); @@ -76,7 +78,7 @@ const DummyIo = struct { std.debug.panic("hwreg Mock RegisterIo used in default-constructed state", .{}); } - pub fn read(_: DummyIo, comptime IntType: type, _: u32) IntType { + pub fn read(_: *const Self, comptime IntType: type, _: u32) IntType { comptime { if (!internal.isSupportedInt(IntType)) { @compileError("unsupported register access width"); From 1f000d7b3e43c303699b141fc6dee3b3a7355a8f Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Wed, 11 Jun 2025 12:06:56 -0300 Subject: [PATCH 16/41] [sdk][lib][zbi-format] Initial version --- sdk/lib/zbi-format/build.zig | 25 +++++ sdk/lib/zbi-format/build.zig.zon | 6 ++ sdk/lib/zbi-format/src/driver_config.zig | 118 +++++++++++++++++++++++ sdk/lib/zbi-format/src/root.zig | 9 ++ 4 files changed, 158 insertions(+) create mode 100644 sdk/lib/zbi-format/build.zig create mode 100644 sdk/lib/zbi-format/build.zig.zon create mode 100644 sdk/lib/zbi-format/src/driver_config.zig create mode 100644 sdk/lib/zbi-format/src/root.zig diff --git a/sdk/lib/zbi-format/build.zig b/sdk/lib/zbi-format/build.zig new file mode 100644 index 0000000..d05928b --- /dev/null +++ b/sdk/lib/zbi-format/build.zig @@ -0,0 +1,25 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. + +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const mod = b.addModule("zbi_format", .{ + .root_source_file = b.path("src/root.zig"), + .target = target, + .optimize = optimize, + }); + + const test_step = b.step("test", "Run unit tests"); + const unit_tests = b.addTest(.{ + .root_module = mod, + .target = b.graph.host, + }); + + const run_unit_tests = b.addRunArtifact(unit_tests); + test_step.dependOn(&run_unit_tests.step); +} diff --git a/sdk/lib/zbi-format/build.zig.zon b/sdk/lib/zbi-format/build.zig.zon new file mode 100644 index 0000000..4364789 --- /dev/null +++ b/sdk/lib/zbi-format/build.zig.zon @@ -0,0 +1,6 @@ +.{ + .name = .zbi_format, + .fingerprint = 0x53525b7b9572617, + .version = "0.0.1", + .paths = .{""}, +} diff --git a/sdk/lib/zbi-format/src/driver_config.zig b/sdk/lib/zbi-format/src/driver_config.zig new file mode 100644 index 0000000..20c1662 --- /dev/null +++ b/sdk/lib/zbi-format/src/driver_config.zig @@ -0,0 +1,118 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. + +const std = @import("std"); + +/// ZBI_TYPE_KERNEL_DRIVER item types (for zbi_header_t.extra) +pub const KernelDriverType = u32; + +// 'PSCI' +pub const ZBI_KERNEL_DRIVER_ARM_PSCI: KernelDriverType = 0x49435350; + +// 'GIC2' +pub const ZBI_KERNEL_DRIVER_ARM_GIC_V2: KernelDriverType = 0x32434947; + +// 'GIC3' +pub const ZBI_KERNEL_DRIVER_ARM_GIC_V3: KernelDriverType = 0x33434947; + +// 'ATIM' +pub const ZBI_KERNEL_DRIVER_ARM_GENERIC_TIMER: KernelDriverType = 0x4d495441; + +// 'ATMM' +pub const ZBI_KERNEL_DRIVER_ARM_GENERIC_TIMER_MMIO: KernelDriverType = 0x4d4d5441; + +// 'PL0U' +pub const ZBI_KERNEL_DRIVER_PL011_UART: KernelDriverType = 0x55304c50; + +// 'AMLU' +pub const ZBI_KERNEL_DRIVER_AMLOGIC_UART: KernelDriverType = 0x554c4d41; + +// 'AMLH' +pub const ZBI_KERNEL_DRIVER_AMLOGIC_HDCP: KernelDriverType = 0x484c4d41; + +// 'DW8U' +pub const ZBI_KERNEL_DRIVER_DW8250_UART: KernelDriverType = 0x44573855; + +// 'RMLH' (typoed, originally intended to by 'AMLR') +pub const ZBI_KERNEL_DRIVER_AMLOGIC_RNG_V1: KernelDriverType = 0x484c4d52; + +// 'AMLR' +pub const ZBI_KERNEL_DRIVER_AMLOGIC_RNG_V2: KernelDriverType = 0x524c4d41; + +// 'WD32' +pub const ZBI_KERNEL_DRIVER_GENERIC32_WATCHDOG: KernelDriverType = 0x32334457; + +// 'GENI' +pub const ZBI_KERNEL_DRIVER_GENI_UART: KernelDriverType = 0x494e4547; + +// '8250' +pub const ZBI_KERNEL_DRIVER_I8250_PIO_UART: KernelDriverType = 0x30353238; + +// '825M' +pub const ZBI_KERNEL_DRIVER_I8250_MMIO32_UART: KernelDriverType = 0x4d353238; + +// '825B' +pub const ZBI_KERNEL_DRIVER_I8250_MMIO8_UART: KernelDriverType = 0x42353238; + +// 'MMTP' +pub const ZBI_KERNEL_DRIVER_MOTMOT_POWER: KernelDriverType = 0x4d4d5450; + +// '370P' +pub const ZBI_KERNEL_DRIVER_AS370_POWER: KernelDriverType = 0x50303733; + +// 'MNFP' +pub const ZBI_KERNEL_DRIVER_MOONFLOWER_POWER: KernelDriverType = 0x4d4e4650; + +// 'IMXU' +pub const ZBI_KERNEL_DRIVER_IMX_UART: KernelDriverType = 0x55584d49; + +// 'PLIC' +pub const ZBI_KERNEL_DRIVER_RISCV_PLIC: KernelDriverType = 0x43494c50; + +// 'RTIM' +pub const ZBI_KERNEL_DRIVER_RISCV_GENERIC_TIMER: KernelDriverType = 0x4d495452; + +// 'PXAU' +pub const ZBI_KERNEL_DRIVER_PXA_UART: KernelDriverType = 0x50584155; + +// 'EXYU' +pub const ZBI_KERNEL_DRIVER_EXYNOS_USI_UART: KernelDriverType = 0x45585955; + +/// Kernel driver struct that can be used for simple drivers. +/// Used by ZBI_KERNEL_DRIVER_PL011_UART, ZBI_KERNEL_DRIVER_AMLOGIC_UART, and +/// ZBI_KERNEL_DRIVER_GENI_UART, ZBI_KERNEL_DRIVER_I8250_MMIO_UART. +pub const SimpleDriverConfig = packed struct { + mmio_phys: u64, + irq: u32, + flags: u32, +}; + +/// IRQ flags for kernel drivers +pub const IrqFlags = packed struct(u32) { + /// When no flag is set, implies no information was obtained, and the + /// kernel will apply default configuration as it sees fit. + edge_triggered: bool = false, + level_triggered: bool = false, + polarity_low: bool = false, + polarity_high: bool = false, + + _padding: u28 = 0, + + comptime { + std.debug.assert(@sizeOf(@This()) == @sizeOf(u32)); + std.debug.assert(@bitSizeOf(@This()) == @bitSizeOf(u32)); + } + + pub fn toInt(self: @This()) u32 { + return @bitCast(self); + } +}; + +/// Simple PIO driver configuration +/// Used by ZBI_KERNEL_DRIVER_I8250_PIO_UART. +pub const SimplePioConfig = packed struct { + base: u16, + reserved: u16, + irq: u32, +}; diff --git a/sdk/lib/zbi-format/src/root.zig b/sdk/lib/zbi-format/src/root.zig new file mode 100644 index 0000000..0257699 --- /dev/null +++ b/sdk/lib/zbi-format/src/root.zig @@ -0,0 +1,9 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. + +pub const driver_config = @import("driver_config.zig"); + +comptime { + _ = driver_config; +} From e184b828162dde480d0c115136bd9a99839cbdcd Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Mon, 9 Jun 2025 06:54:19 -0300 Subject: [PATCH 17/41] [ulib][uart] Initial version --- slipstream/system/ulib/uart/build.zig | 39 + slipstream/system/ulib/uart/build.zig.zon | 20 + slipstream/system/ulib/uart/root.zig | 18 + slipstream/system/ulib/uart/src/all.zig | 3 + .../system/ulib/uart/src/chars_from.zig | 123 ++ slipstream/system/ulib/uart/src/interrupt.zig | 81 + slipstream/system/ulib/uart/src/mock.zig | 407 +++++ slipstream/system/ulib/uart/src/ns8250.zig | 1456 +++++++++++++++++ slipstream/system/ulib/uart/src/null.zig | 163 ++ slipstream/system/ulib/uart/src/parse.zig | 184 +++ slipstream/system/ulib/uart/src/sync.zig | 71 + slipstream/system/ulib/uart/src/uart.zig | 469 ++++++ .../system/ulib/uart/test/driver_tests.zig | 87 + .../system/ulib/uart/test/parsing_tests.zig | 275 ++++ 14 files changed, 3396 insertions(+) create mode 100644 slipstream/system/ulib/uart/build.zig create mode 100644 slipstream/system/ulib/uart/build.zig.zon create mode 100644 slipstream/system/ulib/uart/root.zig create mode 100644 slipstream/system/ulib/uart/src/all.zig create mode 100644 slipstream/system/ulib/uart/src/chars_from.zig create mode 100644 slipstream/system/ulib/uart/src/interrupt.zig create mode 100644 slipstream/system/ulib/uart/src/mock.zig create mode 100644 slipstream/system/ulib/uart/src/ns8250.zig create mode 100644 slipstream/system/ulib/uart/src/null.zig create mode 100644 slipstream/system/ulib/uart/src/parse.zig create mode 100644 slipstream/system/ulib/uart/src/sync.zig create mode 100644 slipstream/system/ulib/uart/src/uart.zig create mode 100644 slipstream/system/ulib/uart/test/driver_tests.zig create mode 100644 slipstream/system/ulib/uart/test/parsing_tests.zig diff --git a/slipstream/system/ulib/uart/build.zig b/slipstream/system/ulib/uart/build.zig new file mode 100644 index 0000000..8381cad --- /dev/null +++ b/slipstream/system/ulib/uart/build.zig @@ -0,0 +1,39 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. + +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const mod = b.addModule("uart", .{ + .root_source_file = b.path("root.zig"), + .target = target, + .optimize = optimize, + }); + + const deps = [_]struct { name: []const u8, dep_name: []const u8, module_name: []const u8 }{ + .{ .name = "arch", .dep_name = "arch", .module_name = "arch" }, + .{ .name = "zbi_format", .dep_name = "zbi_format", .module_name = "zbi_format" }, + .{ .name = "hwreg", .dep_name = "hwreg", .module_name = "hwreg" }, + .{ .name = "mock_function", .dep_name = "mock_function", .module_name = "mock_function" }, + }; + + for (deps) |dep| { + const dep_module = b.dependency(dep.dep_name, .{}); + mod.addImport(dep.name, dep_module.module(dep.module_name)); + } + + const test_step = b.step("test", "Run unit tests"); + + const unit_tests = b.addTest(.{ + .root_module = mod, + .target = b.graph.host, + }); + + const run_unit_tests = b.addRunArtifact(unit_tests); + test_step.dependOn(&run_unit_tests.step); + b.installArtifact(unit_tests); +} diff --git a/slipstream/system/ulib/uart/build.zig.zon b/slipstream/system/ulib/uart/build.zig.zon new file mode 100644 index 0000000..9627362 --- /dev/null +++ b/slipstream/system/ulib/uart/build.zig.zon @@ -0,0 +1,20 @@ +.{ + .name = .uart, + .fingerprint = 0x7ed180f4050587a0, + .version = "0.0.1", + .paths = .{""}, + .dependencies = .{ + .arch = .{ + .path = "../../../kernel/lib/arch", + }, + .zbi_format = .{ + .path = "../../../../sdk/lib/zbi-format", + }, + .mock_function = .{ + .path = "../mock_function", + }, + .hwreg = .{ + .path = "../hwreg", + }, + }, +} diff --git a/slipstream/system/ulib/uart/root.zig b/slipstream/system/ulib/uart/root.zig new file mode 100644 index 0000000..96c8ee0 --- /dev/null +++ b/slipstream/system/ulib/uart/root.zig @@ -0,0 +1,18 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. + +const uart = @import("src/uart.zig"); +const ns8250 = @import("src/ns8250.zig"); +const chars_from = @import("src/chars_from.zig"); + +comptime { + _ = uart; + _ = ns8250; + _ = chars_from; +} + +test { + _ = @import("test/driver_tests.zig"); + _ = @import("test/parsing_tests.zig"); +} diff --git a/slipstream/system/ulib/uart/src/all.zig b/slipstream/system/ulib/uart/src/all.zig new file mode 100644 index 0000000..6792a60 --- /dev/null +++ b/slipstream/system/ulib/uart/src/all.zig @@ -0,0 +1,3 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. \ No newline at end of file diff --git a/slipstream/system/ulib/uart/src/chars_from.zig b/slipstream/system/ulib/uart/src/chars_from.zig new file mode 100644 index 0000000..daba321 --- /dev/null +++ b/slipstream/system/ulib/uart/src/chars_from.zig @@ -0,0 +1,123 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. + +const std = @import("std"); + +const testing = std.testing; + +// String/slice wrapper that provides an iterator view over the characters. +// If newline_to_crlf is true, then '\n' characters are converted to '\r\n'. +pub fn CharsFrom(comptime newline_to_crlf: bool) type { + return struct { + const Self = @This(); + + pub const ValueType = u8; + + data: []const u8, + + pub const Iterator = struct { + const IterSelf = @This(); + + index: usize, + pending_lf: bool = false, + data: []const u8, + + pub fn init(index: usize, data: []const u8) IterSelf { + return IterSelf{ + .index = index, + .pending_lf = false, + .data = data, + }; + } + + pub fn eql(self: IterSelf, other: IterSelf) bool { + return self.index == other.index and self.pending_lf == other.pending_lf; + } + + pub fn next(self: *IterSelf) void { + if (newline_to_crlf and !self.pending_lf and self.index < self.data.len and self.data[self.index] == '\n') { + // Advance past the synthesized '\r' but not past the real '\n'. + self.pending_lf = true; + } else { + if (newline_to_crlf) { + // TODO (Herrera): Use slipstream assert. + std.debug.assert(!self.pending_lf or (self.index < self.data.len and self.data[self.index] == '\n')); + self.pending_lf = false; + } + self.index += 1; + } + } + + pub fn current(self: *const IterSelf) u8 { + const c = self.data[self.index]; + return if (newline_to_crlf and !self.pending_lf and c == '\n') '\r' else c; + } + }; + + pub fn init(data: []const u8) Self { + return Self{ + .data = data, + }; + } + + pub fn begin(self: Self) Iterator { + return Iterator.init(0, self.data); + } + + pub fn end(self: Self) Iterator { + return Iterator.init(self.data.len, self.data); + } + }; +} + +fn stringFrom(allocator: std.mem.Allocator, chars: anytype) ![]u8 { + var result = std.ArrayList(u8).init(allocator); + defer result.deinit(); + + var it = chars.begin(); + const end_it = chars.end(); + + while (!it.eql(end_it)) { + try result.append(it.current()); + it.next(); + } + + return result.toOwnedSlice(); +} + +test "chars from onlcr" { + var arena = std.heap.ArenaAllocator.init(testing.allocator); + defer arena.deinit(); + const allocator = arena.allocator(); + + { + const chars = CharsFrom(true).init("hello\n"); + const result = try stringFrom(allocator, chars); + try testing.expectEqualStrings("hello\r\n", result); + } + + { + const str = "foo\nbar"; + const chars = CharsFrom(true).init(str); + const result = try stringFrom(allocator, chars); + try testing.expectEqualStrings("foo\r\nbar", result); + } + + { + const str = "\nbye\n"; + const chars = CharsFrom(true).init(str); + const result = try stringFrom(allocator, chars); + try testing.expectEqualStrings("\r\nbye\r\n", result); + } +} + +test "chars from without newline conversion" { + var arena = std.heap.ArenaAllocator.init(testing.allocator); + defer arena.deinit(); + const allocator = arena.allocator(); + + const chars = CharsFrom(false).init("hello\nworld\n"); + const result = try stringFrom(allocator, chars); + try testing.expectEqualStrings("hello\nworld\n", result); +} diff --git a/slipstream/system/ulib/uart/src/interrupt.zig b/slipstream/system/ulib/uart/src/interrupt.zig new file mode 100644 index 0000000..3133820 --- /dev/null +++ b/slipstream/system/ulib/uart/src/interrupt.zig @@ -0,0 +1,81 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. + +const std = @import("std"); + +/// Rx IRQ Handler's will be provided an instance of this struct, providing the thread requirements +/// for calling each of the supported APIs. The synchronization cannot be enforced directly as a +/// protected object since the `Lock` encompasses more than the UART itself in some environments. +pub fn RxInterrupt(comptime Lock: type, comptime Reader: type, comptime Disabler: type) type { + return struct { + lock: *Lock, + reader: Reader, + disabler: Disabler, + + const Self = @This(); + + pub fn init(lock: *Lock, reader: Reader, disabler: Disabler) Self { + return .{ + .lock = lock, + .reader = reader, + .disabler = disabler, + }; + } + + /// Returns characters from performing one read operation from the UART. + pub fn readChar(self: *Self) !u8 { + return self.reader(); + } + + /// Mask RX IRQ and terminates the loop, even if we could still read more characters from the uart. + /// Usually means that the buffer where characters are being written is full. + pub fn disableInterrupt(self: *Self) void { + self.disabler(); + } + + /// In some cases it is desirable to control the locking sequence. Some of these cases involve + /// making sure certain TOCTOU operations do not leave the UART in an invalid state. + pub fn getLock(self: *Self) *Lock { + return self.lock; + } + }; +} + +/// Tx IRQ Handler's will be provided an instance of this struct, providing the thread requirements +/// for calling each of the supported APIs. The synchronization cannot be enforced directly as a +/// protected object since the `Lock` encompasses more than the UART itself in some environments. +pub fn TxInterrupt(comptime Lock: type, comptime Waiter: type, comptime Disabler: type) type { + return struct { + lock: *Lock, + waiter: *Waiter, + disabler: Disabler, + + const Self = @This(); + + pub fn init(lock: *Lock, waiter: *Waiter, disabler: Disabler) Self { + return .{ + .lock = lock, + .waiter = waiter, + .disabler = disabler, + }; + } + + /// Notifies blocked threads, that there is space available in the UART TX Fifo. + pub fn notify(self: *Self) void { + self.waiter.wake(); + } + + /// Disables TX IRQ, usually done when the TX HW Fifo is not empty, such that we can + /// efficiently write larger chunks of data without having to block on individual characters. + pub fn disableInterrupt(self: *Self) void { + self.disabler(); + } + + /// In some scenarios it is desirable to control the locking sequence. While unlikely in the TX + /// path, it shows symmetry with the RX handler. + pub fn getLock(self: *Self) *Lock { + return self.lock; + } + }; +} diff --git a/slipstream/system/ulib/uart/src/mock.zig b/slipstream/system/ulib/uart/src/mock.zig new file mode 100644 index 0000000..6a81fc1 --- /dev/null +++ b/slipstream/system/ulib/uart/src/mock.zig @@ -0,0 +1,407 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. + +const std = @import("std"); +const uart = @import("uart.zig"); +const hwreg = @import("hwreg"); +const mock_function = @import("mock_function"); + +// uart::mock::IoProvider supports testing uart::xyz::Driver hardware drivers. +// uart::mock::Driver supports testing uart::KernelDriver itself. +// It also serves to demonstrate the API required by uart::KernelDriver. + +/// Mock I/O Provider for testing UART drivers. +/// When used with uart.mock.Driver, no actual I/O calls are ever made and +/// this is just a placeholder. When used with other uart drivers, +/// it provides a mock API for testing expected I/O calls from the driver. +pub fn IoProvider(comptime Config: type, comptime IoRegisterType: uart.IoRegisterType) type { + _ = IoRegisterType; + + return struct { + const Self = @This(); + + io: hwreg.Mock, + + pub fn init(_: Config, _: anytype) Self { + return Self{ + .io = hwreg.Mock.init(), + }; + } + + pub fn deinit(self: *Self) void { + self.io.deinit(); + } + + pub fn getIo(self: *Self) *hwreg.Mock.RegisterIo { + return self.io.io(); + } + + // Mock tests of hardware drivers use this to prime the mock with expected + // callbacks from the driver. + pub fn mock(self: *Self) *hwreg.Mock { + return &self.io; + } + }; +} + +// uart::KernelDriver UartDriver API +// +// This pretends to be a hardware driver but is just a mock for tests. If +// uart::mock::Sync is also used to instantiate uart::KernelDriver, then the +// expected synchronization calls are primed into the Driver mock so their +// ordering relative to the hardware driver calls can be tested. The mock +// hardware Driver can also be used with other Sync API providers. +pub const Driver = struct { + const Self = @This(); + + pub const ConfigType = uart.StubConfig; + + // Expected call types - mirrors the C++ variant structure + const ExpectedLock = struct { + unlock: bool = false, + + pub fn eql(self: ExpectedLock, other: ExpectedLock) bool { + return self.unlock == other.unlock; + } + }; + + const ExpectedWait = struct { + pub fn eql(self: ExpectedWait, other: ExpectedWait) bool { + _ = self; + _ = other; + return true; + } + }; + + const ExpectedAssertHeld = struct { + pub fn eql(self: ExpectedAssertHeld, other: ExpectedAssertHeld) bool { + _ = self; + _ = other; + return true; + } + }; + + const ExpectedInit = struct { + pub fn eql(self: ExpectedInit, other: ExpectedInit) bool { + _ = self; + _ = other; + return true; + } + }; + + const ExpectedTxEnable = struct { + pub fn eql(self: ExpectedTxEnable, other: ExpectedTxEnable) bool { + _ = self; + _ = other; + return true; + } + }; + + const ExpectedTxReady = struct { + pub fn eql(self: ExpectedTxReady, other: ExpectedTxReady) bool { + _ = self; + _ = other; + return true; + } + }; + + const ExpectedWrite = struct { + pub fn eql(self: ExpectedWrite, other: ExpectedWrite) bool { + _ = self; + _ = other; + return true; + } + }; + + const ExpectedChar = struct { + c: u8, + + pub fn eql(self: ExpectedChar, other: ExpectedChar) bool { + return self.c == other.c; + } + }; + + const Expected = union(enum) { + lock: ExpectedLock, + wait: ExpectedWait, + assert_held: ExpectedAssertHeld, + init: ExpectedInit, + tx_enable: ExpectedTxEnable, + tx_ready: ExpectedTxReady, + write: ExpectedWrite, + char: ExpectedChar, + }; + + const ExpectedResult = union(enum) { + bool_result: bool, + size_result: usize, + void_result: void, + }; + + mock: mock_function.MockFunction(ExpectedResult, &[_]type{Expected}), + + pub fn init(_: ConfigType) Self { + return Self{ + .mock = mock_function.MockFunction(ExpectedResult, &[_]type{Expected}).init(), + }; + } + + pub fn deinit(self: *Self) void { + self.verifyAndClear(); + self.mock_.deinit(); + } + + pub const kIoType = uart.IoRegisterType.mmio8; + + pub fn config(self: *const Self) ConfigType { + _ = self; + return ConfigType{}; + } + + pub fn ioSlots(self: *const Self) u16 { + _ = self; + return 0; + } + + const IoProviderType = IoProvider(ConfigType, kIoType); + + // Fluent API for priming and checking the mock + + pub fn expectInit(self: *Self) *Self { + _ = self.mock.expectCall(ExpectedResult{ .void_result = {} }, .{Expected{ .init = ExpectedInit{} }}); + return self; + } + + pub fn expectTxReady(self: *Self, ready: bool) *Self { + _ = self.mock.expectCall(ExpectedResult{ .bool_result = ready }, .{Expected{ .tx_ready = ExpectedTxReady{} }}); + return self; + } + + pub fn expectWrite(self: *Self, chars: []const u8) *Self { + // A Write is modeled as an ExpectedWrite yielding the count of + // characters, and then a sequence of one ExpectedChar for each character. + _ = self.mock.expectCall(ExpectedResult{ .size_result = chars.len }, .{Expected{ .write = ExpectedWrite{} }}); + for (chars) |c| { + _ = self.mock.expectCall(ExpectedResult{ .void_result = {} }, .{Expected{ .char = ExpectedChar{ .c = c } }}); + } + return self; + } + + pub fn expectLock(self: *Self) *Self { + _ = self.mock.expectCall(ExpectedResult{ .void_result = {} }, .{Expected{ .lock = ExpectedLock{ .unlock = false } }}); + return self; + } + + pub fn expectUnlock(self: *Self) *Self { + _ = self.mock.expectCall(ExpectedResult{ .void_result = {} }, .{Expected{ .lock = ExpectedLock{ .unlock = true } }}); + return self; + } + + pub fn expectWait(self: *Self, block: bool) *Self { + _ = self.mock.expectCall(ExpectedResult{ .bool_result = block }, .{Expected{ .wait = ExpectedWait{} }}); + return self; + } + + pub fn expectAssertHeld(self: *Self) *Self { + _ = self.mock.expectCall(ExpectedResult{ .void_result = {} }, .{Expected{ .assert_held = ExpectedAssertHeld{} }}); + return self; + } + + pub fn expectEnableTxInterrupt(self: *Self) *Self { + _ = self.mock.expectCall(ExpectedResult{ .void_result = {} }, .{Expected{ .tx_enable = ExpectedTxEnable{} }}); + return self; + } + + pub fn verifyAndClear(self: *Self) void { + self.mock.verifyAndClear(); + } + + // uart::KernelDriver UartDriver API + // + // Each method is a template parameterized by an an IoProvider type that + // provides access to hwreg-compatible types accessing the hardware registers + // via hwreg ReadFrom and WriteTo methods. Real Driver types can be used + // with hwreg::mock::IoProvider in tests independent of actual hardware + // access. The mock Driver to be used with hwreg::mock::IoProvider, but it + // never makes any calls. + + pub fn initHardware(self: *Self, io: *IoProviderType) void { + _ = io; + _ = self.mock.call(.{Expected{ .init = ExpectedInit{} }}); + } + + // Return true if Write can make forward progress right now. + pub fn txReady(self: *Self, io: *IoProviderType) bool { + _ = io; + const result = self.mock_.call(.{Expected{ .tx_ready = ExpectedTxReady{} }}); + return result.bool_result; + } + + // This is called only when TxReady() has just returned true. Advance + // the iterator at least one and as many as is convenient but not past + // end, outputting each character before advancing. + pub fn write(self: *Self, comptime LocalIoProviderType: type, io: *LocalIoProviderType, ready: bool, comptime ItType: type, it: *ItType, end: ItType) ItType { + _ = io; + _ = ready; + + const result = self.mock_.call(.{Expected{ .write = ExpectedWrite{} }}); + const count = result.size_result; + + var i: usize = 0; + while (i < count and !it.eql(end)) { + const current_char = it.current(); + _ = self.mock_.call(.{Expected{ .char = ExpectedChar{ .c = current_char } }}); + it.next(); + i += 1; + } + + return it.*; + } + + pub fn enableTxInterrupt(self: *Self, comptime LocalIoProviderType: type, io: *LocalIoProviderType) void { + _ = io; + _ = self.mock_.call(.{Expected{ .tx_enable = ExpectedTxEnable{} }}); + } + + pub fn read(self: *Self, comptime LocalIoProviderType: type, io: *LocalIoProviderType) ?u8 { + _ = self; + _ = io; + return null; + } + + pub fn setLineControl(self: *Self, comptime LocalIoProviderType: type, io: *LocalIoProviderType, data_bits: ?uart.DataBits, parity: ?uart.Parity, stop_bits: ?uart.StopBits) void { + _ = self; + _ = io; + _ = data_bits; + _ = parity; + _ = stop_bits; + } + + pub fn initInterrupt(self: *Self, comptime LocalIoProviderType: type, io: *LocalIoProviderType, enableInterruptCallback: anytype) void { + _ = self; + _ = io; + _ = enableInterruptCallback; + } + + pub fn interrupt(self: *Self, comptime LocalIoProviderType: type, comptime LockType: type, comptime TxType: type, comptime RxType: type, io: *LocalIoProviderType, lock: *LockType, waiter: anytype, tx: TxType, rx: RxType) void { + _ = self; + _ = io; + _ = lock; + _ = waiter; + _ = tx; + _ = rx; + } + + pub fn enableRxInterrupt(self: *Self, comptime LocalIoProviderType: type, io: *LocalIoProviderType) void { + _ = self; + _ = io; + } +}; + +/// Mock locking types for testing +pub const Locking = enum { locking }; +pub const NoopLocking = enum { noop_locking }; + +pub fn Guard(comptime LockType: type, comptime LockTag: type) type { + return struct { + const Self = @This(); + + lock: *LockType, + + pub fn init(lock: *LockType) Self { + if (LockTag == Locking) { + lock.lock(); + } + return Self{ .lock = lock }; + } + + pub fn deinit(self: Self) void { + if (LockTag == Locking) { + self.lock.unlock(); + } + } + }; +} + +/// Mock Lock for testing synchronization +pub const Lock = struct { + const Self = @This(); + + mock_: ?*mock_function.MockFunction(Driver.ExpectedResult, &[_]type{Driver.Expected}) = null, + + pub fn init() Self { + return Self{}; + } + + pub fn initWithDriver(self: *Self, driver: *Driver) void { + self.mock_ = &driver.mock_; + } + + pub fn lock(self: *Self) void { + if (self.mock_) |mock_fn| { + _ = mock_fn.call(.{Driver.Expected{ .lock = Driver.ExpectedLock{ .unlock = false } }}); + } + } + + pub fn unlock(self: *Self) void { + if (self.mock_) |mock_fn| { + _ = mock_fn.call(.{Driver.Expected{ .lock = Driver.ExpectedLock{ .unlock = true } }}); + } + } + + pub fn assertHeld(self: *Self) void { + if (self.mock_) |mock_fn| { + _ = mock_fn.call(.{Driver.Expected{ .assert_held = Driver.ExpectedAssertHeld{} }}); + } + } +}; + +/// Mock Waiter for testing wait operations +pub const Waiter = struct { + const Self = @This(); + + mock_: ?*mock_function.MockFunction(Driver.ExpectedResult, &[_]type{Driver.Expected}) = null, + + pub fn init() Self { + return Self{}; + } + + pub fn initWithDriver(self: *Self, driver: *Driver) void { + self.mock_ = &driver.mock_; + } + + pub fn wait(self: *Self, guard: anytype, enableTxInterrupt: anytype, args: anytype) void { + _ = guard; + _ = args; + + if (self.mock_) |mock_fn| { + const result = mock_fn.call(.{Driver.Expected{ .wait = Driver.ExpectedWait{} }}); + if (result.bool_result) { + enableTxInterrupt(); + } + } + } +}; + +/// Mock Sync Policy for testing +pub const SyncPolicy = struct { + pub fn Lock(comptime MemberOf: type) type { + _ = MemberOf; + return mock.Lock; + } + + pub fn Guard(comptime LockPolicy: type) type { + return mock.Guard(mock.Lock, LockPolicy); + } + + pub const Waiter = mock.Waiter; + pub const DefaultLockPolicy = Locking; + + pub fn assertHeld(lock: *mock.Lock) void { + lock.assertHeld(); + } +}; + +// Make mock accessible from the mock namespace +const mock = @This(); diff --git a/slipstream/system/ulib/uart/src/ns8250.zig b/slipstream/system/ulib/uart/src/ns8250.zig new file mode 100644 index 0000000..fe88b98 --- /dev/null +++ b/slipstream/system/ulib/uart/src/ns8250.zig @@ -0,0 +1,1456 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. + +const std = @import("std"); +const zbi_format = @import("zbi_format"); +const uart = @import("uart.zig"); +const hwreg = @import("hwreg"); +const mock = @import("mock.zig"); +const sync = @import("sync.zig"); + +const testing = std.testing; +const driver_config = zbi_format.driver_config; + +// Import modern hwreg API +const RegisterBase = hwreg.bitfields.RegisterBase; +const RegisterAddr = hwreg.bitfields.RegisterAddr; +const DefField = hwreg.bitfields.DefField; +const DefBit = hwreg.bitfields.DefBit; +const DefCondBit = hwreg.bitfields.DefCondBit; +const DefCondField = hwreg.bitfields.DefCondField; +const DefCondRsvdzField = hwreg.bitfields.DefCondRsvdzField; +const DefCondRsvdzBit = hwreg.bitfields.DefCondRsvdzBit; +const DefRsvdzField = hwreg.bitfields.DefRsvdzField; +const DefRsvdzBit = hwreg.bitfields.DefRsvdzBit; +const DefEnumField = hwreg.bitfields.DefEnumField; + +// 8250 and derivatives, including 16550. + +pub const default_baud_rate: u32 = 115200; +pub const max_baud_rate: u32 = 115200; + +pub const fifo_depth_16750: u8 = 64; +pub const fifo_depth_16550a: u8 = 16; +pub const fifo_depth_dw8250_minimum: u8 = 16; +pub const fifo_depth_pxa: u8 = 64; +pub const fifo_depth_generic: u8 = 1; + +// Traditional COM1 configuration +pub const legacy_config = driver_config.SimplePioConfig{ + .base = 0x3f8, + .reserved = 0, + .irq = 4, +}; + +pub const InterruptType = enum(u8) { + modem_status = 0b0000, + none = 0b0001, + tx_empty = 0b0010, + rx_data_available = 0b0100, + rx_line_status = 0b0110, + dw8250_busy_detect = 0b0111, // dw8250 only + char_timeout = 0b1100, +}; + +pub const RxBufferRegister = struct { + const Self = @This(); + pub const ValueType = u8; + const ParentType = RegisterBase(Self, ValueType, void); + pub const printer_enabled: bool = ParentType.PrinterEnabled; + + base: ParentType = .{}, + + // Comptime field definitions + pub const Data = DefField(Self, 7, 0, "data"); + + pub fn init() Self { + var self = Self{ .base = .{} }; + Data.init(&self); + return self; + } + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(0); + } + + pub fn data(self: *Self) ValueType { + return Data.get(self); + } + + pub fn setData(self: *Self, value: ValueType) *Self { + Data.set(self, value); + return self; + } + + pub fn readFrom(self: *Self, reg_io: anytype) *Self { + _ = self.base.readFrom(reg_io); + return self; + } + + pub fn writeTo(self: *Self, reg_io: anytype) *Self { + _ = self.base.writeTo(reg_io); + return self; + } +}; + +pub const TxBufferRegister = struct { + const Self = @This(); + pub const ValueType = u8; + const ParentType = RegisterBase(Self, ValueType, void); + pub const printer_enabled: bool = ParentType.PrinterEnabled; + + base: ParentType = .{}, + + // Comptime field definitions + pub const Data = DefField(Self, 7, 0, "data"); + + pub fn init() Self { + var self = Self{ .base = .{} }; + Data.init(&self); + return self; + } + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(0); + } + + pub fn data(self: *Self) ValueType { + return Data.get(self); + } + + pub fn setData(self: *Self, value: ValueType) *Self { + Data.set(self, value); + return self; + } + + pub fn readFrom(self: *Self, reg_io: anytype) *Self { + _ = self.base.readFrom(reg_io); + return self; + } + + pub fn writeTo(self: *Self, reg_io: anytype) *Self { + _ = self.base.writeTo(reg_io); + return self; + } +}; + +pub fn InterruptEnableRegisterBase(comptime driver_type: u32) type { + const is_pxa = driver_type == driver_config.ZBI_KERNEL_DRIVER_PXA_UART; + + return struct { + const Self = @This(); + pub const ValueType = u8; + const ParentType = RegisterBase(Self, ValueType, void); + pub const printer_enabled: bool = ParentType.PrinterEnabled; + + base: ParentType = .{}, + + // Comptime field definitions + pub const DmaRequestEnable = DefCondBit(Self, 7, "dma_request_enable", is_pxa); + pub const UartEnable = DefCondBit(Self, 6, "uart_enable", is_pxa); + pub const NrzCodingEnable = DefCondBit(Self, 5, "nrz_coding_enable", is_pxa); + pub const ReceiverTimeOut = DefCondBit(Self, 4, "receiver_time_out", is_pxa); + pub const RsvdZField = DefCondRsvdzField(Self, 7, 4, !is_pxa); + pub const ModemStatus = DefBit(Self, 3, "modem_status"); + pub const LineStatus = DefBit(Self, 2, "line_status"); + pub const TxEmpty = DefBit(Self, 1, "tx_empty"); + pub const RxAvailable = DefBit(Self, 0, "rx_available"); + + pub fn init() Self { + var self = Self{ .base = .{} }; + if (is_pxa) { + DmaRequestEnable.init(&self); + UartEnable.init(&self); + NrzCodingEnable.init(&self); + ReceiverTimeOut.init(&self); + } else { + RsvdZField.init(&self); + } + ModemStatus.init(&self); + LineStatus.init(&self); + TxEmpty.init(&self); + RxAvailable.init(&self); + return self; + } + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(1); + } + + pub fn dmaRequestEnable(self: *Self) ValueType { + return DmaRequestEnable.get(self); + } + + pub fn setDmaRequestEnable(self: *Self, value: ValueType) *Self { + DmaRequestEnable.set(self, value); + return self; + } + + pub fn uartEnable(self: *Self) ValueType { + return UartEnable.get(self); + } + + pub fn setUartEnable(self: *Self, value: ValueType) *Self { + UartEnable.set(self, value); + return self; + } + + pub fn nrzCodingEnable(self: *Self) ValueType { + return NrzCodingEnable.get(self); + } + + pub fn setNrzCodingEnable(self: *Self, value: ValueType) *Self { + NrzCodingEnable.set(self, value); + return self; + } + + pub fn receiverTimeOut(self: *Self) ValueType { + return ReceiverTimeOut.get(self); + } + + pub fn setReceiverTimeOut(self: *Self, value: ValueType) *Self { + ReceiverTimeOut.set(self, value); + return self; + } + + pub fn modemStatus(self: *Self) ValueType { + return ModemStatus.get(self); + } + + pub fn setModemStatus(self: *Self, value: ValueType) *Self { + ModemStatus.set(self, value); + return self; + } + + pub fn lineStatus(self: *Self) ValueType { + return LineStatus.get(self); + } + + pub fn setLineStatus(self: *Self, value: ValueType) *Self { + LineStatus.set(self, value); + return self; + } + + pub fn txEmpty(self: *Self) ValueType { + return TxEmpty.get(self); + } + + pub fn setTxEmpty(self: *Self, value: ValueType) *Self { + TxEmpty.set(self, value); + return self; + } + + pub fn rxAvailable(self: *Self) ValueType { + return RxAvailable.get(self); + } + + pub fn setRxAvailable(self: *Self, value: ValueType) *Self { + RxAvailable.set(self, value); + return self; + } + + pub fn readFrom(self: *Self, reg_io: anytype) *Self { + _ = self.base.readFrom(reg_io); + return self; + } + + pub fn writeTo(self: *Self, reg_io: anytype) *Self { + _ = self.base.writeTo(reg_io); + return self; + } + }; +} + +// Default interrupt enable register is without the additional PXA bits +pub const InterruptEnableRegister = InterruptEnableRegisterBase(0); + +pub const InterruptIdentRegister = struct { + const Self = @This(); + pub const ValueType = u8; + const ParentType = RegisterBase(Self, ValueType, void); + pub const printer_enabled: bool = ParentType.PrinterEnabled; + + base: ParentType = .{}, + + // Comptime field definitions + pub const FifosEnabled = DefField(Self, 7, 6, "fifos_enabled"); + pub const ExtendedFifoEnabled = DefBit(Self, 5, "extended_fifo_enabled"); + pub const RsvdZBit = DefRsvdzBit(Self, 4); + pub const InterruptId = DefEnumField(Self, InterruptType, 3, 0, "interrupt_id"); + + pub fn init() Self { + var self = Self{ .base = .{} }; + FifosEnabled.init(&self); + ExtendedFifoEnabled.init(&self); + RsvdZBit.init(&self); + InterruptId.init(&self); + return self; + } + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(2); + } + + pub fn fifosEnabled(self: *Self) ValueType { + return FifosEnabled.get(self); + } + + pub fn extendedFifoEnabled(self: *Self) ValueType { + return ExtendedFifoEnabled.get(self); + } + + pub fn interruptId(self: *Self) InterruptType { + return InterruptId.get(self); + } + + pub fn getInterruptId(self: *const Self) InterruptType { + return InterruptId.get(@constCast(self)); + } + + pub fn readFrom(self: *Self, reg_io: anytype) *Self { + _ = self.base.readFrom(reg_io); + return self; + } +}; + +pub fn FifoControlRegisterBase(comptime driver_type: u32) type { + const is_pxa = driver_type == driver_config.ZBI_KERNEL_DRIVER_PXA_UART; + const is_dw8250 = driver_type == driver_config.ZBI_KERNEL_DRIVER_DW8250_UART; + const is_normal = !is_pxa and !is_dw8250; + + return struct { + const Self = @This(); + pub const ValueType = u8; + const ParentType = RegisterBase(Self, ValueType, void); + pub const printer_enabled: bool = ParentType.PrinterEnabled; + + base: ParentType = .{}, + + // Comptime field definitions + pub const ReceiverTrigger = DefField(Self, 7, 6, "receiver_trigger"); + + // PXA specific registers + pub const PeripheralBus32bit = DefCondBit(Self, 5, "peripheral_bus_32bit", is_pxa); + pub const TrailingBytes = DefCondBit(Self, 4, "trailing_bytes", is_pxa); + pub const TransmitTriggerPxa = DefCondBit(Self, 3, "transmit_trigger", is_pxa); + + // Dw8250 specific registers + pub const TransmitTriggerDw8250 = DefCondField(Self, 5, 4, "transmit_trigger", is_dw8250); + + pub const ExtendedFifoEnable = DefCondBit(Self, 5, "extended_fifo_enable", is_normal); + pub const RsvdzBit = DefCondRsvdzBit(Self, 4, is_normal); + pub const DmaMode = DefCondBit(Self, 3, "dma_mode", is_normal or is_dw8250); + pub const TxFifoReset = DefBit(Self, 2, "tx_fifo_reset"); + pub const RxFifoReset = DefBit(Self, 1, "rx_fifo_reset"); + pub const FifoEnable = DefBit(Self, 0, "fifo_enable"); + + pub const max_trigger_level: u8 = 0b11; + + pub fn init() Self { + var self = Self{ .base = .{} }; + ReceiverTrigger.init(&self); + PeripheralBus32bit.init(&self); + TrailingBytes.init(&self); + TransmitTriggerPxa.init(&self); + TransmitTriggerDw8250.init(&self); + ExtendedFifoEnable.init(&self); + RsvdzBit.init(&self); + DmaMode.init(&self); + TxFifoReset.init(&self); + RxFifoReset.init(&self); + FifoEnable.init(&self); + return self; + } + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(2); + } + + pub fn receiverTrigger(self: *Self) ValueType { + return ReceiverTrigger.get(self); + } + + pub fn setReceiverTrigger(self: *Self, value: ValueType) *Self { + ReceiverTrigger.set(self, value); + return self; + } + + pub fn peripheralBus32bit(self: *Self) ValueType { + return PeripheralBus32bit.get(self); + } + + pub fn setPeripheralBus32bit(self: *Self, value: ValueType) *Self { + PeripheralBus32bit.set(self, value); + return self; + } + + pub fn trailingBytes(self: *Self) ValueType { + return TrailingBytes.get(self); + } + + pub fn setTrailingBytes(self: *Self, value: ValueType) *Self { + TrailingBytes.set(self, value); + return self; + } + + pub fn transmitTriggerPxa(self: *Self) ValueType { + return TransmitTriggerPxa.get(self); + } + + pub fn setTransmitTriggerPxa(self: *Self, value: ValueType) *Self { + TransmitTriggerPxa.set(self, value); + return self; + } + + pub fn transmitTriggerDw8250(self: *Self) ValueType { + return TransmitTriggerDw8250.get(self); + } + + pub fn setTransmitTriggerDw8250(self: *Self, value: ValueType) *Self { + TransmitTriggerDw8250.set(self, value); + return self; + } + + pub fn extendedFifoEnable(self: *Self) ValueType { + return ExtendedFifoEnable.get(self); + } + + pub fn setExtendedFifoEnable(self: *Self, value: ValueType) *Self { + ExtendedFifoEnable.set(self, value); + return self; + } + + pub fn dmaMode(self: *Self) ValueType { + return DmaMode.get(self); + } + + pub fn setDmaMode(self: *Self, value: ValueType) *Self { + DmaMode.set(self, value); + return self; + } + + pub fn txFifoReset(self: *Self) ValueType { + return TxFifoReset.get(self); + } + + pub fn setTxFifoReset(self: *Self, value: ValueType) *Self { + TxFifoReset.set(self, value); + return self; + } + + pub fn rxFifoReset(self: *Self) ValueType { + return RxFifoReset.get(self); + } + + pub fn setRxFifoReset(self: *Self, value: ValueType) *Self { + RxFifoReset.set(self, value); + return self; + } + + pub fn fifoEnable(self: *Self) ValueType { + return FifoEnable.get(self); + } + + pub fn setFifoEnable(self: *Self, value: ValueType) *Self { + FifoEnable.set(self, value); + return self; + } + + pub fn writeTo(self: *Self, reg_io: anytype) *Self { + _ = self.base.writeTo(reg_io); + return self; + } + }; +} + +// Default fifo control register is without the additional PXA or Dw8250 bits +pub const FifoControlRegister = FifoControlRegisterBase(0); + +pub const LineControlRegister = struct { + const Self = @This(); + pub const ValueType = u8; + const ParentType = RegisterBase(Self, ValueType, void); + pub const printer_enabled: bool = ParentType.PrinterEnabled; + + base: ParentType = .{}, + + // Comptime field definitions + pub const DivisorLatchAccess = DefBit(Self, 7, "divisor_latch_access"); + pub const BreakControl = DefBit(Self, 6, "break_control"); + pub const StickParity = DefBit(Self, 5, "stick_parity"); + pub const EvenParity = DefBit(Self, 4, "even_parity"); + pub const ParityEnable = DefBit(Self, 3, "parity_enable"); + pub const StopBits = DefBit(Self, 2, "stop_bits"); + pub const WordLength = DefField(Self, 1, 0, "word_length"); + + pub const word_length_5: u8 = 0b00; + pub const word_length_6: u8 = 0b01; + pub const word_length_7: u8 = 0b10; + pub const word_length_8: u8 = 0b11; + + pub const stop_bits_1: u8 = 0b0; + pub const stop_bits_2: u8 = 0b1; + + pub fn init() Self { + var self = Self{ .base = .{} }; + DivisorLatchAccess.init(&self); + BreakControl.init(&self); + StickParity.init(&self); + EvenParity.init(&self); + ParityEnable.init(&self); + StopBits.init(&self); + WordLength.init(&self); + return self; + } + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(3); + } + + pub fn divisorLatchAccess(self: *Self) ValueType { + return DivisorLatchAccess.get(self); + } + + pub fn setDivisorLatchAccess(self: *Self, value: ValueType) *Self { + DivisorLatchAccess.set(self, value); + return self; + } + + pub fn breakControl(self: *Self) ValueType { + return BreakControl.get(self); + } + + pub fn setBreakControl(self: *Self, value: ValueType) *Self { + BreakControl.set(self, value); + return self; + } + + pub fn stickParity(self: *Self) ValueType { + return StickParity.get(self); + } + + pub fn setStickParity(self: *Self, value: ValueType) *Self { + StickParity.set(self, value); + return self; + } + + pub fn evenParity(self: *Self) ValueType { + return EvenParity.get(self); + } + + pub fn setEvenParity(self: *Self, value: ValueType) *Self { + EvenParity.set(self, value); + return self; + } + + pub fn parityEnable(self: *Self) ValueType { + return ParityEnable.get(self); + } + + pub fn setParityEnable(self: *Self, value: ValueType) *Self { + ParityEnable.set(self, value); + return self; + } + + pub fn stopBits(self: *Self) ValueType { + return StopBits.get(self); + } + + pub fn setStopBits(self: *Self, value: ValueType) *Self { + StopBits.set(self, value); + return self; + } + + pub fn wordLength(self: *Self) ValueType { + return WordLength.get(self); + } + + pub fn setWordLength(self: *Self, value: ValueType) *Self { + WordLength.set(self, value); + return self; + } + + pub fn readFrom(self: *Self, reg_io: anytype) *Self { + _ = self.base.readFrom(reg_io); + return self; + } + + pub fn writeTo(self: *Self, reg_io: anytype) *Self { + _ = self.base.writeTo(reg_io); + return self; + } +}; + +pub const ModemControlRegister = struct { + const Self = @This(); + pub const ValueType = u8; + const ParentType = RegisterBase(Self, ValueType, void); + pub const printer_enabled: bool = ParentType.PrinterEnabled; + + base: ParentType = .{}, + + // Comptime field definitions + pub const RsvdZField = DefRsvdzField(Self, 7, 6); + pub const AutomaticFlowControlEnable = DefBit(Self, 5, "automatic_flow_control_enable"); + pub const Loop = DefBit(Self, 4, "loop"); + pub const AuxiliaryOut2 = DefBit(Self, 3, "auxiliary_out_2"); + pub const AuxiliaryOut1 = DefBit(Self, 2, "auxiliary_out_1"); + pub const RequestToSend = DefBit(Self, 1, "request_to_send"); + pub const DataTerminalReady = DefBit(Self, 0, "data_terminal_ready"); + + pub fn init() Self { + var self = Self{ .base = .{} }; + RsvdZField.init(&self); + AutomaticFlowControlEnable.init(&self); + Loop.init(&self); + AuxiliaryOut2.init(&self); + AuxiliaryOut1.init(&self); + RequestToSend.init(&self); + DataTerminalReady.init(&self); + return self; + } + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(4); + } + + pub fn automaticFlowControlEnable(self: *Self) ValueType { + return AutomaticFlowControlEnable.get(self); + } + + pub fn setAutomaticFlowControlEnable(self: *Self, value: ValueType) *Self { + AutomaticFlowControlEnable.set(self, value); + return self; + } + + pub fn loop(self: *Self) ValueType { + return Loop.get(self); + } + + pub fn setLoop(self: *Self, value: ValueType) *Self { + Loop.set(self, value); + return self; + } + + pub fn auxiliaryOut2(self: *Self) ValueType { + return AuxiliaryOut2.get(self); + } + + pub fn setAuxiliaryOut2(self: *Self, value: ValueType) *Self { + AuxiliaryOut2.set(self, value); + return self; + } + + pub fn auxiliaryOut1(self: *Self) ValueType { + return AuxiliaryOut1.get(self); + } + + pub fn setAuxiliaryOut1(self: *Self, value: ValueType) *Self { + AuxiliaryOut1.set(self, value); + return self; + } + + pub fn requestToSend(self: *Self) ValueType { + return RequestToSend.get(self); + } + + pub fn setRequestToSend(self: *Self, value: ValueType) *Self { + RequestToSend.set(self, value); + return self; + } + + pub fn dataTerminalReady(self: *Self) ValueType { + return DataTerminalReady.get(self); + } + + pub fn setDataTerminalReady(self: *Self, value: ValueType) *Self { + DataTerminalReady.set(self, value); + return self; + } + + pub fn readFrom(self: *Self, reg_io: anytype) *Self { + _ = self.base.readFrom(reg_io); + return self; + } + + pub fn writeTo(self: *Self, reg_io: anytype) *Self { + _ = self.base.writeTo(reg_io); + return self; + } +}; + +pub const LineStatusRegister = struct { + const Self = @This(); + pub const ValueType = u8; + const ParentType = RegisterBase(Self, ValueType, void); + pub const printer_enabled: bool = ParentType.PrinterEnabled; + + base: ParentType = .{}, + + // Comptime field definitions + pub const ErrorInRxFifo = DefBit(Self, 7, "error_in_rx_fifo"); + pub const TxEmpty = DefBit(Self, 6, "tx_empty"); + pub const TxRegisterEmpty = DefBit(Self, 5, "tx_register_empty"); + pub const BreakInterrupt = DefBit(Self, 4, "break_interrupt"); + pub const FramingError = DefBit(Self, 3, "framing_error"); + pub const ParityError = DefBit(Self, 2, "parity_error"); + pub const OverrunError = DefBit(Self, 1, "overrun_error"); + pub const DataReady = DefBit(Self, 0, "data_ready"); + + pub fn init() Self { + var self = Self{ .base = .{} }; + ErrorInRxFifo.init(&self); + TxEmpty.init(&self); + TxRegisterEmpty.init(&self); + BreakInterrupt.init(&self); + FramingError.init(&self); + ParityError.init(&self); + OverrunError.init(&self); + DataReady.init(&self); + return self; + } + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(5); + } + + pub fn errorInRxFifo(self: *Self) ValueType { + return ErrorInRxFifo.get(self); + } + + pub fn txEmpty(self: *Self) ValueType { + return TxEmpty.get(self); + } + + pub fn txRegisterEmpty(self: *Self) ValueType { + return TxRegisterEmpty.get(self); + } + + pub fn breakInterrupt(self: *Self) ValueType { + return BreakInterrupt.get(self); + } + + pub fn framingError(self: *Self) ValueType { + return FramingError.get(self); + } + + pub fn parityError(self: *Self) ValueType { + return ParityError.get(self); + } + + pub fn overrunError(self: *Self) ValueType { + return OverrunError.get(self); + } + + pub fn dataReady(self: *Self) ValueType { + return DataReady.get(self); + } + + pub fn readFrom(self: *Self, reg_io: anytype) *Self { + _ = self.base.readFrom(reg_io); + return self; + } +}; + +pub const ModemStatusRegister = struct { + const Self = @This(); + pub const ValueType = u8; + const ParentType = RegisterBase(Self, ValueType, void); + pub const printer_enabled: bool = ParentType.PrinterEnabled; + + base: ParentType = .{}, + + // Comptime field definitions + pub const DataCarrierDetect = DefBit(Self, 7, "data_carrier_detect"); + pub const RingIndicator = DefBit(Self, 6, "ring_indicator"); + pub const DataSetReady = DefBit(Self, 5, "data_set_ready"); + pub const ClearToSend = DefBit(Self, 4, "clear_to_send"); + pub const DeltaDataCarrierDetect = DefBit(Self, 3, "delta_data_carrier_detect"); + pub const TrailingEdgeRingIndicator = DefBit(Self, 2, "trailing_edge_ring_indicator"); + pub const DeltaDataSetReady = DefBit(Self, 1, "delta_data_set_ready"); + pub const DeltaClearToSend = DefBit(Self, 0, "delta_clear_to_send"); + + pub fn init() Self { + var self = Self{ .base = .{} }; + DataCarrierDetect.init(&self); + RingIndicator.init(&self); + DataSetReady.init(&self); + ClearToSend.init(&self); + DeltaDataCarrierDetect.init(&self); + TrailingEdgeRingIndicator.init(&self); + DeltaDataSetReady.init(&self); + DeltaClearToSend.init(&self); + return self; + } + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(6); + } + + pub fn dataCarrierDetect(self: *Self) ValueType { + return DataCarrierDetect.get(self); + } + + pub fn ringIndicator(self: *Self) ValueType { + return RingIndicator.get(self); + } + + pub fn dataSetReady(self: *Self) ValueType { + return DataSetReady.get(self); + } + + pub fn clearToSend(self: *Self) ValueType { + return ClearToSend.get(self); + } + + pub fn deltaDataCarrierDetect(self: *Self) ValueType { + return DeltaDataCarrierDetect.get(self); + } + + pub fn trailingEdgeRingIndicator(self: *Self) ValueType { + return TrailingEdgeRingIndicator.get(self); + } + + pub fn deltaDataSetReady(self: *Self) ValueType { + return DeltaDataSetReady.get(self); + } + + pub fn deltaClearToSend(self: *Self) ValueType { + return DeltaClearToSend.get(self); + } + + pub fn readFrom(self: *Self, reg_io: anytype) *Self { + _ = self.base.readFrom(reg_io); + return self; + } +}; + +pub const ScratchRegister = struct { + const Self = @This(); + pub const ValueType = u8; + const ParentType = RegisterBase(Self, ValueType, void); + pub const printer_enabled: bool = ParentType.PrinterEnabled; + + base: ParentType = .{}, + + // Comptime field definitions + pub const Data = DefField(Self, 7, 0, "data"); + + pub fn init() Self { + var self = Self{ .base = .{} }; + Data.init(&self); + return self; + } + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(7); + } + + pub fn data(self: *Self) ValueType { + return Data.get(self); + } + + pub fn setData(self: *Self, value: ValueType) *Self { + Data.set(self, value); + return self; + } + + pub fn readFrom(self: *Self, reg_io: anytype) *Self { + _ = self.base.readFrom(reg_io); + return self; + } + + pub fn writeTo(self: *Self, reg_io: anytype) *Self { + _ = self.base.writeTo(reg_io); + return self; + } +}; + +pub const DivisorLatchLowerRegister = struct { + const Self = @This(); + pub const ValueType = u8; + const ParentType = RegisterBase(Self, ValueType, void); + pub const printer_enabled: bool = ParentType.PrinterEnabled; + + base: ParentType = .{}, + + // Comptime field definitions + pub const Data = DefField(Self, 7, 0, "data"); + + pub fn init() Self { + var self = Self{ .base = .{} }; + Data.init(&self); + return self; + } + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(0); + } + + pub fn data(self: *Self) ValueType { + return Data.get(self); + } + + pub fn setData(self: *Self, value: ValueType) *Self { + Data.set(self, value); + return self; + } + + pub fn writeTo(self: *Self, reg_io: anytype) *Self { + _ = self.base.writeTo(reg_io); + return self; + } +}; + +pub const DivisorLatchUpperRegister = struct { + const Self = @This(); + pub const ValueType = u8; + const ParentType = RegisterBase(Self, ValueType, void); + pub const printer_enabled: bool = ParentType.PrinterEnabled; + + base: ParentType = .{}, + + // Comptime field definitions + pub const Data = DefField(Self, 7, 0, "data"); + + pub fn init() Self { + var self = Self{ .base = .{} }; + Data.init(&self); + return self; + } + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(1); + } + + pub fn data(self: *Self) ValueType { + return Data.get(self); + } + + pub fn setData(self: *Self, value: ValueType) *Self { + Data.set(self, value); + return self; + } + + pub fn writeTo(self: *Self, reg_io: anytype) *Self { + _ = self.base.writeTo(reg_io); + return self; + } +}; + +// dW8250 only +pub const UartStatusRegister = struct { + const Self = @This(); + pub const ValueType = u32; + const ParentType = RegisterBase(Self, ValueType, void); + pub const printer_enabled: bool = ParentType.PrinterEnabled; + + base: ParentType = .{}, + + // Comptime field definitions + pub const RsvdZField = DefRsvdzField(Self, 31, 5); + // Bits 4...1 are optionally configured in the dw8250 core. + pub const ReceiveFifoFull = DefBit(Self, 4, "receive_fifo_full"); + pub const ReceiveFifoNotEmpty = DefBit(Self, 3, "receive_fifo_not_empty"); + pub const TransmitFifoEmpty = DefBit(Self, 2, "transmit_fifo_empty"); + pub const TransmitFifoNotFull = DefBit(Self, 1, "transmit_fifo_not_full"); + pub const UartBusy = DefBit(Self, 0, "uart_busy"); + + pub fn init() Self { + var self = Self{ .base = .{} }; + RsvdZField.init(&self); + ReceiveFifoFull.init(&self); + ReceiveFifoNotEmpty.init(&self); + TransmitFifoEmpty.init(&self); + TransmitFifoNotFull.init(&self); + UartBusy.init(&self); + return self; + } + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(0x7c / 4); + } + + pub fn receiveFifoFull(self: *Self) ValueType { + return ReceiveFifoFull.get(self); + } + + pub fn receiveFifoNotEmpty(self: *Self) ValueType { + return ReceiveFifoNotEmpty.get(self); + } + + pub fn transmitFifoEmpty(self: *Self) ValueType { + return TransmitFifoEmpty.get(self); + } + + pub fn transmitFifoNotFull(self: *Self) ValueType { + return TransmitFifoNotFull.get(self); + } + + pub fn uartBusy(self: *Self) ValueType { + return UartBusy.get(self); + } + + pub fn readFrom(self: *Self, reg_io: anytype) *Self { + _ = self.base.readFrom(reg_io); + return self; + } +}; + +// The scaled number of IoSlots used by this driver +pub fn getIoSlots(comptime kdrv_extra: u32) u32 { + if (kdrv_extra == driver_config.ZBI_KERNEL_DRIVER_DW8250_UART) { + return (0x7c + @sizeOf(u32)) / 4; + } + return 8; +} + +// This provides the actual driver logic common to MMIO and PIO variants. +pub fn DriverImpl(comptime kdrv_extra: u32, comptime KdrvConfig: type, comptime io_reg_type: uart.IoRegisterType, comptime io_slots: u32) type { + return struct { + const Self = @This(); + const Base = uart.DriverBase(Self, kdrv_extra, KdrvConfig, io_reg_type, io_slots); + + pub const ConfigType = KdrvConfig; + + // Aliases to pick the correct FCR and IER register based on which type we are + const FifoControlRegister = FifoControlRegisterBase(kdrv_extra); + const InterruptEnableRegister = InterruptEnableRegisterBase(kdrv_extra); + + fifo_depth: u8 = fifo_depth_generic, + kdrv_extra: u32 = kdrv_extra, + base: Base = undefined, + + pub const config_name = switch (kdrv_extra) { + driver_config.ZBI_KERNEL_DRIVER_I8250_PIO_UART => "ioport", + driver_config.ZBI_KERNEL_DRIVER_I8250_MMIO32_UART => "mmio", + driver_config.ZBI_KERNEL_DRIVER_I8250_MMIO8_UART => "ns8250-8bit", + driver_config.ZBI_KERNEL_DRIVER_DW8250_UART => "dw8250", + driver_config.ZBI_KERNEL_DRIVER_PXA_UART => "pxa", + else => "ns8250", + }; + + pub fn tryMatchString(string: []const u8) ?uart.Config(Self) { + if (kdrv_extra == driver_config.ZBI_KERNEL_DRIVER_I8250_PIO_UART) { + if (std.mem.eql(u8, string, "legacy")) { + return uart.Config(Self).initWithConfig(legacy_config); + } + } + return Base.tryMatchString(string); + } + + pub fn initWithConfig(config: KdrvConfig) Self { + return Self{ + .base = Base.init(config), + }; + } + + pub fn initHardware(self: *Self, comptime IoProviderType: type, io: *IoProviderType) void { + // Get basic config done so that tx functions. + + if (kdrv_extra == driver_config.ZBI_KERNEL_DRIVER_PXA_UART) { + // Disable all interrupts but keep the uart enable bit + var ier = Self.InterruptEnableRegister.get().fromValue(0); + _ = ier.setUartEnable(1); + _ = ier.writeTo(io.getIo()); + + // PXA has a different enough FCR to configure differently than the others + var fcr = Self.FifoControlRegister.get().fromValue(0); + _ = fcr.setFifoEnable(1); + _ = fcr.setRxFifoReset(1); + _ = fcr.setTxFifoReset(1); + _ = fcr.setReceiverTrigger(0); // Trigger at 1 byte in the rx fifo. + _ = fcr.setTransmitTrigger(1); // Trigger at empty tx fifo. + _ = fcr.writeTo(io.getIo()); + } else { + // Disable all interrupts + var ier = Self.InterruptEnableRegister.get().readFrom(io.getIo()); + _ = ier.setRxAvailable(0).setTxEmpty(0).setLineStatus(0).setModemStatus(0); + _ = ier.writeTo(io.getIo()); + + // Extended FIFO mode must be enabled while the divisor latch is. + // Be sure to preserve the line controls, modulo divisor latch access, + // which should be disabled immediately after configuring the FIFO. + var lcr = LineControlRegister.get().readFrom(io.getIo()); + _ = lcr.setDivisorLatchAccess(1).writeTo(io.getIo()); + + var fcr = Self.FifoControlRegister.get().fromValue(0); + _ = fcr.setReceiverTrigger(Self.FifoControlRegister.max_trigger_level); + _ = fcr.setTxFifoReset(1); + _ = fcr.setRxFifoReset(1); + _ = fcr.setFifoEnable(1); + + if (kdrv_extra == driver_config.ZBI_KERNEL_DRIVER_DW8250_UART) { + // dw8250 does not have an extended fifo enable bit in bit 5, but + // instead has a TX fifo threshold field in bits 4-5 + _ = fcr.setTransmitTriggerDw8250(0); + _ = fcr.setDmaMode(0); + } else { + _ = fcr.setExtendedFifoEnable(1); + _ = fcr.setDmaMode(0); + } + _ = fcr.writeTo(io.getIo()); + + // Commit divisor by clearing the latch + _ = lcr.setDivisorLatchAccess(0).writeTo(io.getIo()); + } + + // Drive flow control bits high since we don't actively manage them + var mcr = ModemControlRegister.get().fromValue(0); + _ = mcr.setAutomaticFlowControlEnable(0); + _ = mcr.setLoop(0); + _ = mcr.setAuxiliaryOut2(0); + _ = mcr.setAuxiliaryOut1(0); + _ = mcr.setRequestToSend(1); + _ = mcr.setDataTerminalReady(1); + _ = mcr.writeTo(io.getIo()); + + // Figure out the FIFO depth + var iir = InterruptIdentRegister.get().readFrom(io.getIo()); + if (iir.fifosEnabled() != 0) { + if (kdrv_extra == driver_config.ZBI_KERNEL_DRIVER_PXA_UART) { + self.fifo_depth = fifo_depth_pxa; + } else if (kdrv_extra == driver_config.ZBI_KERNEL_DRIVER_DW8250_UART) { + // The fifo depth isn't easily known on the dw8250, but it + // must be at least 16 bytes if the fifo is enabled + self.fifo_depth = fifo_depth_dw8250_minimum; + } else { + // This is a 16750 or a 16550A + self.fifo_depth = if (iir.extendedFifoEnabled() != 0) fifo_depth_16750 else fifo_depth_16550a; + } + } else { + self.fifo_depth = fifo_depth_generic; + } + } + + pub fn setLineControl(self: *Self, comptime IoProviderType: type, io: *IoProviderType, data_bits: ?uart.DataBits, parity: ?uart.Parity, stop_bits: ?uart.StopBits) void { + _ = self; + const divisor = max_baud_rate / default_baud_rate; + + var lcr = LineControlRegister.get().fromValue(0); + _ = lcr.setDivisorLatchAccess(1).writeTo(io.getIo()); + + var dll = DivisorLatchLowerRegister.get().fromValue(0); + _ = dll.setData(@intCast(divisor)); + _ = dll.writeTo(io.getIo()); + + var dlh = DivisorLatchUpperRegister.get().fromValue(0); + _ = dlh.setData(@intCast(divisor >> 8)); + _ = dlh.writeTo(io.getIo()); + + lcr = LineControlRegister.get().fromValue(0); + _ = lcr.setDivisorLatchAccess(0); + + if (data_bits) |bits| { + _ = lcr.setWordLength(switch (bits) { + .five => LineControlRegister.word_length_5, + .six => LineControlRegister.word_length_6, + .seven => LineControlRegister.word_length_7, + .eight => LineControlRegister.word_length_8, + }); + } + + if (parity) |p| { + _ = lcr.setParityEnable(if (p != .none) 1 else 0); + _ = lcr.setEvenParity(if (p == .even) 1 else 0); + } + + if (stop_bits) |bits| { + _ = lcr.setStopBits(switch (bits) { + .one => LineControlRegister.stop_bits_1, + .two => LineControlRegister.stop_bits_2, + }); + } + + _ = lcr.writeTo(io.getIo()); + } + + pub fn txReady(_: *Self, comptime IoProviderType: type, io: *IoProviderType) bool { + var lsr = LineStatusRegister.get().readFrom(io.getIo()); + return lsr.txRegisterEmpty() != 0; + } + + pub fn write(self: *Self, comptime IoProviderType: type, io: *IoProviderType, ready: bool, comptime ItType: type, it: *ItType, end: ItType) ItType { + _ = ready; + // The FIFO is empty now and we know the size, so fill it completely + var tx = TxBufferRegister.get().fromValue(0); + var space = self.fifo_depth; + while (!it.eql(end) and space > 0) : (space -= 1) { + _ = tx.setData(it.current()); + _ = tx.writeTo(io.getIo()); + it.next(); + } + return it.*; + } + + pub fn read(self: *Self, comptime IoProviderType: type, io: *IoProviderType) ?u8 { + _ = self; + var lsr = LineStatusRegister.get().readFrom(io.getIo()); + if (lsr.dataReady() != 0) { + var rx = RxBufferRegister.get().readFrom(io.getIo()); + return @intCast(rx.data()); + } + return null; + } + + pub fn enableTxInterrupt(self: *Self, comptime IoProviderType: type, io: *IoProviderType, enable: bool) void { + _ = self; + var ier = Self.InterruptEnableRegister.get().readFrom(io.getIo()); + _ = ier.setTxEmpty(if (enable) 1 else 0); + _ = ier.writeTo(io.getIo()); + } + + pub fn enableRxInterrupt(self: *Self, comptime IoProviderType: type, io: *IoProviderType, enable: bool) void { + _ = self; + var ier = Self.InterruptEnableRegister.get().readFrom(io.getIo()); + _ = ier.setRxAvailable(if (enable) 1 else 0).writeTo(io.getIo()); + } + + pub fn initInterrupt(self: *Self, comptime IoProviderType: type, io: *IoProviderType, enableInterruptCallback: anytype) void { + // In x86 drivers enabling the interrupt after setting up the hardware + // may cause the Rx Interrupt never to fire + if (kdrv_extra == driver_config.ZBI_KERNEL_DRIVER_I8250_PIO_UART or + kdrv_extra == driver_config.ZBI_KERNEL_DRIVER_I8250_MMIO32_UART) + { + enableInterruptCallback(); + } + // Enable receive interrupts + self.enableRxInterrupt(IoProviderType, io, true); + + // Modem Control Register: Auxiliary Output 2 is another IRQ enable bit + var mcr = ModemControlRegister.get().readFrom(io.getIo()); + _ = mcr.setAuxiliaryOut2(1).writeTo(io.getIo()); + + // Since these are level triggered interrupts nominally, it's safe and correct to + // enable the interrupt after configuring the hardware, since no interrupt edges can + // be lost + if (kdrv_extra == driver_config.ZBI_KERNEL_DRIVER_DW8250_UART or + kdrv_extra == driver_config.ZBI_KERNEL_DRIVER_I8250_MMIO8_UART or + kdrv_extra == driver_config.ZBI_KERNEL_DRIVER_PXA_UART) + { + enableInterruptCallback(); + } + } + + pub fn interrupt(self: *Self, comptime IoProviderType: type, comptime LockType: type, comptime TxType: type, comptime RxType: type, io: *IoProviderType, lock: *LockType, waiter: anytype, tx: TxType, rx: RxType) void { + var iir = InterruptIdentRegister.get(); + var id: InterruptType = undefined; + while (true) { + id = iir.readFrom(io.getIo()).getInterruptId(); + if (id == .none) break; + + if (kdrv_extra == driver_config.ZBI_KERNEL_DRIVER_DW8250_UART) { + if (id == .dw8250_busy_detect) { + // dw8250 only. From the manual: + // "Master has tried to write to the Line Control Register while the DW_apb_uart is busy + // (USR[0] is set to one)." Read the UART Status Register to clear it. + _ = UartStatusRegister.get().readFrom(io.getIo()); + } + } + + // Reading LSR will clear kRxLineStatus signal + var lsr = LineStatusRegister.get().readFrom(io.getIo()); + + // Notify TX + if (lsr.txRegisterEmpty() != 0) { + const Context = struct { + fn callback() void { + self.enableTxInterrupt(IoProviderType, io, false); + } + }; + const TxInterruptType = uart.TxInterrupt(LockType, @TypeOf(waiter), @TypeOf(Context.callback)); + const ctx = Context{}; + const tx_irq = TxInterruptType.init(lock, waiter, ctx.callback); + tx(tx_irq); + } + + // Drain RX while the line status bit is ready + var should_drain_rx = true; + while (should_drain_rx and lsr.dataReady() != 0) { + const ReadCharContext = struct { + fn callback() u8 { + return @intCast(RxBufferRegister.get().readFrom(io.getIo()).data()); + } + }; + + const DisableContext = struct { + should_drain_rx: *bool, + + fn callback(sd: *bool) void { + // If the buffer is full, disable the receive interrupt instead and + // exit the loop + self.enableRxInterrupt(IoProviderType, io, false); + sd.* = false; + } + }; + + const readCharContext = ReadCharContext{}; + const disableContext = DisableContext{ .should_drain_rx = &should_drain_rx }; + const RxInterruptType = uart.RxInterrupt( + LockType, + @TypeOf(readCharContext.callback), + @TypeOf(disableContext.callback), + ); + + const rx_irq = RxInterruptType.init( + lock, + readCharContext.callback, + disableContext.callback, + ); + rx(rx_irq); + lsr = LineStatusRegister.get().readFrom(io.getIo()); + } + } + } + + pub fn getConfig(self: *const Self) ConfigType { + return self.base.config; + } + + pub fn getIoSlots(self: *const Self) uart.IoSlotType(io_reg_type) { + return self.base.getIoSlots(); + } + }; +} + +// uart::KernelDriver UartDriver API for PIO via MMIO where offsets expressed in bytes are scaled +// by 4. Additionally all read or write operations are performed in 4 byte regions. +pub const Mmio32Driver = DriverImpl(driver_config.ZBI_KERNEL_DRIVER_I8250_MMIO32_UART, driver_config.SimpleDriverConfig, uart.IoRegisterType.mmio32, getIoSlots(driver_config.ZBI_KERNEL_DRIVER_I8250_MMIO32_UART)); + +// uart::KernelDriver UartDriver API for PIO via MMIO where offsets are expressed in bytes. +pub const Mmio8Driver = DriverImpl(driver_config.ZBI_KERNEL_DRIVER_I8250_MMIO8_UART, driver_config.SimpleDriverConfig, uart.IoRegisterType.mmio8, getIoSlots(driver_config.ZBI_KERNEL_DRIVER_I8250_MMIO8_UART)); + +// uart::KernelDriver UartDriver API for direct PIO. +pub const PioDriver = DriverImpl(driver_config.ZBI_KERNEL_DRIVER_I8250_PIO_UART, driver_config.SimplePioConfig, uart.IoRegisterType.pio, getIoSlots(driver_config.ZBI_KERNEL_DRIVER_I8250_PIO_UART)); + +// uart::KernelDriver UartDriver API for a DW8250 style variant. +pub const Dw8250Driver = DriverImpl(driver_config.ZBI_KERNEL_DRIVER_DW8250_UART, driver_config.SimpleDriverConfig, uart.IoRegisterType.mmio32, getIoSlots(driver_config.ZBI_KERNEL_DRIVER_DW8250_UART)); + +// uart::KernelDriver UartDriver API for a PXA style variant. +pub const PxaDriver = DriverImpl(driver_config.ZBI_KERNEL_DRIVER_PXA_UART, driver_config.SimpleDriverConfig, uart.IoRegisterType.mmio32, getIoSlots(driver_config.ZBI_KERNEL_DRIVER_PXA_UART)); + +const SimpleTestDriver = uart.KernelDriver(Mmio32Driver, mock.IoProvider(driver_config.SimpleDriverConfig, uart.IoRegisterType.mmio32), sync.UnsynchronizedPolicy); + +const test_config = driver_config.SimpleDriverConfig{ + .mmio_phys = 0, + .irq = 0, + .flags = 0, +}; + +test "ns8250 HelloWorld" { + const default_line_controls: u8 = 0b0000_0011; + + var driver = SimpleTestDriver.init(test_config); + defer driver.deinit(); + + _ = driver.getIo() + .mock() + // Init() + .expectRead(u8, @as(u8, 0b0000_0000), 1) + .expectWrite(u8, @as(u8, 0b0000_0000), 1) // Init + .expectRead(u8, @as(u8, default_line_controls), 3) + .expectWrite(u8, @as(u8, default_line_controls | 0b1000_0000), 3) + .expectWrite(u8, @as(u8, 0b1110_0111), 2) + .expectWrite(u8, @as(u8, default_line_controls), 3) + .expectWrite(u8, @as(u8, 0b0000_0011), 4) + .expectRead(u8, @as(u8, 0b1110_0001), 2) + // Write() + .expectRead(u8, @as(u8, 0b0110_0000), 5) // TxReady -> true + .expectWrite(u8, @as(u8, 'h'), 0) // Write + .expectWrite(u8, @as(u8, 'i'), 0) + .expectWrite(u8, @as(u8, '\r'), 0) + .expectWrite(u8, @as(u8, '\n'), 0); + + driver.initHardware(SimpleTestDriver.DefaultLockPolicy); + try testing.expectEqual(@as(usize, 3), driver.write(SimpleTestDriver.DefaultLockPolicy, "hi\n", {})); +} + +test "ns8250 SetLineControl8N1" { + const default_line_controls: u8 = 0b0000_0011; + + var driver = SimpleTestDriver.init(test_config); + defer driver.deinit(); + + _ = driver.getIo() + .mock() + // Init() + .expectRead(u8, @as(u8, 0b0000_0000), 1) + .expectWrite(u8, @as(u8, 0b0000_0000), 1) // Init + .expectRead(u8, @as(u8, default_line_controls), 3) + .expectWrite(u8, @as(u8, default_line_controls | 0b1000_0000), 3) + .expectWrite(u8, @as(u8, 0b1110_0111), 2) + .expectWrite(u8, @as(u8, default_line_controls), 3) + .expectWrite(u8, @as(u8, 0b0000_0011), 4) + .expectRead(u8, @as(u8, 0b1110_0001), 2) + // SetLineControl() + .expectWrite(u8, @as(u8, 0b1000_0000), 3) // SetLineControl + .expectWrite(u8, @as(u8, 0b0000_0001), 0) + .expectWrite(u8, @as(u8, 0b0000_0000), 1) + .expectWrite(u8, @as(u8, 0b0000_0011), 3); + + driver.initHardware(SimpleTestDriver.DefaultLockPolicy); + driver.setLineControl(SimpleTestDriver.DefaultLockPolicy, uart.DataBits.eight, uart.Parity.none, uart.StopBits.one); +} + +test "ns8250 SetLineControl7E1" { + const default_line_controls: u8 = 0b0000_0011; + + var driver = SimpleTestDriver.init(test_config); + defer driver.deinit(); + + _ = driver.getIo() + .mock() + // Init() + .expectRead(u8, @as(u8, 0b0000_0000), 1) + .expectWrite(u8, @as(u8, 0b0000_0000), 1) // Init + .expectRead(u8, @as(u8, default_line_controls), 3) + .expectWrite(u8, @as(u8, default_line_controls | 0b1000_0000), 3) + .expectWrite(u8, @as(u8, 0b1110_0111), 2) + .expectWrite(u8, @as(u8, default_line_controls), 3) + .expectWrite(u8, @as(u8, 0b0000_0011), 4) + .expectRead(u8, @as(u8, 0b1110_0001), 2) + // SetLineControl() + .expectWrite(u8, @as(u8, 0b1000_0000), 3) // SetLineControl + .expectWrite(u8, @as(u8, 0b0000_0001), 0) + .expectWrite(u8, @as(u8, 0b0000_0000), 1) + .expectWrite(u8, @as(u8, 0b0001_1010), 3); + + driver.initHardware(SimpleTestDriver.DefaultLockPolicy); + driver.setLineControl(SimpleTestDriver.DefaultLockPolicy, uart.DataBits.seven, uart.Parity.even, uart.StopBits.one); +} + +test "ns8250 Read" { + const default_line_controls: u8 = 0b0000_0011; + + var driver = SimpleTestDriver.init(test_config); + defer driver.deinit(); + + _ = driver.getIo() + .mock() + // Init() + .expectRead(u8, @as(u8, 0b0000_0000), 1) + .expectWrite(u8, @as(u8, 0b0000_0000), 1) // Init + .expectRead(u8, @as(u8, default_line_controls), 3) + .expectWrite(u8, @as(u8, default_line_controls | 0b1000_0000), 3) + .expectWrite(u8, @as(u8, 0b1110_0111), 2) + .expectWrite(u8, @as(u8, default_line_controls), 3) + .expectWrite(u8, @as(u8, 0b0000_0011), 4) + .expectRead(u8, @as(u8, 0b1110_0001), 2) + // Write() + .expectRead(u8, @as(u8, 0b0110_0000), 5) // TxReady -> true + .expectWrite(u8, @as(u8, '?'), 0) // Write + .expectWrite(u8, @as(u8, '\r'), 0) + .expectWrite(u8, @as(u8, '\n'), 0) + // Read() + .expectRead(u8, @as(u8, 0b0110_0001), 5) // Read (data_ready) + .expectRead(u8, @as(u8, 'q'), 0) // Read (data) + // Read() + .expectRead(u8, @as(u8, 0b0110_0001), 5) // Read (data_ready) + .expectRead(u8, @as(u8, '\r'), 0); // Read (data) + + driver.initHardware(SimpleTestDriver.DefaultLockPolicy); + try testing.expectEqual(@as(usize, 2), driver.write(SimpleTestDriver.DefaultLockPolicy, "?\n", {})); + try testing.expectEqual(@as(u8, 'q'), driver.read(SimpleTestDriver.DefaultLockPolicy).?); + try testing.expectEqual(@as(u8, '\r'), driver.read(SimpleTestDriver.DefaultLockPolicy).?); +} diff --git a/slipstream/system/ulib/uart/src/null.zig b/slipstream/system/ulib/uart/src/null.zig new file mode 100644 index 0000000..2381491 --- /dev/null +++ b/slipstream/system/ulib/uart/src/null.zig @@ -0,0 +1,163 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. + +const std = @import("std"); +const uart = @import("uart.zig"); +const zbi_format = @import("zbi_format"); + +const driver_config = zbi_format.driver_config; + +// uart::null::Driver is a bit bucket. +// It also serves to demonstrate the API required by uart::KernelDriver. + +pub const Driver = struct { + const Self = @This(); + + pub const ConfigType = uart.StubConfig; + + pub const devicetree_bindings: [0][]const u8 = .{}; + pub const config_name: []const u8 = "none"; + pub const io_type: uart.IoRegisterType = .none; + pub const driver_type: u32 = 0; + pub const extra: u32 = 0; + + pub fn tryMatch(header: *const anyopaque) ?uart.Config(Driver) { + _ = header; + return null; + } + + pub fn tryMatchAcpi(debug_port: *const anyopaque) ?uart.Config(Driver) { + _ = debug_port; + return null; + } + + pub fn tryMatchString(str: []const u8) ?uart.Config(Driver) { + if (std.mem.eql(u8, str, config_name)) { + return uart.Config(Driver){}; + } + return null; + } + + pub fn trySelect(decoder: *const anyopaque) bool { + _ = decoder; + return false; + } + + pub fn init() Driver { + return .{}; + } + + pub fn initWithConfig(_: ConfigType) Driver { + return .{}; + } + + pub fn initWithTaggedConfig(tagged_config: anytype) Driver { + _ = tagged_config; + return .{}; + } + + pub fn eql(self: *const Self, other: *const Self) bool { + _ = self; + _ = other; + return true; + } + + pub fn fillItem(_: *const Self, _: *anyopaque) void { + @panic("should never be called"); + } + + pub fn matchDevicetree(decoder: *const anyopaque) bool { + _ = decoder; + return false; + } + + //pub fn unparse(self: *const Self, writer: anytype) !void { + // _ = self; + // try writer.writeAll(config_name); + //} + + // uart::KernelDriver UartDriver API + // + // Each method is a template parameterized by an IoProvider type for + // accessing the hardware registers so that real Driver types can be used + // with mock in tests independent of actual hardware access. The + // null Driver never uses the `io` arguments. + + pub fn initHardware(self: *Self, comptime IoProviderType: type, io: *IoProviderType) void { + _ = self; + _ = io; + } + + pub fn setLineControl(self: *Self, comptime IoProviderType: type, io: *IoProviderType, data_bits: ?uart.DataBits, parity: ?uart.Parity, stop_bits: ?uart.StopBits) void { + _ = self; + _ = io; + _ = data_bits; + _ = parity; + _ = stop_bits; + } + + // Return true if Write can make forward progress right now. + pub fn txReady(self: *Self, comptime IoProviderType: type, io: *IoProviderType) bool { + _ = self; + _ = io; + return true; + } + + // This is called only when txReady() has just returned true. Advance + // the iterator at least one and as many as is convenient but not past + // end, outputting each character before advancing. + pub fn write(self: *Self, comptime IoProviderType: type, io: *IoProviderType, ready: bool, comptime ItType: type, it: *ItType, end: ItType) ItType { + _ = self; + _ = io; + _ = ready; + _ = it; + return end; + } + + // Poll for an incoming character and return one if there is one. + pub fn read(self: *Self, comptime IoProviderType: type, io: *IoProviderType) ?u8 { + _ = self; + _ = io; + return null; + } + + // Enable transmit interrupts so interrupt will be called when txReady(). + pub fn enableTxInterrupt(self: *Self, comptime IoProviderType: type, io: *IoProviderType, enable: bool) void { + _ = self; + _ = io; + _ = enable; + } + + // Enable receive interrupts so interrupt will be called when rxReady(). + pub fn enableRxInterrupt(self: *Self, comptime IoProviderType: type, io: *IoProviderType, enable: bool) void { + _ = self; + _ = io; + _ = enable; + } + + // Set the UART up to deliver interrupts. This is called after initHardware. + pub fn initInterrupt(self: *Self, comptime IoProviderType: type, io: *IoProviderType, enable_interrupt_callback: anytype) void { + _ = self; + _ = io; + _ = enable_interrupt_callback; + } + + pub fn interrupt(self: *Self, comptime IoProviderType: type, comptime LockType: type, comptime TxType: type, comptime RxType: type, io: *IoProviderType, lock: *LockType, waiter: *anyopaque, tx: TxType, rx: RxType) void { + _ = self; + _ = io; + _ = lock; + _ = waiter; + _ = tx; + _ = rx; + } + + // This tells the IoProvider what device resources to provide. + pub fn getConfig(_: *const Self) ConfigType { + return .{}; + } + + pub fn getIoSlots(_: *const Self) usize { + return 0; + } +}; diff --git a/slipstream/system/ulib/uart/src/parse.zig b/slipstream/system/ulib/uart/src/parse.zig new file mode 100644 index 0000000..7c3629f --- /dev/null +++ b/slipstream/system/ulib/uart/src/parse.zig @@ -0,0 +1,184 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. + +const std = @import("std"); +const zbi_format = @import("zbi_format"); + +/// Parse a comma-separated list of integers of the form `,1,2,3,...,1000`, +/// where the list must begin with a comma. +/// +/// Input integers may be decimal (42), hexadecimal (0x2a) or octal (052). +/// +/// Returns the number of elements parsed from the string. +pub fn parseInts(string: []const u8, args: anytype) usize { + var count: usize = 0; + var remaining = string; + + inline for (args) |arg| { + const parsed_count = parseNext(&remaining, arg); + if (parsed_count == 0) { + break; + } + count += parsed_count; + } + + return count; +} + +fn parseNext(string: *[]const u8, arg: anytype) usize { + if (string.len < 2 or string.*[0] != ',') { + string.* = string.*[string.len..]; // Consume remaining string + return 0; + } + string.* = string.*[1..]; // Remove the leading comma + + // Parse the leading sign and 0x (hex) or 0 (octal) indicator + var negative = false; + if ((string.*[0] == '-' or string.*[0] == '+') and string.len > 1) { + negative = string.*[0] == '-'; + string.* = string.*[1..]; + } + + var base: u8 = 10; + if (string.*[0] == '0' and string.len > 1) { + if (string.*[1] == 'x') { + base = 16; + string.* = string.*[2..]; + } else { + base = 8; + } + // Trim leading zeros + while (string.*[0] == '0' and string.len > 1) { + string.* = string.*[1..]; + } + } + + // Find the end of the number using appropriate character validation + var end: usize = 0; + while (end < string.len) : (end += 1) { + const c = string.*[end]; + const is_valid = switch (base) { + 16 => std.ascii.isHex(c), + 8 => c >= '0' and c <= '7', + 10 => std.ascii.isDigit(c), + else => unreachable, + }; + if (!is_valid) break; + } + + if (end == 0) { + // The comma was followed by a non-numerical character + return 0; + } + + // Since leading zeros were removed, any string of digits that doesn't fit + // in a reasonable buffer would be an integer that overflows anyway. + // Using a 32-character buffer as in the C++ version. + var buf: [32]u8 = undefined; + if (end >= buf.len) { + return 0; // Number too long, would overflow + } + + // Copy the number string and null-terminate for parsing + const num_str = string.*[0..end]; + @memcpy(buf[0..end], num_str); + string.* = string.*[end..]; + + // Parse the number + const value = std.fmt.parseInt(u64, buf[0..end], base) catch return 0; + + // Handle the conversion more carefully to avoid overflow + // First, handle the sign conversion + var final_value: u64 = value; + if (negative) { + // Convert to negative by bitwise complement + 1 (two's complement) + final_value = (~value) +% 1; + } + + // Now truncate to the target type size + arg.* = @truncate(final_value); + + return 1; +} + +/// Write a comma-separated list of integers to the output stream in hexadecimal format +pub fn unparseInts(writer: anytype, args: anytype) !void { + inline for (args) |arg| { + try writer.print(",{x}", .{arg}); + } +} + +/// Write a comma-separated list of integers to the output stream in hexadecimal format with 0x prefix +pub fn unparseIntsHex(writer: anytype, args: anytype) !void { + inline for (args) |arg| { + try writer.print(",{#x}", .{arg}); + } +} + +/// Parse configuration for SimpleDriverConfig (zbi_dcfg_simple_t equivalent) +pub fn parseConfigSimple(string: []const u8) ?zbi_format.driver_config.SimpleDriverConfig { + var config: zbi_format.driver_config.SimpleDriverConfig = .{ + .mmio_phys = 0, + .irq = 0, + .flags = 0, + }; + if (parseInts(string, .{ + &config.mmio_phys, + &config.irq, + &config.flags, + }) > 1) { + return config; + } + return null; +} + +/// Unparse configuration for SimpleDriverConfig +pub fn unparseConfigSimple(config: zbi_format.driver_config.SimpleDriverConfig, writer: anytype) !void { + try unparseInts(writer, .{ config.mmio_phys, config.irq }); +} + +/// Parse configuration for SimplePioConfig (zbi_dcfg_simple_pio_t equivalent) +pub fn parseConfigSimplePio(string: []const u8) ?zbi_format.driver_config.SimplePioConfig { + var config: zbi_format.driver_config.SimplePioConfig = .{ + .base = 0, + .reserved = 0, + .irq = 0, + }; + if (parseInts(string, .{ + &config.base, + &config.irq, + }) == 2) { + return config; + } + return null; +} + +/// Unparse configuration for SimplePioConfig +pub fn unparseConfigSimplePio(config: zbi_format.driver_config.SimplePioConfig, writer: anytype) !void { + try unparseInts(writer, .{ config.base, config.irq }); +} + +/// Generic config parsing function that dispatches to specific parsers +pub fn parseConfigGeneric(comptime ConfigType: type, string: []const u8) ?ConfigType { + if (ConfigType == void) { + @compileError("missing specialization"); + } else if (ConfigType == zbi_format.driver_config.SimpleDriverConfig) { + return parseConfigSimple(string); + } else if (ConfigType == zbi_format.driver_config.SimplePioConfig) { + return parseConfigSimplePio(string); + } + return null; +} + +/// Generic config unparsing function that dispatches to specific unparsers +pub fn unparseConfigGeneric(config: anytype, writer: anytype) !void { + const ConfigType = @TypeOf(config); + if (ConfigType == zbi_format.driver_config.SimpleDriverConfig) { + try unparseConfigSimple(config, writer); + } else if (ConfigType == zbi_format.driver_config.SimplePioConfig) { + try unparseConfigSimplePio(config, writer); + } else { + @compileError("missing specialization"); + } +} diff --git a/slipstream/system/ulib/uart/src/sync.zig b/slipstream/system/ulib/uart/src/sync.zig new file mode 100644 index 0000000..cf332d5 --- /dev/null +++ b/slipstream/system/ulib/uart/src/sync.zig @@ -0,0 +1,71 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. + +const std = @import("std"); +const arch = @import("arch").intrin; + +/// Degenerate case of unsynchronized policy, that provides the expected API to be implemented when +/// another policy needs to be used. +pub const UnsynchronizedPolicy = struct { + /// The Lock consists of any environment specific capability providing synchronization + /// primitives. + /// * Lock must be default-constructible. + /// * Lock acquisition and release is private to the declared Guard. + /// * Lock must model a version of TA Capabilities. + /// * MemberOf is the containing type where Lock is embedded as a member. + pub fn Lock(comptime MemberOf: type) type { + _ = MemberOf; + return struct { + pub const Self = @This(); + pub fn init() Self { + return .{}; + } + }; + } + + /// The Guard consists of any environment specific scoped capability that presents itself + /// as a RAII for acquiring the opaque LockType. + /// * Guard must be constructible from (Lock*, const char* id). + /// * LockPolicy is forwarded to Guard type. + pub fn Guard(comptime LockPolicy: type) type { + _ = LockPolicy; + return struct { + pub const Self = @This(); + pub fn init(lock: anytype, comptime src: std.builtin.SourceLocation) Self { + std.debug.print("init: {s}\n", .{src.file ++ ":" ++ std.fmt.comptimePrint("{}", .{src.line})}); + _ = lock; + return .{}; + } + pub fn deinit(self: *Self) void { + _ = self; + } + }; + } + + /// The Waiter consists of any environment specific object, that provides a mechanism for + /// waiting for an event to happen. + /// * Waiter must implement Wait(Guard&, T&& enable_tx_interrupt). + /// + /// While the library only requires Wait to be implemented, users should provide a wake mechanism + /// in blocking implementations, such that observers stuck waiting can resume their work. + /// + /// Wait is guaranteed to be called while guard holds the underlying capability. + pub const Waiter = struct { + pub fn wait(_: *const Waiter, guard: anytype, enable_tx_interrupt: anytype, args: anytype) void { + _ = guard; + _ = enable_tx_interrupt; + _ = args; + arch.yield(); + } + }; + + /// Default Lock Policy to be used. + /// The meaning of the policy is only meaningful to the Guard or this SyncPolicy. + pub const DefaultLockPolicy = struct {}; + + /// Delegate for Lock Holding assertions. + pub fn assertHeld(comptime lock: anytype) void { + _ = lock; + } +}; diff --git a/slipstream/system/ulib/uart/src/uart.zig b/slipstream/system/ulib/uart/src/uart.zig new file mode 100644 index 0000000..450412c --- /dev/null +++ b/slipstream/system/ulib/uart/src/uart.zig @@ -0,0 +1,469 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. + +const std = @import("std"); +const zbi_format = @import("zbi_format"); +const parse = @import("parse.zig"); +const chars_from = @import("chars_from.zig"); + +pub const StubConfig = struct {}; +pub const driver_config = zbi_format.driver_config; + +// Tagged configuration type, used to represent the configuration of `Driver` even if multiple types +// of driver have the same `config_type`. +pub fn Config(comptime Driver: type) type { + return struct { + const Self = @This(); + + pub const UartType = Driver; + pub const ConfigType = Driver.ConfigType; + + config: ConfigType, + + pub fn init() Self { + return Self{ .config = .{} }; + } + + pub fn initWithConfig(cfg: ConfigType) Self { + return Self{ .config = cfg }; + } + + pub fn eql(self: *Self, rhs: *Self) bool { + if (ConfigType == StubConfig) { + return true; + } else if (ConfigType == driver_config.SimplePioConfig) { + return self.config.base == rhs.config.base and self.config.irq == rhs.config.irq; + } else if (ConfigType == driver_config.SimpleDriverConfig) { + return self.config.mmio_phys == rhs.config.mmio_phys and + self.config.irq == rhs.config.irq and + self.config.flags == rhs.config.flags; + } + return false; + } + + pub fn eqlOther(self: *Self, comptime OtherDriver: type, rhs: Config(OtherDriver)) bool { + _ = self; + _ = rhs; + return false; + } + + pub fn asBytes(self: *const Self) []const u8 { + return std.mem.asBytes(&self.config); + } + }; +} + +/// Number of bits transmitted per character. +pub const DataBits = enum { + five, + six, + seven, + eight, +}; + +pub const Parity = enum { + /// No bits dedicated to parity. + none, + + // Parity bit present; is 0 if the number of 1s in the word is even. + even, + + // Parity bit present; is 0 if the number of 1s in the word is odd. + odd, +}; + +/// The duration of the stop period in terms of the transmitted bit rate. +pub const StopBits = enum { + one, + two, +}; + +//pub fn unparseConfig(comptime config: type, out: anytype) void { +// if (config == void) { +// @compileError("Cannot unparse config of type void"); +// } +// _ = out; +//} + +pub const IoRegisterType = enum { + /// Null/Stub drivers. + none, + + /// MMIO is performed without any scaling what so ever, this means that + /// registers offsets are treated as byte offsets from the base address. + mmio8, + + /// MMIO is performed with an scaling factor of 4, this means that + /// register offsets are treated as 4-byte offsets from the base address. + mmio32, + + /// PIO. + pio, +}; + +pub fn IoSlotType(comptime io_reg_type: IoRegisterType) type { + return switch (io_reg_type) { + .pio => u16, + else => usize, + }; +} + +pub fn MmioDriver(comptime UartDriver: type) bool { + return UartDriver.kIoType == .mmio32 or UartDriver.kIoType == .mmio8; +} + +// Constant indicating that the number of `io_slots()` is to be determined at +// runtime. +pub const dynamic_io_slot: usize = std.math.maxInt(usize); + +// Communicates the range where the configuration dictates the registers are located. +// +// It may need to be translated if the addressing used for the configuration is different from +// the one used for execution (e.g. physical and virtual addressing). +pub const MmioRange = struct { + address: u64, + size: u64, +}; + +pub fn DriverBase(comptime Driver: type, comptime drvExtra: u32, comptime DriverConfig: type, comptime IoRegType: IoRegisterType, comptime IoSlots: IoSlotType(IoRegType)) type { + return struct { + const Self = @This(); + pub const ConfigType = DriverConfig; + + config: ConfigType, + pub const devicetree_bindings: []const []const u8 = &.{}; + pub const io_type: IoRegType = IoRegType; + //type_id: u32, + pub const extra: u32 = drvExtra; + + //pub fn tryMatch(header: anytype, payload: anytype) ?Config { + // if (header.type == ZBI_TYPE_KERNEL_DRIVER and header.extra == extra and + // header.length >= @sizeOf(config_type)) { + // return Config.init(@ptrCast(*const config_type, payload).*); + // } + // return null; + //} + + pub fn tryMatchString(string: []const u8) ?Config(Driver) { + std.debug.print("TryMatch::BASE\n", .{}); + const config_name = Driver.config_name; + if (string.len >= config_name.len and + std.mem.eql(u8, string[0..config_name.len], config_name)) + { + const remaining = string[config_name.len..]; + if (parse.parseConfigGeneric(DriverConfig, remaining)) |config| { + return Config(Driver).initWithConfig(config); + } + } + return null; + } + + // API to match DBG2 Table (ACPI). Currently only 16550 compatible uarts are supported. + //pub fn tryMatchAcpi(debug_port: acpi_lite.AcpiDebugPortDescriptor) ?Config(Driver) { + // _ = debug_port; + // return null; + //} + + pub fn init(cfg: ConfigType) Self { + return Self{ .config = cfg }; + } + + pub fn initWithConfig(tagged_config: Config(Driver)) Self { + return Self.init(tagged_config.config); + } + + // Number of 'slots' to perform I/O operations. + pub fn getIoSlots(_: *const Self) IoSlotType(IoRegType) { + if (IoSlots != dynamic_io_slot) { + return IoSlots; + } + @compileError("IoSlots must be different from kDynamicIoSlot or ioSlots implementation must be provided in derived class."); + } + }; +} + +pub fn BasicIoProvider(comptime ConfigType: type, comptime IoType: IoRegisterType) type { + _ = IoType; + return struct { + const Self = @This(); + + pub fn init(cfg: ConfigType, io_slots: usize) Self { + _ = cfg; + _ = io_slots; + return Self{}; + } + + pub fn deinit(self: *Self) void { + _ = self; + } + + pub fn io(self: *Self) ?*anyopaque { + _ = self; + return null; + } + }; +} + +pub fn directMapMmio(phys: u64, size: usize) *volatile anyopaque { + _ = size; + return @ptrFromInt(phys); +} + +pub fn BasicIoProviderStub(comptime ConfigType: type) type { + return struct { + const Self = @This(); + + pub fn init(cfg: ConfigType, io_slots: usize) Self { + _ = cfg; + _ = io_slots; + return Self{}; + } + + pub fn deinit(self: *Self) void { + _ = self; + } + + pub fn io(self: *Self) ?*anyopaque { + _ = self; + return null; + } + }; +} + +pub fn BasicIoProviderMmio(comptime IoType: IoRegisterType) type { + return struct { + const Self = @This(); + + io_reg: union(enum) { + //mmio: hwreg.RegisterMmio, + //mmio_scaled: hwreg.RegisterMmioScaled(u32), + }, + + pub fn init(cfg: zbi_format.SimpleDriverConfig, io_slots: usize) Self { + return Self.initWithMapper(cfg, io_slots, directMapMmio); + } + + pub fn initWithMapper(cfg: zbi_format.SimpleDriverConfig, io_slots: usize, comptime mapMmio: fn (u64, usize) *volatile anyopaque) Self { + _ = cfg; + _ = io_slots; + _ = mapMmio; + switch (IoType) { + .mmio8 => { + return Self{ + //.io_reg = .{ .mmio = hwreg.RegisterMmio.init(mapMmio(cfg.mmio_phys, io_slots)) }, + }; + }, + .mmio32 => { + return Self{ + //.io_reg = .{ .mmio_scaled = hwreg.RegisterMmioScaled(u32).init(mapMmio(cfg.mmio_phys, io_slots * 4)) }, + }; + }, + else => { + @compileError("PIO uses a different specialization"); + }, + } + } + + pub fn deinit(self: *Self) void { + _ = self; + } + + pub fn io(self: *Self) *@TypeOf(self.io_reg) { + return &self.io_reg; + } + }; +} + +pub fn BasicIoProviderPio(comptime IoType: IoRegisterType) type { + //if (builtin.cpu.arch != .x86_64 and builtin.cpu.arch != .x86) { + // @compileError("PIO only supported on x86"); + //} + + return struct { + const Self = @This(); + + //io_reg: hwreg.RegisterDirectPio, + + pub fn init(cfg: zbi_format.SimplePioConfig, io_slots: u16) Self { + _ = cfg; + if (IoType != .pio) { + @compileError("Expected PIO IoType"); + } + std.debug.assert(io_slots > 0); + return Self{ + //.io_reg = hwreg.RegisterDirectPio.init(cfg.base), + }; + } + + pub fn deinit(self: *Self) void { + _ = self; + } + + //pub fn io(self: *Self) *hwreg.RegisterDirectPio { + // return &self.io_reg; + //} + }; +} + +pub fn KernelDriver(comptime UartDriver: type, comptime IoProviderType: type, comptime SyncPolicy: type) type { + return struct { + const Self = @This(); + + const Waiter = SyncPolicy.Waiter; + + const Guard = SyncPolicy.Guard; + + const Lock = SyncPolicy.Lock; + + pub const DefaultLockPolicy = SyncPolicy.DefaultLockPolicy; + + pub const UartType = UartDriver; + pub const ConfigType = UartDriver.ConfigType; + + lock: SyncPolicy.Lock(Self), + waiter: Waiter, + uart: UartType, + io: IoProviderType, + + // This sets up the object but not the device itself. The device might + // already have been set up by a previous instantiation's Init function, + // or might never actually be set up because this instantiation gets + // replaced with a different one before ever calling Init. + pub fn init(args: ConfigType) Self { + const uart = UartType.initWithConfig(args); + var self = Self{ + .lock = SyncPolicy.Lock(Self).init(), + .waiter = .{}, + .uart = uart, + .io = IoProviderType.init(uart.getConfig(), uart.getIoSlots()), + }; + + if (UartDriver == @import("mock.zig").Driver) { + // Initialize the mock sync object with the mock driver if needed + self.lock.initWithDriver(&self.uart); + self.waiter.initWithDriver(&self.uart); + } + return self; + } + + pub fn deinit(self: *Self) void { + self.io.deinit(); + } + + pub fn mmioRange(self: *const Self, comptime LockPolicy: type) MmioRange { + const guard = SyncPolicy.Guard(LockPolicy).init(&self.lock); + defer guard.deinit(); + + if (!MmioDriver(UartDriver)) { + @compileError("mmioRange only available for MMIO drivers"); + } + + return self.uart.mmioRange(); + } + + pub fn takeUart(self: *Self, comptime LockPolicy: type) UartType { + const guard = SyncPolicy.Guard(LockPolicy).init(&self.lock); + defer guard.deinit(); + + return self.uart; + } + + // Returns a copy of the underlying uart config. + pub fn config(self: *const Self, comptime LockPolicy: type) ConfigType { + const guard = SyncPolicy.Guard(LockPolicy).init(&self.lock); + defer guard.deinit(); + + return self.uart.config(); + } + + // Access IoProvider object. + pub fn getIo(self: *Self) *IoProviderType { + return &self.io; + } + + // Set up the device for nonblocking output and polling input. + // If the device is handed off from a different instantiation, + // this won't be called in the new instantiation. + pub fn initHardware(self: *Self, comptime LockPolicy: type) void { + var guard = SyncPolicy.Guard(LockPolicy).init(&self.lock, @src()); + defer guard.deinit(); + + self.uart.initHardware(IoProviderType, &self.io); + } + + //pub fn unparse(self: *const Self, comptime LockPolicy: type, writer: anytype) !void { + // const guard = SyncPolicy.Guard(LockPolicy).init(&self.lock); + // defer guard.deinit(); + + // try self.uart.unparse(writer); + //} + + // Configure the UART line control settings. + pub fn setLineControl(self: *Self, comptime LockPolicy: type, data_bits: ?DataBits, parity: ?Parity, stop_bits: ?StopBits) void { + var guard = SyncPolicy.Guard(LockPolicy).init(&self.lock, @src()); + defer guard.deinit(); + + self.uart.setLineControl(IoProviderType, &self.io, data_bits, parity, stop_bits); + } + + pub fn initInterrupt(self: *Self, comptime LockPolicy: type, enableInterruptCallback: anytype) void { + var guard = SyncPolicy.Guard(LockPolicy).init(&self.lock, @src()); + defer guard.deinit(); + + self.uart.initInterrupt(IoProviderType, &self.io, enableInterruptCallback); + } + + pub fn interrupt(self: *Self, tx: anytype, rx: anytype) void { + // Interrupt is responsible for properly acquiring and releasing sync + // where needed. + self.uart.interrupt(IoProviderType, @TypeOf(self.lock), @TypeOf(tx), @TypeOf(rx), &self.io_provider, &self.lock, &self.waiter, tx, rx); + } + + pub fn write(self: *Self, comptime LockPolicy: type, str: []const u8, waiter_args: anytype) !usize { + var chars = chars_from.CharsFrom(true).init(str); // Massage into u8 with \n -> CRLF. + var it = chars.begin(); + + var guard = SyncPolicy.Guard(LockPolicy).init(&self.lock, @src()); + defer guard.deinit(); + + while (!it.eql(chars.end())) { + // Wait until the UART is ready for Write. + var ready = self.uart.txReady(IoProviderType, &self.io); + while (!ready) { + // Block or just unlock and spin or whatever "wait" means to Sync. + // If that means blocking for interrupt wakeup, enable tx interrupts. + + self.waiter.wait(&guard, (struct { + const SelfInner = @This(); + uart_self: *Self = undefined, + + pub fn enableTxInterrupt(self_inner: *SelfInner) void { + SyncPolicy.assertHeld(&self_inner.uart_self.lock); + self_inner.uart_self.uart.enableTxInterrupt(IoProviderType, &self_inner.uart_self.io, true); + } + }{ .uart_self = self }), waiter_args); + ready = self.uart.txReady(IoProviderType, &self.io); + } + // Advance the iterator by writing some. + it = self.uart.write(IoProviderType, &self.io, ready, @TypeOf(it), &it, chars.end()); + } + return str.len; + } + + // This is a direct polling read, not used in interrupt-based operation. + pub fn read(self: *Self, comptime LockPolicy: type) ?u8 { + var guard = SyncPolicy.Guard(LockPolicy).init(&self.lock, @src()); + defer guard.deinit(); + + return self.uart.read(IoProviderType, &self.io); + } + + pub fn enableRxInterrupt(self: *Self, comptime LockPolicy: type) void { + var guard = SyncPolicy.Guard(LockPolicy).init(&self.lock, @src()); + defer guard.deinit(); + + self.uart.enableRxInterrupt(IoProviderType, &self.io, true); + } + }; +} diff --git a/slipstream/system/ulib/uart/test/driver_tests.zig b/slipstream/system/ulib/uart/test/driver_tests.zig new file mode 100644 index 0000000..d06b33f --- /dev/null +++ b/slipstream/system/ulib/uart/test/driver_tests.zig @@ -0,0 +1,87 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. + +const std = @import("std"); +const uart = @import("../src/uart.zig"); +const ns8250 = @import("../src/ns8250.zig"); +const zbi_format = @import("zbi_format"); +const null_driver = @import("../src/null.zig"); +const mock = @import("../src/mock.zig"); +const sync = @import("../src/sync.zig"); + +const driver_config = zbi_format.driver_config; +const testing = std.testing; + +test "uart config" { + //var all_configs: uart.Config(uart.all.Driver) = undefined; + + const dcfg = driver_config.SimpleDriverConfig{ + .mmio_phys = 1, + .irq = 2, + .flags = 3, + }; + + { + const dcfg2 = driver_config.SimpleDriverConfig{ + .mmio_phys = 2, + .irq = 2, + .flags = 3, + }; + const cfg1 = uart.Config(ns8250.Mmio32Driver).initWithConfig(dcfg); + const cfg2 = uart.Config(ns8250.Mmio32Driver).initWithConfig(dcfg); + const cfg3 = uart.Config(ns8250.Mmio32Driver).initWithConfig(dcfg2); + + const dcfg_pio = driver_config.SimplePioConfig{ + .base = 123, + .reserved = 0, + .irq = 4, + }; + const dcfg_pio2 = driver_config.SimplePioConfig{ + .base = 124, + .reserved = 0, + .irq = 4, + }; + + const cfg4 = uart.Config(ns8250.PioDriver).initWithConfig(dcfg_pio); + const cfg5 = uart.Config(ns8250.PioDriver).initWithConfig(dcfg_pio); + const cfg6 = uart.Config(ns8250.PioDriver).initWithConfig(dcfg_pio2); + const cfg7 = uart.Config(null_driver.Driver).init(); + const cfg8 = uart.Config(null_driver.Driver).init(); + const cfg9 = uart.Config(ns8250.Mmio8Driver).initWithConfig(dcfg); + + // Check operator== and !=. + + // MMIO + try testing.expectEqualDeep(cfg1, cfg1); + try testing.expectEqualDeep(cfg1, cfg2); + try testing.expectEqualDeep(cfg2, cfg1); + try testing.expectError(error.TestExpectedEqual, testing.expectEqualDeep(cfg1, cfg3)); + try testing.expect(@TypeOf(cfg1) != @TypeOf(cfg4)); + + // MMIO vs other types + try testing.expect(@TypeOf(cfg1) != @TypeOf(cfg7)); + try testing.expect(@TypeOf(cfg9) != @TypeOf(cfg1)); + + // PIO + try testing.expectEqualDeep(cfg4, cfg4); + try testing.expectEqualDeep(cfg4, cfg5); + try testing.expectEqualDeep(cfg5, cfg4); + try testing.expectError(error.TestExpectedEqual, testing.expectEqualDeep(cfg4, cfg6)); + + // Stub driver + try testing.expectEqualDeep(cfg7, cfg7); + try testing.expectEqualDeep(cfg7, cfg8); + } +} + +test "uart null driver" { + const TestDriver = uart.KernelDriver(null_driver.Driver, mock.IoProvider(uart.StubConfig, uart.IoRegisterType.none), sync.UnsynchronizedPolicy); + var driver = TestDriver.init(.{}); + defer driver.deinit(); + + driver.initHardware(TestDriver.DefaultLockPolicy); + try testing.expectEqual(@as(usize, 3), driver.write(TestDriver.DefaultLockPolicy, "hi!", {})); + try testing.expectEqual(@as(usize, 12), driver.write(TestDriver.DefaultLockPolicy, "hello world\n", {})); + try testing.expectEqual(@as(?u8, null), driver.read(TestDriver.DefaultLockPolicy)); +} diff --git a/slipstream/system/ulib/uart/test/parsing_tests.zig b/slipstream/system/ulib/uart/test/parsing_tests.zig new file mode 100644 index 0000000..fafb786 --- /dev/null +++ b/slipstream/system/ulib/uart/test/parsing_tests.zig @@ -0,0 +1,275 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. + +const std = @import("std"); +const uart = @import("../src/uart.zig"); +const ns8250 = @import("../src/ns8250.zig"); +const parse = @import("../src/parse.zig"); + +const testing = std.testing; + +fn testOneUint(comptime T: type) !void { + // No leading comma. + { + var u: T = 0xe; + try testing.expectEqual(@as(usize, 0), parse.parseInts("", .{&u})); + } + + // Fewer elements than integers. + { + var u: T = 0xe; + try testing.expectEqual(@as(usize, 0), parse.parseInts(",", .{&u})); + } + + { + var u: T = 0xe; + try testing.expectEqual(@as(usize, 1), parse.parseInts(",12", .{&u})); + try testing.expectEqual(@as(T, 12), u); + } + + { + var u: T = 0xe; + try testing.expectEqual(@as(usize, 1), parse.parseInts(",-12", .{&u})); + try testing.expectEqual(@as(T, @bitCast(@as(std.meta.Int(.signed, @bitSizeOf(T)), -12))), u); + } + + { + var u: T = 0xe; + try testing.expectEqual(@as(usize, 1), parse.parseInts(",0xa", .{&u})); + try testing.expectEqual(@as(T, 0xa), u); + } + + { + var u: T = 0xe; + try testing.expectEqual(@as(usize, 1), parse.parseInts(",-0xa", .{&u})); + try testing.expectEqual(@as(T, @bitCast(@as(std.meta.Int(.signed, @bitSizeOf(T)), -0xa))), u); + } + + { + var u: T = 0xe; + try testing.expectEqual(@as(usize, 1), parse.parseInts(",010", .{&u})); + try testing.expectEqual(@as(T, 8), u); + } + + { + var u: T = 0xe; + try testing.expectEqual(@as(usize, 1), parse.parseInts(",-010", .{&u})); + try testing.expectEqual(@as(T, @bitCast(@as(std.meta.Int(.signed, @bitSizeOf(T)), -8))), u); + } + + // More elements than integers. + { + var u: T = 0xe; + try testing.expectEqual(@as(usize, 1), parse.parseInts(",12,34", .{&u})); + try testing.expectEqual(@as(T, 12), u); + } +} + +fn testTwoUints(comptime TA: type, comptime TB: type) !void { + // No leading comma. + { + var uA: TA = 0xe; + var uB: TB = 0xe; + try testing.expectEqual(@as(usize, 0), parse.parseInts("", .{ &uA, &uB })); + } + + // Fewer elements than integers: no elements. + { + var uA: TA = 0xe; + var uB: TB = 0xe; + try testing.expectEqual(@as(usize, 0), parse.parseInts(",", .{ &uA, &uB })); + } + + // Fewer elements than integers: one element. + { + var uA: TA = 0xe; + var uB: TB = 0xe; + try testing.expectEqual(@as(usize, 1), parse.parseInts(",12", .{ &uA, &uB })); + try testing.expectEqual(@as(TA, 12), uA); + try testing.expectEqual(@as(TB, 0xe), uB); + } + + { + var uA: TA = 0xe; + var uB: TB = 0xe; + try testing.expectEqual(@as(usize, 2), parse.parseInts(",12,34", .{ &uA, &uB })); + try testing.expectEqual(@as(TA, 12), uA); + try testing.expectEqual(@as(TB, 34), uB); + } + + { + var uA: TA = 0xe; + var uB: TB = 0xe; + try testing.expectEqual(@as(usize, 2), parse.parseInts(",0x12,34", .{ &uA, &uB })); + try testing.expectEqual(@as(TA, 0x12), uA); + try testing.expectEqual(@as(TB, 34), uB); + } + + { + var uA: TA = 0xe; + var uB: TB = 0xe; + try testing.expectEqual(@as(usize, 2), parse.parseInts(",12,0x34", .{ &uA, &uB })); + try testing.expectEqual(@as(TA, 12), uA); + try testing.expectEqual(@as(TB, 0x34), uB); + } + + { + var uA: TA = 0xe; + var uB: TB = 0xe; + try testing.expectEqual(@as(usize, 2), parse.parseInts(",0x12,0x34", .{ &uA, &uB })); + try testing.expectEqual(@as(TA, 0x12), uA); + try testing.expectEqual(@as(TB, 0x34), uB); + } + + // More elements than integers. + { + var uA: TA = undefined; + var uB: TB = undefined; + try testing.expectEqual(@as(usize, 2), parse.parseInts(",12,34,56", .{ &uA, &uB })); + } +} + +test "parsing - no uints" { + try testing.expectEqual(@as(usize, 0), parse.parseInts("", .{})); + try testing.expectEqual(@as(usize, 0), parse.parseInts(",12", .{})); + try testing.expectEqual(@as(usize, 0), parse.parseInts(",12,34", .{})); +} + +test "parsing - large values" { + { + var uint64: u64 = 0xe; + try testing.expectEqual(@as(usize, 1), parse.parseInts(",0xffffffffffffffff", .{&uint64})); + try testing.expectEqual(@as(u64, @bitCast(@as(i64, -1))), uint64); + } + { + var uint64: u64 = 0xe; + try testing.expectEqual(@as(usize, 1), parse.parseInts(",0x0123456789", .{&uint64})); + try testing.expectEqual(@as(u64, 0x0123456789), uint64); + } +} + +test "parsing - overflow" { + { + var uint8: u8 = 0xe; + try testing.expectEqual(@as(usize, 1), parse.parseInts(",0xabc", .{&uint8})); + try testing.expectEqual(@as(u8, 0xbc), uint8); + } + { + var uint8: u8 = 0xe; + try testing.expectEqual(@as(usize, 1), parse.parseInts(",0x100", .{&uint8})); + try testing.expectEqual(@as(u8, 0x00), uint8); + } +} + +test "parsing - long strings" { + // Long string of zeros followed by "52" + const waylong = "," ++ "0" ** 100 ++ "52"; + var uint8: u8 = 0; + try testing.expectEqual(@as(usize, 1), parse.parseInts(waylong, .{&uint8})); + try testing.expectEqual(@as(u8, 0o52), uint8); + + // Long string with hex prefix + const waylong_hex = ",0x" ++ "0" ** 100 ++ "52"; + uint8 = 0; + try testing.expectEqual(@as(usize, 1), parse.parseInts(waylong_hex, .{&uint8})); + try testing.expectEqual(@as(u8, 0x52), uint8); + + // Extreme overflow - string of 100 '1's + const longoverflow = "," ++ "1" ** 100; + var uint64: u64 = 0; + try testing.expectEqual(@as(usize, 0), parse.parseInts(longoverflow, .{&uint64})); +} + +test "parsing - one u8" { + try testOneUint(u8); +} + +test "parsing - one u16" { + try testOneUint(u16); +} + +test "parsing - one u32" { + try testOneUint(u32); +} + +test "parsing - one u64" { + try testOneUint(u64); +} + +test "parsing - two u8s" { + try testTwoUints(u8, u8); +} + +test "parsing - u8 and u16" { + try testTwoUints(u8, u16); +} + +test "parsing - u8 and u32" { + try testTwoUints(u8, u32); +} + +test "parsing - u8 and u64" { + try testTwoUints(u8, u64); +} + +test "parsing - two u16s" { + try testTwoUints(u16, u16); +} + +test "parsing - u16 and u32" { + try testTwoUints(u16, u32); +} + +test "parsing - u16 and u64" { + try testTwoUints(u16, u64); +} + +test "parsing - two u32s" { + try testTwoUints(u32, u32); +} + +test "parsing - u32 and u64" { + try testTwoUints(u32, u64); +} + +test "parsing - two u64s" { + try testTwoUints(u64, u64); +} + +test "ns8250 8-bit mmio driver parsing" { + { + const driver_config = ns8250.Mmio8Driver.tryMatchString("ns8250-8bit,0xa,0xb"); + + try testing.expect(driver_config != null); + + const config = driver_config.?.config; + try testing.expectEqual(@as(u64, 0xa), config.mmio_phys); + try testing.expectEqual(@as(u32, 0xb), config.irq); + try testing.expectEqual(@as(u32, 0), config.flags); + } + + { + const driver_config = ns8250.Mmio8Driver.tryMatchString("ns8250-8bit,0xa,0xb,0xc"); + + try testing.expect(driver_config != null); + + const config = driver_config.?.config; + try testing.expectEqual(@as(u64, 0xa), config.mmio_phys); + try testing.expectEqual(@as(u32, 0xb), config.irq); + try testing.expectEqual(@as(u32, 0xc), config.flags); + } +} + +test "ns8250 legacy driver parsing" { + const driver_config = ns8250.PioDriver.tryMatchString("legacy"); + + try testing.expect(driver_config != null); + + const config_name = ns8250.PioDriver.config_name; + try testing.expectEqualStrings("ioport", config_name); + + const config = driver_config.?.config; + try testing.expectEqual(@as(u16, 0x3f8), config.base); + try testing.expectEqual(@as(u32, 4), config.irq); +} From 492efeeb78878fe9fffd2d23425e9c7b31276925 Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Mon, 9 Jun 2025 13:07:46 -0300 Subject: [PATCH 18/41] [ulib][hwreg] Fix const correctness --- .../system/ulib/hwreg/src/bitfields.zig | 72 +++++-- slipstream/system/ulib/uart/src/ns8250.zig | 204 ++++++++---------- 2 files changed, 143 insertions(+), 133 deletions(-) diff --git a/slipstream/system/ulib/hwreg/src/bitfields.zig b/slipstream/system/ulib/hwreg/src/bitfields.zig index e4aba0b..5bc7427 100644 --- a/slipstream/system/ulib/hwreg/src/bitfields.zig +++ b/slipstream/system/ulib/hwreg/src/bitfields.zig @@ -224,6 +224,36 @@ pub fn BitfieldRef(comptime IntType: type) type { }; } +pub fn BitfieldRefConst(comptime IntType: type) type { + return struct { + const Self = @This(); + + value_ptr: *const IntType, + shift: u32, + mask: IntType, + + pub fn init(value_ptr: *const IntType, comptime bit_high_incl: u32, comptime bit_low: u32) Self { + return Self{ + .value_ptr = value_ptr, + .shift = bit_low, + .mask = internal.computeMask(IntType, bit_high_incl - bit_low + 1), + }; + } + + pub fn initUnshifted(value_ptr: *const IntType, comptime bit_high_incl: u32, comptime bit_low: u32) Self { + return Self{ + .value_ptr = value_ptr, + .shift = 0, + .mask = @as(IntType, internal.computeMask(IntType, bit_high_incl - bit_low + 1)) << @intCast(bit_low), + }; + } + + pub fn get(self: Self) IntType { + return @intCast((self.value_ptr.* >> @intCast(self.shift)) & self.mask); + } + }; +} + pub fn DefField(comptime ParentType: type, comptime bit_high: u32, comptime bit_low: u32, comptime name: []const u8) type { return DefCondField(ParentType, bit_high, bit_low, name, true); } @@ -282,11 +312,11 @@ fn Field(comptime ParentType: type, comptime bit_high: u32, comptime bit_low: u3 InternalField.init(parent.base.getParams(), name, bit_high, bit_low); } - pub fn get(parent: *ParentType) ParentType.ValueType { + pub fn get(parent: *const ParentType) ParentType.ValueType { if (unshifted) { - return BitfieldRef(ParentType.ValueType).initUnshifted(parent.base.regValuePtr(), bit_high, bit_low).get(); + return BitfieldRefConst(ParentType.ValueType).initUnshifted(parent.base.regValuePtrConst(), bit_high, bit_low).get(); } else { - return BitfieldRef(ParentType.ValueType).init(parent.base.regValuePtr(), bit_high, bit_low).get(); + return BitfieldRefConst(ParentType.ValueType).init(parent.base.regValuePtrConst(), bit_high, bit_low).get(); } } @@ -336,8 +366,8 @@ fn EnumField(comptime ParentType: type, comptime EnumType: type, comptime bit_hi InternalField.init(parent.base.getParams(), name, bit_high, bit_low); } - pub fn get(parent: *ParentType) EnumType { - const raw_value = BitfieldRef(ParentType.ValueType).init(parent.base.regValuePtr(), bit_high, bit_low).get(); + pub fn get(parent: *const ParentType) EnumType { + const raw_value = BitfieldRefConst(ParentType.ValueType).init(parent.base.regValuePtrConst(), bit_high, bit_low).get(); return @enumFromInt(raw_value); } @@ -426,11 +456,11 @@ fn Subfield(comptime ParentType: type, comptime bit_high: u32, comptime bit_low: } return struct { - pub fn get(parent: *ParentType) FieldType { + pub fn get(parent: *const ParentType) FieldType { if (unshifted) { - return BitfieldRef(FieldType).initUnshifted(&@field(parent, name), bit_high, bit_low).get(); + return BitfieldRefConst(FieldType).initUnshifted(&@field(parent, name), bit_high, bit_low).get(); } else { - return BitfieldRef(FieldType).init(&@field(parent, name), bit_high, bit_low).get(); + return BitfieldRefConst(FieldType).init(&@field(parent, name), bit_high, bit_low).get(); } } @@ -501,8 +531,8 @@ fn EnumSubfield(comptime ParentType: type, comptime EnumType: type, comptime bit InternalField.init(parent.base.getParams(), name, bit_high, bit_low); } - pub fn get(parent: *ParentType) EnumType { - const raw_value = BitfieldRef(FieldType).init(&@field(parent, name), bit_high, bit_low).get(); + pub fn get(parent: *const ParentType) EnumType { + const raw_value = BitfieldRefConst(FieldType).init(&@field(parent, name), bit_high, bit_low).get(); return @enumFromInt(raw_value); } @@ -526,7 +556,7 @@ fn StructSubBitTest(comptime IntType: type) type { pub const MidBit = DefSubbit(Self, 1, "field"); pub const LastBit = DefSubbit(Self, @bitSizeOf(IntType) - 1, "field"); - pub fn firstBit(self: *Self) IntType { + pub fn firstBit(self: *const Self) IntType { return FirstBit.get(self); } @@ -535,7 +565,7 @@ fn StructSubBitTest(comptime IntType: type) type { return self; } - pub fn midBit(self: *Self) IntType { + pub fn midBit(self: *const Self) IntType { return MidBit.get(self); } @@ -544,7 +574,7 @@ fn StructSubBitTest(comptime IntType: type) type { return self; } - pub fn lastBit(self: *Self) IntType { + pub fn lastBit(self: *const Self) IntType { return LastBit.get(self); } @@ -606,7 +636,7 @@ fn StructSubFieldTest(comptime IntType: type) type { pub const Range1 = DefSubfield(Self, 2, 1, "field3"); pub const Range2 = DefSubfield(Self, 5, 3, "field3"); - pub fn wholeLength(self: *Self) IntType { + pub fn wholeLength(self: *const Self) IntType { return WholeLength.get(self); } @@ -615,7 +645,7 @@ fn StructSubFieldTest(comptime IntType: type) type { return self; } - pub fn singleBit(self: *Self) IntType { + pub fn singleBit(self: *const Self) IntType { return SingleBit.get(self); } @@ -624,7 +654,7 @@ fn StructSubFieldTest(comptime IntType: type) type { return self; } - pub fn range1(self: *Self) IntType { + pub fn range1(self: *const Self) IntType { return Range1.get(self); } @@ -633,7 +663,7 @@ fn StructSubFieldTest(comptime IntType: type) type { return self; } - pub fn range2(self: *Self) IntType { + pub fn range2(self: *const Self) IntType { return Range2.get(self); } @@ -726,7 +756,7 @@ fn StructEnumSubFieldTest(comptime IntType: type) type { pub const Range1 = DefEnumSubfield(Self, EnumRange, 2, 1, "field3"); pub const Range2 = DefEnumSubfield(Self, EnumRange, 5, 3, "field3"); - pub fn wholeLength(self: *Self) EnumWholeRange { + pub fn wholeLength(self: *const Self) EnumWholeRange { return WholeLength.get(self); } @@ -735,7 +765,7 @@ fn StructEnumSubFieldTest(comptime IntType: type) type { return self; } - pub fn singleBit(self: *Self) EnumBit { + pub fn singleBit(self: *const Self) EnumBit { return SingleBit.get(self); } @@ -744,7 +774,7 @@ fn StructEnumSubFieldTest(comptime IntType: type) type { return self; } - pub fn range1(self: *Self) EnumRange { + pub fn range1(self: *const Self) EnumRange { return Range1.get(self); } @@ -753,7 +783,7 @@ fn StructEnumSubFieldTest(comptime IntType: type) type { return self; } - pub fn range2(self: *Self) EnumRange { + pub fn range2(self: *const Self) EnumRange { return Range2.get(self); } diff --git a/slipstream/system/ulib/uart/src/ns8250.zig b/slipstream/system/ulib/uart/src/ns8250.zig index fe88b98..c940a55 100644 --- a/slipstream/system/ulib/uart/src/ns8250.zig +++ b/slipstream/system/ulib/uart/src/ns8250.zig @@ -74,8 +74,8 @@ pub const RxBufferRegister = struct { return RegisterAddr(Self).init(0); } - pub fn data(self: *Self) ValueType { - return Data.get(self); + pub fn data(self: *const Self) ValueType { + return Data.get(@constCast(self)); } pub fn setData(self: *Self, value: ValueType) *Self { @@ -115,8 +115,8 @@ pub const TxBufferRegister = struct { return RegisterAddr(Self).init(0); } - pub fn data(self: *Self) ValueType { - return Data.get(self); + pub fn data(self: *const Self) ValueType { + return Data.get(@constCast(self)); } pub fn setData(self: *Self, value: ValueType) *Self { @@ -178,8 +178,8 @@ pub fn InterruptEnableRegisterBase(comptime driver_type: u32) type { return RegisterAddr(Self).init(1); } - pub fn dmaRequestEnable(self: *Self) ValueType { - return DmaRequestEnable.get(self); + pub fn dmaRequestEnable(self: *const Self) ValueType { + return DmaRequestEnable.get(@constCast(self)); } pub fn setDmaRequestEnable(self: *Self, value: ValueType) *Self { @@ -187,8 +187,8 @@ pub fn InterruptEnableRegisterBase(comptime driver_type: u32) type { return self; } - pub fn uartEnable(self: *Self) ValueType { - return UartEnable.get(self); + pub fn uartEnable(self: *const Self) ValueType { + return UartEnable.get(@constCast(self)); } pub fn setUartEnable(self: *Self, value: ValueType) *Self { @@ -196,8 +196,8 @@ pub fn InterruptEnableRegisterBase(comptime driver_type: u32) type { return self; } - pub fn nrzCodingEnable(self: *Self) ValueType { - return NrzCodingEnable.get(self); + pub fn nrzCodingEnable(self: *const Self) ValueType { + return NrzCodingEnable.get(@constCast(self)); } pub fn setNrzCodingEnable(self: *Self, value: ValueType) *Self { @@ -205,8 +205,8 @@ pub fn InterruptEnableRegisterBase(comptime driver_type: u32) type { return self; } - pub fn receiverTimeOut(self: *Self) ValueType { - return ReceiverTimeOut.get(self); + pub fn receiverTimeOut(self: *const Self) ValueType { + return ReceiverTimeOut.get(@constCast(self)); } pub fn setReceiverTimeOut(self: *Self, value: ValueType) *Self { @@ -214,8 +214,8 @@ pub fn InterruptEnableRegisterBase(comptime driver_type: u32) type { return self; } - pub fn modemStatus(self: *Self) ValueType { - return ModemStatus.get(self); + pub fn modemStatus(self: *const Self) ValueType { + return ModemStatus.get(@constCast(self)); } pub fn setModemStatus(self: *Self, value: ValueType) *Self { @@ -223,8 +223,8 @@ pub fn InterruptEnableRegisterBase(comptime driver_type: u32) type { return self; } - pub fn lineStatus(self: *Self) ValueType { - return LineStatus.get(self); + pub fn lineStatus(self: *const Self) ValueType { + return LineStatus.get(@constCast(self)); } pub fn setLineStatus(self: *Self, value: ValueType) *Self { @@ -232,8 +232,8 @@ pub fn InterruptEnableRegisterBase(comptime driver_type: u32) type { return self; } - pub fn txEmpty(self: *Self) ValueType { - return TxEmpty.get(self); + pub fn txEmpty(self: *const Self) ValueType { + return TxEmpty.get(@constCast(self)); } pub fn setTxEmpty(self: *Self, value: ValueType) *Self { @@ -241,8 +241,8 @@ pub fn InterruptEnableRegisterBase(comptime driver_type: u32) type { return self; } - pub fn rxAvailable(self: *Self) ValueType { - return RxAvailable.get(self); + pub fn rxAvailable(self: *const Self) ValueType { + return RxAvailable.get(@constCast(self)); } pub fn setRxAvailable(self: *Self, value: ValueType) *Self { @@ -292,15 +292,15 @@ pub const InterruptIdentRegister = struct { return RegisterAddr(Self).init(2); } - pub fn fifosEnabled(self: *Self) ValueType { - return FifosEnabled.get(self); + pub fn fifosEnabled(self: *const Self) ValueType { + return FifosEnabled.get(@constCast(self)); } - pub fn extendedFifoEnabled(self: *Self) ValueType { + pub fn extendedFifoEnabled(self: *const Self) ValueType { return ExtendedFifoEnabled.get(self); } - pub fn interruptId(self: *Self) InterruptType { + pub fn interruptId(self: *const Self) InterruptType { return InterruptId.get(self); } @@ -367,7 +367,7 @@ pub fn FifoControlRegisterBase(comptime driver_type: u32) type { return RegisterAddr(Self).init(2); } - pub fn receiverTrigger(self: *Self) ValueType { + pub fn receiverTrigger(self: *const Self) ValueType { return ReceiverTrigger.get(self); } @@ -376,7 +376,7 @@ pub fn FifoControlRegisterBase(comptime driver_type: u32) type { return self; } - pub fn peripheralBus32bit(self: *Self) ValueType { + pub fn peripheralBus32bit(self: *const Self) ValueType { return PeripheralBus32bit.get(self); } @@ -385,7 +385,7 @@ pub fn FifoControlRegisterBase(comptime driver_type: u32) type { return self; } - pub fn trailingBytes(self: *Self) ValueType { + pub fn trailingBytes(self: *const Self) ValueType { return TrailingBytes.get(self); } @@ -394,25 +394,24 @@ pub fn FifoControlRegisterBase(comptime driver_type: u32) type { return self; } - pub fn transmitTriggerPxa(self: *Self) ValueType { - return TransmitTriggerPxa.get(self); - } - - pub fn setTransmitTriggerPxa(self: *Self, value: ValueType) *Self { - TransmitTriggerPxa.set(self, value); - return self; - } - - pub fn transmitTriggerDw8250(self: *Self) ValueType { - return TransmitTriggerDw8250.get(self); + pub fn transmitTrigger(self: *const Self) ValueType { + if (is_pxa) { + return TransmitTriggerPxa.get(self); + } else { + return TransmitTriggerDw8250.get(self); + } } - pub fn setTransmitTriggerDw8250(self: *Self, value: ValueType) *Self { - TransmitTriggerDw8250.set(self, value); + pub fn setTransmitTrigger(self: *Self, value: ValueType) *Self { + if (is_pxa) { + TransmitTriggerPxa.set(self, value); + } else { + TransmitTriggerDw8250.set(self, value); + } return self; } - pub fn extendedFifoEnable(self: *Self) ValueType { + pub fn extendedFifoEnable(self: *const Self) ValueType { return ExtendedFifoEnable.get(self); } @@ -421,7 +420,7 @@ pub fn FifoControlRegisterBase(comptime driver_type: u32) type { return self; } - pub fn dmaMode(self: *Self) ValueType { + pub fn dmaMode(self: *const Self) ValueType { return DmaMode.get(self); } @@ -430,7 +429,7 @@ pub fn FifoControlRegisterBase(comptime driver_type: u32) type { return self; } - pub fn txFifoReset(self: *Self) ValueType { + pub fn txFifoReset(self: *const Self) ValueType { return TxFifoReset.get(self); } @@ -439,7 +438,7 @@ pub fn FifoControlRegisterBase(comptime driver_type: u32) type { return self; } - pub fn rxFifoReset(self: *Self) ValueType { + pub fn rxFifoReset(self: *const Self) ValueType { return RxFifoReset.get(self); } @@ -448,7 +447,7 @@ pub fn FifoControlRegisterBase(comptime driver_type: u32) type { return self; } - pub fn fifoEnable(self: *Self) ValueType { + pub fn fifoEnable(self: *const Self) ValueType { return FifoEnable.get(self); } @@ -508,7 +507,7 @@ pub const LineControlRegister = struct { return RegisterAddr(Self).init(3); } - pub fn divisorLatchAccess(self: *Self) ValueType { + pub fn divisorLatchAccess(self: *const Self) ValueType { return DivisorLatchAccess.get(self); } @@ -517,7 +516,7 @@ pub const LineControlRegister = struct { return self; } - pub fn breakControl(self: *Self) ValueType { + pub fn breakControl(self: *const Self) ValueType { return BreakControl.get(self); } @@ -526,7 +525,7 @@ pub const LineControlRegister = struct { return self; } - pub fn stickParity(self: *Self) ValueType { + pub fn stickParity(self: *const Self) ValueType { return StickParity.get(self); } @@ -535,7 +534,7 @@ pub const LineControlRegister = struct { return self; } - pub fn evenParity(self: *Self) ValueType { + pub fn evenParity(self: *const Self) ValueType { return EvenParity.get(self); } @@ -544,7 +543,7 @@ pub const LineControlRegister = struct { return self; } - pub fn parityEnable(self: *Self) ValueType { + pub fn parityEnable(self: *const Self) ValueType { return ParityEnable.get(self); } @@ -553,7 +552,7 @@ pub const LineControlRegister = struct { return self; } - pub fn stopBits(self: *Self) ValueType { + pub fn stopBits(self: *const Self) ValueType { return StopBits.get(self); } @@ -562,7 +561,7 @@ pub const LineControlRegister = struct { return self; } - pub fn wordLength(self: *Self) ValueType { + pub fn wordLength(self: *const Self) ValueType { return WordLength.get(self); } @@ -615,7 +614,7 @@ pub const ModemControlRegister = struct { return RegisterAddr(Self).init(4); } - pub fn automaticFlowControlEnable(self: *Self) ValueType { + pub fn automaticFlowControlEnable(self: *const Self) ValueType { return AutomaticFlowControlEnable.get(self); } @@ -624,7 +623,7 @@ pub const ModemControlRegister = struct { return self; } - pub fn loop(self: *Self) ValueType { + pub fn loop(self: *const Self) ValueType { return Loop.get(self); } @@ -633,7 +632,7 @@ pub const ModemControlRegister = struct { return self; } - pub fn auxiliaryOut2(self: *Self) ValueType { + pub fn auxiliaryOut2(self: *const Self) ValueType { return AuxiliaryOut2.get(self); } @@ -642,7 +641,7 @@ pub const ModemControlRegister = struct { return self; } - pub fn auxiliaryOut1(self: *Self) ValueType { + pub fn auxiliaryOut1(self: *const Self) ValueType { return AuxiliaryOut1.get(self); } @@ -651,7 +650,7 @@ pub const ModemControlRegister = struct { return self; } - pub fn requestToSend(self: *Self) ValueType { + pub fn requestToSend(self: *const Self) ValueType { return RequestToSend.get(self); } @@ -660,7 +659,7 @@ pub const ModemControlRegister = struct { return self; } - pub fn dataTerminalReady(self: *Self) ValueType { + pub fn dataTerminalReady(self: *const Self) ValueType { return DataTerminalReady.get(self); } @@ -715,35 +714,35 @@ pub const LineStatusRegister = struct { return RegisterAddr(Self).init(5); } - pub fn errorInRxFifo(self: *Self) ValueType { + pub fn errorInRxFifo(self: *const Self) ValueType { return ErrorInRxFifo.get(self); } - pub fn txEmpty(self: *Self) ValueType { + pub fn txEmpty(self: *const Self) ValueType { return TxEmpty.get(self); } - pub fn txRegisterEmpty(self: *Self) ValueType { + pub fn txRegisterEmpty(self: *const Self) ValueType { return TxRegisterEmpty.get(self); } - pub fn breakInterrupt(self: *Self) ValueType { + pub fn breakInterrupt(self: *const Self) ValueType { return BreakInterrupt.get(self); } - pub fn framingError(self: *Self) ValueType { + pub fn framingError(self: *const Self) ValueType { return FramingError.get(self); } - pub fn parityError(self: *Self) ValueType { + pub fn parityError(self: *const Self) ValueType { return ParityError.get(self); } - pub fn overrunError(self: *Self) ValueType { + pub fn overrunError(self: *const Self) ValueType { return OverrunError.get(self); } - pub fn dataReady(self: *Self) ValueType { + pub fn dataReady(self: *const Self) ValueType { return DataReady.get(self); } @@ -788,35 +787,35 @@ pub const ModemStatusRegister = struct { return RegisterAddr(Self).init(6); } - pub fn dataCarrierDetect(self: *Self) ValueType { + pub fn dataCarrierDetect(self: *const Self) ValueType { return DataCarrierDetect.get(self); } - pub fn ringIndicator(self: *Self) ValueType { + pub fn ringIndicator(self: *const Self) ValueType { return RingIndicator.get(self); } - pub fn dataSetReady(self: *Self) ValueType { + pub fn dataSetReady(self: *const Self) ValueType { return DataSetReady.get(self); } - pub fn clearToSend(self: *Self) ValueType { + pub fn clearToSend(self: *const Self) ValueType { return ClearToSend.get(self); } - pub fn deltaDataCarrierDetect(self: *Self) ValueType { + pub fn deltaDataCarrierDetect(self: *const Self) ValueType { return DeltaDataCarrierDetect.get(self); } - pub fn trailingEdgeRingIndicator(self: *Self) ValueType { + pub fn trailingEdgeRingIndicator(self: *const Self) ValueType { return TrailingEdgeRingIndicator.get(self); } - pub fn deltaDataSetReady(self: *Self) ValueType { + pub fn deltaDataSetReady(self: *const Self) ValueType { return DeltaDataSetReady.get(self); } - pub fn deltaClearToSend(self: *Self) ValueType { + pub fn deltaClearToSend(self: *const Self) ValueType { return DeltaClearToSend.get(self); } @@ -847,7 +846,7 @@ pub const ScratchRegister = struct { return RegisterAddr(Self).init(7); } - pub fn data(self: *Self) ValueType { + pub fn data(self: *const Self) ValueType { return Data.get(self); } @@ -888,7 +887,7 @@ pub const DivisorLatchLowerRegister = struct { return RegisterAddr(Self).init(0); } - pub fn data(self: *Self) ValueType { + pub fn data(self: *const Self) ValueType { return Data.get(self); } @@ -924,7 +923,7 @@ pub const DivisorLatchUpperRegister = struct { return RegisterAddr(Self).init(1); } - pub fn data(self: *Self) ValueType { + pub fn data(self: *const Self) ValueType { return Data.get(self); } @@ -972,23 +971,23 @@ pub const UartStatusRegister = struct { return RegisterAddr(Self).init(0x7c / 4); } - pub fn receiveFifoFull(self: *Self) ValueType { + pub fn receiveFifoFull(self: *const Self) ValueType { return ReceiveFifoFull.get(self); } - pub fn receiveFifoNotEmpty(self: *Self) ValueType { + pub fn receiveFifoNotEmpty(self: *const Self) ValueType { return ReceiveFifoNotEmpty.get(self); } - pub fn transmitFifoEmpty(self: *Self) ValueType { + pub fn transmitFifoEmpty(self: *const Self) ValueType { return TransmitFifoEmpty.get(self); } - pub fn transmitFifoNotFull(self: *Self) ValueType { + pub fn transmitFifoNotFull(self: *const Self) ValueType { return TransmitFifoNotFull.get(self); } - pub fn uartBusy(self: *Self) ValueType { + pub fn uartBusy(self: *const Self) ValueType { return UartBusy.get(self); } @@ -1057,11 +1056,9 @@ pub fn DriverImpl(comptime kdrv_extra: u32, comptime KdrvConfig: type, comptime // PXA has a different enough FCR to configure differently than the others var fcr = Self.FifoControlRegister.get().fromValue(0); - _ = fcr.setFifoEnable(1); - _ = fcr.setRxFifoReset(1); - _ = fcr.setTxFifoReset(1); + _ = fcr.setFifoEnable(1).setRxFifoReset(1).setTxFifoReset(1); _ = fcr.setReceiverTrigger(0); // Trigger at 1 byte in the rx fifo. - _ = fcr.setTransmitTrigger(1); // Trigger at empty tx fifo. + _ = fcr.setTransmitTrigger(0); // Trigger at empty tx fifo. _ = fcr.writeTo(io.getIo()); } else { // Disable all interrupts @@ -1076,19 +1073,14 @@ pub fn DriverImpl(comptime kdrv_extra: u32, comptime KdrvConfig: type, comptime _ = lcr.setDivisorLatchAccess(1).writeTo(io.getIo()); var fcr = Self.FifoControlRegister.get().fromValue(0); - _ = fcr.setReceiverTrigger(Self.FifoControlRegister.max_trigger_level); - _ = fcr.setTxFifoReset(1); - _ = fcr.setRxFifoReset(1); - _ = fcr.setFifoEnable(1); + _ = fcr.setRxFifoReset(1).setTxFifoReset(1).setFifoEnable(1).setReceiverTrigger(Self.FifoControlRegister.max_trigger_level); if (kdrv_extra == driver_config.ZBI_KERNEL_DRIVER_DW8250_UART) { // dw8250 does not have an extended fifo enable bit in bit 5, but // instead has a TX fifo threshold field in bits 4-5 - _ = fcr.setTransmitTriggerDw8250(0); - _ = fcr.setDmaMode(0); + _ = fcr.setTransmitTrigger(0); } else { _ = fcr.setExtendedFifoEnable(1); - _ = fcr.setDmaMode(0); } _ = fcr.writeTo(io.getIo()); @@ -1098,13 +1090,7 @@ pub fn DriverImpl(comptime kdrv_extra: u32, comptime KdrvConfig: type, comptime // Drive flow control bits high since we don't actively manage them var mcr = ModemControlRegister.get().fromValue(0); - _ = mcr.setAutomaticFlowControlEnable(0); - _ = mcr.setLoop(0); - _ = mcr.setAuxiliaryOut2(0); - _ = mcr.setAuxiliaryOut1(0); - _ = mcr.setRequestToSend(1); - _ = mcr.setDataTerminalReady(1); - _ = mcr.writeTo(io.getIo()); + _ = mcr.setDataTerminalReady(1).setRequestToSend(1).writeTo(io.getIo()); // Figure out the FIFO depth var iir = InterruptIdentRegister.get().readFrom(io.getIo()); @@ -1152,8 +1138,7 @@ pub fn DriverImpl(comptime kdrv_extra: u32, comptime KdrvConfig: type, comptime } if (parity) |p| { - _ = lcr.setParityEnable(if (p != .none) 1 else 0); - _ = lcr.setEvenParity(if (p == .even) 1 else 0); + _ = lcr.setParityEnable(if (p != .none) 1 else 0).setEvenParity(if (p == .even) 1 else 0); } if (stop_bits) |bits| { @@ -1171,8 +1156,7 @@ pub fn DriverImpl(comptime kdrv_extra: u32, comptime KdrvConfig: type, comptime return lsr.txRegisterEmpty() != 0; } - pub fn write(self: *Self, comptime IoProviderType: type, io: *IoProviderType, ready: bool, comptime ItType: type, it: *ItType, end: ItType) ItType { - _ = ready; + pub fn write(self: *Self, comptime IoProviderType: type, io: *IoProviderType, _: bool, comptime ItType: type, it: *ItType, end: ItType) ItType { // The FIFO is empty now and we know the size, so fill it completely var tx = TxBufferRegister.get().fromValue(0); var space = self.fifo_depth; @@ -1184,8 +1168,7 @@ pub fn DriverImpl(comptime kdrv_extra: u32, comptime KdrvConfig: type, comptime return it.*; } - pub fn read(self: *Self, comptime IoProviderType: type, io: *IoProviderType) ?u8 { - _ = self; + pub fn read(_: *Self, comptime IoProviderType: type, io: *IoProviderType) ?u8 { var lsr = LineStatusRegister.get().readFrom(io.getIo()); if (lsr.dataReady() != 0) { var rx = RxBufferRegister.get().readFrom(io.getIo()); @@ -1194,15 +1177,12 @@ pub fn DriverImpl(comptime kdrv_extra: u32, comptime KdrvConfig: type, comptime return null; } - pub fn enableTxInterrupt(self: *Self, comptime IoProviderType: type, io: *IoProviderType, enable: bool) void { - _ = self; + pub fn enableTxInterrupt(_: *Self, comptime IoProviderType: type, io: *IoProviderType, enable: bool) void { var ier = Self.InterruptEnableRegister.get().readFrom(io.getIo()); - _ = ier.setTxEmpty(if (enable) 1 else 0); - _ = ier.writeTo(io.getIo()); + _ = ier.setTxEmpty(if (enable) 1 else 0).writeTo(io.getIo()); } - pub fn enableRxInterrupt(self: *Self, comptime IoProviderType: type, io: *IoProviderType, enable: bool) void { - _ = self; + pub fn enableRxInterrupt(_: *Self, comptime IoProviderType: type, io: *IoProviderType, enable: bool) void { var ier = Self.InterruptEnableRegister.get().readFrom(io.getIo()); _ = ier.setRxAvailable(if (enable) 1 else 0).writeTo(io.getIo()); } From 635d90cc8ec0a4338f9668d04e696aa91f86027f Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Tue, 10 Jun 2025 14:19:48 -0300 Subject: [PATCH 19/41] [ulib][hwreg] Fix interrupt callback --- slipstream/system/ulib/uart/src/ns8250.zig | 6 +++--- slipstream/system/ulib/uart/src/uart.zig | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/slipstream/system/ulib/uart/src/ns8250.zig b/slipstream/system/ulib/uart/src/ns8250.zig index c940a55..0e84b90 100644 --- a/slipstream/system/ulib/uart/src/ns8250.zig +++ b/slipstream/system/ulib/uart/src/ns8250.zig @@ -1187,13 +1187,13 @@ pub fn DriverImpl(comptime kdrv_extra: u32, comptime KdrvConfig: type, comptime _ = ier.setRxAvailable(if (enable) 1 else 0).writeTo(io.getIo()); } - pub fn initInterrupt(self: *Self, comptime IoProviderType: type, io: *IoProviderType, enableInterruptCallback: anytype) void { + pub fn initInterrupt(self: *Self, comptime IoProviderType: type, io: *IoProviderType, enableInterruptCallback: uart.InterruptCallbackFn, context: *anyopaque) void { // In x86 drivers enabling the interrupt after setting up the hardware // may cause the Rx Interrupt never to fire if (kdrv_extra == driver_config.ZBI_KERNEL_DRIVER_I8250_PIO_UART or kdrv_extra == driver_config.ZBI_KERNEL_DRIVER_I8250_MMIO32_UART) { - enableInterruptCallback(); + enableInterruptCallback(context); } // Enable receive interrupts self.enableRxInterrupt(IoProviderType, io, true); @@ -1209,7 +1209,7 @@ pub fn DriverImpl(comptime kdrv_extra: u32, comptime KdrvConfig: type, comptime kdrv_extra == driver_config.ZBI_KERNEL_DRIVER_I8250_MMIO8_UART or kdrv_extra == driver_config.ZBI_KERNEL_DRIVER_PXA_UART) { - enableInterruptCallback(); + enableInterruptCallback(context); } } diff --git a/slipstream/system/ulib/uart/src/uart.zig b/slipstream/system/ulib/uart/src/uart.zig index 450412c..7cb43e7 100644 --- a/slipstream/system/ulib/uart/src/uart.zig +++ b/slipstream/system/ulib/uart/src/uart.zig @@ -126,6 +126,8 @@ pub const MmioRange = struct { size: u64, }; +pub const InterruptCallbackFn = *const fn (*anyopaque) void; + pub fn DriverBase(comptime Driver: type, comptime drvExtra: u32, comptime DriverConfig: type, comptime IoRegType: IoRegisterType, comptime IoSlots: IoSlotType(IoRegType)) type { return struct { const Self = @This(); @@ -407,11 +409,11 @@ pub fn KernelDriver(comptime UartDriver: type, comptime IoProviderType: type, co self.uart.setLineControl(IoProviderType, &self.io, data_bits, parity, stop_bits); } - pub fn initInterrupt(self: *Self, comptime LockPolicy: type, enableInterruptCallback: anytype) void { + pub fn initInterrupt(self: *Self, comptime LockPolicy: type, enableInterruptCallback: InterruptCallbackFn, context: *anyopaque) void { var guard = SyncPolicy.Guard(LockPolicy).init(&self.lock, @src()); defer guard.deinit(); - self.uart.initInterrupt(IoProviderType, &self.io, enableInterruptCallback); + self.uart.initInterrupt(IoProviderType, &self.io, enableInterruptCallback, context); } pub fn interrupt(self: *Self, tx: anytype, rx: anytype) void { From 18735d59684d0ec8716c8c6725ce77fafefb80f1 Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Tue, 10 Jun 2025 14:53:45 -0300 Subject: [PATCH 20/41] [ulib][uart] Add pl011 impl --- slipstream/system/ulib/uart/root.zig | 2 + slipstream/system/ulib/uart/src/pl011.zig | 1007 +++++++++++++++++++++ 2 files changed, 1009 insertions(+) create mode 100644 slipstream/system/ulib/uart/src/pl011.zig diff --git a/slipstream/system/ulib/uart/root.zig b/slipstream/system/ulib/uart/root.zig index 96c8ee0..584dc84 100644 --- a/slipstream/system/ulib/uart/root.zig +++ b/slipstream/system/ulib/uart/root.zig @@ -4,11 +4,13 @@ const uart = @import("src/uart.zig"); const ns8250 = @import("src/ns8250.zig"); +const pl011 = @import("src/pl011.zig"); const chars_from = @import("src/chars_from.zig"); comptime { _ = uart; _ = ns8250; + _ = pl011; _ = chars_from; } diff --git a/slipstream/system/ulib/uart/src/pl011.zig b/slipstream/system/ulib/uart/src/pl011.zig new file mode 100644 index 0000000..5684040 --- /dev/null +++ b/slipstream/system/ulib/uart/src/pl011.zig @@ -0,0 +1,1007 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. +//! PrimeCell® UART (PL011) Technical Reference Manual +//! Revision: r1p5 +//! URL: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0183g/index.html + +const std = @import("std"); +const zbi_format = @import("zbi_format"); +const uart = @import("uart.zig"); +const hwreg = @import("hwreg"); +const tx_interrupt = @import("interrupt.zig").TxInterrupt; +const rx_interrupt = @import("interrupt.zig").RxInterrupt; + +const testing = std.testing; +const driver_config = zbi_format.driver_config; +const mock = @import("mock.zig"); +const sync = @import("sync.zig"); + +// Import hwreg API +const RegisterBase = hwreg.bitfields.RegisterBase; +const RegisterAddr = hwreg.bitfields.RegisterAddr; +const DefField = hwreg.bitfields.DefField; +const DefBit = hwreg.bitfields.DefBit; +const DefEnumField = hwreg.bitfields.DefEnumField; +const DefRsvdzField = hwreg.bitfields.DefRsvdzField; + +pub const qemu_config = driver_config.SimpleDriverConfig{ + .mmio_phys = 0x09000000, + .irq = 33, + .flags = driver_config.ZBI_KERNEL_DRIVER_IRQ_FLAGS_LEVEL_TRIGGERED | + driver_config.ZBI_KERNEL_DRIVER_IRQ_FLAGS_POLARITY_HIGH, +}; + +/// We use expanded title (first clause in the Function column of the manual) +/// rather than the acronym (Name column in the manual) for readability, except +/// for the RS-232 standard acronyms and tx/rx for transmit/receive. +pub const DataRegister = struct { + const Self = @This(); + pub const ValueType = u16; + const ParentType = RegisterBase(Self, ValueType, void); + pub const printer_enabled: bool = ParentType.PrinterEnabled; + + base: ParentType = .{}, + + // Field definitions + // 15:12 Reserved. + pub const OverrunError = DefBit(Self, 11, "overrun_error"); + pub const BreakError = DefBit(Self, 10, "break_error"); + pub const ParityError = DefBit(Self, 9, "parity_error"); + pub const FramingError = DefBit(Self, 8, "framing_error"); + pub const Data = DefField(Self, 7, 0, "data"); + + pub fn init() Self { + var self = Self{ .base = .{} }; + OverrunError.init(&self); + BreakError.init(&self); + ParityError.init(&self); + FramingError.init(&self); + Data.init(&self); + return self; + } + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(0); + } + + pub fn overrunError(self: *const Self) ValueType { + return OverrunError.get(self); + } + + pub fn setOverrunError(self: *Self, value: ValueType) *Self { + OverrunError.set(self, value); + return self; + } + + pub fn breakError(self: *const Self) ValueType { + return BreakError.get(self); + } + + pub fn setBreakError(self: *Self, value: ValueType) *Self { + BreakError.set(self, value); + return self; + } + + pub fn parityError(self: *const Self) ValueType { + return ParityError.get(self); + } + + pub fn setParityError(self: *Self, value: ValueType) *Self { + ParityError.set(self, value); + return self; + } + + pub fn framingError(self: *const Self) ValueType { + return FramingError.get(self); + } + + pub fn setFramingError(self: *Self, value: ValueType) *Self { + FramingError.set(self, value); + return self; + } + + pub fn data(self: *const Self) ValueType { + return Data.get(self); + } + + pub fn setData(self: *Self, value: ValueType) *Self { + Data.set(self, value); + return self; + } + + pub fn readFrom(self: *Self, reg_io: anytype) *Self { + _ = self.base.readFrom(reg_io); + return self; + } + + pub fn writeTo(self: *Self, reg_io: anytype) *Self { + _ = self.base.writeTo(reg_io); + return self; + } +}; + +pub const FlagRegister = struct { + const Self = @This(); + pub const ValueType = u16; + const ParentType = RegisterBase(Self, ValueType, void); + pub const printer_enabled: bool = ParentType.PrinterEnabled; + + base: ParentType = .{}, + + // Field definitions + // 15:9 Reserved, do not modify. + pub const Ri = DefBit(Self, 8, "ri"); + pub const TxFifoEmpty = DefBit(Self, 7, "tx_fifo_empty"); + pub const RxFifoFull = DefBit(Self, 6, "rx_fifo_full"); + pub const TxFifoFull = DefBit(Self, 5, "tx_fifo_full"); + pub const RxFifoEmpty = DefBit(Self, 4, "rx_fifo_empty"); + pub const Busy = DefBit(Self, 3, "busy"); + pub const Dcd = DefBit(Self, 2, "dcd"); + pub const Dsr = DefBit(Self, 1, "dsr"); + pub const Cts = DefBit(Self, 0, "cts"); + + pub fn init() Self { + var self = Self{ .base = .{} }; + Ri.init(&self); + TxFifoEmpty.init(&self); + RxFifoFull.init(&self); + TxFifoFull.init(&self); + RxFifoEmpty.init(&self); + Busy.init(&self); + Dcd.init(&self); + Dsr.init(&self); + Cts.init(&self); + return self; + } + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(0x18); + } + + pub fn ri(self: *const Self) ValueType { + return Ri.get(self); + } + + pub fn setRi(self: *Self, value: ValueType) *Self { + Ri.set(self, value); + return self; + } + + pub fn txFifoEmpty(self: *const Self) ValueType { + return TxFifoEmpty.get(self); + } + + pub fn setTxFifoEmpty(self: *Self, value: ValueType) *Self { + TxFifoEmpty.set(self, value); + return self; + } + + pub fn rxFifoFull(self: *const Self) ValueType { + return RxFifoFull.get(self); + } + + pub fn setRxFifoFull(self: *Self, value: ValueType) *Self { + RxFifoFull.set(self, value); + return self; + } + + pub fn txFifoFull(self: *const Self) ValueType { + return TxFifoFull.get(self); + } + + pub fn setTxFifoFull(self: *Self, value: ValueType) *Self { + TxFifoFull.set(self, value); + return self; + } + + pub fn rxFifoEmpty(self: *const Self) ValueType { + return RxFifoEmpty.get(self); + } + + pub fn setRxFifoEmpty(self: *Self, value: ValueType) *Self { + RxFifoEmpty.set(self, value); + return self; + } + + pub fn busy(self: *const Self) ValueType { + return Busy.get(self); + } + + pub fn setBusy(self: *Self, value: ValueType) *Self { + Busy.set(self, value); + return self; + } + + pub fn dcd(self: *const Self) ValueType { + return Dcd.get(self); + } + + pub fn setDcd(self: *Self, value: ValueType) *Self { + Dcd.set(self, value); + return self; + } + + pub fn dsr(self: *const Self) ValueType { + return Dsr.get(self); + } + + pub fn setDsr(self: *Self, value: ValueType) *Self { + Dsr.set(self, value); + return self; + } + + pub fn cts(self: *const Self) ValueType { + return Cts.get(self); + } + + pub fn setCts(self: *Self, value: ValueType) *Self { + Cts.set(self, value); + return self; + } + + pub fn readFrom(self: *Self, reg_io: anytype) *Self { + _ = self.base.readFrom(reg_io); + return self; + } + + pub fn writeTo(self: *Self, reg_io: anytype) *Self { + _ = self.base.writeTo(reg_io); + return self; + } +}; + +pub const ControlRegister = struct { + const Self = @This(); + pub const ValueType = u16; + const ParentType = RegisterBase(Self, ValueType, void); + pub const printer_enabled: bool = ParentType.PrinterEnabled; + + base: ParentType = .{}, + + // Field definitions + pub const CtsEnable = DefBit(Self, 15, "cts_enable"); + pub const RtsEnable = DefBit(Self, 14, "rts_enable"); + pub const Out2 = DefBit(Self, 13, "out2"); + pub const Out1 = DefBit(Self, 12, "out1"); + pub const Rts = DefBit(Self, 11, "rts"); + pub const Dtr = DefBit(Self, 10, "dtr"); + pub const RxEnable = DefBit(Self, 9, "rx_enable"); + pub const TxEnable = DefBit(Self, 8, "tx_enable"); + pub const LoopbackEnable = DefBit(Self, 7, "loopback_enable"); + // 6:3 Reserved, do not modify. + pub const SirLowPower = DefBit(Self, 2, "sir_low_power"); + pub const SirEnable = DefBit(Self, 1, "sir_enable"); + pub const UartEnable = DefBit(Self, 0, "uart_enable"); + + pub fn init() Self { + var self = Self{ .base = .{} }; + CtsEnable.init(&self); + RtsEnable.init(&self); + Out2.init(&self); + Out1.init(&self); + Rts.init(&self); + Dtr.init(&self); + RxEnable.init(&self); + TxEnable.init(&self); + LoopbackEnable.init(&self); + SirLowPower.init(&self); + SirEnable.init(&self); + UartEnable.init(&self); + return self; + } + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(0x30); + } + + pub fn ctsEnable(self: *const Self) ValueType { + return CtsEnable.get(self); + } + + pub fn setCtsEnable(self: *Self, value: ValueType) *Self { + CtsEnable.set(self, value); + return self; + } + + pub fn rtsEnable(self: *const Self) ValueType { + return RtsEnable.get(self); + } + + pub fn setRtsEnable(self: *Self, value: ValueType) *Self { + RtsEnable.set(self, value); + return self; + } + + pub fn out2(self: *const Self) ValueType { + return Out2.get(self); + } + + pub fn setOut2(self: *Self, value: ValueType) *Self { + Out2.set(self, value); + return self; + } + + pub fn out1(self: *const Self) ValueType { + return Out1.get(self); + } + + pub fn setOut1(self: *Self, value: ValueType) *Self { + Out1.set(self, value); + return self; + } + + pub fn rts(self: *const Self) ValueType { + return Rts.get(self); + } + + pub fn setRts(self: *Self, value: ValueType) *Self { + Rts.set(self, value); + return self; + } + + pub fn dtr(self: *const Self) ValueType { + return Dtr.get(self); + } + + pub fn setDtr(self: *Self, value: ValueType) *Self { + Dtr.set(self, value); + return self; + } + + pub fn rxEnable(self: *const Self) ValueType { + return RxEnable.get(self); + } + + pub fn setRxEnable(self: *Self, value: ValueType) *Self { + RxEnable.set(self, value); + return self; + } + + pub fn txEnable(self: *const Self) ValueType { + return TxEnable.get(self); + } + + pub fn setTxEnable(self: *Self, value: ValueType) *Self { + TxEnable.set(self, value); + return self; + } + + pub fn loopbackEnable(self: *const Self) ValueType { + return LoopbackEnable.get(self); + } + + pub fn setLoopbackEnable(self: *Self, value: ValueType) *Self { + LoopbackEnable.set(self, value); + return self; + } + + pub fn sirLowPower(self: *const Self) ValueType { + return SirLowPower.get(self); + } + + pub fn setSirLowPower(self: *Self, value: ValueType) *Self { + SirLowPower.set(self, value); + return self; + } + + pub fn sirEnable(self: *const Self) ValueType { + return SirEnable.get(self); + } + + pub fn setSirEnable(self: *Self, value: ValueType) *Self { + SirEnable.set(self, value); + return self; + } + + pub fn uartEnable(self: *const Self) ValueType { + return UartEnable.get(self); + } + + pub fn setUartEnable(self: *Self, value: ValueType) *Self { + UartEnable.set(self, value); + return self; + } + + pub fn readFrom(self: *Self, reg_io: anytype) *Self { + _ = self.base.readFrom(reg_io); + return self; + } + + pub fn writeTo(self: *Self, reg_io: anytype) *Self { + _ = self.base.writeTo(reg_io); + return self; + } +}; + +pub const InterruptFifoLevelSelectRegister = struct { + const Self = @This(); + pub const ValueType = u16; + const ParentType = RegisterBase(Self, ValueType, void); + pub const printer_enabled: bool = ParentType.PrinterEnabled; + + base: ParentType = .{}, + + // Field definitions + // 15:6 Reserved, do not modify. + + pub const Level = enum(u8) { + one_eighth = 0b000, + one_quarter = 0b001, + one_half = 0b010, + three_quarters = 0b011, + seven_eighths = 0b100, + }; + + pub const Rx = DefEnumField(Self, Level, 5, 3, "rx"); + pub const Tx = DefEnumField(Self, Level, 2, 0, "tx"); + + pub fn init() Self { + var self = Self{ .base = .{} }; + Rx.init(&self); + Tx.init(&self); + return self; + } + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(0x34); + } + + pub fn rx(self: *const Self) Level { + return Rx.get(self); + } + + pub fn setRx(self: *Self, value: Level) *Self { + Rx.set(self, value); + return self; + } + + pub fn tx(self: *const Self) Level { + return Tx.get(self); + } + + pub fn setTx(self: *Self, value: Level) *Self { + Tx.set(self, value); + return self; + } + + pub fn readFrom(self: *Self, reg_io: anytype) *Self { + _ = self.base.readFrom(reg_io); + return self; + } + + pub fn writeTo(self: *Self, reg_io: anytype) *Self { + _ = self.base.writeTo(reg_io); + return self; + } +}; + +/// The three interrupt-related registers have the same fields. Neither +/// inheritance nor template tricks seem to work with hwreg types, so rather +/// than repeating the same fields in three types, just use one type with +/// three different get functions. +pub const InterruptRegister = struct { + const Self = @This(); + pub const ValueType = u16; + const ParentType = RegisterBase(Self, ValueType, void); + pub const printer_enabled: bool = ParentType.PrinterEnabled; + + base: ParentType = .{}, + + // Field definitions + // 15:11 Reserved, do not modify. + pub const OverrunError = DefBit(Self, 10, "overrun_error"); + pub const BreakError = DefBit(Self, 9, "break_error"); + pub const ParityError = DefBit(Self, 8, "parity_error"); + pub const FramingError = DefBit(Self, 7, "framing_error"); + pub const RxTimeout = DefBit(Self, 6, "rx_timeout"); + pub const Tx = DefBit(Self, 5, "tx"); + pub const Rx = DefBit(Self, 4, "rx"); + pub const Dsr = DefBit(Self, 3, "dsr"); + pub const Dcd = DefBit(Self, 2, "dcd"); + pub const Cts = DefBit(Self, 1, "cts"); + pub const Ri = DefBit(Self, 0, "ri"); + + pub fn init() Self { + var self = Self{ .base = .{} }; + OverrunError.init(&self); + BreakError.init(&self); + ParityError.init(&self); + FramingError.init(&self); + RxTimeout.init(&self); + Tx.init(&self); + Rx.init(&self); + Dsr.init(&self); + Dcd.init(&self); + Cts.init(&self); + Ri.init(&self); + return self; + } + + pub fn get(offset: u32) RegisterAddr(Self) { + return RegisterAddr(Self).init(offset); + } + + pub fn overrunError(self: *const Self) ValueType { + return OverrunError.get(self); + } + + pub fn setOverrunError(self: *Self, value: ValueType) *Self { + OverrunError.set(self, value); + return self; + } + + pub fn breakError(self: *const Self) ValueType { + return BreakError.get(self); + } + + pub fn setBreakError(self: *Self, value: ValueType) *Self { + BreakError.set(self, value); + return self; + } + + pub fn parityError(self: *const Self) ValueType { + return ParityError.get(self); + } + + pub fn setParityError(self: *Self, value: ValueType) *Self { + ParityError.set(self, value); + return self; + } + + pub fn framingError(self: *const Self) ValueType { + return FramingError.get(self); + } + + pub fn setFramingError(self: *Self, value: ValueType) *Self { + FramingError.set(self, value); + return self; + } + + pub fn rxTimeout(self: *const Self) ValueType { + return RxTimeout.get(self); + } + + pub fn setRxTimeout(self: *Self, value: ValueType) *Self { + RxTimeout.set(self, value); + return self; + } + + pub fn tx(self: *const Self) ValueType { + return Tx.get(self); + } + + pub fn setTx(self: *Self, value: ValueType) *Self { + Tx.set(self, value); + return self; + } + + pub fn rx(self: *const Self) ValueType { + return Rx.get(self); + } + + pub fn setRx(self: *Self, value: ValueType) *Self { + Rx.set(self, value); + return self; + } + + pub fn dsr(self: *const Self) ValueType { + return Dsr.get(self); + } + + pub fn setDsr(self: *Self, value: ValueType) *Self { + Dsr.set(self, value); + return self; + } + + pub fn dcd(self: *const Self) ValueType { + return Dcd.get(self); + } + + pub fn setDcd(self: *Self, value: ValueType) *Self { + Dcd.set(self, value); + return self; + } + + pub fn cts(self: *const Self) ValueType { + return Cts.get(self); + } + + pub fn setCts(self: *Self, value: ValueType) *Self { + Cts.set(self, value); + return self; + } + + pub fn ri(self: *const Self) ValueType { + return Ri.get(self); + } + + pub fn setRi(self: *Self, value: ValueType) *Self { + Ri.set(self, value); + return self; + } + + pub fn readFrom(self: *Self, reg_io: anytype) *Self { + _ = self.base.readFrom(reg_io); + return self; + } + + pub fn writeTo(self: *Self, reg_io: anytype) *Self { + _ = self.base.writeTo(reg_io); + return self; + } +}; + +pub const InterruptMaskSetClearRegister = struct { + pub fn get() RegisterAddr(InterruptRegister) { + return InterruptRegister.get(0x38); + } +}; + +pub const InterruptMaskedStatusRegister = struct { + pub fn get() RegisterAddr(InterruptRegister) { + return InterruptRegister.get(0x40); + } +}; + +pub const InterruptClearRegister = struct { + pub fn get() RegisterAddr(InterruptRegister) { + return InterruptRegister.get(0x44); + } +}; + +pub const LineControlRegister = struct { + const Self = @This(); + pub const ValueType = u16; + const ParentType = RegisterBase(Self, ValueType, void); + pub const printer_enabled: bool = ParentType.PrinterEnabled; + + base: ParentType = .{}, + + // Field definitions + pub const Reserved = DefField(Self, 15, 8, "reserved"); + pub const EnableStickyParity = DefBit(Self, 7, "enable_sticky_parity"); + pub const WordLength = DefField(Self, 6, 5, "word_length"); + pub const FifoEnable = DefBit(Self, 4, "fifo_enable"); + pub const EnableTwoStopBits = DefBit(Self, 3, "enable_two_stop_bits"); + pub const EnableEvenParity = DefBit(Self, 2, "enable_even_parity"); + pub const EnableParity = DefBit(Self, 1, "enable_parity"); + pub const EnableSendBreak = DefBit(Self, 0, "enable_send_break"); + + pub fn init() Self { + var self = Self{ .base = .{} }; + Reserved.init(&self); + EnableStickyParity.init(&self); + WordLength.init(&self); + FifoEnable.init(&self); + EnableTwoStopBits.init(&self); + EnableEvenParity.init(&self); + EnableParity.init(&self); + EnableSendBreak.init(&self); + return self; + } + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(0x2C); + } + + pub fn reserved(self: *const Self) ValueType { + return Reserved.get(self); + } + + pub fn setReserved(self: *Self, value: ValueType) *Self { + Reserved.set(self, value); + return self; + } + + pub fn enableStickyParity(self: *const Self) ValueType { + return EnableStickyParity.get(self); + } + + pub fn setEnableStickyParity(self: *Self, value: ValueType) *Self { + EnableStickyParity.set(self, value); + return self; + } + + pub fn wordLength(self: *const Self) ValueType { + return WordLength.get(self); + } + + pub fn setWordLength(self: *Self, value: ValueType) *Self { + WordLength.set(self, value); + return self; + } + + pub fn fifoEnable(self: *const Self) ValueType { + return FifoEnable.get(self); + } + + pub fn setFifoEnable(self: *Self, value: ValueType) *Self { + FifoEnable.set(self, value); + return self; + } + + pub fn enableTwoStopBits(self: *const Self) ValueType { + return EnableTwoStopBits.get(self); + } + + pub fn setEnableTwoStopBits(self: *Self, value: ValueType) *Self { + EnableTwoStopBits.set(self, value); + return self; + } + + pub fn enableEvenParity(self: *const Self) ValueType { + return EnableEvenParity.get(self); + } + + pub fn setEnableEvenParity(self: *Self, value: ValueType) *Self { + EnableEvenParity.set(self, value); + return self; + } + + pub fn enableParity(self: *const Self) ValueType { + return EnableParity.get(self); + } + + pub fn setEnableParity(self: *Self, value: ValueType) *Self { + EnableParity.set(self, value); + return self; + } + + pub fn enableSendBreak(self: *const Self) ValueType { + return EnableSendBreak.get(self); + } + + pub fn setEnableSendBreak(self: *Self, value: ValueType) *Self { + EnableSendBreak.set(self, value); + return self; + } + + pub fn readFrom(self: *Self, reg_io: anytype) *Self { + _ = self.base.readFrom(reg_io); + return self; + } + + pub fn writeTo(self: *Self, reg_io: anytype) *Self { + _ = self.base.writeTo(reg_io); + return self; + } +}; + +/// The number of `IoSlots` used by this driver, determined by the last accessed register, see +/// `LineControlRegister`. For unscaled MMIO, this corresponds to the size of the MMIO region +/// from a provided base address. +pub const io_slots: usize = 0x2C + @sizeOf(u16); + +pub const Driver = struct { + const Self = @This(); + const Base = uart.DriverBase(Self, driver_config.ZBI_KERNEL_DRIVER_PL011_UART, driver_config.SimpleDriverConfig, uart.IoRegisterType.mmio8, io_slots); + + base: Base, + + pub const ConfigType = driver_config.SimpleDriverConfig; + pub const devicetree_bindings: []const []const u8 = &.{ "arm,primecell", "arm,pl011" }; + pub const config_name: []const u8 = "pl011"; + pub const kIoType = uart.IoRegisterType.mmio8; + + pub fn initWithConfig(config: ConfigType) Self { + return Self{ + .base = Base.init(config), + }; + } + + pub fn initWithTaggedConfig(tagged_config: uart.Config(Self)) Self { + return Self.initWithConfig(tagged_config.config); + } + + pub fn tryMatchString(string: []const u8) ?uart.Config(Self) { + if (std.mem.eql(u8, string, "qemu")) { + return uart.Config(Self).initWithConfig(qemu_config); + } + return Base.tryMatchString(string); + } + + pub fn getConfig(self: *const Self) ConfigType { + return self.base.config; + } + + pub fn getIoSlots(self: *const Self) usize { + return self.base.getIoSlots(); + } + + /// Initialize the UART driver + pub fn initHardware(self: *Self, comptime IoProviderType: type, io: *IoProviderType) void { + _ = self; + // Other line control settings were initialized by the hardware or the boot + // loader and we just use them as they are. + var lcr = LineControlRegister.get().readFrom(io.getIo()); + _ = lcr.setFifoEnable(1).writeTo(io.getIo()); + + var cr = ControlRegister.get().fromValue(0); + _ = cr.setTxEnable(1).setUartEnable(1).writeTo(io.getIo()); + } + + /// Check if the transmitter is ready to send data + pub fn txReady(_: *Self, comptime IoProviderType: type, io: *IoProviderType) bool { + var flag_reg = FlagRegister.get().readFrom(io.getIo()); + return flag_reg.txFifoEmpty() != 0; + } + + /// Write a single character to the UART + pub fn write(_: *Self, comptime IoProviderType: type, io: *IoProviderType, _: bool, comptime ItType: type, it: *ItType, _: ItType) ItType { + var data_reg = DataRegister.get().fromValue(0); + _ = data_reg.setData(it.current()).writeTo(io.getIo()); + it.next(); + return it.*; + } + + /// Read a single character from the UART + pub fn read(_: *Self, comptime IoProviderType: type, io: *IoProviderType) ?u8 { + var flag_reg = FlagRegister.get().readFrom(io.getIo()); + if (flag_reg.rxFifoEmpty() != 0) { + return null; + } + var data_reg = DataRegister.get().readFrom(io.getIo()); + return @truncate(data_reg.data()); + } + + /// Enable or disable transmit interrupt + pub fn enableTxInterrupt(_: *Self, comptime IoProviderType: type, io: *IoProviderType, enable: bool) void { + var imscr = InterruptMaskSetClearRegister.get().readFrom(io.getIo()); + _ = imscr.setTx(if (enable) 1 else 0).writeTo(io.getIo()); + } + + /// Enable or disable receive interrupt + pub fn enableRxInterrupt(_: *Self, comptime IoProviderType: type, io: *IoProviderType, enable: bool) void { + var imscr = InterruptMaskSetClearRegister.get().readFrom(io.getIo()); + _ = imscr.setRx(if (enable) 1 else 0).setRxTimeout(if (enable) 1 else 0); + _ = imscr.writeTo(io.getIo()); + } + + /// Initialize interrupt handling + pub fn initInterrupt(self: *Self, comptime IoProviderType: type, io: *IoProviderType, enable_interrupt_callback: uart.InterruptCallbackFn, context: *anyopaque) void { + // Clear any pending interrupts. + var icr = InterruptClearRegister.get().fromValue(0x3ff); + _ = icr.writeTo(io.getIo()); + + // Set the FIFO trigger levels to fastest trigger (1/8 capacity). + var fifo = InterruptFifoLevelSelectRegister.get().fromValue(0); + _ = fifo.setRx(InterruptFifoLevelSelectRegister.Level.one_eighth) + .setTx(InterruptFifoLevelSelectRegister.Level.one_eighth) + .writeTo(io.getIo()); + + // Enable receive interrupts and then finally enable reception itself. + // Transmit interrupts are enabled only when there is a blocked writer. + self.enableRxInterrupt(IoProviderType, io, true); + var cr = ControlRegister.get().readFrom(io.getIo()); + _ = cr.setRxEnable(1).writeTo(io.getIo()); + + enable_interrupt_callback(context); + } + + /// Handle interrupts + pub fn interrupt(self: *Self, comptime IoProviderType: type, comptime LockType: type, io: *IoProviderType, lock: *LockType, waiter: anytype, tx: uart.TxCallbackFn, tx_context: *anyopaque, rx: uart.RxCallbackFn, rx_context: *anyopaque) void { + var misr = InterruptMaskedStatusRegister.get().readFrom(io.getIo()); + if (misr.rxTimeout() != 0 or misr.rx() != 0) { + var full = false; + while (!full and FlagRegister.get().readFrom(io.getIo()).rxFifoEmpty() == 0) { + // Read the character if there's a place to put it. + const ReadCharContext = struct { + io_prov: *IoProviderType, + + fn callback(ctx: *anyopaque) u8 { + var driver: *@This() = @ptrCast(ctx); + return @truncate(DataRegister.get().readFrom(driver.io_prov.getIo()).data()); + } + }; + + const DisableContext = struct { + io_prov: *IoProviderType, + full_ptr: *bool, + + fn callback(ctx: *anyopaque) void { + // If the buffer is full, disable the receive interrupt instead + // and stop checking. + var driver: *Self = @ptrCast(ctx); + driver.enableRxInterrupt(IoProviderType, ctx.io_prov, false); + ctx.full_ptr.* = true; + } + }; + + const readCharContext = ReadCharContext{ .io_prov = io }; + const disableContext = DisableContext{ .io_prov = io, .full_ptr = &full }; + const RxInterruptType = rx_interrupt(LockType); + + const rx_irq = RxInterruptType.init( + lock, + ReadCharContext.callback, + &readCharContext, + DisableContext.callback, + &disableContext, + ); + rx(rx_irq, rx_context); + } + } + + if (misr.tx() != 0) { + const TxContext = struct { + driver: *@This(), + io_prov: *IoProviderType, + + fn callback(ctx: *anyopaque) void { + var driver: *@This() = @ptrCast(ctx); + driver.enableTxInterrupt(IoProviderType, io, false); + } + }; + + const txContext = TxContext{ .driver = self, .io_prov = io }; + const TxInterruptType = @import("interrupt.zig").TxInterrupt(LockType, @TypeOf(waiter), @TypeOf(txContext.callback)); + const tx_irq = TxInterruptType.init( + lock, + waiter, + txContext.callback, + ); + tx(tx_irq, tx_context); + } + } +}; + +// Test driver and configuration +const SimpleTestDriver = uart.KernelDriver(Driver, mock.IoProvider(driver_config.SimpleDriverConfig, uart.IoRegisterType.mmio8), sync.UnsynchronizedPolicy); + +const test_config = driver_config.SimpleDriverConfig{ + .mmio_phys = 0, + .irq = 0, + .flags = 0, +}; + +test "pl011 HelloWorld" { + var driver = SimpleTestDriver.init(test_config); + defer driver.deinit(); + + _ = driver.getIo() + .mock() + // Init() + .expectRead(u16, @as(u16, 0b0000_0000_0110_0000), 0x2C) // Read state from LCR + .expectWrite(u16, @as(u16, 0b0000_0000_0111_0000), 0x2C) // Writeback with FIFO enabled + .expectWrite(u16, @as(u16, 0b0001_0000_0001), 0x30) // Init + .expectRead(u16, @as(u16, 0b1000_0000), 0x18) // TxReady -> true + .expectWrite(u16, @as(u16, 'h'), 0) // Write + .expectRead(u16, @as(u16, 0b0000_0000), 0x18) // TxReady -> false + .expectRead(u16, @as(u16, 0b1000_0000), 0x18) // RxReady -> true + .expectWrite(u16, @as(u16, 'i'), 0) // Write + .expectRead(u16, @as(u16, 0b1000_0000), 0x18) // TxReady -> true + .expectWrite(u16, @as(u16, '\r'), 0) // Write + .expectRead(u16, @as(u16, 0b1000_0000), 0x18) // TxReady -> true + .expectWrite(u16, @as(u16, '\n'), 0); // Write + + driver.initHardware(SimpleTestDriver.DefaultLockPolicy); + try testing.expectEqual(@as(usize, 3), driver.write(SimpleTestDriver.DefaultLockPolicy, "hi\n", {})); +} + +test "pl011 read" { + var driver = SimpleTestDriver.init(test_config); + defer driver.deinit(); + + _ = driver.getIo() + .mock() + // Init() + .expectRead(u16, @as(u16, 0b0000_0000_0110_0000), 0x2C) // Read state from LCR + .expectWrite(u16, @as(u16, 0b0000_0000_0111_0000), 0x2C) // Writeback with FIFO enabled + .expectWrite(u16, @as(u16, 0b0001_0000_0001), 0x30) // Init + .expectRead(u16, @as(u16, 0b1000_0000), 0x18) // TxReady -> true + .expectWrite(u16, @as(u16, '?'), 0) // Write + .expectRead(u16, @as(u16, 0b1000_0000), 0x18) // TxReady -> true + .expectWrite(u16, @as(u16, '\r'), 0) // Write + .expectRead(u16, @as(u16, 0b1000_0000), 0x18) // TxReady -> true + .expectWrite(u16, @as(u16, '\n'), 0) // Write + .expectRead(u16, @as(u16, 0b1000_0000), 0x18) // Read (rx_fifo_empty) + .expectRead(u16, @as(u16, 'q'), 0) // Read (data) + .expectRead(u16, @as(u16, 0b1000_0000), 0x18) // Read (rx_fifo_empty) + .expectRead(u16, @as(u16, '\r'), 0); // Read (data) + + driver.initHardware(SimpleTestDriver.DefaultLockPolicy); + try testing.expectEqual(@as(usize, 2), driver.write(SimpleTestDriver.DefaultLockPolicy, "?\n", {})); + try testing.expectEqual(@as(u8, 'q'), driver.read(SimpleTestDriver.DefaultLockPolicy).?); + try testing.expectEqual(@as(u8, '\r'), driver.read(SimpleTestDriver.DefaultLockPolicy).?); +} From 3d5418d283494aff796fb33f2f17e6a2f66995da Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Wed, 11 Jun 2025 08:18:00 -0300 Subject: [PATCH 21/41] [ulib][uart] Refactor interrupt --- slipstream/system/ulib/uart/src/interrupt.zig | 33 +- slipstream/system/ulib/uart/src/ns8250.zig | 41 ++- slipstream/system/ulib/uart/src/pl011.zig | 316 ++++++++++++++++-- slipstream/system/ulib/uart/src/uart.zig | 10 +- 4 files changed, 338 insertions(+), 62 deletions(-) diff --git a/slipstream/system/ulib/uart/src/interrupt.zig b/slipstream/system/ulib/uart/src/interrupt.zig index 3133820..a800be6 100644 --- a/slipstream/system/ulib/uart/src/interrupt.zig +++ b/slipstream/system/ulib/uart/src/interrupt.zig @@ -7,31 +7,38 @@ const std = @import("std"); /// Rx IRQ Handler's will be provided an instance of this struct, providing the thread requirements /// for calling each of the supported APIs. The synchronization cannot be enforced directly as a /// protected object since the `Lock` encompasses more than the UART itself in some environments. -pub fn RxInterrupt(comptime Lock: type, comptime Reader: type, comptime Disabler: type) type { +pub fn RxInterrupt(comptime Lock: type, comptime ReaderCtx: type, comptime DisablerCtx: type) type { + const ReaderCallbackFn = *const fn (*ReaderCtx) u8; + const DisablerCallbackFn = *const fn (*DisablerCtx) void; + return struct { lock: *Lock, - reader: Reader, - disabler: Disabler, + reader: ReaderCallbackFn, + disabler: DisablerCallbackFn, + reader_context: *ReaderCtx, + disabler_context: *DisablerCtx, const Self = @This(); - pub fn init(lock: *Lock, reader: Reader, disabler: Disabler) Self { + pub fn init(lock: *Lock, reader: ReaderCallbackFn, reader_context: *ReaderCtx, disabler: DisablerCallbackFn, disabler_context: *DisablerCtx) Self { return .{ .lock = lock, .reader = reader, .disabler = disabler, + .reader_context = reader_context, + .disabler_context = disabler_context, }; } /// Returns characters from performing one read operation from the UART. - pub fn readChar(self: *Self) !u8 { - return self.reader(); + pub fn readChar(self: *Self) u8 { + return self.reader(self.reader_context); } /// Mask RX IRQ and terminates the loop, even if we could still read more characters from the uart. /// Usually means that the buffer where characters are being written is full. pub fn disableInterrupt(self: *Self) void { - self.disabler(); + self.disabler(self.disabler_context); } /// In some cases it is desirable to control the locking sequence. Some of these cases involve @@ -45,19 +52,23 @@ pub fn RxInterrupt(comptime Lock: type, comptime Reader: type, comptime Disabler /// Tx IRQ Handler's will be provided an instance of this struct, providing the thread requirements /// for calling each of the supported APIs. The synchronization cannot be enforced directly as a /// protected object since the `Lock` encompasses more than the UART itself in some environments. -pub fn TxInterrupt(comptime Lock: type, comptime Waiter: type, comptime Disabler: type) type { +pub fn TxInterrupt(comptime Lock: type, comptime Waiter: type, comptime DisablerCtx: type) type { + const DisablerCallbackFn = *const fn (*DisablerCtx) void; + return struct { lock: *Lock, waiter: *Waiter, - disabler: Disabler, + disabler: DisablerCallbackFn, + disabler_context: *DisablerCtx = undefined, const Self = @This(); - pub fn init(lock: *Lock, waiter: *Waiter, disabler: Disabler) Self { + pub fn init(lock: *Lock, waiter: *Waiter, disabler: DisablerCallbackFn, disabler_context: *DisablerCtx) Self { return .{ .lock = lock, .waiter = waiter, .disabler = disabler, + .disabler_context = disabler_context, }; } @@ -69,7 +80,7 @@ pub fn TxInterrupt(comptime Lock: type, comptime Waiter: type, comptime Disabler /// Disables TX IRQ, usually done when the TX HW Fifo is not empty, such that we can /// efficiently write larger chunks of data without having to block on individual characters. pub fn disableInterrupt(self: *Self) void { - self.disabler(); + self.disabler(self.disabler_context); } /// In some scenarios it is desirable to control the locking sequence. While unlikely in the TX diff --git a/slipstream/system/ulib/uart/src/ns8250.zig b/slipstream/system/ulib/uart/src/ns8250.zig index 0e84b90..79597fc 100644 --- a/slipstream/system/ulib/uart/src/ns8250.zig +++ b/slipstream/system/ulib/uart/src/ns8250.zig @@ -1213,7 +1213,7 @@ pub fn DriverImpl(comptime kdrv_extra: u32, comptime KdrvConfig: type, comptime } } - pub fn interrupt(self: *Self, comptime IoProviderType: type, comptime LockType: type, comptime TxType: type, comptime RxType: type, io: *IoProviderType, lock: *LockType, waiter: anytype, tx: TxType, rx: RxType) void { + pub fn interrupt(self: *Self, comptime IoProviderType: type, comptime LockType: type, comptime WaiterType: type, io: *IoProviderType, lock: *LockType, waiter: *WaiterType, tx: uart.TxCallbackFn, txContext: anytype, rx: uart.RxCallbackFn, rxContext: anytype) void { var iir = InterruptIdentRegister.get(); var id: InterruptType = undefined; while (true) { @@ -1234,51 +1234,56 @@ pub fn DriverImpl(comptime kdrv_extra: u32, comptime KdrvConfig: type, comptime // Notify TX if (lsr.txRegisterEmpty() != 0) { - const Context = struct { - fn callback() void { - self.enableTxInterrupt(IoProviderType, io, false); + const DisableContext = struct { + driver: *DriverImpl, + io_prov: *IoProviderType, + + fn callback(ctx: *@This()) void { + ctx.driver.enableRxInterrupt(IoProviderType, ctx.io_prov, false); } }; - const TxInterruptType = uart.TxInterrupt(LockType, @TypeOf(waiter), @TypeOf(Context.callback)); - const ctx = Context{}; - const tx_irq = TxInterruptType.init(lock, waiter, ctx.callback); - tx(tx_irq); + const TxInterruptType = uart.TxInterrupt(LockType, WaiterType, DisableContext); + var disableContext = DisableContext{ .driver = self, .io_prov = io }; + var txIrq = TxInterruptType.init(lock, waiter, DisableContext.callback, &disableContext); + tx(&txIrq, txContext); } // Drain RX while the line status bit is ready var should_drain_rx = true; while (should_drain_rx and lsr.dataReady() != 0) { const ReadCharContext = struct { - fn callback() u8 { + fn callback(_: anytype) u8 { return @intCast(RxBufferRegister.get().readFrom(io.getIo()).data()); } }; const DisableContext = struct { + driver: *DriverImpl, should_drain_rx: *bool, - fn callback(sd: *bool) void { + fn callback(ctx: *@This()) void { // If the buffer is full, disable the receive interrupt instead and // exit the loop - self.enableRxInterrupt(IoProviderType, io, false); - sd.* = false; + ctx.driver.enableRxInterrupt(IoProviderType, ctx.io_prov, false); + ctx.should_drain_rx.* = false; } }; - const readCharContext = ReadCharContext{}; const disableContext = DisableContext{ .should_drain_rx = &should_drain_rx }; const RxInterruptType = uart.RxInterrupt( LockType, - @TypeOf(readCharContext.callback), - @TypeOf(disableContext.callback), + ReadCharContext, + DisableContext, ); - const rx_irq = RxInterruptType.init( + var rxIrq = RxInterruptType.init( lock, - readCharContext.callback, + ReadCharContext.callback, + {}, disableContext.callback, + &disableContext, ); - rx(rx_irq); + rx(&rxIrq, rxContext); lsr = LineStatusRegister.get().readFrom(io.getIo()); } } diff --git a/slipstream/system/ulib/uart/src/pl011.zig b/slipstream/system/ulib/uart/src/pl011.zig index 5684040..ea944b9 100644 --- a/slipstream/system/ulib/uart/src/pl011.zig +++ b/slipstream/system/ulib/uart/src/pl011.zig @@ -7,16 +7,16 @@ const std = @import("std"); const zbi_format = @import("zbi_format"); -const uart = @import("uart.zig"); const hwreg = @import("hwreg"); -const tx_interrupt = @import("interrupt.zig").TxInterrupt; -const rx_interrupt = @import("interrupt.zig").RxInterrupt; -const testing = std.testing; -const driver_config = zbi_format.driver_config; +const uart = @import("uart.zig"); +const uart_interrupt = @import("interrupt.zig"); const mock = @import("mock.zig"); const sync = @import("sync.zig"); +const testing = std.testing; +const driver_config = zbi_format.driver_config; + // Import hwreg API const RegisterBase = hwreg.bitfields.RegisterBase; const RegisterAddr = hwreg.bitfields.RegisterAddr; @@ -859,7 +859,7 @@ pub const Driver = struct { } /// Initialize interrupt handling - pub fn initInterrupt(self: *Self, comptime IoProviderType: type, io: *IoProviderType, enable_interrupt_callback: uart.InterruptCallbackFn, context: *anyopaque) void { + pub fn initInterrupt(self: *Self, comptime IoProviderType: type, io: *IoProviderType, enable_interrupt_callback: uart.InterruptCallbackFn, context: anytype) void { // Clear any pending interrupts. var icr = InterruptClearRegister.get().fromValue(0x3ff); _ = icr.writeTo(io.getIo()); @@ -880,7 +880,7 @@ pub const Driver = struct { } /// Handle interrupts - pub fn interrupt(self: *Self, comptime IoProviderType: type, comptime LockType: type, io: *IoProviderType, lock: *LockType, waiter: anytype, tx: uart.TxCallbackFn, tx_context: *anyopaque, rx: uart.RxCallbackFn, rx_context: *anyopaque) void { + pub fn interrupt(self: *Self, comptime IoProviderType: type, comptime LockType: type, comptime WaiterType: type, io: *IoProviderType, lock: *LockType, waiter: *WaiterType, tx: uart.TxCallbackFn, txContext: anytype, rx: uart.RxCallbackFn, rxContext: anytype) void { var misr = InterruptMaskedStatusRegister.get().readFrom(io.getIo()); if (misr.rxTimeout() != 0 or misr.rx() != 0) { var full = false; @@ -889,59 +889,57 @@ pub const Driver = struct { const ReadCharContext = struct { io_prov: *IoProviderType, - fn callback(ctx: *anyopaque) u8 { - var driver: *@This() = @ptrCast(ctx); - return @truncate(DataRegister.get().readFrom(driver.io_prov.getIo()).data()); + fn callback(ctx: *@This()) u8 { + return @truncate(DataRegister.get().readFrom(ctx.io_prov.*.getIo()).data()); } }; const DisableContext = struct { + driver: *Driver, io_prov: *IoProviderType, full_ptr: *bool, - fn callback(ctx: *anyopaque) void { + fn callback(ctx: *@This()) void { // If the buffer is full, disable the receive interrupt instead // and stop checking. - var driver: *Self = @ptrCast(ctx); - driver.enableRxInterrupt(IoProviderType, ctx.io_prov, false); + ctx.driver.enableRxInterrupt(IoProviderType, ctx.io_prov, false); ctx.full_ptr.* = true; } }; - const readCharContext = ReadCharContext{ .io_prov = io }; - const disableContext = DisableContext{ .io_prov = io, .full_ptr = &full }; - const RxInterruptType = rx_interrupt(LockType); - - const rx_irq = RxInterruptType.init( + const RxInterruptType = uart_interrupt.RxInterrupt(LockType, ReadCharContext, DisableContext); + var readCharContext = ReadCharContext{ .io_prov = io }; + var disableContext = DisableContext{ .driver = self, .io_prov = io, .full_ptr = &full }; + var rxIrq = RxInterruptType.init( lock, ReadCharContext.callback, &readCharContext, DisableContext.callback, &disableContext, ); - rx(rx_irq, rx_context); + rx(&rxIrq, rxContext); } } if (misr.tx() != 0) { - const TxContext = struct { - driver: *@This(), + const DisableContext = struct { + driver: *Driver, io_prov: *IoProviderType, - fn callback(ctx: *anyopaque) void { - var driver: *@This() = @ptrCast(ctx); - driver.enableTxInterrupt(IoProviderType, io, false); + fn callback(ctx: *@This()) void { + ctx.driver.enableTxInterrupt(IoProviderType, ctx.io_prov, false); } }; - const txContext = TxContext{ .driver = self, .io_prov = io }; - const TxInterruptType = @import("interrupt.zig").TxInterrupt(LockType, @TypeOf(waiter), @TypeOf(txContext.callback)); - const tx_irq = TxInterruptType.init( + const TxInterruptType = uart_interrupt.TxInterrupt(LockType, WaiterType, DisableContext); + var disableContext = DisableContext{ .driver = self, .io_prov = io }; + var txIrq = TxInterruptType.init( lock, waiter, - txContext.callback, + DisableContext.callback, + &disableContext, ); - tx(tx_irq, tx_context); + tx(&txIrq, txContext); } } }; @@ -1005,3 +1003,263 @@ test "pl011 read" { try testing.expectEqual(@as(u8, 'q'), driver.read(SimpleTestDriver.DefaultLockPolicy).?); try testing.expectEqual(@as(u8, '\r'), driver.read(SimpleTestDriver.DefaultLockPolicy).?); } + +// Helper for initializing the driver. +fn initDriver(driver: *SimpleTestDriver) void { + _ = driver.getIo() + .mock() + .expectRead(u16, @as(u16, 0b0000_0000_0110_0000), 0x2C) // Read state from LCR + .expectWrite(u16, @as(u16, 0b0000_0000_0111_0000), 0x2C) // Writeback with FIFO enabled + .expectWrite(u16, @as(u16, 0b0001_0000_0001), 0x30); // Init + + driver.initHardware(SimpleTestDriver.DefaultLockPolicy); + driver.getIo().mock().verifyAndClear(); +} + +// Helper for initializing the driver with interrupt support. +fn initDriverWithInterrupt(driver: *SimpleTestDriver) void { + _ = driver.getIo() + .mock() + .expectRead(u16, @as(u16, 0b0000_0000_0110_0000), 0x2C) // Read state from LCR + .expectWrite(u16, @as(u16, 0b0000_0000_0111_0000), 0x2C) // Writeback with FIFO enabled + .expectWrite(u16, @as(u16, 0b0001_0000_0001), 0x30) // Init + .expectWrite(u16, @as(u16, 0b0000_0011_1111_1111), 0x44) // Clear Interrupts + .expectWrite(u16, @as(u16, 0b0000), 0x34) // Rx and Tx Fifo to 1/8 + .expectRead(u16, @as(u16, 0b000), 0x38) // Read Interrupt Mask + .expectWrite(u16, @as(u16, 0b1010000), 0x38) // Write Interrupt Mask with Rx and Rx Timeout + .expectRead(u16, @as(u16, 0b0001_0000_0001), 0x30) // Read Control Register State + .expectWrite(u16, @as(u16, 0b0011_0000_0001), 0x30); // Enable RX + + driver.initHardware(SimpleTestDriver.DefaultLockPolicy); + driver.initInterrupt(SimpleTestDriver.DefaultLockPolicy, struct { + pub fn call(_: anytype) void {} + }.call, {}); + driver.getIo().mock().verifyAndClear(); +} + +test "pl011 init interrupt" { + var driver = SimpleTestDriver.init(test_config); + defer driver.deinit(); + + initDriver(&driver); + + // Enable Rx Interrupt. + _ = driver.getIo() + .mock() + .expectWrite(u16, @as(u16, 0b0000_0011_1111_1111), 0x44) // Clear Interrupts + .expectWrite(u16, @as(u16, 0b0000), 0x34) // Rx and Tx Fifo to 1/8 + .expectRead(u16, @as(u16, 0b000), 0x38) // Read Interrupt Mask + .expectWrite(u16, @as(u16, 0b1010000), 0x38) // Write Interrupt Mask with Rx and Rx Timeout + .expectRead(u16, @as(u16, 0b0001_0000_0001), 0x30) // Read Control Register State + .expectWrite(u16, @as(u16, 0b0011_0000_0001), 0x30); // Enable RX + + var unmasked_irq = false; + + driver.initInterrupt(SimpleTestDriver.DefaultLockPolicy, struct { + pub fn setTrue(ctx: anytype) void { + ctx.* = true; + } + }.setTrue, &unmasked_irq); + + try testing.expect(unmasked_irq); +} + +test "pl011 rx irq empty fifo" { + var driver = SimpleTestDriver.init(test_config); + defer driver.deinit(); + + initDriverWithInterrupt(&driver); + + // Now actual IRQ Handler expectations. + _ = driver.getIo() + .mock() + .expectRead(u16, @as(u16, 0b1_0000), 0x40) // Read Interrupt Masked Status Register + .expectRead(u16, @as(u16, 0b1_0000), 0x18); // Check if fifo is empty. + + // Empty Fifo bit is set, so it should just return. + + var call_count: u32 = 0; + driver.interrupt( + struct { + pub fn call(tx_irq: anytype, ctx: anytype) void { + _ = tx_irq; + _ = ctx; + testing.expect(false) catch unreachable; // Unexpected call on tx irq callback + } + }.call, + {}, + struct { + pub fn call(rx_irq: anytype, ctx: anytype) void { + _ = rx_irq; + ctx.* += 1; + } + }.call, + &call_count, + ); + + driver.getIo().mock().verifyAndClear(); + try testing.expect(call_count == 0); +} + +const UnsyncronizedGuard = sync.UnsynchronizedPolicy.Guard(sync.UnsynchronizedPolicy.DefaultLockPolicy); + +test "pl011 rx irq with non empty fifo and non full queue" { + var driver = SimpleTestDriver.init(test_config); + defer driver.deinit(); + + initDriverWithInterrupt(&driver); + + // Now actual IRQ Handler expectations. + _ = driver.getIo() + .mock() + .expectRead(u16, @as(u16, 0b1_0000), 0x40) // Read Interrupt Masked Status Register + .expectRead(u16, @as(u16, 0), 0x18) // Check if fifo is empty. + .expectRead(u16, @as(u16, 123), 0) // Read 123 from data register. + .expectRead(u16, @as(u16, 0), 0x18) // Read flag register + .expectRead(u16, @as(u16, 111), 0) // Read 111 from data register + .expectRead(u16, @as(u16, 0b1_0000), 0x18); // Read from flag register, fifo is empty. + + var call_count: u32 = 0; + driver.interrupt( + struct { + pub fn call(tx_irq: anytype, ctx: anytype) void { + _ = tx_irq; + _ = ctx; + testing.expect(false) catch unreachable; // Unexpected call on tx irq callback + } + }.call, + {}, + struct { + pub fn call(rx_irq: anytype, ctx: anytype) void { + const expected_c: u8 = if (ctx.* == 0) 123 else 111; + ctx.* += 1; + var guard = UnsyncronizedGuard.init(rx_irq.getLock(), @src()); + defer guard.deinit(); + const c = rx_irq.readChar(); + testing.expectEqual(expected_c, c) catch unreachable; + } + }.call, + &call_count, + ); + + driver.getIo().mock().verifyAndClear(); + try testing.expectEqual(@as(u32, 2), call_count); +} + +test "pl011 rx timeout irq with non empty fifo and non full queue" { + var driver = SimpleTestDriver.init(test_config); + defer driver.deinit(); + + initDriverWithInterrupt(&driver); + + // Now actual IRQ Handler expectations. + _ = driver.getIo() + .mock() + .expectRead(u16, @as(u16, 0b100_0000), 0x40) // Read Interrupt Masked Status Register + .expectRead(u16, @as(u16, 0), 0x18) // Check if fifo is empty. + .expectRead(u16, @as(u16, 123), 0) // Read 123 from data register. + .expectRead(u16, @as(u16, 0), 0x18) // Read flag register + .expectRead(u16, @as(u16, 111), 0) // Read 111 from data register + .expectRead(u16, @as(u16, 0b1_0000), 0x18); // Read from flag register, fifo is empty. + + var call_count: u32 = 0; + driver.interrupt( + struct { + pub fn call(tx_irq: anytype, ctx: anytype) void { + _ = tx_irq; + _ = ctx; + testing.expect(false) catch unreachable; // Unexpected call on tx irq callback + } + }.call, + {}, + struct { + pub fn call(rx_irq: anytype, ctx: anytype) void { + const expected_c: u8 = if (ctx.* == 0) 123 else 111; + ctx.* += 1; + var guard = UnsyncronizedGuard.init(rx_irq.getLock(), @src()); + defer guard.deinit(); + const c = rx_irq.readChar(); + testing.expectEqual(expected_c, c) catch unreachable; + } + }.call, + &call_count, + ); + + driver.getIo().mock().verifyAndClear(); + try testing.expectEqual(@as(u32, 2), call_count); +} + +test "pl011 rx irq with non empty fifo and full queue" { + var driver = SimpleTestDriver.init(test_config); + defer driver.deinit(); + + initDriverWithInterrupt(&driver); + + // Now actual IRQ Handler expectations. + _ = driver.getIo() + .mock() + .expectRead(u16, @as(u16, 0b1_0000), 0x40) // Read Interrupt Masked Status Register + .expectRead(u16, @as(u16, 0), 0x18) // Check if fifo is empty. + .expectRead(u16, @as(u16, 0b1010000), 0x38) // IMSR Should have Rx and Rx timeout enabled. + .expectWrite(u16, @as(u16, 0), 0x38); // The SW queue is full, so we should disable the RX interrupts. + + var call_count: u32 = 0; + driver.interrupt( + struct { + pub fn call(tx_irq: anytype, ctx: anytype) void { + _ = tx_irq; + _ = ctx; + testing.expect(false) catch unreachable; // Unexpected call on tx irq callback + } + }.call, + {}, + struct { + pub fn call(rx_irq: anytype, ctx: anytype) void { + var guard = UnsyncronizedGuard.init(rx_irq.getLock(), @src()); + defer guard.deinit(); + rx_irq.disableInterrupt(); + ctx.* += 1; + } + }.call, + &call_count, + ); + + driver.getIo().mock().verifyAndClear(); + try testing.expectEqual(@as(u32, 1), call_count); +} + +test "pl011 tx irq only" { + var driver = SimpleTestDriver.init(test_config); + defer driver.deinit(); + + initDriverWithInterrupt(&driver); + + _ = driver.getIo() + .mock() + .expectRead(u16, @as(u16, 0b00100000), 0x40) // Read Masked Interrupt Status Register. + .expectRead(u16, @as(u16, 0b01110000), 0x38) // Read Interrupt Mask Set Clear Register. Enabled should be Rx,Tx and Rx Timeout. + .expectWrite(u16, @as(u16, 0b01010000), 0x38); // Mask the Tx Interrupt. + + var call_count: u32 = 0; + driver.interrupt( + struct { + pub fn call(tx_irq: anytype, ctx: anytype) void { + ctx.* += 1; + var guard = UnsyncronizedGuard.init(tx_irq.getLock(), @src()); + defer guard.deinit(); + tx_irq.disableInterrupt(); + } + }.call, + &call_count, + struct { + pub fn call(rx_irq: anytype, ctx: anytype) void { + _ = rx_irq; + _ = ctx; + testing.expect(false) catch unreachable; // Unexpected call on rx irq callback + } + }.call, + {}, + ); + + try testing.expectEqual(@as(u32, 1), call_count); +} diff --git a/slipstream/system/ulib/uart/src/uart.zig b/slipstream/system/ulib/uart/src/uart.zig index 7cb43e7..172311e 100644 --- a/slipstream/system/ulib/uart/src/uart.zig +++ b/slipstream/system/ulib/uart/src/uart.zig @@ -126,7 +126,9 @@ pub const MmioRange = struct { size: u64, }; -pub const InterruptCallbackFn = *const fn (*anyopaque) void; +pub const InterruptCallbackFn = *const fn (anytype) void; +pub const TxCallbackFn = *const fn (anytype, anytype) void; +pub const RxCallbackFn = *const fn (anytype, anytype) void; pub fn DriverBase(comptime Driver: type, comptime drvExtra: u32, comptime DriverConfig: type, comptime IoRegType: IoRegisterType, comptime IoSlots: IoSlotType(IoRegType)) type { return struct { @@ -409,17 +411,17 @@ pub fn KernelDriver(comptime UartDriver: type, comptime IoProviderType: type, co self.uart.setLineControl(IoProviderType, &self.io, data_bits, parity, stop_bits); } - pub fn initInterrupt(self: *Self, comptime LockPolicy: type, enableInterruptCallback: InterruptCallbackFn, context: *anyopaque) void { + pub fn initInterrupt(self: *Self, comptime LockPolicy: type, enableInterruptCallback: InterruptCallbackFn, context: anytype) void { var guard = SyncPolicy.Guard(LockPolicy).init(&self.lock, @src()); defer guard.deinit(); self.uart.initInterrupt(IoProviderType, &self.io, enableInterruptCallback, context); } - pub fn interrupt(self: *Self, tx: anytype, rx: anytype) void { + pub fn interrupt(self: *Self, tx: TxCallbackFn, tx_context: anytype, rx: RxCallbackFn, rx_context: anytype) void { // Interrupt is responsible for properly acquiring and releasing sync // where needed. - self.uart.interrupt(IoProviderType, @TypeOf(self.lock), @TypeOf(tx), @TypeOf(rx), &self.io_provider, &self.lock, &self.waiter, tx, rx); + self.uart.interrupt(IoProviderType, SyncPolicy.Lock(Self), Waiter, &self.io, &self.lock, &self.waiter, tx, tx_context, rx, rx_context); } pub fn write(self: *Self, comptime LockPolicy: type, str: []const u8, waiter_args: anytype) !usize { From b97abd69abedb47c103c436792b45577297adcf7 Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Wed, 11 Jun 2025 08:18:21 -0300 Subject: [PATCH 22/41] [ulib][uart] Remove print --- slipstream/system/ulib/uart/src/sync.zig | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/slipstream/system/ulib/uart/src/sync.zig b/slipstream/system/ulib/uart/src/sync.zig index cf332d5..8f6b07a 100644 --- a/slipstream/system/ulib/uart/src/sync.zig +++ b/slipstream/system/ulib/uart/src/sync.zig @@ -32,9 +32,7 @@ pub const UnsynchronizedPolicy = struct { _ = LockPolicy; return struct { pub const Self = @This(); - pub fn init(lock: anytype, comptime src: std.builtin.SourceLocation) Self { - std.debug.print("init: {s}\n", .{src.file ++ ":" ++ std.fmt.comptimePrint("{}", .{src.line})}); - _ = lock; + pub fn init(_: anytype, comptime _: std.builtin.SourceLocation) Self { return .{}; } pub fn deinit(self: *Self) void { From 9501a449cd764a74f7dd9f7916448672c0ea205c Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Wed, 11 Jun 2025 13:24:16 -0300 Subject: [PATCH 23/41] [ulib][uart] Refactor init signature|usage --- slipstream/system/ulib/uart/src/mock.zig | 141 ++++++++---------- slipstream/system/ulib/uart/src/ns8250.zig | 43 ++++-- slipstream/system/ulib/uart/src/null.zig | 46 +++--- slipstream/system/ulib/uart/src/pl011.zig | 59 +++++--- slipstream/system/ulib/uart/src/sync.zig | 21 ++- slipstream/system/ulib/uart/src/uart.zig | 68 +++++---- .../system/ulib/uart/test/driver_tests.zig | 94 +++++++++++- 7 files changed, 292 insertions(+), 180 deletions(-) diff --git a/slipstream/system/ulib/uart/src/mock.zig b/slipstream/system/ulib/uart/src/mock.zig index 6a81fc1..1a66142 100644 --- a/slipstream/system/ulib/uart/src/mock.zig +++ b/slipstream/system/ulib/uart/src/mock.zig @@ -16,14 +16,14 @@ const mock_function = @import("mock_function"); /// this is just a placeholder. When used with other uart drivers, /// it provides a mock API for testing expected I/O calls from the driver. pub fn IoProvider(comptime Config: type, comptime IoRegisterType: uart.IoRegisterType) type { - _ = IoRegisterType; - return struct { const Self = @This(); io: hwreg.Mock, - pub fn init(_: Config, _: anytype) Self { + pub fn init(_: anytype, _: anytype) Self { + _ = Config; + _ = IoRegisterType; return Self{ .io = hwreg.Mock.init(), }; @@ -57,6 +57,12 @@ pub const Driver = struct { pub const ConfigType = uart.StubConfig; + //pub const devicetree_bindings: [0][]const u8 = .{}; + pub const config_name: []const u8 = "mock"; + //pub const io_type: uart.IoRegisterType = .mmio8; + //pub const driver_type: u32 = 0; + //pub const extra: u32 = 0; + // Expected call types - mirrors the C++ variant structure const ExpectedLock = struct { unlock: bool = false, @@ -141,31 +147,39 @@ pub const Driver = struct { mock: mock_function.MockFunction(ExpectedResult, &[_]type{Expected}), - pub fn init(_: ConfigType) Self { - return Self{ - .mock = mock_function.MockFunction(ExpectedResult, &[_]type{Expected}).init(), - }; + pub fn init(args: anytype) Self { + if (@TypeOf(args) == Self) { + return args; + } else { + return Self{ + .mock = mock_function.MockFunction(ExpectedResult, &[_]type{Expected}).init(), + }; + } } pub fn deinit(self: *Self) void { self.verifyAndClear(); - self.mock_.deinit(); + self.mock.deinit(); + } + + pub fn tryMatchString(str: []const u8) ?uart.Config(Self) { + if (std.mem.eql(u8, str, config_name)) { + return uart.Config(Self).init(); + } + return null; } pub const kIoType = uart.IoRegisterType.mmio8; - pub fn config(self: *const Self) ConfigType { - _ = self; + pub fn getConfig(_: *const Self) ConfigType { return ConfigType{}; } - pub fn ioSlots(self: *const Self) u16 { - _ = self; + pub fn getIoSlots(_: *const Self) u16 { return 0; } const IoProviderType = IoProvider(ConfigType, kIoType); - // Fluent API for priming and checking the mock pub fn expectInit(self: *Self) *Self { @@ -226,15 +240,15 @@ pub const Driver = struct { // access. The mock Driver to be used with hwreg::mock::IoProvider, but it // never makes any calls. - pub fn initHardware(self: *Self, io: *IoProviderType) void { + pub fn hardwareInit(self: *Self, comptime LocalIoProviderType: type, io: *LocalIoProviderType) void { _ = io; _ = self.mock.call(.{Expected{ .init = ExpectedInit{} }}); } // Return true if Write can make forward progress right now. - pub fn txReady(self: *Self, io: *IoProviderType) bool { + pub fn txReady(self: *Self, comptime LocalIoProviderType: type, io: *LocalIoProviderType) bool { _ = io; - const result = self.mock_.call(.{Expected{ .tx_ready = ExpectedTxReady{} }}); + const result = self.mock.call(.{Expected{ .tx_ready = ExpectedTxReady{} }}); return result.bool_result; } @@ -245,13 +259,13 @@ pub const Driver = struct { _ = io; _ = ready; - const result = self.mock_.call(.{Expected{ .write = ExpectedWrite{} }}); + const result = self.mock.call(.{Expected{ .write = ExpectedWrite{} }}); const count = result.size_result; var i: usize = 0; while (i < count and !it.eql(end)) { const current_char = it.current(); - _ = self.mock_.call(.{Expected{ .char = ExpectedChar{ .c = current_char } }}); + _ = self.mock.call(.{Expected{ .char = ExpectedChar{ .c = current_char } }}); it.next(); i += 1; } @@ -259,43 +273,10 @@ pub const Driver = struct { return it.*; } - pub fn enableTxInterrupt(self: *Self, comptime LocalIoProviderType: type, io: *LocalIoProviderType) void { - _ = io; - _ = self.mock_.call(.{Expected{ .tx_enable = ExpectedTxEnable{} }}); - } - - pub fn read(self: *Self, comptime LocalIoProviderType: type, io: *LocalIoProviderType) ?u8 { - _ = self; - _ = io; - return null; - } - - pub fn setLineControl(self: *Self, comptime LocalIoProviderType: type, io: *LocalIoProviderType, data_bits: ?uart.DataBits, parity: ?uart.Parity, stop_bits: ?uart.StopBits) void { - _ = self; - _ = io; - _ = data_bits; - _ = parity; - _ = stop_bits; - } - - pub fn initInterrupt(self: *Self, comptime LocalIoProviderType: type, io: *LocalIoProviderType, enableInterruptCallback: anytype) void { - _ = self; - _ = io; - _ = enableInterruptCallback; - } - - pub fn interrupt(self: *Self, comptime LocalIoProviderType: type, comptime LockType: type, comptime TxType: type, comptime RxType: type, io: *LocalIoProviderType, lock: *LockType, waiter: anytype, tx: TxType, rx: RxType) void { - _ = self; - _ = io; - _ = lock; - _ = waiter; - _ = tx; - _ = rx; - } - - pub fn enableRxInterrupt(self: *Self, comptime LocalIoProviderType: type, io: *LocalIoProviderType) void { - _ = self; + pub fn enableTxInterrupt(self: *Self, comptime LocalIoProviderType: type, io: *LocalIoProviderType, enable: bool) void { _ = io; + _ = enable; + _ = self.mock.call(.{Expected{ .tx_enable = ExpectedTxEnable{} }}); } }; @@ -316,6 +297,14 @@ pub fn Guard(comptime LockType: type, comptime LockTag: type) type { return Self{ .lock = lock }; } + pub fn initWithTag(comptime T: type, lock: *LockType, comptime _: std.builtin.SourceLocation) Self { + _ = T; + if (LockTag == Locking) { + lock.lock(); + } + return Self{ .lock = lock }; + } + pub fn deinit(self: Self) void { if (LockTag == Locking) { self.lock.unlock(); @@ -328,32 +317,28 @@ pub fn Guard(comptime LockType: type, comptime LockTag: type) type { pub const Lock = struct { const Self = @This(); - mock_: ?*mock_function.MockFunction(Driver.ExpectedResult, &[_]type{Driver.Expected}) = null, + mock: *mock_function.MockFunction(Driver.ExpectedResult, &[_]type{Driver.Expected}), pub fn init() Self { - return Self{}; + return Self{ + .mock = undefined, + }; } - pub fn initWithDriver(self: *Self, driver: *Driver) void { - self.mock_ = &driver.mock_; + pub fn driverInit(self: *Self, driver: *Driver) void { + self.mock = &driver.mock; } pub fn lock(self: *Self) void { - if (self.mock_) |mock_fn| { - _ = mock_fn.call(.{Driver.Expected{ .lock = Driver.ExpectedLock{ .unlock = false } }}); - } + _ = self.mock.call(.{Driver.Expected{ .lock = Driver.ExpectedLock{ .unlock = false } }}); } pub fn unlock(self: *Self) void { - if (self.mock_) |mock_fn| { - _ = mock_fn.call(.{Driver.Expected{ .lock = Driver.ExpectedLock{ .unlock = true } }}); - } + _ = self.mock.call(.{Driver.Expected{ .lock = Driver.ExpectedLock{ .unlock = true } }}); } pub fn assertHeld(self: *Self) void { - if (self.mock_) |mock_fn| { - _ = mock_fn.call(.{Driver.Expected{ .assert_held = Driver.ExpectedAssertHeld{} }}); - } + _ = self.mock.call(.{Driver.Expected{ .assert_held = Driver.ExpectedAssertHeld{} }}); } }; @@ -361,25 +346,27 @@ pub const Lock = struct { pub const Waiter = struct { const Self = @This(); - mock_: ?*mock_function.MockFunction(Driver.ExpectedResult, &[_]type{Driver.Expected}) = null, + mock: *mock_function.MockFunction(Driver.ExpectedResult, &[_]type{Driver.Expected}), pub fn init() Self { - return Self{}; + return Self{ + .mock = undefined, + }; } - pub fn initWithDriver(self: *Self, driver: *Driver) void { - self.mock_ = &driver.mock_; + pub fn driverInit(self: *Self, driver: *Driver) void { + self.mock = &driver.mock; } - pub fn wait(self: *Self, guard: anytype, enableTxInterrupt: anytype, args: anytype) void { + pub fn wait(self: *Self, comptime GuardType: type, guard: *GuardType, enableTxInterrupt: anytype, args: anytype) void { _ = guard; _ = args; - if (self.mock_) |mock_fn| { - const result = mock_fn.call(.{Driver.Expected{ .wait = Driver.ExpectedWait{} }}); - if (result.bool_result) { - enableTxInterrupt(); - } + const result = self.mock.call(.{Driver.Expected{ .wait = Driver.ExpectedWait{} }}); + if (result.bool_result) { + // Create a mutable copy to call the method + var enable_tx = enableTxInterrupt; + enable_tx.enableTxInterrupt(); } } }; diff --git a/slipstream/system/ulib/uart/src/ns8250.zig b/slipstream/system/ulib/uart/src/ns8250.zig index 79597fc..6cce8af 100644 --- a/slipstream/system/ulib/uart/src/ns8250.zig +++ b/slipstream/system/ulib/uart/src/ns8250.zig @@ -997,8 +997,14 @@ pub const UartStatusRegister = struct { } }; -// The scaled number of IoSlots used by this driver -pub fn getIoSlots(comptime kdrv_extra: u32) u32 { +// The scaled number of `IoSlots` used by this driver, for PIO corresponds to the number of +// IO Ports used by the driver. + +// Accomodates for the registers specific to this model that are used in the implementation. +// Specifically `UartStatusRegister`. For Scaled MMIO, this corresponds to the number of +// unscaled registers that need to be accessed by the implementation. The MMIO region size +// can be obtained by scaling the register by their access width(`sizeof(uint32_t)`). +inline fn getIoSlots(comptime kdrv_extra: u32) u32 { if (kdrv_extra == driver_config.ZBI_KERNEL_DRIVER_DW8250_UART) { return (0x7c + @sizeOf(u32)) / 4; } @@ -1039,13 +1045,28 @@ pub fn DriverImpl(comptime kdrv_extra: u32, comptime KdrvConfig: type, comptime return Base.tryMatchString(string); } - pub fn initWithConfig(config: KdrvConfig) Self { - return Self{ - .base = Base.init(config), - }; + pub fn init(args: anytype) Self { + switch (@TypeOf(args)) { + uart.Config(Self) => { + return Self{ + .base = Base.initWithTaggedConfig(args), + }; + }, + KdrvConfig => { + return Self{ + .base = Base.initWithConfig(args), + }; + }, + Self => { + return args; + }, + else => { + @compileError("args must be of type " ++ @typeName(uart.Config(Self)) ++ " or " ++ @typeName(KdrvConfig)); + }, + } } - pub fn initHardware(self: *Self, comptime IoProviderType: type, io: *IoProviderType) void { + pub fn hardwareInit(self: *Self, comptime IoProviderType: type, io: *IoProviderType) void { // Get basic config done so that tx functions. if (kdrv_extra == driver_config.ZBI_KERNEL_DRIVER_PXA_UART) { @@ -1347,7 +1368,7 @@ test "ns8250 HelloWorld" { .expectWrite(u8, @as(u8, '\r'), 0) .expectWrite(u8, @as(u8, '\n'), 0); - driver.initHardware(SimpleTestDriver.DefaultLockPolicy); + driver.hardwareInit(SimpleTestDriver.DefaultLockPolicy); try testing.expectEqual(@as(usize, 3), driver.write(SimpleTestDriver.DefaultLockPolicy, "hi\n", {})); } @@ -1374,7 +1395,7 @@ test "ns8250 SetLineControl8N1" { .expectWrite(u8, @as(u8, 0b0000_0000), 1) .expectWrite(u8, @as(u8, 0b0000_0011), 3); - driver.initHardware(SimpleTestDriver.DefaultLockPolicy); + driver.hardwareInit(SimpleTestDriver.DefaultLockPolicy); driver.setLineControl(SimpleTestDriver.DefaultLockPolicy, uart.DataBits.eight, uart.Parity.none, uart.StopBits.one); } @@ -1401,7 +1422,7 @@ test "ns8250 SetLineControl7E1" { .expectWrite(u8, @as(u8, 0b0000_0000), 1) .expectWrite(u8, @as(u8, 0b0001_1010), 3); - driver.initHardware(SimpleTestDriver.DefaultLockPolicy); + driver.hardwareInit(SimpleTestDriver.DefaultLockPolicy); driver.setLineControl(SimpleTestDriver.DefaultLockPolicy, uart.DataBits.seven, uart.Parity.even, uart.StopBits.one); } @@ -1434,7 +1455,7 @@ test "ns8250 Read" { .expectRead(u8, @as(u8, 0b0110_0001), 5) // Read (data_ready) .expectRead(u8, @as(u8, '\r'), 0); // Read (data) - driver.initHardware(SimpleTestDriver.DefaultLockPolicy); + driver.hardwareInit(SimpleTestDriver.DefaultLockPolicy); try testing.expectEqual(@as(usize, 2), driver.write(SimpleTestDriver.DefaultLockPolicy, "?\n", {})); try testing.expectEqual(@as(u8, 'q'), driver.read(SimpleTestDriver.DefaultLockPolicy).?); try testing.expectEqual(@as(u8, '\r'), driver.read(SimpleTestDriver.DefaultLockPolicy).?); diff --git a/slipstream/system/ulib/uart/src/null.zig b/slipstream/system/ulib/uart/src/null.zig index 2381491..d7d7fd4 100644 --- a/slipstream/system/ulib/uart/src/null.zig +++ b/slipstream/system/ulib/uart/src/null.zig @@ -22,39 +22,29 @@ pub const Driver = struct { pub const driver_type: u32 = 0; pub const extra: u32 = 0; - pub fn tryMatch(header: *const anyopaque) ?uart.Config(Driver) { - _ = header; - return null; - } - - pub fn tryMatchAcpi(debug_port: *const anyopaque) ?uart.Config(Driver) { - _ = debug_port; - return null; - } - pub fn tryMatchString(str: []const u8) ?uart.Config(Driver) { if (std.mem.eql(u8, str, config_name)) { - return uart.Config(Driver){}; + return uart.Config(Driver).init(); } return null; } - pub fn trySelect(decoder: *const anyopaque) bool { - _ = decoder; - return false; - } - - pub fn init() Driver { - return .{}; - } - - pub fn initWithConfig(_: ConfigType) Driver { - return .{}; - } - - pub fn initWithTaggedConfig(tagged_config: anytype) Driver { - _ = tagged_config; - return .{}; + pub fn init(args: anytype) Self { + switch (@TypeOf(args)) { + Self => { + return args; + }, + uart.StubConfig => { + return Self{}; + }, + uart.Config(Driver) => { + return Self{}; + }, + void => { + return Self{}; + }, + else => @compileError("Invalid arguments to null driver constructor: " ++ @typeName(@TypeOf(args))), + } } pub fn eql(self: *const Self, other: *const Self) bool { @@ -84,7 +74,7 @@ pub const Driver = struct { // with mock in tests independent of actual hardware access. The // null Driver never uses the `io` arguments. - pub fn initHardware(self: *Self, comptime IoProviderType: type, io: *IoProviderType) void { + pub fn hardwareInit(self: *Self, comptime IoProviderType: type, io: *IoProviderType) void { _ = self; _ = io; } diff --git a/slipstream/system/ulib/uart/src/pl011.zig b/slipstream/system/ulib/uart/src/pl011.zig index ea944b9..150479c 100644 --- a/slipstream/system/ulib/uart/src/pl011.zig +++ b/slipstream/system/ulib/uart/src/pl011.zig @@ -28,8 +28,10 @@ const DefRsvdzField = hwreg.bitfields.DefRsvdzField; pub const qemu_config = driver_config.SimpleDriverConfig{ .mmio_phys = 0x09000000, .irq = 33, - .flags = driver_config.ZBI_KERNEL_DRIVER_IRQ_FLAGS_LEVEL_TRIGGERED | - driver_config.ZBI_KERNEL_DRIVER_IRQ_FLAGS_POLARITY_HIGH, + .flags = (driver_config.IrqFlags{ + .level_triggered = true, + .polarity_high = true, + }).toInt(), }; /// We use expanded title (first clause in the Function column of the manual) @@ -780,19 +782,9 @@ pub const Driver = struct { base: Base, pub const ConfigType = driver_config.SimpleDriverConfig; - pub const devicetree_bindings: []const []const u8 = &.{ "arm,primecell", "arm,pl011" }; - pub const config_name: []const u8 = "pl011"; - pub const kIoType = uart.IoRegisterType.mmio8; - - pub fn initWithConfig(config: ConfigType) Self { - return Self{ - .base = Base.init(config), - }; - } - - pub fn initWithTaggedConfig(tagged_config: uart.Config(Self)) Self { - return Self.initWithConfig(tagged_config.config); - } + //pub const devicetree_bindings: []const []const u8 = &.{ "arm,primecell", "arm,pl011" }; + //pub const config_name: []const u8 = "pl011"; + //pub const kIoType = uart.IoRegisterType.mmio8; pub fn tryMatchString(string: []const u8) ?uart.Config(Self) { if (std.mem.eql(u8, string, "qemu")) { @@ -801,6 +793,24 @@ pub const Driver = struct { return Base.tryMatchString(string); } + pub fn init(args: anytype) Self { + switch (@TypeOf(args)) { + uart.Config(Self) => { + return Self{ + .base = Base.initWithTaggedConfig(args), + }; + }, + ConfigType => { + return Self{ + .base = Base.initWithConfig(args), + }; + }, + else => { + @compileError("args must be of type " ++ @typeName(uart.Config(Self)) ++ " or " ++ @typeName(ConfigType)); + }, + } + } + pub fn getConfig(self: *const Self) ConfigType { return self.base.config; } @@ -810,7 +820,7 @@ pub const Driver = struct { } /// Initialize the UART driver - pub fn initHardware(self: *Self, comptime IoProviderType: type, io: *IoProviderType) void { + pub fn hardwareInit(self: *Self, comptime IoProviderType: type, io: *IoProviderType) void { _ = self; // Other line control settings were initialized by the hardware or the boot // loader and we just use them as they are. @@ -973,7 +983,7 @@ test "pl011 HelloWorld" { .expectRead(u16, @as(u16, 0b1000_0000), 0x18) // TxReady -> true .expectWrite(u16, @as(u16, '\n'), 0); // Write - driver.initHardware(SimpleTestDriver.DefaultLockPolicy); + driver.hardwareInit(SimpleTestDriver.DefaultLockPolicy); try testing.expectEqual(@as(usize, 3), driver.write(SimpleTestDriver.DefaultLockPolicy, "hi\n", {})); } @@ -998,7 +1008,7 @@ test "pl011 read" { .expectRead(u16, @as(u16, 0b1000_0000), 0x18) // Read (rx_fifo_empty) .expectRead(u16, @as(u16, '\r'), 0); // Read (data) - driver.initHardware(SimpleTestDriver.DefaultLockPolicy); + driver.hardwareInit(SimpleTestDriver.DefaultLockPolicy); try testing.expectEqual(@as(usize, 2), driver.write(SimpleTestDriver.DefaultLockPolicy, "?\n", {})); try testing.expectEqual(@as(u8, 'q'), driver.read(SimpleTestDriver.DefaultLockPolicy).?); try testing.expectEqual(@as(u8, '\r'), driver.read(SimpleTestDriver.DefaultLockPolicy).?); @@ -1012,7 +1022,7 @@ fn initDriver(driver: *SimpleTestDriver) void { .expectWrite(u16, @as(u16, 0b0000_0000_0111_0000), 0x2C) // Writeback with FIFO enabled .expectWrite(u16, @as(u16, 0b0001_0000_0001), 0x30); // Init - driver.initHardware(SimpleTestDriver.DefaultLockPolicy); + driver.hardwareInit(SimpleTestDriver.DefaultLockPolicy); driver.getIo().mock().verifyAndClear(); } @@ -1030,7 +1040,7 @@ fn initDriverWithInterrupt(driver: *SimpleTestDriver) void { .expectRead(u16, @as(u16, 0b0001_0000_0001), 0x30) // Read Control Register State .expectWrite(u16, @as(u16, 0b0011_0000_0001), 0x30); // Enable RX - driver.initHardware(SimpleTestDriver.DefaultLockPolicy); + driver.hardwareInit(SimpleTestDriver.DefaultLockPolicy); driver.initInterrupt(SimpleTestDriver.DefaultLockPolicy, struct { pub fn call(_: anytype) void {} }.call, {}); @@ -1101,6 +1111,7 @@ test "pl011 rx irq empty fifo" { try testing.expect(call_count == 0); } +const UnsyncronizedLock = sync.UnsynchronizedPolicy.Lock(SimpleTestDriver); const UnsyncronizedGuard = sync.UnsynchronizedPolicy.Guard(sync.UnsynchronizedPolicy.DefaultLockPolicy); test "pl011 rx irq with non empty fifo and non full queue" { @@ -1133,7 +1144,7 @@ test "pl011 rx irq with non empty fifo and non full queue" { pub fn call(rx_irq: anytype, ctx: anytype) void { const expected_c: u8 = if (ctx.* == 0) 123 else 111; ctx.* += 1; - var guard = UnsyncronizedGuard.init(rx_irq.getLock(), @src()); + var guard = UnsyncronizedGuard.initWithTag(UnsyncronizedLock, rx_irq.getLock(), @src()); defer guard.deinit(); const c = rx_irq.readChar(); testing.expectEqual(expected_c, c) catch unreachable; @@ -1176,7 +1187,7 @@ test "pl011 rx timeout irq with non empty fifo and non full queue" { pub fn call(rx_irq: anytype, ctx: anytype) void { const expected_c: u8 = if (ctx.* == 0) 123 else 111; ctx.* += 1; - var guard = UnsyncronizedGuard.init(rx_irq.getLock(), @src()); + var guard = UnsyncronizedGuard.initWithTag(UnsyncronizedLock, rx_irq.getLock(), @src()); defer guard.deinit(); const c = rx_irq.readChar(); testing.expectEqual(expected_c, c) catch unreachable; @@ -1215,7 +1226,7 @@ test "pl011 rx irq with non empty fifo and full queue" { {}, struct { pub fn call(rx_irq: anytype, ctx: anytype) void { - var guard = UnsyncronizedGuard.init(rx_irq.getLock(), @src()); + var guard = UnsyncronizedGuard.initWithTag(UnsyncronizedLock, rx_irq.getLock(), @src()); defer guard.deinit(); rx_irq.disableInterrupt(); ctx.* += 1; @@ -1245,7 +1256,7 @@ test "pl011 tx irq only" { struct { pub fn call(tx_irq: anytype, ctx: anytype) void { ctx.* += 1; - var guard = UnsyncronizedGuard.init(tx_irq.getLock(), @src()); + var guard = UnsyncronizedGuard.initWithTag(UnsyncronizedLock, tx_irq.getLock(), @src()); defer guard.deinit(); tx_irq.disableInterrupt(); } diff --git a/slipstream/system/ulib/uart/src/sync.zig b/slipstream/system/ulib/uart/src/sync.zig index 8f6b07a..121237b 100644 --- a/slipstream/system/ulib/uart/src/sync.zig +++ b/slipstream/system/ulib/uart/src/sync.zig @@ -15,10 +15,10 @@ pub const UnsynchronizedPolicy = struct { /// * Lock must model a version of TA Capabilities. /// * MemberOf is the containing type where Lock is embedded as a member. pub fn Lock(comptime MemberOf: type) type { - _ = MemberOf; return struct { pub const Self = @This(); pub fn init() Self { + _ = MemberOf; return .{}; } }; @@ -29,15 +29,14 @@ pub const UnsynchronizedPolicy = struct { /// * Guard must be constructible from (Lock*, const char* id). /// * LockPolicy is forwarded to Guard type. pub fn Guard(comptime LockPolicy: type) type { - _ = LockPolicy; return struct { pub const Self = @This(); - pub fn init(_: anytype, comptime _: std.builtin.SourceLocation) Self { + pub fn initWithTag(comptime LockType: type, lock: *LockType, comptime _: std.builtin.SourceLocation) Self { + _ = LockPolicy; + _ = lock; return .{}; } - pub fn deinit(self: *Self) void { - _ = self; - } + pub fn deinit(_: *Self) void {} }; } @@ -50,9 +49,15 @@ pub const UnsynchronizedPolicy = struct { /// /// Wait is guaranteed to be called while guard holds the underlying capability. pub const Waiter = struct { - pub fn wait(_: *const Waiter, guard: anytype, enable_tx_interrupt: anytype, args: anytype) void { + pub const Self = @This(); + pub fn init() Self { + return .{}; + } + + pub fn wait(self: *Waiter, comptime GuardType: type, guard: *GuardType, enableTxInterrupt: anytype, args: anytype) void { + _ = self; _ = guard; - _ = enable_tx_interrupt; + _ = enableTxInterrupt; _ = args; arch.yield(); } diff --git a/slipstream/system/ulib/uart/src/uart.zig b/slipstream/system/ulib/uart/src/uart.zig index 172311e..711b04c 100644 --- a/slipstream/system/ulib/uart/src/uart.zig +++ b/slipstream/system/ulib/uart/src/uart.zig @@ -6,6 +6,7 @@ const std = @import("std"); const zbi_format = @import("zbi_format"); const parse = @import("parse.zig"); const chars_from = @import("chars_from.zig"); +const mock = @import("mock.zig"); pub const StubConfig = struct {}; pub const driver_config = zbi_format.driver_config; @@ -130,17 +131,16 @@ pub const InterruptCallbackFn = *const fn (anytype) void; pub const TxCallbackFn = *const fn (anytype, anytype) void; pub const RxCallbackFn = *const fn (anytype, anytype) void; -pub fn DriverBase(comptime Driver: type, comptime drvExtra: u32, comptime DriverConfig: type, comptime IoRegType: IoRegisterType, comptime IoSlots: IoSlotType(IoRegType)) type { +pub fn DriverBase(comptime Driver: type, comptime drvExtra: u32, comptime KdrvConfig: type, comptime IoRegType: IoRegisterType, comptime IoSlots: IoSlotType(IoRegType)) type { return struct { const Self = @This(); - pub const ConfigType = DriverConfig; - - config: ConfigType, + pub const ConfigType = KdrvConfig; pub const devicetree_bindings: []const []const u8 = &.{}; pub const io_type: IoRegType = IoRegType; - //type_id: u32, pub const extra: u32 = drvExtra; + config: ConfigType, + //pub fn tryMatch(header: anytype, payload: anytype) ?Config { // if (header.type == ZBI_TYPE_KERNEL_DRIVER and header.extra == extra and // header.length >= @sizeOf(config_type)) { @@ -156,7 +156,7 @@ pub fn DriverBase(comptime Driver: type, comptime drvExtra: u32, comptime Driver std.mem.eql(u8, string[0..config_name.len], config_name)) { const remaining = string[config_name.len..]; - if (parse.parseConfigGeneric(DriverConfig, remaining)) |config| { + if (parse.parseConfigGeneric(KdrvConfig, remaining)) |config| { return Config(Driver).initWithConfig(config); } } @@ -169,11 +169,11 @@ pub fn DriverBase(comptime Driver: type, comptime drvExtra: u32, comptime Driver // return null; //} - pub fn init(cfg: ConfigType) Self { + pub fn initWithConfig(cfg: ConfigType) Self { return Self{ .config = cfg }; } - pub fn initWithConfig(tagged_config: Config(Driver)) Self { + pub fn initWithTaggedConfig(tagged_config: Config(Driver)) Self { return Self.init(tagged_config.config); } @@ -334,29 +334,35 @@ pub fn KernelDriver(comptime UartDriver: type, comptime IoProviderType: type, co // already have been set up by a previous instantiation's Init function, // or might never actually be set up because this instantiation gets // replaced with a different one before ever calling Init. - pub fn init(args: ConfigType) Self { - const uart = UartType.initWithConfig(args); + pub fn init(args: anytype) Self { var self = Self{ .lock = SyncPolicy.Lock(Self).init(), - .waiter = .{}, - .uart = uart, - .io = IoProviderType.init(uart.getConfig(), uart.getIoSlots()), + .waiter = Waiter.init(), + .uart = UartType.init(args), + .io = undefined, }; + self.io = IoProviderType.init(self.uart.getConfig(), self.uart.getIoSlots()); - if (UartDriver == @import("mock.zig").Driver) { + return self; + } + + pub fn mockInit(self: *Self) void { + if (UartDriver == mock.Driver) { // Initialize the mock sync object with the mock driver if needed - self.lock.initWithDriver(&self.uart); - self.waiter.initWithDriver(&self.uart); + self.lock.driverInit(&self.uart); + self.waiter.driverInit(&self.uart); } - return self; } pub fn deinit(self: *Self) void { self.io.deinit(); + if (@hasDecl(UartType, "deinit")) { + self.uart.deinit(); + } } pub fn mmioRange(self: *const Self, comptime LockPolicy: type) MmioRange { - const guard = SyncPolicy.Guard(LockPolicy).init(&self.lock); + const guard = SyncPolicy.Guard(LockPolicy).initWithTag(Lock(Self), &self.lock, @src()); defer guard.deinit(); if (!MmioDriver(UartDriver)) { @@ -367,18 +373,18 @@ pub fn KernelDriver(comptime UartDriver: type, comptime IoProviderType: type, co } pub fn takeUart(self: *Self, comptime LockPolicy: type) UartType { - const guard = SyncPolicy.Guard(LockPolicy).init(&self.lock); + const guard = SyncPolicy.Guard(LockPolicy).initWithTag(Lock(Self), &self.lock, @src()); defer guard.deinit(); return self.uart; } // Returns a copy of the underlying uart config. - pub fn config(self: *const Self, comptime LockPolicy: type) ConfigType { - const guard = SyncPolicy.Guard(LockPolicy).init(&self.lock); + pub fn getConfig(self: *const Self, comptime LockPolicy: type) ConfigType { + const guard = SyncPolicy.Guard(LockPolicy).initWithTag(Lock(Self), &self.lock, @src()); defer guard.deinit(); - return self.uart.config(); + return self.uart.getConfig(); } // Access IoProvider object. @@ -389,11 +395,11 @@ pub fn KernelDriver(comptime UartDriver: type, comptime IoProviderType: type, co // Set up the device for nonblocking output and polling input. // If the device is handed off from a different instantiation, // this won't be called in the new instantiation. - pub fn initHardware(self: *Self, comptime LockPolicy: type) void { - var guard = SyncPolicy.Guard(LockPolicy).init(&self.lock, @src()); + pub fn hardwareInit(self: *Self, comptime LockPolicy: type) void { + var guard = SyncPolicy.Guard(LockPolicy).initWithTag(Lock(Self), &self.lock, @src()); defer guard.deinit(); - self.uart.initHardware(IoProviderType, &self.io); + self.uart.hardwareInit(IoProviderType, &self.io); } //pub fn unparse(self: *const Self, comptime LockPolicy: type, writer: anytype) !void { @@ -405,14 +411,14 @@ pub fn KernelDriver(comptime UartDriver: type, comptime IoProviderType: type, co // Configure the UART line control settings. pub fn setLineControl(self: *Self, comptime LockPolicy: type, data_bits: ?DataBits, parity: ?Parity, stop_bits: ?StopBits) void { - var guard = SyncPolicy.Guard(LockPolicy).init(&self.lock, @src()); + var guard = SyncPolicy.Guard(LockPolicy).initWithTag(Lock(Self), &self.lock, @src()); defer guard.deinit(); self.uart.setLineControl(IoProviderType, &self.io, data_bits, parity, stop_bits); } pub fn initInterrupt(self: *Self, comptime LockPolicy: type, enableInterruptCallback: InterruptCallbackFn, context: anytype) void { - var guard = SyncPolicy.Guard(LockPolicy).init(&self.lock, @src()); + var guard = SyncPolicy.Guard(LockPolicy).initWithTag(Lock(Self), &self.lock, @src()); defer guard.deinit(); self.uart.initInterrupt(IoProviderType, &self.io, enableInterruptCallback, context); @@ -428,7 +434,7 @@ pub fn KernelDriver(comptime UartDriver: type, comptime IoProviderType: type, co var chars = chars_from.CharsFrom(true).init(str); // Massage into u8 with \n -> CRLF. var it = chars.begin(); - var guard = SyncPolicy.Guard(LockPolicy).init(&self.lock, @src()); + var guard = SyncPolicy.Guard(LockPolicy).initWithTag(Lock(Self), &self.lock, @src()); defer guard.deinit(); while (!it.eql(chars.end())) { @@ -438,7 +444,7 @@ pub fn KernelDriver(comptime UartDriver: type, comptime IoProviderType: type, co // Block or just unlock and spin or whatever "wait" means to Sync. // If that means blocking for interrupt wakeup, enable tx interrupts. - self.waiter.wait(&guard, (struct { + self.waiter.wait(Guard(LockPolicy), &guard, (struct { const SelfInner = @This(); uart_self: *Self = undefined, @@ -457,14 +463,14 @@ pub fn KernelDriver(comptime UartDriver: type, comptime IoProviderType: type, co // This is a direct polling read, not used in interrupt-based operation. pub fn read(self: *Self, comptime LockPolicy: type) ?u8 { - var guard = SyncPolicy.Guard(LockPolicy).init(&self.lock, @src()); + var guard = SyncPolicy.Guard(LockPolicy).initWithTag(Lock(Self), &self.lock, @src()); defer guard.deinit(); return self.uart.read(IoProviderType, &self.io); } pub fn enableRxInterrupt(self: *Self, comptime LockPolicy: type) void { - var guard = SyncPolicy.Guard(LockPolicy).init(&self.lock, @src()); + var guard = SyncPolicy.Guard(LockPolicy).initWithTag(Lock(Self), &self.lock, @src()); defer guard.deinit(); self.uart.enableRxInterrupt(IoProviderType, &self.io, true); diff --git a/slipstream/system/ulib/uart/test/driver_tests.zig b/slipstream/system/ulib/uart/test/driver_tests.zig index d06b33f..f213ba5 100644 --- a/slipstream/system/ulib/uart/test/driver_tests.zig +++ b/slipstream/system/ulib/uart/test/driver_tests.zig @@ -13,6 +13,98 @@ const sync = @import("../src/sync.zig"); const driver_config = zbi_format.driver_config; const testing = std.testing; +test "uart nonblocking" { + var mock_uart = mock.Driver.init(.{}); + + _ = mock_uart.expectLock() + .expectInit() + .expectUnlock() + // First Write call -> sends all chars, no waiting. + .expectLock() + .expectTxReady(true) + .expectWrite("hi!") + .expectUnlock() + // Second Write call -> sends half, then waits. + .expectLock() + .expectTxReady(true) + .expectWrite("hello ") + .expectTxReady(false) + .expectWait(false) + .expectTxReady(true) + .expectWrite("world\r\n") + .expectUnlock(); + + const TestDriver = uart.KernelDriver(mock.Driver, mock.IoProvider(uart.StubConfig, uart.IoRegisterType.none), mock.SyncPolicy); + var driver = TestDriver.init(mock_uart); + defer driver.deinit(); + + driver.mockInit(); + driver.hardwareInit(mock.Locking); + try testing.expectEqual(@as(usize, 3), driver.write(mock.Locking, "hi!", .{})); + try testing.expectEqual(@as(usize, 12), driver.write(mock.Locking, "hello world\n", .{})); +} + +test "uart lock policy" { + var mock_uart = mock.Driver.init(.{}); + + _ = mock_uart.expectLock() + .expectInit() + .expectUnlock() + // First Write call -> sends all chars, no waiting. + .expectTxReady(true) + .expectWrite("hi!") + // Second Write call -> sends half, then waits. + .expectTxReady(true) + .expectWrite("hello ") + .expectTxReady(false) + .expectWait(false) + .expectTxReady(true) + .expectWrite("world\r\n"); + + const TestDriver = uart.KernelDriver(mock.Driver, mock.IoProvider(uart.StubConfig, uart.IoRegisterType.none), mock.SyncPolicy); + var driver = TestDriver.init(mock_uart); + defer driver.deinit(); + + driver.mockInit(); + driver.hardwareInit(mock.Locking); + // Just check that lock args are forwarded correctly. + try testing.expectEqual(@as(usize, 3), driver.write(mock.NoopLocking, "hi!", .{})); + try testing.expectEqual(@as(usize, 12), driver.write(mock.NoopLocking, "hello world\n", .{})); +} + +test "uart blocking" { + var mock_uart = mock.Driver.init(.{}); + + _ = mock_uart.expectLock() + .expectInit() + .expectUnlock() + // First Write call -> sends all chars, no waiting. + .expectLock() + .expectTxReady(true) + .expectWrite("hi!") + .expectUnlock() + // Second Write call -> sends half, then waits. + .expectLock() + .expectTxReady(true) + .expectWrite("hello ") + .expectTxReady(false) + .expectWait(true) + .expectAssertHeld() + .expectEnableTxInterrupt() + .expectTxReady(true) + .expectWrite("world\r\n") + .expectUnlock(); + + const TestDriver = uart.KernelDriver(mock.Driver, mock.IoProvider(uart.StubConfig, uart.IoRegisterType.none), mock.SyncPolicy); + var driver = TestDriver.init(mock_uart); + defer driver.deinit(); + + driver.mockInit(); + driver.hardwareInit(mock.Locking); + try testing.expectEqual(@as(usize, 3), driver.write(mock.Locking, "hi!", .{})); + try testing.expectEqual(@as(usize, 12), driver.write(mock.Locking, "hello world\n", .{})); +} + test "uart config" { //var all_configs: uart.Config(uart.all.Driver) = undefined; @@ -80,7 +172,7 @@ test "uart null driver" { var driver = TestDriver.init(.{}); defer driver.deinit(); - driver.initHardware(TestDriver.DefaultLockPolicy); + driver.hardwareInit(TestDriver.DefaultLockPolicy); try testing.expectEqual(@as(usize, 3), driver.write(TestDriver.DefaultLockPolicy, "hi!", {})); try testing.expectEqual(@as(usize, 12), driver.write(TestDriver.DefaultLockPolicy, "hello world\n", {})); try testing.expectEqual(@as(?u8, null), driver.read(TestDriver.DefaultLockPolicy)); From 6e875dc7a9e6d8bdbf52e26930f079260f781c75 Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Wed, 11 Jun 2025 12:07:39 -0300 Subject: [PATCH 24/41] [git] Fix zig-cache exclusion --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index de0e4d9..45efb36 100644 --- a/.gitignore +++ b/.gitignore @@ -58,6 +58,8 @@ tmp/ !/third_party/rust_crates/vendor/**/tmp/ json_generator_tests_*.txt tables_generator_tests_*.txt +zig-cache/ +zig-out/ ### fx configuration and cache files. # NOTE: For any new files, please write files under the top-level .fx/ directory @@ -92,7 +94,6 @@ tables_generator_tests_*.txt /out/ /prebuilt/ /test_data/ -/zig-out/ # Third party repos. /third_party/* From 507f460b928ddb5d88fbc4f83e547ac5a7bf30ea Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Thu, 12 Jun 2025 14:10:42 -0300 Subject: [PATCH 25/41] [ulib][uart] Add all::Config type --- slipstream/system/ulib/uart/root.zig | 2 + slipstream/system/ulib/uart/src/all.zig | 258 +++++++++++++++++- slipstream/system/ulib/uart/src/uart.zig | 10 +- .../system/ulib/uart/test/driver_tests.zig | 104 ++++++- 4 files changed, 367 insertions(+), 7 deletions(-) diff --git a/slipstream/system/ulib/uart/root.zig b/slipstream/system/ulib/uart/root.zig index 584dc84..a4f4efb 100644 --- a/slipstream/system/ulib/uart/root.zig +++ b/slipstream/system/ulib/uart/root.zig @@ -6,12 +6,14 @@ const uart = @import("src/uart.zig"); const ns8250 = @import("src/ns8250.zig"); const pl011 = @import("src/pl011.zig"); const chars_from = @import("src/chars_from.zig"); +const all = @import("src/all.zig"); comptime { _ = uart; _ = ns8250; _ = pl011; _ = chars_from; + _ = all; } test { diff --git a/slipstream/system/ulib/uart/src/all.zig b/slipstream/system/ulib/uart/src/all.zig index 6792a60..7756488 100644 --- a/slipstream/system/ulib/uart/src/all.zig +++ b/slipstream/system/ulib/uart/src/all.zig @@ -1,3 +1,259 @@ //! Copyright 2025 The Drift Authors. All rights reserved. //! Use of this source code is governed by a BSD-style license that can be -//! found in the LICENSE file. \ No newline at end of file +//! found in the LICENSE file. + +const builtin = @import("builtin"); +const std = @import("std"); +const uart = @import("uart.zig"); +const ns8250 = @import("ns8250.zig"); +const pl011 = @import("pl011.zig"); +const null_driver = @import("null.zig"); +const mock = @import("mock.zig"); +const sync = @import("sync.zig"); +const hwreg_internal = @import("hwreg").internal; + +const testing = std.testing; + +/// A dummy driver type that should never be instantiated or called. +/// This exists to allow trailing commas in driver lists and provides +/// clear error messages if accidentally used. +pub const DummyDriver = struct { + const Self = @This(); + + pub const ConfigType = uart.StubConfig; + pub const config_name: []const u8 = "dummy"; + pub const io_type: uart.IoRegisterType = .none; + pub const driver_type: u32 = 0; + pub const extra: u32 = 0; + + pub fn tryMatch(_: anytype) ?uart.Config(DummyDriver) { + return null; + } + + pub fn trySelect(_: anytype) bool { + return false; + } + + pub fn init() Self { + return .{}; + } + + pub fn initWithConfig(_: ConfigType) Self { + return .{}; + } + + pub fn getConfig(_: *const Self) ConfigType { + std.debug.panic("DummyDriver should never be called!", .{}); + } + + pub fn unparse(_: *const Self, _: anytype) void { + std.debug.panic("DummyDriver should never be called!", .{}); + } +}; + +/// Union type containing all supported UART drivers. +/// This is equivalent to the C++ std::variant<...> containing all driver types. +pub const Driver = union(enum) { + null_driver: null_driver.Driver, + mmio32: ns8250.Mmio32Driver, + mmio8: ns8250.Mmio8Driver, + dw8250: ns8250.Dw8250Driver, + pxa: ns8250.PxaDriver, + //amlogic: if (builtin.cpu.arch == .aarch64 or @hasDecl(@This(), "UART_ALL_DRIVERS")) amlogic.Driver else void, + //geni: if (builtin.cpu.arch == .aarch64 or @hasDecl(@This(), "UART_ALL_DRIVERS")) geni.Driver else void, + //pl011: if (builtin.cpu.arch == .aarch64 or @hasDecl(@This(), "UART_ALL_DRIVERS")) pl011.Driver else void, + //exynos_usi: if (builtin.cpu.arch == .aarch64 or builtin.cpu.arch == .riscv64 or @hasDecl(@This(), "UART_ALL_DRIVERS")) exynos_usi.Driver else void, + pio: if (builtin.cpu.arch == .x86_64 or builtin.cpu.arch == .x86) ns8250.PioDriver else void, + dummy: DummyDriver, +}; + +/// Concept equivalent for MatchableDriver - checks if a driver has TryMatch method +fn isMatchableDriver(comptime UartDriver: type, comptime Args: type) bool { + _ = Args; // Args type used for concept checking, not directly referenced + return @hasDecl(UartDriver, "tryMatch") and + @typeInfo(@TypeOf(@field(UartDriver, "tryMatch"))).@"fn".return_type != null; +} + +/// Concept equivalent for SelectableDriver - checks if a driver has TrySelect method +fn isSelectableDriver(comptime UartDriver: type, comptime Args: type) bool { + _ = Args; // Args type used for concept checking, not directly referenced + return @hasDecl(UartDriver, "trySelect") and + @typeInfo(@TypeOf(@field(UartDriver, "trySelect"))).@"fn".return_type == bool; +} + +/// Configuration type that represents a tagged configuration from any supported driver. +/// This is equivalent to the C++ template Config class. +pub fn Config(comptime UartDriver: type) type { + return struct { + const Self = @This(); + + /// Type alias for the underlying driver variant type. + pub const DriverVariant = UartDriver; + + /// Variant holding configurations from all drivers + configs: ConfigVariant(UartDriver), + + /// Returns a Config object if any supported driver provides a TryMatch static method + /// that can be invoked with the provided arguments. Otherwise null is returned. + /// The matching order is determined by the position in the list of drivers. + pub fn match(args: anytype) ?Self { + if (UartDriver == Driver) { + // Handle the main Driver union case + return matchDriverUnion(args); + } else { + // Handle single driver case + return matchSingleDriver(UartDriver, args); + } + } + + /// Returns an empty Config object if any supported driver provides a TrySelect static method + /// that succeeds when invoked with args. Otherwise null is returned. + /// This allows separating driver type selection from actual configuration. + pub fn select(args: anytype) ?Self { + if (UartDriver == Driver) { + // Handle the main Driver union case + return selectDriverUnion(args); + } else { + // Handle single driver case + return selectSingleDriver(UartDriver, args); + } + } + + fn matchDriverUnion(args: anytype) ?Self { + const ArgsType = @TypeOf(args); + + // Try each driver type in the union + inline for (@typeInfo(Driver).@"union".fields) |field| { + if (field.type == void) continue; + + if (comptime isMatchableDriver(field.type, ArgsType)) { + if (field.type.tryMatch(args)) |config| { + std.debug.print("attempted match to {s}", .{field.type.config_name}); + std.debug.print(" OK\n", .{}); + var result: Self = undefined; + result.configs = @unionInit(ConfigVariant(Driver), field.name, config); + return result; + } + std.debug.print("attempted match to {s}", .{field.type.config_name}); + std.debug.print(" FAIL\n", .{}); + } + } + return null; + } + + fn selectDriverUnion(args: anytype) ?Self { + const ArgsType = @TypeOf(args); + + // Try each driver type in the union + inline for (@typeInfo(Driver).@"union".fields) |field| { + if (field.type == void) continue; + + if (comptime isSelectableDriver(field.type, ArgsType)) { + if (field.type.trySelect(args)) { + var result: Self = undefined; + const empty_config = uart.Config(field.type).init(); + result.configs = @unionInit(ConfigVariant(Driver), field.name, empty_config); + return result; + } + } + } + return null; + } + + fn matchSingleDriver(comptime DriverType: type, args: anytype) ?Self { + const ArgsType = @TypeOf(args); + if (comptime isMatchableDriver(DriverType, ArgsType)) { + if (DriverType.tryMatch(args)) |config| { + var result: Self = undefined; + result.configs = config; + return result; + } + } + return null; + } + + fn selectSingleDriver(comptime DriverType: type, args: anytype) ?Self { + const ArgsType = @TypeOf(args); + if (comptime isSelectableDriver(DriverType, ArgsType)) { + if (DriverType.trySelect(args)) { + var result: Self = undefined; + result.configs = uart.Config(DriverType).init(); + return result; + } + } + return null; + } + + pub fn init() Self { + if (UartDriver == Driver) { + // Default to null driver for the main Driver union + return Self{ + .configs = @unionInit(ConfigVariant(Driver), "null_driver", uart.Config(null_driver.Driver).init()), + }; + } else { + // For single driver types, create default config + return Self{ + .configs = uart.Config(UartDriver).init(), + }; + } + } + + /// Constructor from a specific UART config + pub fn initFromConfig(comptime T: type, config: uart.Config(T)) Self { + if (UartDriver == Driver) { + // Find the matching field in the Driver union + inline for (@typeInfo(Driver).@"union".fields) |field| { + if (field.type == T) { + var result: Self = undefined; + result.configs = @unionInit(ConfigVariant(Driver), field.name, config); + return result; + } + } + @compileError("Driver type not found in union"); + } else if (UartDriver == T) { + return Self{ .configs = config }; + } else { + @compileError("Mismatched driver types"); + } + } + + /// Constructor from a UART driver instance + pub fn initFromUart(comptime T: type, driver: T) Self { + return initFromConfig(T, uart.Config(T){ .config = driver.getConfig() }); + } + + /// Constructor from a KernelDriver instance + pub fn initFromKernelDriver(comptime T: type, comptime IoProvider: type, comptime Sync: type, kernel_driver: *uart.KernelDriver(T, IoProvider, Sync)) Self { + const KernelDriverType = uart.KernelDriver(T, IoProvider, Sync); + return initFromConfig(T, uart.Config(T){ .config = kernel_driver.getConfig(KernelDriverType.DefaultLockPolicy) }); + } + + /// Visitor to access the active configuration object using hwreg's Visit function + pub fn visit(self: *Self, visitor: anytype) void { + hwreg_internal.visit(visitor, self.configs, .{}); + } + + pub fn visitConst(self: *const Self, visitor: anytype) void { + hwreg_internal.visit(visitor, self.configs, .{}); + } + }; +} + +/// Generate the configuration variant type based on the driver variant +fn ConfigVariant(comptime UartDriver: type) type { + if (UartDriver == Driver) { + // For the main Driver union, create a variant of all configs + return union(enum) { + null_driver: uart.Config(null_driver.Driver), + mmio32: uart.Config(ns8250.Mmio32Driver), + mmio8: uart.Config(ns8250.Mmio8Driver), + dw8250: uart.Config(ns8250.Dw8250Driver), + pxa: uart.Config(ns8250.PxaDriver), + pio: if (builtin.cpu.arch == .x86_64 or builtin.cpu.arch == .x86) uart.Config(ns8250.PioDriver) else void, + dummy: uart.Config(DummyDriver), + }; + } else { + // For single driver types, just return the config directly + return uart.Config(UartDriver); + } +} diff --git a/slipstream/system/ulib/uart/src/uart.zig b/slipstream/system/ulib/uart/src/uart.zig index 711b04c..c65715e 100644 --- a/slipstream/system/ulib/uart/src/uart.zig +++ b/slipstream/system/ulib/uart/src/uart.zig @@ -361,8 +361,8 @@ pub fn KernelDriver(comptime UartDriver: type, comptime IoProviderType: type, co } } - pub fn mmioRange(self: *const Self, comptime LockPolicy: type) MmioRange { - const guard = SyncPolicy.Guard(LockPolicy).initWithTag(Lock(Self), &self.lock, @src()); + pub fn mmioRange(self: *Self, comptime LockPolicy: type) MmioRange { + var guard = SyncPolicy.Guard(LockPolicy).initWithTag(Lock(Self), &self.lock, @src()); defer guard.deinit(); if (!MmioDriver(UartDriver)) { @@ -373,15 +373,15 @@ pub fn KernelDriver(comptime UartDriver: type, comptime IoProviderType: type, co } pub fn takeUart(self: *Self, comptime LockPolicy: type) UartType { - const guard = SyncPolicy.Guard(LockPolicy).initWithTag(Lock(Self), &self.lock, @src()); + var guard = SyncPolicy.Guard(LockPolicy).initWithTag(Lock(Self), &self.lock, @src()); defer guard.deinit(); return self.uart; } // Returns a copy of the underlying uart config. - pub fn getConfig(self: *const Self, comptime LockPolicy: type) ConfigType { - const guard = SyncPolicy.Guard(LockPolicy).initWithTag(Lock(Self), &self.lock, @src()); + pub fn getConfig(self: *Self, comptime LockPolicy: type) ConfigType { + var guard = SyncPolicy.Guard(LockPolicy).initWithTag(Lock(Self), &self.lock, @src()); defer guard.deinit(); return self.uart.getConfig(); diff --git a/slipstream/system/ulib/uart/test/driver_tests.zig b/slipstream/system/ulib/uart/test/driver_tests.zig index f213ba5..557da92 100644 --- a/slipstream/system/ulib/uart/test/driver_tests.zig +++ b/slipstream/system/ulib/uart/test/driver_tests.zig @@ -9,6 +9,7 @@ const zbi_format = @import("zbi_format"); const null_driver = @import("../src/null.zig"); const mock = @import("../src/mock.zig"); const sync = @import("../src/sync.zig"); +const all = @import("../src/all.zig"); const driver_config = zbi_format.driver_config; const testing = std.testing; @@ -106,7 +107,7 @@ test "uart blocking" { } test "uart config" { - //var all_configs: uart.Config(uart.all.Driver) = undefined; + var all_config: all.Config(all.Driver) = undefined; const dcfg = driver_config.SimpleDriverConfig{ .mmio_phys = 1, @@ -165,6 +166,107 @@ test "uart config" { try testing.expectEqualDeep(cfg7, cfg7); try testing.expectEqualDeep(cfg7, cfg8); } + + // uart::foo::Driver + const uart_driver = ns8250.Mmio32Driver.init(dcfg); + all_config = all.Config(all.Driver).initFromUart(@TypeOf(uart_driver), uart_driver); + + // From uart driver. + all_config.visitConst(struct { + const expected_dcfg = dcfg; + fn visitFn(config: anytype) void { + const ConfigType = @TypeOf(config); + if (ConfigType == uart.Config(ns8250.Mmio32Driver)) { + testing.expectEqual(@as(u64, expected_dcfg.mmio_phys), config.config.mmio_phys) catch unreachable; + testing.expectEqual(@as(u32, expected_dcfg.irq), config.config.irq) catch unreachable; + testing.expectEqual(@as(u32, expected_dcfg.flags), config.config.flags) catch unreachable; + } else { + testing.expect(false) catch unreachable; // Unexpected configuration + } + } + }.visitFn); + + // From kernel driver + const pio_cfg = driver_config.SimplePioConfig{ + .base = 0x3f8, + .reserved = 0, + .irq = 3, + }; + + const TestKernelDriver = uart.KernelDriver(ns8250.PioDriver, mock.IoProvider(ns8250.PioDriver.ConfigType, uart.IoRegisterType.pio), sync.UnsynchronizedPolicy); + var driver = TestKernelDriver.init(pio_cfg); + defer driver.deinit(); + + var all_configs = all.Config(all.Driver).initFromKernelDriver(ns8250.PioDriver, mock.IoProvider(ns8250.PioDriver.ConfigType, uart.IoRegisterType.pio), sync.UnsynchronizedPolicy, &driver); + + // Test Visit functionality + all_configs.visitConst(struct { + const expected_pio_cfg = pio_cfg; + fn visitFn(config: anytype) void { + const ConfigType = @TypeOf(config); + if (ConfigType == uart.Config(ns8250.PioDriver)) { + testing.expectEqual(@as(u16, expected_pio_cfg.base), config.config.base) catch unreachable; + testing.expectEqual(@as(u32, expected_pio_cfg.irq), config.config.irq) catch unreachable; + } else { + testing.expect(false) catch unreachable; // Unexpected configuration + } + } + }.visitFn); + + // Construct from uart. + { + const dcfg1 = driver_config.SimpleDriverConfig{ + .mmio_phys = 1, + .irq = 2, + .flags = 3, + }; + + const uart_driver1 = ns8250.Mmio32Driver.init(dcfg1); + const all_configs1 = all.Config(all.Driver).initFromUart(@TypeOf(uart_driver1), uart_driver1); + + all_configs1.visitConst(struct { + const expected_dcfg = dcfg1; + fn visitFn(config: anytype) void { + const ConfigType = @TypeOf(config); + if (ConfigType == uart.Config(ns8250.Mmio32Driver)) { + testing.expectEqual(@as(u64, expected_dcfg.mmio_phys), config.config.mmio_phys) catch unreachable; + testing.expectEqual(@as(u32, expected_dcfg.irq), config.config.irq) catch unreachable; + testing.expectEqual(@as(u32, expected_dcfg.flags), config.config.flags) catch unreachable; + } else { + testing.expect(false) catch unreachable; // Unexpected configuration + } + } + }.visitFn); + } + + // Construct from kernel driver. + { + // From kernel driver. + const pio_cfg1 = driver_config.SimplePioConfig{ + .base = 0x3f8, + .reserved = 0, + .irq = 3, + }; + + const TestKernelDriver1 = uart.KernelDriver(ns8250.PioDriver, mock.IoProvider(ns8250.PioDriver.ConfigType, uart.IoRegisterType.pio), sync.UnsynchronizedPolicy); + var driver1 = TestKernelDriver1.init(pio_cfg1); + defer driver.deinit(); + + const all_configs1 = all.Config(all.Driver).initFromKernelDriver(ns8250.PioDriver, mock.IoProvider(ns8250.PioDriver.ConfigType, uart.IoRegisterType.pio), sync.UnsynchronizedPolicy, &driver1); + + all_configs1.visitConst(struct { + const expected_pio_cfg = pio_cfg1; + fn visitFn(config: anytype) void { + const ConfigType = @TypeOf(config); + if (ConfigType == uart.Config(ns8250.PioDriver)) { + testing.expectEqual(@as(u16, expected_pio_cfg.base), config.config.base) catch unreachable; + testing.expectEqual(@as(u32, expected_pio_cfg.irq), config.config.irq) catch unreachable; + } else { + testing.expect(false) catch unreachable; // Unexpected configuration + } + } + }.visitFn); + } } test "uart null driver" { From 88e9fb1473f897ef22e2a43db8cf639a3edc4357 Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Thu, 12 Jun 2025 14:17:40 -0300 Subject: [PATCH 26/41] [ulib][uart] Refactor ConfigVariant to be generated in comptime --- slipstream/system/ulib/uart/src/all.zig | 53 +++++++++++++++---------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/slipstream/system/ulib/uart/src/all.zig b/slipstream/system/ulib/uart/src/all.zig index 7756488..3380fb9 100644 --- a/slipstream/system/ulib/uart/src/all.zig +++ b/slipstream/system/ulib/uart/src/all.zig @@ -90,8 +90,37 @@ pub fn Config(comptime UartDriver: type) type { /// Type alias for the underlying driver variant type. pub const DriverVariant = UartDriver; + /// Generate the configuration variant type based on the driver variant + fn ConfigVariant() type { + if (UartDriver == Driver) { + // For the main Driver union, create a variant of all configs + const union_info = @typeInfo(Driver).@"union"; + var fields: [union_info.fields.len]std.builtin.Type.UnionField = undefined; + + inline for (union_info.fields, 0..) |field, i| { + fields[i] = std.builtin.Type.UnionField{ + .name = field.name, + .type = if (field.type == void) void else uart.Config(field.type), + .alignment = field.alignment, + }; + } + + return @Type(std.builtin.Type{ + .@"union" = std.builtin.Type.Union{ + .layout = union_info.layout, + .tag_type = union_info.tag_type, + .fields = &fields, + .decls = &[_]std.builtin.Type.Declaration{}, + }, + }); + } else { + // For single driver types, just return the config directly + return uart.Config(UartDriver); + } + } + /// Variant holding configurations from all drivers - configs: ConfigVariant(UartDriver), + configs: ConfigVariant(), /// Returns a Config object if any supported driver provides a TryMatch static method /// that can be invoked with the provided arguments. Otherwise null is returned. @@ -188,7 +217,7 @@ pub fn Config(comptime UartDriver: type) type { if (UartDriver == Driver) { // Default to null driver for the main Driver union return Self{ - .configs = @unionInit(ConfigVariant(Driver), "null_driver", uart.Config(null_driver.Driver).init()), + .configs = @unionInit(ConfigVariant(), "null_driver", uart.Config(null_driver.Driver).init()), }; } else { // For single driver types, create default config @@ -205,7 +234,7 @@ pub fn Config(comptime UartDriver: type) type { inline for (@typeInfo(Driver).@"union".fields) |field| { if (field.type == T) { var result: Self = undefined; - result.configs = @unionInit(ConfigVariant(Driver), field.name, config); + result.configs = @unionInit(ConfigVariant(), field.name, config); return result; } } @@ -239,21 +268,3 @@ pub fn Config(comptime UartDriver: type) type { }; } -/// Generate the configuration variant type based on the driver variant -fn ConfigVariant(comptime UartDriver: type) type { - if (UartDriver == Driver) { - // For the main Driver union, create a variant of all configs - return union(enum) { - null_driver: uart.Config(null_driver.Driver), - mmio32: uart.Config(ns8250.Mmio32Driver), - mmio8: uart.Config(ns8250.Mmio8Driver), - dw8250: uart.Config(ns8250.Dw8250Driver), - pxa: uart.Config(ns8250.PxaDriver), - pio: if (builtin.cpu.arch == .x86_64 or builtin.cpu.arch == .x86) uart.Config(ns8250.PioDriver) else void, - dummy: uart.Config(DummyDriver), - }; - } else { - // For single driver types, just return the config directly - return uart.Config(UartDriver); - } -} From 6ab489ce9601b9d5c7df5620f88bf3a25274e758 Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Fri, 13 Jun 2025 14:52:19 -0300 Subject: [PATCH 27/41] [ulib][uart] Fix all::KernelDriver impl --- slipstream/system/ulib/uart/src/all.zig | 566 +++++++++++++----- slipstream/system/ulib/uart/src/uart.zig | 8 +- .../system/ulib/uart/test/driver_tests.zig | 109 +++- 3 files changed, 523 insertions(+), 160 deletions(-) diff --git a/slipstream/system/ulib/uart/src/all.zig b/slipstream/system/ulib/uart/src/all.zig index 3380fb9..5c57966 100644 --- a/slipstream/system/ulib/uart/src/all.zig +++ b/slipstream/system/ulib/uart/src/all.zig @@ -21,39 +21,70 @@ pub const DummyDriver = struct { const Self = @This(); pub const ConfigType = uart.StubConfig; + pub const config_name: []const u8 = "dummy"; pub const io_type: uart.IoRegisterType = .none; pub const driver_type: u32 = 0; pub const extra: u32 = 0; - pub fn tryMatch(_: anytype) ?uart.Config(DummyDriver) { - return null; + base: null_driver.Driver, + + pub fn init(args: anytype) Self { + switch (@TypeOf(args)) { + Self => { + return args; + }, + else => Self{ .base = null_driver.Driver.init(args) }, + } + } + + pub fn hardwareInit(self: *Self, comptime IoProviderType: type, io: *IoProviderType) void { + self.base.hardwareInit(IoProviderType, io); + } + + pub fn txReady(self: *Self, comptime IoProviderType: type, io: *IoProviderType) bool { + return self.base.txReady(IoProviderType, io); + } + + pub fn write(self: *Self, comptime IoProviderType: type, io: *IoProviderType, ready: bool, comptime ItType: type, it: *ItType, end: ItType) ItType { + return self.base.write(IoProviderType, io, ready, ItType, it, end); + } + + pub fn read(self: *Self, comptime IoProviderType: type, io: *IoProviderType) ?u8 { + return self.base.read(IoProviderType, io); + } + + // Enable transmit interrupts so interrupt will be called when txReady(). + pub fn enableTxInterrupt(self: *Self, comptime IoProviderType: type, io: *IoProviderType, enable: bool) void { + self.base.enableTxInterrupt(IoProviderType, io, enable); } - pub fn trySelect(_: anytype) bool { - return false; + // Enable receive interrupts so interrupt will be called when rxReady(). + pub fn enableRxInterrupt(self: *Self, comptime IoProviderType: type, io: *IoProviderType, enable: bool) void { + self.base.enableRxInterrupt(IoProviderType, io, enable); } - pub fn init() Self { - return .{}; + // Set the UART up to deliver interrupts. This is called after initHardware. + pub fn initInterrupt(self: *Self, comptime IoProviderType: type, io: *IoProviderType, enable_interrupt_callback: anytype) void { + self.base.initInterrupt(IoProviderType, io, enable_interrupt_callback); } - pub fn initWithConfig(_: ConfigType) Self { - return .{}; + pub fn interrupt(self: *Self, comptime IoProviderType: type, comptime LockType: type, comptime TxType: type, comptime RxType: type, io: *IoProviderType, lock: *LockType, waiter: *anyopaque, tx: TxType, rx: RxType) void { + self.base.interrupt(IoProviderType, LockType, TxType, RxType, io, lock, waiter, tx, rx); } - pub fn getConfig(_: *const Self) ConfigType { - std.debug.panic("DummyDriver should never be called!", .{}); + pub fn getConfig(self: *const Self) ConfigType { + return self.base.getConfig(); } - pub fn unparse(_: *const Self, _: anytype) void { - std.debug.panic("DummyDriver should never be called!", .{}); + pub fn getIoSlots(self: *const Self) usize { + return self.base.getIoSlots(); } }; /// Union type containing all supported UART drivers. /// This is equivalent to the C++ std::variant<...> containing all driver types. -pub const Driver = union(enum) { +pub const WithAllDrivers = union(enum) { null_driver: null_driver.Driver, mmio32: ns8250.Mmio32Driver, mmio8: ns8250.Mmio8Driver, @@ -67,6 +98,15 @@ pub const Driver = union(enum) { dummy: DummyDriver, }; +// The hardware support object underlying whichever KernelDriver type is the +// active variant can be extracted into this type and then used to construct a +// new uart::all::KernelDriver instantiation in a different environment. +// +// The underlying UartDriver types and ktl::variant (aka std::variant) hold +// only non-pointer data that can be transferred directly from one environment +// to another, e.g. to hand off from physboot to the kernel. +pub const Driver = WithAllDrivers; + /// Concept equivalent for MatchableDriver - checks if a driver has TryMatch method fn isMatchableDriver(comptime UartDriver: type, comptime Args: type) bool { _ = Args; // Args type used for concept checking, not directly referenced @@ -84,6 +124,10 @@ fn isSelectableDriver(comptime UartDriver: type, comptime Args: type) bool { /// Configuration type that represents a tagged configuration from any supported driver. /// This is equivalent to the C++ template Config class. pub fn Config(comptime UartDriver: type) type { + if (@typeInfo(UartDriver) != .@"union") { + @compileError("UartDriver must be a union type"); + } + return struct { const Self = @This(); @@ -92,179 +136,401 @@ pub fn Config(comptime UartDriver: type) type { /// Generate the configuration variant type based on the driver variant fn ConfigVariant() type { - if (UartDriver == Driver) { - // For the main Driver union, create a variant of all configs - const union_info = @typeInfo(Driver).@"union"; - var fields: [union_info.fields.len]std.builtin.Type.UnionField = undefined; - - inline for (union_info.fields, 0..) |field, i| { - fields[i] = std.builtin.Type.UnionField{ - .name = field.name, - .type = if (field.type == void) void else uart.Config(field.type), - .alignment = field.alignment, - }; - } - - return @Type(std.builtin.Type{ - .@"union" = std.builtin.Type.Union{ - .layout = union_info.layout, - .tag_type = union_info.tag_type, - .fields = &fields, - .decls = &[_]std.builtin.Type.Declaration{}, - }, - }); - } else { - // For single driver types, just return the config directly - return uart.Config(UartDriver); + // For the main Driver union, create a variant of all configs + const union_info = @typeInfo(UartDriver).@"union"; + var fields: [union_info.fields.len]std.builtin.Type.UnionField = undefined; + + inline for (union_info.fields, 0..) |field, i| { + fields[i] = std.builtin.Type.UnionField{ + .name = field.name, + .type = if (field.type == void) void else uart.Config(field.type), + .alignment = field.alignment, + }; } + + return @Type(std.builtin.Type{ + .@"union" = std.builtin.Type.Union{ + .layout = union_info.layout, + .tag_type = union_info.tag_type, + .fields = &fields, + .decls = &[_]std.builtin.Type.Declaration{}, + }, + }); } /// Variant holding configurations from all drivers configs: ConfigVariant(), - /// Returns a Config object if any supported driver provides a TryMatch static method - /// that can be invoked with the provided arguments. Otherwise null is returned. - /// The matching order is determined by the position in the list of drivers. - pub fn match(args: anytype) ?Self { - if (UartDriver == Driver) { - // Handle the main Driver union case - return matchDriverUnion(args); - } else { - // Handle single driver case - return matchSingleDriver(UartDriver, args); - } + // /// Returns a Config object if any supported driver provides a TryMatch static method + // /// that can be invoked with the provided arguments. Otherwise null is returned. + // /// The matching order is determined by the position in the list of drivers. + // pub fn match(args: anytype) ?Self { + // const ArgsType = @TypeOf(args); + + // // Try each driver type in the union + // inline for (@typeInfo(Driver).@"union".fields) |field| { + // if (field.type == void) continue; + + // if (comptime isMatchableDriver(field.type, ArgsType)) { + // if (field.type.tryMatch(args)) |config| { + // std.debug.print("attempted match to {s}", .{field.type.config_name}); + // std.debug.print(" OK\n", .{}); + // var result: Self = undefined; + // result.configs = @unionInit(ConfigVariant(Driver), field.name, config); + // return result; + // } + // std.debug.print("attempted match to {s}", .{field.type.config_name}); + // std.debug.print(" FAIL\n", .{}); + // } + // } + // return null; + // } + + // /// Returns an empty Config object if any supported driver provides a TrySelect static method + // /// that succeeds when invoked with args. Otherwise null is returned. + // /// This allows separating driver type selection from actual configuration. + // pub fn select(args: anytype) ?Self { + // const ArgsType = @TypeOf(args); + + // // Try each driver type in the union + // inline for (@typeInfo(Driver).@"union".fields) |field| { + // if (field.type == void) continue; + + // if (comptime isSelectableDriver(field.type, ArgsType)) { + // if (field.type.trySelect(args)) { + // var result: Self = undefined; + // const empty_config = uart.Config(field.type).init(); + // result.configs = @unionInit(ConfigVariant(Driver), field.name, empty_config); + // return result; + // } + // } + // } + // return null; + //} + + pub fn init() Self { + // Default to null driver for the main Driver union + return Self{ + .configs = @unionInit(ConfigVariant(), "null_driver", uart.Config(null_driver.Driver).init()), + }; } - /// Returns an empty Config object if any supported driver provides a TrySelect static method - /// that succeeds when invoked with args. Otherwise null is returned. - /// This allows separating driver type selection from actual configuration. - pub fn select(args: anytype) ?Self { - if (UartDriver == Driver) { - // Handle the main Driver union case - return selectDriverUnion(args); - } else { - // Handle single driver case - return selectSingleDriver(UartDriver, args); + /// Constructor from a specific UART config + pub fn initFromConfig(comptime T: type, config: uart.Config(T)) Self { + // Find the matching field in the Driver union + inline for (@typeInfo(Driver).@"union".fields) |field| { + if (field.type == T) { + var result: Self = undefined; + result.configs = @unionInit(ConfigVariant(), field.name, config); + return result; + } } + @compileError("Driver type not found in union"); } - fn matchDriverUnion(args: anytype) ?Self { - const ArgsType = @TypeOf(args); + /// Constructor from a UART driver instance + pub fn initFromUart(comptime T: type, driver: T) Self { + return initFromConfig(T, uart.Config(T){ .config = driver.getConfig() }); + } - // Try each driver type in the union - inline for (@typeInfo(Driver).@"union".fields) |field| { - if (field.type == void) continue; - - if (comptime isMatchableDriver(field.type, ArgsType)) { - if (field.type.tryMatch(args)) |config| { - std.debug.print("attempted match to {s}", .{field.type.config_name}); - std.debug.print(" OK\n", .{}); - var result: Self = undefined; - result.configs = @unionInit(ConfigVariant(Driver), field.name, config); - return result; - } - std.debug.print("attempted match to {s}", .{field.type.config_name}); - std.debug.print(" FAIL\n", .{}); - } + /// Constructor from a KernelDriver instance + pub fn initFromKernelDriver(comptime T: type, comptime IoProvider: type, comptime Sync: type, kernel_driver: *uart.KernelDriver(T, IoProvider, Sync)) Self { + const KernelDriverType = uart.KernelDriver(T, IoProvider, Sync); + return initFromConfig(T, uart.Config(T){ .config = kernel_driver.getConfig(KernelDriverType.DefaultLockPolicy) }); + } + + // Visitor to access the active configuration object using hwreg's Visit function + pub fn visit(self: *Self, visitor_fn: anytype, args: anytype) void { + hwreg_internal.visit(visitor_fn, self.configs, args); + } + + pub fn visitConst(self: *const Self, visitor_fn: anytype, args: anytype) void { + hwreg_internal.visit(visitor_fn, self.configs, args); + } + }; +} + +/// Instantiates the Driver with a configuration. +/// Equivalent to the C++ MakeDriver template function. +// pub fn MakeDriver(comptime UartDriver: type, config: Config(UartDriver)) UartDriver { +// // Handle the main Driver union case +// var driver: UartDriver = undefined; + +// const visitor = struct { +// driver_ptr: *UartDriver, + +// const VisitorSelf = @This(); + +// pub fn visitFn(self: VisitorSelf, uart_config: anytype) void { +// const ConfigType = @TypeOf(uart_config); +// if (@hasField(ConfigType, "config")) { +// // Extract the underlying driver type from the config +// const underlying_driver = @TypeOf(uart_config.config); + +// // Find the corresponding field in the Driver union +// inline for (@typeInfo(Driver).@"union".fields) |field| { +// if (field.type != void and @hasDecl(field.type, "ConfigType") and +// field.type.ConfigType == underlying_driver) +// { +// const driver_instance = field.type.init(uart_config.config); +// self.driver_ptr.* = @unionInit(UartDriver, field.name, driver_instance); +// return; +// } +// } +// } +// } +// }{ .driver_ptr = &driver }; +// config.visitConst(visitor.visitFn, .{visitor}); +// return driver; +// } + +/// Extracts configuration from a Driver instance. +/// Equivalent to the C++ GetConfig template function. +// pub fn GetConfig(comptime UartDriver: type, driver: Driver) Config(UartDriver) { +// var cfg: Config(UartDriver) = undefined; + +// // Use a visitor pattern to extract config from the driver union +// switch (driver) { +// inline else => |driver_instance| { +// const DriverType = @TypeOf(driver_instance); +// if (DriverType != void) { +// const config = uart.Config(DriverType){ .config = driver_instance.getConfig() }; +// cfg = Config(UartDriver).initFromConfig(DriverType, config); +// } +// }, +// } + +// return cfg; +// } + +/// KernelDriver is a variant across all the KernelDriver types. +/// This is equivalent to the C++ template uart::all::KernelDriver class. +pub fn KernelDriver(comptime IoProviderType: type, comptime SyncPolicy: type, comptime UartDriver: type) type { + if (@typeInfo(UartDriver) != .@"union") { + @compileError("UartDriver must be a union type"); + } + + return struct { + const Self = @This(); + + pub const uart_type = UartDriver; + + /// Individual KernelDriver type for a specific UART driver + fn OneDriver(comptime Uart: type) type { + return uart.KernelDriver(Uart, IoProviderType, SyncPolicy); + } + + /// Generate the variant type containing all possible OneDriver types + fn OneDriverVariant() type { + // For the main Driver union, create a variant of all OneDriver types + const union_info = @typeInfo(Driver).@"union"; + var fields: [union_info.fields.len]std.builtin.Type.UnionField = undefined; + + inline for (union_info.fields, 0..) |field, i| { + fields[i] = std.builtin.Type.UnionField{ + .name = field.name, + .type = if (field.type == void) void else OneDriver(field.type), + .alignment = @alignOf(if (field.type == void) void else OneDriver(field.type)), + }; } - return null; + + return @Type(std.builtin.Type{ + .@"union" = std.builtin.Type.Union{ + .layout = .auto, + .tag_type = union_info.tag_type, + .fields = &fields, + .decls = &[_]std.builtin.Type.Declaration{}, + }, + }); } - fn selectDriverUnion(args: anytype) ?Self { - const ArgsType = @TypeOf(args); + variant: OneDriverVariant(), - // Try each driver type in the union - inline for (@typeInfo(Driver).@"union".fields) |field| { - if (field.type == void) continue; - - if (comptime isSelectableDriver(field.type, ArgsType)) { - if (field.type.trySelect(args)) { - var result: Self = undefined; - const empty_config = uart.Config(field.type).init(); - result.configs = @unionInit(ConfigVariant(Driver), field.name, empty_config); - return result; + /// Default constructor - in default-constructed state, it's the null driver. + pub fn init() Self { + return Self{ + .variant = @unionInit(OneDriverVariant(), "null_driver", OneDriver(null_driver.Driver).init(uart.StubConfig{})), + }; + } + + /// Construct from a Config object + pub fn initFromConfig(config: Config(UartDriver)) Self { + var self = Self{ .variant = undefined }; + const Visitor = struct { + self_ptr: *Self, + + pub fn visitFn(uart_config: anytype, visitor_self: *@This()) void { + const ConfigType = @TypeOf(uart_config); + if (ConfigType != void) { + // Find the corresponding field in the Driver union + inline for (@typeInfo(Driver).@"union".fields) |field| { + if (field.type != void and field.type.ConfigType == @TypeOf(uart_config.config)) { + const driver_instance = OneDriver(field.type).init(uart_config.config); + visitor_self.self_ptr.variant = @unionInit(OneDriverVariant(), field.name, driver_instance); + return; + } + } } } - } - return null; + }; + var visitor = Visitor{ .self_ptr = &self }; + config.visitConst(Visitor.visitFn, .{&visitor}); + return self; } - fn matchSingleDriver(comptime DriverType: type, args: anytype) ?Self { - const ArgsType = @TypeOf(args); - if (comptime isMatchableDriver(DriverType, ArgsType)) { - if (DriverType.tryMatch(args)) |config| { - var result: Self = undefined; - result.configs = config; - return result; + /// Construct from a uart driver instance + pub fn initFromUart(uart_driver: uart_type) Self { + var self = Self{ .variant = undefined }; + const Visitor = struct { + self_ptr: *Self, + + pub fn visitFn(variant: anytype, visitor_self: *@This()) void { + const DriverType = @TypeOf(variant); + // Find the corresponding field in the Driver union + inline for (@typeInfo(Driver).@"union".fields) |field| { + if (field.type != void and field.type == DriverType) { + const driver_instance = OneDriver(field.type).init(variant); + visitor_self.self_ptr.variant = @unionInit(OneDriverVariant(), field.name, driver_instance); + return; + } + } } - } - return null; + }; + var visitor = Visitor{ .self_ptr = &self }; + hwreg_internal.visit(Visitor.visitFn, uart_driver, .{&visitor}); + + return self; } - fn selectSingleDriver(comptime DriverType: type, args: anytype) ?Self { - const ArgsType = @TypeOf(args); - if (comptime isSelectableDriver(DriverType, ArgsType)) { - if (DriverType.trySelect(args)) { - var result: Self = undefined; - result.configs = uart.Config(DriverType).init(); - return result; + /// Get the configuration from the active driver + pub fn getConfig(self: *Self) Config(UartDriver) { + var result: Config(UartDriver) = undefined; + + const Visitor = struct { + result_ptr: *Config(UartDriver), + + pub fn visitFn(kernel_driver: anytype, visitor_self: *@This()) void { + const DriverType = @TypeOf(kernel_driver); + if (DriverType != void and @hasDecl(DriverType, "getConfig")) { + const driver_config = kernel_driver.getConfig(DriverType.DefaultLockPolicy); + const uart_config = uart.Config(DriverType.UartType){ .config = driver_config }; + visitor_self.result_ptr.* = Config(UartDriver).initFromConfig(DriverType.UartType, uart_config); + } } - } - return null; + }; + + var visitor = Visitor{ .result_ptr = &result }; + self.visitConst(Visitor.visitFn, .{&visitor}); + return result; } - pub fn init() Self { - if (UartDriver == Driver) { - // Default to null driver for the main Driver union - return Self{ - .configs = @unionInit(ConfigVariant(), "null_driver", uart.Config(null_driver.Driver).init()), - }; - } else { - // For single driver types, create default config - return Self{ - .configs = uart.Config(UartDriver).init(), - }; - } + /// Apply visitor function to the active driver + pub fn visit(self: *Self, visitor_fn: anytype, args: anytype) void { + hwreg_internal.visit(visitor_fn, self.variant, args); } - /// Constructor from a specific UART config - pub fn initFromConfig(comptime T: type, config: uart.Config(T)) Self { - if (UartDriver == Driver) { - // Find the matching field in the Driver union - inline for (@typeInfo(Driver).@"union".fields) |field| { - if (field.type == T) { - var result: Self = undefined; - result.configs = @unionInit(ConfigVariant(), field.name, config); - return result; + /// Apply visitor function to the active driver (const version) + pub fn visitConst(self: *const Self, visitor_fn: anytype, args: anytype) void { + hwreg_internal.visit(visitor_fn, self.variant, args); + } + + /// Takes ownership of the underlying hardware management and state. + /// This object will be left in an invalid state. + pub fn takeUart(self: *Self) uart_type { + var result: uart_type = undefined; + + const Visitor = struct { + result_ptr: *uart_type, + + const VisitorSelf = @This(); + + pub fn visitFn(driver: anytype, visitor_self: *VisitorSelf) void { + const DriverType = @TypeOf(driver); + inline for (@typeInfo(OneDriverVariant()).@"union".fields) |field| { + if (field.type != void and field.type == DriverType) { + var mut_driver = driver; + const uart_instance = mut_driver.takeUart(DriverType.DefaultLockPolicy); + visitor_self.result_ptr.* = @unionInit(uart_type, field.name, uart_instance); + return; + } } } - @compileError("Driver type not found in union"); - } else if (UartDriver == T) { - return Self{ .configs = config }; - } else { - @compileError("Mismatched driver types"); - } - } + }; + var visitor = Visitor{ .result_ptr = &result }; + self.visit(Visitor.visitFn, .{&visitor}); - /// Constructor from a UART driver instance - pub fn initFromUart(comptime T: type, driver: T) Self { - return initFromConfig(T, uart.Config(T){ .config = driver.getConfig() }); - } + // Set to moved-from state + self.variant = undefined; - /// Constructor from a KernelDriver instance - pub fn initFromKernelDriver(comptime T: type, comptime IoProvider: type, comptime Sync: type, kernel_driver: *uart.KernelDriver(T, IoProvider, Sync)) Self { - const KernelDriverType = uart.KernelDriver(T, IoProvider, Sync); - return initFromConfig(T, uart.Config(T){ .config = kernel_driver.getConfig(KernelDriverType.DefaultLockPolicy) }); + return result; } - /// Visitor to access the active configuration object using hwreg's Visit function - pub fn visit(self: *Self, visitor: anytype) void { - hwreg_internal.visit(visitor, self.configs, .{}); + /// Returns true if the active driver is backed by TargetUartDriver + pub fn holdsAlternative(self: *const Self, comptime TargetUartDriver: type) bool { + inline for (@typeInfo(Driver).@"union".fields) |field| { + if (field.type == TargetUartDriver) { + return std.meta.activeTag(self.variant) == @field(std.meta.Tag(OneDriverVariant()), field.name); + } + } + return false; } - pub fn visitConst(self: *const Self, visitor: anytype) void { - hwreg_internal.visit(visitor, self.configs, .{}); + /// Delegation methods - forward common operations to the active driver + pub fn deinit(self: *Self) void { + const Visitor = struct { + pub fn visitFn(variant: anytype) void { + if (@hasDecl(@TypeOf(variant), "deinit")) { + var mut_variant = variant; + mut_variant.deinit(); + } + } + }; + self.visit(Visitor.visitFn, .{}); } + + // pub fn hardwareInit(self: *Self, comptime LockPolicy: type) void { + // const visitor = struct { + // pub fn visitFn(_: @This(), driver: anytype) void { + // if (@hasDecl(@TypeOf(driver.*), "hardwareInit")) { + // driver.hardwareInit(LockPolicy); + // } + // } + // }{}; + // self.visit(visitor.visitFn, .{visitor}); + // } + + // pub fn write(self: *Self, comptime LockPolicy: type, str: []const u8, waiter_args: anytype) !usize { + // var result: usize = 0; + // self.visit(struct { + // result_ptr: *usize, + // str: []const u8, + // waiter_args: @TypeOf(waiter_args), + + // const VisitorSelf = @This(); + + // pub fn visitFn(visitor_self: VisitorSelf, driver: anytype) void { + // if (@hasDecl(@TypeOf(driver.*), "write")) { + // visitor_self.result_ptr.* = driver.write(LockPolicy, visitor_self.str, visitor_self.waiter_args) catch 0; + // } + // } + // }{ .result_ptr = &result, .str = str, .waiter_args = waiter_args }.visitFn); + // return result; + // } + + // pub fn read(self: *Self, comptime LockPolicy: type) ?u8 { + // var result: ?u8 = null; + // self.visit(struct { + // result_ptr: *?u8, + + // const VisitorSelf = @This(); + + // pub fn visitFn(visitor_self: VisitorSelf, driver: anytype) void { + // if (@hasDecl(@TypeOf(driver.*), "read")) { + // visitor_self.result_ptr.* = driver.read(LockPolicy); + // } + // } + // }{ .result_ptr = &result }.visitFn); + // return result; + // } }; } - diff --git a/slipstream/system/ulib/uart/src/uart.zig b/slipstream/system/ulib/uart/src/uart.zig index c65715e..e80a802 100644 --- a/slipstream/system/ulib/uart/src/uart.zig +++ b/slipstream/system/ulib/uart/src/uart.zig @@ -361,8 +361,8 @@ pub fn KernelDriver(comptime UartDriver: type, comptime IoProviderType: type, co } } - pub fn mmioRange(self: *Self, comptime LockPolicy: type) MmioRange { - var guard = SyncPolicy.Guard(LockPolicy).initWithTag(Lock(Self), &self.lock, @src()); + pub fn mmioRange(self: *const Self, comptime LockPolicy: type) MmioRange { + var guard = SyncPolicy.Guard(LockPolicy).initWithTag(Lock(Self), @constCast(&self.lock), @src()); defer guard.deinit(); if (!MmioDriver(UartDriver)) { @@ -380,8 +380,8 @@ pub fn KernelDriver(comptime UartDriver: type, comptime IoProviderType: type, co } // Returns a copy of the underlying uart config. - pub fn getConfig(self: *Self, comptime LockPolicy: type) ConfigType { - var guard = SyncPolicy.Guard(LockPolicy).initWithTag(Lock(Self), &self.lock, @src()); + pub fn getConfig(self: *const Self, comptime LockPolicy: type) ConfigType { + var guard = SyncPolicy.Guard(LockPolicy).initWithTag(Lock(Self), @constCast(&self.lock), @src()); defer guard.deinit(); return self.uart.getConfig(); diff --git a/slipstream/system/ulib/uart/test/driver_tests.zig b/slipstream/system/ulib/uart/test/driver_tests.zig index 557da92..3da22a5 100644 --- a/slipstream/system/ulib/uart/test/driver_tests.zig +++ b/slipstream/system/ulib/uart/test/driver_tests.zig @@ -184,7 +184,7 @@ test "uart config" { testing.expect(false) catch unreachable; // Unexpected configuration } } - }.visitFn); + }.visitFn, .{}); // From kernel driver const pio_cfg = driver_config.SimplePioConfig{ @@ -197,7 +197,7 @@ test "uart config" { var driver = TestKernelDriver.init(pio_cfg); defer driver.deinit(); - var all_configs = all.Config(all.Driver).initFromKernelDriver(ns8250.PioDriver, mock.IoProvider(ns8250.PioDriver.ConfigType, uart.IoRegisterType.pio), sync.UnsynchronizedPolicy, &driver); + var all_configs = all.Config(all.Driver).initFromUart(ns8250.PioDriver, driver.takeUart(TestKernelDriver.DefaultLockPolicy)); // Test Visit functionality all_configs.visitConst(struct { @@ -211,7 +211,7 @@ test "uart config" { testing.expect(false) catch unreachable; // Unexpected configuration } } - }.visitFn); + }.visitFn, .{}); // Construct from uart. { @@ -236,7 +236,7 @@ test "uart config" { testing.expect(false) catch unreachable; // Unexpected configuration } } - }.visitFn); + }.visitFn, .{}); } // Construct from kernel driver. @@ -265,13 +265,65 @@ test "uart config" { testing.expect(false) catch unreachable; // Unexpected configuration } } - }.visitFn); + }.visitFn, .{}); } } +test "all config" { + const AllDriver = all.KernelDriver(mock.IoProvider(uart.StubConfig, uart.IoRegisterType.none), sync.UnsynchronizedPolicy, all.Driver); + + // Assignment + var all_driver = AllDriver.init(); + defer all_driver.deinit(); + + const pio_cfg = driver_config.SimplePioConfig{ + .base = 0x3f8, + .reserved = 0, + .irq = 3, + }; + + const TestKernelDriver = uart.KernelDriver(ns8250.PioDriver, mock.IoProvider(ns8250.PioDriver.ConfigType, uart.IoRegisterType.pio), sync.UnsynchronizedPolicy); + var driver = TestKernelDriver.init(pio_cfg); + defer driver.deinit(); + + all_driver = AllDriver.initFromUart(all.Driver{ .pio = driver.takeUart(TestKernelDriver.DefaultLockPolicy) }); + const all_config = all_driver.getConfig(); + + all_config.visitConst(struct { + const expected_pio_cfg = pio_cfg; + fn visitFn(config: anytype) void { + const ConfigType = @TypeOf(config); + if (ConfigType == uart.Config(ns8250.PioDriver)) { + testing.expectEqual(@as(u16, expected_pio_cfg.base), config.config.base) catch unreachable; + testing.expectEqual(@as(u32, expected_pio_cfg.irq), config.config.irq) catch unreachable; + } else { + testing.expect(false) catch unreachable; // Unexpected configuration + } + } + }.visitFn, .{}); + + // Constructor + var all_driver2 = all.KernelDriver(mock.IoProvider(uart.StubConfig, uart.IoRegisterType.none), sync.UnsynchronizedPolicy, all.Driver).initFromConfig(all_config); + defer all_driver2.deinit(); + + all_driver2.visitConst(struct { + const expected_pio_cfg = pio_cfg; + fn visitFn(drv: anytype) void { + const DriverType = @TypeOf(drv); + if (DriverType.UartType == ns8250.PioDriver) { + const config = drv.getConfig(DriverType.DefaultLockPolicy); + testing.expectEqual(@as(u16, expected_pio_cfg.base), config.base) catch unreachable; + testing.expectEqual(@as(u32, expected_pio_cfg.irq), config.irq) catch unreachable; + } else { + testing.expect(false) catch unreachable; // Unexpected driver type + } + } + }.visitFn, .{}); +} + test "uart null driver" { const TestDriver = uart.KernelDriver(null_driver.Driver, mock.IoProvider(uart.StubConfig, uart.IoRegisterType.none), sync.UnsynchronizedPolicy); - var driver = TestDriver.init(.{}); + var driver = TestDriver.init(uart.StubConfig{}); defer driver.deinit(); driver.hardwareInit(TestDriver.DefaultLockPolicy); @@ -279,3 +331,48 @@ test "uart null driver" { try testing.expectEqual(@as(usize, 12), driver.write(TestDriver.DefaultLockPolicy, "hello world\n", {})); try testing.expectEqual(@as(?u8, null), driver.read(TestDriver.DefaultLockPolicy)); } + +test "uart all driver" { + const AllConfig = all.Config(all.Driver); + const AllDriver = all.KernelDriver(mock.IoProvider(uart.StubConfig, uart.IoRegisterType.none), sync.UnsynchronizedPolicy, all.Driver); + + // Default constructed configuration should default to being the null_driver.Driver + var driver = AllDriver.initFromConfig(AllConfig.init()); + + // Make sure Unparse is instantiated. + //driver.unparse(); + + // Use selected driver + driver.visit(struct { + fn visitFn(drv: anytype) void { + const DriverType = @TypeOf(drv); + if (DriverType == void) { + testing.expect(false) catch unreachable; // Unexpected driver type + } else { + var mut_drv = drv; + mut_drv.hardwareInit(DriverType.DefaultLockPolicy); + const write_result = mut_drv.write(DriverType.DefaultLockPolicy, "hi!", {}) catch 0; + testing.expectEqual(@as(usize, 3), write_result) catch unreachable; + } + } + }.visitFn, .{}); + + // Transfer state to a new instantiation and pick up using it + var new_driver = AllDriver.initFromUart(driver.takeUart()); + defer new_driver.deinit(); + + new_driver.visit(struct { + fn visitFn(drv: anytype) void { + const DriverType = @TypeOf(drv); + if (DriverType == void) { + testing.expect(false) catch unreachable; // Unexpected driver type + } else { + var mut_drv = drv; + const write_result = mut_drv.write(DriverType.DefaultLockPolicy, "hello world\n", {}) catch 0; + testing.expectEqual(@as(usize, 12), write_result) catch unreachable; + const read_result = mut_drv.read(DriverType.DefaultLockPolicy); + testing.expectEqual(@as(?u8, null), read_result) catch unreachable; + } + } + }.visitFn, .{}); +} From 41e3dcc47800553cbcea152edf15d93a4eaa0bfe Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Fri, 13 Jun 2025 14:54:05 -0300 Subject: [PATCH 28/41] [ulib][mock_function] Abort in case expect fail in verifyAndClear --- slipstream/system/ulib/mock_function/src/mock_function.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slipstream/system/ulib/mock_function/src/mock_function.zig b/slipstream/system/ulib/mock_function/src/mock_function.zig index ce90d3c..d66ac6e 100644 --- a/slipstream/system/ulib/mock_function/src/mock_function.zig +++ b/slipstream/system/ulib/mock_function/src/mock_function.zig @@ -185,7 +185,7 @@ pub fn MockFunction(comptime ReturnType: type, comptime arg_types: []const type) /// Verify that all expectations were met and clear the mock pub fn verifyAndClear(self: *Self) void { - testing.expect(self.expectation_index == self.expectations.items.len) catch {}; + testing.expect(self.expectation_index == self.expectations.items.len) catch unreachable; self.expectations.clearRetainingCapacity(); self.expectation_index = 0; } From 976abae5f930e0a0afcad40ba3a6b1446171afa8 Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Mon, 16 Jun 2025 13:13:54 -0300 Subject: [PATCH 29/41] [kernel][x86] Fix import order/pattern --- slipstream/kernel/arch/x86/IdleStates.zig | 3 ++- slipstream/kernel/arch/x86/faults.zig | 1 + slipstream/kernel/arch/x86/mp.zig | 1 + slipstream/kernel/arch/x86/spin_lock.zig | 3 ++- slipstream/kernel/arch/x86/x86.zig | 1 + 5 files changed, 7 insertions(+), 2 deletions(-) diff --git a/slipstream/kernel/arch/x86/IdleStates.zig b/slipstream/kernel/arch/x86/IdleStates.zig index f19d262..360baf0 100644 --- a/slipstream/kernel/arch/x86/IdleStates.zig +++ b/slipstream/kernel/arch/x86/IdleStates.zig @@ -2,9 +2,10 @@ //! Use of this source code is governed by a BSD-style license that can be //! found in the LICENSE file. -const std = @import("std"); const IdleState = @This(); +const std = @import("std"); + pub const X86_MAX_CSTATES = 12; // A human-readable name for the state diff --git a/slipstream/kernel/arch/x86/faults.zig b/slipstream/kernel/arch/x86/faults.zig index 32d6c80..3294bab 100644 --- a/slipstream/kernel/arch/x86/faults.zig +++ b/slipstream/kernel/arch/x86/faults.zig @@ -3,6 +3,7 @@ //! found in the LICENSE file. const std = @import("std"); + const regs = @import("regs.zig"); const x86 = @import("x86.zig"); diff --git a/slipstream/kernel/arch/x86/mp.zig b/slipstream/kernel/arch/x86/mp.zig index 7f81ad5..0ca3a02 100644 --- a/slipstream/kernel/arch/x86/mp.zig +++ b/slipstream/kernel/arch/x86/mp.zig @@ -3,6 +3,7 @@ //! found in the LICENSE file. const std = @import("std"); + const cpu = @import("../../kernel/cpu.zig"); const Thread = @import("../../kernel/Thread.zig"); const PerCpu = @import("../../kernel/PerCpu.zig"); diff --git a/slipstream/kernel/arch/x86/spin_lock.zig b/slipstream/kernel/arch/x86/spin_lock.zig index 5ea0970..4397c33 100644 --- a/slipstream/kernel/arch/x86/spin_lock.zig +++ b/slipstream/kernel/arch/x86/spin_lock.zig @@ -3,9 +3,10 @@ //! found in the LICENSE file. const std = @import("std"); +const arch = @import("lib/arch").intrin; + const assert = @import("../../kernel/assert.zig"); const mp = @import("mp.zig"); -const arch = @import("arch").intrin; const ArchSpinLock = @import("../../kernel/arch/SpinLock.zig"); inline fn archSpinLockCore(lock: *ArchSpinLock, val: u32) void { diff --git a/slipstream/kernel/arch/x86/x86.zig b/slipstream/kernel/arch/x86/x86.zig index c178bd5..b3074ba 100644 --- a/slipstream/kernel/arch/x86/x86.zig +++ b/slipstream/kernel/arch/x86/x86.zig @@ -3,6 +3,7 @@ //! found in the LICENSE file. const std = @import("std"); + const regs = @import("regs.zig"); pub const registers = @cImport({ From d1661fe38f10247e7fc6ab939ce67d87d879c7cf Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Mon, 16 Jun 2025 13:21:07 -0300 Subject: [PATCH 30/41] [kernel] Fix import order/pattern --- slipstream/kernel/arch/x86/.build.zig | 4 ++-- slipstream/kernel/build.zig | 12 ++++++------ slipstream/kernel/build.zig.zon | 16 ++++++++-------- slipstream/kernel/kernel/PerCpu.zig | 7 +++++-- slipstream/kernel/kernel/Scheduler.zig | 3 ++- slipstream/kernel/kernel/Thread.zig | 10 ++++++---- slipstream/kernel/kernel/arch/SpinLock.zig | 5 ++++- slipstream/kernel/kernel/assert.zig | 4 +++- slipstream/kernel/kernel/cpu.zig | 1 - .../kernel/kernel/platform/boot_timestamps.zig | 2 +- slipstream/kernel/kernel/spin_lock.zig | 1 + slipstream/kernel/kernel/spin_tracing_config.zig | 3 +-- 12 files changed, 39 insertions(+), 29 deletions(-) diff --git a/slipstream/kernel/arch/x86/.build.zig b/slipstream/kernel/arch/x86/.build.zig index 4a50670..60ea6c1 100644 --- a/slipstream/kernel/arch/x86/.build.zig +++ b/slipstream/kernel/arch/x86/.build.zig @@ -3,10 +3,10 @@ //! found in the LICENSE file. const std = @import("std"); +const cfg = @import("drift/build_config"); + const Target = std.Target; const Module = std.Build.Module; - -const cfg = @import("drift/build_config"); const BuildConfig = cfg.BuildConfig; // These set the ABI contract between C++ and assembly code. diff --git a/slipstream/kernel/build.zig b/slipstream/kernel/build.zig index 43f7e5d..f4c22f6 100644 --- a/slipstream/kernel/build.zig +++ b/slipstream/kernel/build.zig @@ -3,12 +3,12 @@ //! found in the LICENSE file. const std = @import("std"); +const cfg = @import("drift/build_config"); + const Build = std.Build; const Target = std.Target; const Module = std.Build.Module; -const cfg = @import("drift/build_config"); - pub fn build(b: *Build) !void { const build_config: *cfg.BuildConfig = try cfg.BuildConfig.init(b); @@ -41,10 +41,10 @@ pub fn build(b: *Build) !void { standalone(build_config, kernel); const deps = [_]struct { name: []const u8, dep_name: []const u8, module_name: []const u8 }{ - .{ .name = "arch", .dep_name = "arch", .module_name = "arch" }, - .{ .name = "dbl", .dep_name = "dbl", .module_name = "dbl" }, - .{ .name = "lazy_init", .dep_name = "lazy_init", .module_name = "lazy_init" }, - .{ .name = "lockdep", .dep_name = "lockdep", .module_name = "lockdep" }, + .{ .name = "lib/arch", .dep_name = "lib/arch", .module_name = "arch" }, + .{ .name = "ulib/dbl", .dep_name = "ulib/dbl", .module_name = "dbl" }, + .{ .name = "ulib/lazy_init", .dep_name = "ulib/lazy_init", .module_name = "lazy_init" }, + .{ .name = "ulib/lockdep", .dep_name = "ulib/lockdep", .module_name = "lockdep" }, }; for (deps) |dep| { diff --git a/slipstream/kernel/build.zig.zon b/slipstream/kernel/build.zig.zon index dad160a..e4795de 100644 --- a/slipstream/kernel/build.zig.zon +++ b/slipstream/kernel/build.zig.zon @@ -7,17 +7,17 @@ .@"drift/build_config" = .{ .path = "../../build", }, - .lazy_init = .{ - .path = "../system/ulib/lazy_init", + .@"lib/arch" = .{ + .path = "../../slipstream/kernel/lib/arch", }, - .dbl = .{ - .path = "../system/ulib/dbl", + .@"ulib/dbl" = .{ + .path = "../../slipstream/system/ulib/dbl", }, - .lockdep = .{ - .path = "../system/ulib/lockdep", + .@"ulib/lazy_init" = .{ + .path = "../../slipstream/system/ulib/lazy_init", }, - .arch = .{ - .path = "lib/arch", + .@"ulib/lockdep" = .{ + .path = "../../slipstream/system/ulib/lockdep", }, }, } diff --git a/slipstream/kernel/kernel/PerCpu.zig b/slipstream/kernel/kernel/PerCpu.zig index fc98938..af905d3 100644 --- a/slipstream/kernel/kernel/PerCpu.zig +++ b/slipstream/kernel/kernel/PerCpu.zig @@ -2,17 +2,20 @@ //! Use of this source code is governed by a BSD-style license that can be //! found in the LICENSE file. +const PerCpu = @This(); + const std = @import("std"); +const lazy_init = @import("ulib/lazy_init"); + const assert = @import("assert.zig"); -const lazy_init = @import("lazy_init"); const Scheduler = @import("Scheduler.zig"); const defines = @import("../arch/x86/defines.zig"); const arch = @import("arch/mp.zig").Impl; const cpu = @import("cpu.zig"); -const PerCpu = @This(); const CpuNum = cpu.CpuNum; +// per cpu scheduler scheduler: Scheduler, // The percpu for the boot processor. diff --git a/slipstream/kernel/kernel/Scheduler.zig b/slipstream/kernel/kernel/Scheduler.zig index 6720d05..cbed8e7 100644 --- a/slipstream/kernel/kernel/Scheduler.zig +++ b/slipstream/kernel/kernel/Scheduler.zig @@ -2,9 +2,10 @@ //! Use of this source code is governed by a BSD-style license that can be //! found in the LICENSE file. -const cpu = @import("cpu.zig"); const Scheduler = @This(); +const cpu = @import("cpu.zig"); + const CpuNum = cpu.CpuNum; // The CPU this scheduler instance is associated with. diff --git a/slipstream/kernel/kernel/Thread.zig b/slipstream/kernel/kernel/Thread.zig index 18393b0..b23387b 100644 --- a/slipstream/kernel/kernel/Thread.zig +++ b/slipstream/kernel/kernel/Thread.zig @@ -2,14 +2,16 @@ //! Use of this source code is governed by a BSD-style license that can be //! found in the LICENSE file. +const Thread = @This(); + const std = @import("std"); -const dbl = @import("dbl"); -const lazy_init = @import("lazy_init"); +const dbl = @import("ulib/dbl"); +const lazy_init = @import("ulib/lazy_init"); + const assert = @import("assert.zig"); -const arch = @import("arch/mp.zig").Impl; const spin_lock = @import("spin_lock.zig"); const PerCpu = @import("PerCpu.zig"); -const Thread = @This(); +const arch = @import("arch/mp.zig").Impl; const SpinLock = spin_lock.SpinLock; const List = dbl.DoublyLinkedList(*Thread); diff --git a/slipstream/kernel/kernel/arch/SpinLock.zig b/slipstream/kernel/kernel/arch/SpinLock.zig index 6571976..88964f6 100644 --- a/slipstream/kernel/kernel/arch/SpinLock.zig +++ b/slipstream/kernel/kernel/arch/SpinLock.zig @@ -2,12 +2,15 @@ //! Use of this source code is governed by a BSD-style license that can be //! found in the LICENSE file. +const SpinLock = @This(); + const std = @import("std"); const builtin = @import("builtin"); + const cpu = @import("../cpu.zig"); const arch = @import("../arch/mp.zig").Impl; const spin_tracing_config = @import("../spin_tracing_config.zig"); -const SpinLock = @This(); + const CpuNum = cpu.CpuNum; diff --git a/slipstream/kernel/kernel/assert.zig b/slipstream/kernel/kernel/assert.zig index 1d36160..e8b8d12 100644 --- a/slipstream/kernel/kernel/assert.zig +++ b/slipstream/kernel/kernel/assert.zig @@ -3,9 +3,11 @@ //! found in the LICENSE file. const std = @import("std"); -const debug = @import("../top/debug.zig"); const builtin = @import("builtin"); +const debug = @import("../top/debug.zig"); + + /// Assert that x is true, else panic pub fn assert(comptime src: std.builtin.SourceLocation, x: bool, comptime expression: []const u8) void { if (!x) { diff --git a/slipstream/kernel/kernel/cpu.zig b/slipstream/kernel/kernel/cpu.zig index 2eeb37c..c806ea8 100644 --- a/slipstream/kernel/kernel/cpu.zig +++ b/slipstream/kernel/kernel/cpu.zig @@ -3,7 +3,6 @@ //! found in the LICENSE file. const std = @import("std"); - const build_options = @import("build_options"); // types and routines for dealing with lists of cpus and cpu masks diff --git a/slipstream/kernel/kernel/platform/boot_timestamps.zig b/slipstream/kernel/kernel/platform/boot_timestamps.zig index 24d10b5..d54f9af 100644 --- a/slipstream/kernel/kernel/platform/boot_timestamps.zig +++ b/slipstream/kernel/kernel/platform/boot_timestamps.zig @@ -2,7 +2,7 @@ //! Use of this source code is governed by a BSD-style license that can be //! found in the LICENSE file. -const arch = @import("arch"); +const arch = @import("lib/arch"); // Samples taken at the first instruction in the kernel. pub export var kernel_entry_ticks: arch.EarlyTicks = undefined; diff --git a/slipstream/kernel/kernel/spin_lock.zig b/slipstream/kernel/kernel/spin_lock.zig index 374eca0..022857e 100644 --- a/slipstream/kernel/kernel/spin_lock.zig +++ b/slipstream/kernel/kernel/spin_lock.zig @@ -3,6 +3,7 @@ //! found in the LICENSE file. const std = @import("std"); + const assert = @import("assert.zig"); const cpu = @import("cpu.zig"); const ArchSpinLock = @import("arch/SpinLock.zig"); diff --git a/slipstream/kernel/kernel/spin_tracing_config.zig b/slipstream/kernel/kernel/spin_tracing_config.zig index 630631e..df432fd 100644 --- a/slipstream/kernel/kernel/spin_tracing_config.zig +++ b/slipstream/kernel/kernel/spin_tracing_config.zig @@ -3,8 +3,7 @@ //! found in the LICENSE file. const std = @import("std"); - -pub const build_options = @import("build_options"); +const build_options = @import("build_options"); pub const scheduler_lock_spin_tracing_enabled = build_options.SCHEDULER_LOCK_SPIN_TRACING_ENABLED; pub const scheduler_lock_spin_tracing_compressed = build_options.SCHEDULER_LOCK_SPIN_TRACING_COMPRESSED; From 4f08d74186450cd8eb3ac49eb14b3e41a4dea6fc Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Mon, 16 Jun 2025 17:07:50 -0300 Subject: [PATCH 31/41] [ulib][hwreg] Fix import order/pattern --- slipstream/system/ulib/hwreg/build.zig | 4 ++-- slipstream/system/ulib/hwreg/build.zig.zon | 8 ++++---- slipstream/system/ulib/hwreg/src/bitfields.zig | 3 ++- slipstream/system/ulib/hwreg/src/mmio.zig | 3 ++- slipstream/system/ulib/hwreg/src/mock.zig | 7 ++++--- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/slipstream/system/ulib/hwreg/build.zig b/slipstream/system/ulib/hwreg/build.zig index 70b0aef..9a3011c 100644 --- a/slipstream/system/ulib/hwreg/build.zig +++ b/slipstream/system/ulib/hwreg/build.zig @@ -16,8 +16,8 @@ pub fn build(b: *std.Build) void { const deps = [_]struct { name: []const u8, dep_name: []const u8, module_name: []const u8 }{ .{ .name = "public", .dep_name = "public", .module_name = "public" }, - .{ .name = "mock_function", .dep_name = "mock_function", .module_name = "mock_function" }, - .{ .name = "mmio-ptr", .dep_name = "mmio-ptr", .module_name = "mmio-ptr" }, + .{ .name = "ulib/mock_function", .dep_name = "ulib/mock_function", .module_name = "mock_function" }, + .{ .name = "ulib/mmio-ptr", .dep_name = "ulib/mmio-ptr", .module_name = "mmio-ptr" }, }; for (deps) |dep| { diff --git a/slipstream/system/ulib/hwreg/build.zig.zon b/slipstream/system/ulib/hwreg/build.zig.zon index c67e3ae..9357ff7 100644 --- a/slipstream/system/ulib/hwreg/build.zig.zon +++ b/slipstream/system/ulib/hwreg/build.zig.zon @@ -7,11 +7,11 @@ .public = .{ .path = "../../public", }, - .mock_function = .{ - .path = "../mock_function", - }, - .@"mmio-ptr" = .{ + .@"ulib/mmio-ptr" = .{ .path = "../mmio-ptr", }, + .@"ulib/mock_function" = .{ + .path = "../mock_function", + }, }, } diff --git a/slipstream/system/ulib/hwreg/src/bitfields.zig b/slipstream/system/ulib/hwreg/src/bitfields.zig index 5bc7427..fffc8ef 100644 --- a/slipstream/system/ulib/hwreg/src/bitfields.zig +++ b/slipstream/system/ulib/hwreg/src/bitfields.zig @@ -3,11 +3,12 @@ //! found in the LICENSE file. const std = @import("std"); + +const Mock = @import("mock.zig"); const internal = @import("internal.zig"); const mmio = @import("mmio.zig"); const testing = std.testing; -const Mock = @import("mock.zig"); /// Tag that can be passed as the third template parameter for RegisterBase to enable /// the pretty-printing interfaces on a register. diff --git a/slipstream/system/ulib/hwreg/src/mmio.zig b/slipstream/system/ulib/hwreg/src/mmio.zig index 7ce6f44..b45b4e6 100644 --- a/slipstream/system/ulib/hwreg/src/mmio.zig +++ b/slipstream/system/ulib/hwreg/src/mmio.zig @@ -3,8 +3,9 @@ //! found in the LICENSE file. const std = @import("std"); +const mmio_ptr = @import("ulib/mmio-ptr"); + const internal = @import("internal.zig"); -const mmio_ptr = @import("mmio-ptr"); /// This can be passed to readFrom and writeTo methods. The RegisterAddr object holds an offset from /// an MMIO base address stored in this object. diff --git a/slipstream/system/ulib/hwreg/src/mock.zig b/slipstream/system/ulib/hwreg/src/mock.zig index 4c4eca0..f5dbd5d 100644 --- a/slipstream/system/ulib/hwreg/src/mock.zig +++ b/slipstream/system/ulib/hwreg/src/mock.zig @@ -2,11 +2,12 @@ //! Use of this source code is governed by a BSD-style license that can be //! found in the LICENSE file. +const Mock = @This(); + const std = @import("std"); -const internal = @import("internal.zig"); -const mock_function = @import("mock_function"); +const mock_function = @import("ulib/mock_function"); -const Mock = @This(); +const internal = @import("internal.zig"); // The io() pointer can be passed to ReadFrom and WriteTo methods. The // Mock should first be primed with expectRead() and expectWrite() calls. From 58ad180e54054cf6db8ce3de551211ea55685ee5 Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Mon, 16 Jun 2025 17:13:10 -0300 Subject: [PATCH 32/41] [ulib][uart] Fix import order/pattern --- slipstream/system/ulib/uart/build.zig | 8 ++++---- slipstream/system/ulib/uart/build.zig.zon | 8 ++++---- slipstream/system/ulib/uart/src/all.zig | 6 ++++-- slipstream/system/ulib/uart/src/mock.zig | 5 +++-- slipstream/system/ulib/uart/src/ns8250.zig | 8 +++++--- slipstream/system/ulib/uart/src/null.zig | 3 ++- slipstream/system/ulib/uart/src/parse.zig | 2 +- slipstream/system/ulib/uart/src/pl011.zig | 4 ++-- slipstream/system/ulib/uart/src/sync.zig | 6 ++++-- slipstream/system/ulib/uart/src/uart.zig | 3 ++- slipstream/system/ulib/uart/test/driver_tests.zig | 3 ++- slipstream/system/ulib/uart/test/parsing_tests.zig | 1 + 12 files changed, 34 insertions(+), 23 deletions(-) diff --git a/slipstream/system/ulib/uart/build.zig b/slipstream/system/ulib/uart/build.zig index 8381cad..51f7f60 100644 --- a/slipstream/system/ulib/uart/build.zig +++ b/slipstream/system/ulib/uart/build.zig @@ -15,10 +15,10 @@ pub fn build(b: *std.Build) void { }); const deps = [_]struct { name: []const u8, dep_name: []const u8, module_name: []const u8 }{ - .{ .name = "arch", .dep_name = "arch", .module_name = "arch" }, - .{ .name = "zbi_format", .dep_name = "zbi_format", .module_name = "zbi_format" }, - .{ .name = "hwreg", .dep_name = "hwreg", .module_name = "hwreg" }, - .{ .name = "mock_function", .dep_name = "mock_function", .module_name = "mock_function" }, + .{ .name = "lib/arch", .dep_name = "lib/arch", .module_name = "arch" }, + .{ .name = "sdk/zbi_format", .dep_name = "sdk/zbi_format", .module_name = "zbi_format" }, + .{ .name = "ulib/hwreg", .dep_name = "ulib/hwreg", .module_name = "hwreg" }, + .{ .name = "ulib/mock_function", .dep_name = "ulib/mock_function", .module_name = "mock_function" }, }; for (deps) |dep| { diff --git a/slipstream/system/ulib/uart/build.zig.zon b/slipstream/system/ulib/uart/build.zig.zon index 9627362..4bb4a8b 100644 --- a/slipstream/system/ulib/uart/build.zig.zon +++ b/slipstream/system/ulib/uart/build.zig.zon @@ -4,16 +4,16 @@ .version = "0.0.1", .paths = .{""}, .dependencies = .{ - .arch = .{ + .@"lib/arch" = .{ .path = "../../../kernel/lib/arch", }, - .zbi_format = .{ + .@"sdk/zbi_format" = .{ .path = "../../../../sdk/lib/zbi-format", }, - .mock_function = .{ + .@"ulib/mock_function" = .{ .path = "../mock_function", }, - .hwreg = .{ + .@"ulib/hwreg" = .{ .path = "../hwreg", }, }, diff --git a/slipstream/system/ulib/uart/src/all.zig b/slipstream/system/ulib/uart/src/all.zig index 5c57966..037517a 100644 --- a/slipstream/system/ulib/uart/src/all.zig +++ b/slipstream/system/ulib/uart/src/all.zig @@ -2,17 +2,19 @@ //! Use of this source code is governed by a BSD-style license that can be //! found in the LICENSE file. -const builtin = @import("builtin"); const std = @import("std"); +const builtin = @import("builtin"); +const hwreg = @import("ulib/hwreg"); + const uart = @import("uart.zig"); const ns8250 = @import("ns8250.zig"); const pl011 = @import("pl011.zig"); const null_driver = @import("null.zig"); const mock = @import("mock.zig"); const sync = @import("sync.zig"); -const hwreg_internal = @import("hwreg").internal; const testing = std.testing; +const hwreg_internal = hwreg.internal; /// A dummy driver type that should never be instantiated or called. /// This exists to allow trailing commas in driver lists and provides diff --git a/slipstream/system/ulib/uart/src/mock.zig b/slipstream/system/ulib/uart/src/mock.zig index 1a66142..f703493 100644 --- a/slipstream/system/ulib/uart/src/mock.zig +++ b/slipstream/system/ulib/uart/src/mock.zig @@ -3,9 +3,10 @@ //! found in the LICENSE file. const std = @import("std"); +const hwreg = @import("ulib/hwreg"); +const mock_function = @import("ulib/mock_function"); + const uart = @import("uart.zig"); -const hwreg = @import("hwreg"); -const mock_function = @import("mock_function"); // uart::mock::IoProvider supports testing uart::xyz::Driver hardware drivers. // uart::mock::Driver supports testing uart::KernelDriver itself. diff --git a/slipstream/system/ulib/uart/src/ns8250.zig b/slipstream/system/ulib/uart/src/ns8250.zig index 6cce8af..df87f7e 100644 --- a/slipstream/system/ulib/uart/src/ns8250.zig +++ b/slipstream/system/ulib/uart/src/ns8250.zig @@ -3,11 +3,13 @@ //! found in the LICENSE file. const std = @import("std"); -const zbi_format = @import("zbi_format"); -const uart = @import("uart.zig"); -const hwreg = @import("hwreg"); + +const hwreg = @import("ulib/hwreg"); +const zbi_format = @import("sdk/zbi_format"); + const mock = @import("mock.zig"); const sync = @import("sync.zig"); +const uart = @import("uart.zig"); const testing = std.testing; const driver_config = zbi_format.driver_config; diff --git a/slipstream/system/ulib/uart/src/null.zig b/slipstream/system/ulib/uart/src/null.zig index d7d7fd4..b9d9e77 100644 --- a/slipstream/system/ulib/uart/src/null.zig +++ b/slipstream/system/ulib/uart/src/null.zig @@ -3,8 +3,9 @@ //! found in the LICENSE file. const std = @import("std"); +const zbi_format = @import("sdk/zbi_format"); + const uart = @import("uart.zig"); -const zbi_format = @import("zbi_format"); const driver_config = zbi_format.driver_config; diff --git a/slipstream/system/ulib/uart/src/parse.zig b/slipstream/system/ulib/uart/src/parse.zig index 7c3629f..60e2c7d 100644 --- a/slipstream/system/ulib/uart/src/parse.zig +++ b/slipstream/system/ulib/uart/src/parse.zig @@ -3,7 +3,7 @@ //! found in the LICENSE file. const std = @import("std"); -const zbi_format = @import("zbi_format"); +const zbi_format = @import("sdk/zbi_format"); /// Parse a comma-separated list of integers of the form `,1,2,3,...,1000`, /// where the list must begin with a comma. diff --git a/slipstream/system/ulib/uart/src/pl011.zig b/slipstream/system/ulib/uart/src/pl011.zig index 150479c..47a3953 100644 --- a/slipstream/system/ulib/uart/src/pl011.zig +++ b/slipstream/system/ulib/uart/src/pl011.zig @@ -6,8 +6,8 @@ //! URL: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0183g/index.html const std = @import("std"); -const zbi_format = @import("zbi_format"); -const hwreg = @import("hwreg"); +const zbi_format = @import("sdk/zbi_format"); +const hwreg = @import("ulib/hwreg"); const uart = @import("uart.zig"); const uart_interrupt = @import("interrupt.zig"); diff --git a/slipstream/system/ulib/uart/src/sync.zig b/slipstream/system/ulib/uart/src/sync.zig index 121237b..12d8582 100644 --- a/slipstream/system/ulib/uart/src/sync.zig +++ b/slipstream/system/ulib/uart/src/sync.zig @@ -3,7 +3,9 @@ //! found in the LICENSE file. const std = @import("std"); -const arch = @import("arch").intrin; +const lib_arch = @import("lib/arch"); + +const Arch = lib_arch.intrin; /// Degenerate case of unsynchronized policy, that provides the expected API to be implemented when /// another policy needs to be used. @@ -59,7 +61,7 @@ pub const UnsynchronizedPolicy = struct { _ = guard; _ = enableTxInterrupt; _ = args; - arch.yield(); + Arch.yield(); } }; diff --git a/slipstream/system/ulib/uart/src/uart.zig b/slipstream/system/ulib/uart/src/uart.zig index e80a802..707a337 100644 --- a/slipstream/system/ulib/uart/src/uart.zig +++ b/slipstream/system/ulib/uart/src/uart.zig @@ -3,7 +3,8 @@ //! found in the LICENSE file. const std = @import("std"); -const zbi_format = @import("zbi_format"); +const zbi_format = @import("sdk/zbi_format"); + const parse = @import("parse.zig"); const chars_from = @import("chars_from.zig"); const mock = @import("mock.zig"); diff --git a/slipstream/system/ulib/uart/test/driver_tests.zig b/slipstream/system/ulib/uart/test/driver_tests.zig index 3da22a5..98b60f1 100644 --- a/slipstream/system/ulib/uart/test/driver_tests.zig +++ b/slipstream/system/ulib/uart/test/driver_tests.zig @@ -3,9 +3,10 @@ //! found in the LICENSE file. const std = @import("std"); +const zbi_format = @import("sdk/zbi_format"); + const uart = @import("../src/uart.zig"); const ns8250 = @import("../src/ns8250.zig"); -const zbi_format = @import("zbi_format"); const null_driver = @import("../src/null.zig"); const mock = @import("../src/mock.zig"); const sync = @import("../src/sync.zig"); diff --git a/slipstream/system/ulib/uart/test/parsing_tests.zig b/slipstream/system/ulib/uart/test/parsing_tests.zig index fafb786..08ea326 100644 --- a/slipstream/system/ulib/uart/test/parsing_tests.zig +++ b/slipstream/system/ulib/uart/test/parsing_tests.zig @@ -3,6 +3,7 @@ //! found in the LICENSE file. const std = @import("std"); + const uart = @import("../src/uart.zig"); const ns8250 = @import("../src/ns8250.zig"); const parse = @import("../src/parse.zig"); From 437b330dc104ab76cd87d1439ea31bf94abbe41c Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Wed, 18 Jun 2025 11:14:55 -0300 Subject: [PATCH 33/41] [ulib][hwlib] Rename mock.zig to Mock.zig --- slipstream/system/ulib/hwreg/src/{mock.zig => Mock.zig} | 0 slipstream/system/ulib/hwreg/src/bitfields.zig | 2 +- slipstream/system/ulib/hwreg/src/root.zig | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename slipstream/system/ulib/hwreg/src/{mock.zig => Mock.zig} (100%) diff --git a/slipstream/system/ulib/hwreg/src/mock.zig b/slipstream/system/ulib/hwreg/src/Mock.zig similarity index 100% rename from slipstream/system/ulib/hwreg/src/mock.zig rename to slipstream/system/ulib/hwreg/src/Mock.zig diff --git a/slipstream/system/ulib/hwreg/src/bitfields.zig b/slipstream/system/ulib/hwreg/src/bitfields.zig index fffc8ef..c331cb3 100644 --- a/slipstream/system/ulib/hwreg/src/bitfields.zig +++ b/slipstream/system/ulib/hwreg/src/bitfields.zig @@ -4,7 +4,7 @@ const std = @import("std"); -const Mock = @import("mock.zig"); +const Mock = @import("Mock.zig"); const internal = @import("internal.zig"); const mmio = @import("mmio.zig"); diff --git a/slipstream/system/ulib/hwreg/src/root.zig b/slipstream/system/ulib/hwreg/src/root.zig index a1c15aa..1a77d61 100644 --- a/slipstream/system/ulib/hwreg/src/root.zig +++ b/slipstream/system/ulib/hwreg/src/root.zig @@ -4,7 +4,7 @@ pub const bitfields = @import("bitfields.zig"); pub const internal = @import("internal.zig"); -pub const Mock = @import("mock.zig"); +pub const Mock = @import("Mock.zig"); comptime { _ = bitfields; From ccb54566cf992a9b6c6dff74cf961fd0fc3b3c34 Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Wed, 18 Jun 2025 11:16:16 -0300 Subject: [PATCH 34/41] [ulib][hwreg] Fix dependencies paths --- slipstream/system/ulib/hwreg/build.zig | 2 +- slipstream/system/ulib/hwreg/build.zig.zon | 8 ++++---- slipstream/system/ulib/hwreg/src/bitfields.zig | 4 +++- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/slipstream/system/ulib/hwreg/build.zig b/slipstream/system/ulib/hwreg/build.zig index 9a3011c..256cea4 100644 --- a/slipstream/system/ulib/hwreg/build.zig +++ b/slipstream/system/ulib/hwreg/build.zig @@ -15,7 +15,7 @@ pub fn build(b: *std.Build) void { }); const deps = [_]struct { name: []const u8, dep_name: []const u8, module_name: []const u8 }{ - .{ .name = "public", .dep_name = "public", .module_name = "public" }, + .{ .name = "slipstream/public", .dep_name = "slipstream/public", .module_name = "public" }, .{ .name = "ulib/mock_function", .dep_name = "ulib/mock_function", .module_name = "mock_function" }, .{ .name = "ulib/mmio-ptr", .dep_name = "ulib/mmio-ptr", .module_name = "mmio-ptr" }, }; diff --git a/slipstream/system/ulib/hwreg/build.zig.zon b/slipstream/system/ulib/hwreg/build.zig.zon index 9357ff7..4a2d23c 100644 --- a/slipstream/system/ulib/hwreg/build.zig.zon +++ b/slipstream/system/ulib/hwreg/build.zig.zon @@ -4,14 +4,14 @@ .version = "0.0.1", .paths = .{""}, .dependencies = .{ - .public = .{ - .path = "../../public", + .@"slipstream/public" = .{ + .path = "../../../../slipstream/system/public", }, .@"ulib/mmio-ptr" = .{ - .path = "../mmio-ptr", + .path = "../../../../slipstream/system/ulib/mmio-ptr", }, .@"ulib/mock_function" = .{ - .path = "../mock_function", + .path = "../../../../slipstream/system/ulib/mock_function", }, }, } diff --git a/slipstream/system/ulib/hwreg/src/bitfields.zig b/slipstream/system/ulib/hwreg/src/bitfields.zig index c331cb3..f694484 100644 --- a/slipstream/system/ulib/hwreg/src/bitfields.zig +++ b/slipstream/system/ulib/hwreg/src/bitfields.zig @@ -3,12 +3,14 @@ //! found in the LICENSE file. const std = @import("std"); +const slipstream = @import("slipstream/public"); const Mock = @import("Mock.zig"); const internal = @import("internal.zig"); const mmio = @import("mmio.zig"); const testing = std.testing; +const slipstreamAssert = slipstream.assert.slipstreamAssert; /// Tag that can be passed as the third template parameter for RegisterBase to enable /// the pretty-printing interfaces on a register. @@ -2832,7 +2834,7 @@ test "Variant" { pub fn write(self: @This(), comptime IntType: type, value: IntType, offset: u32) void { _ = self; _ = offset; - std.debug.assert(value == 17); + slipstreamAssert(@src(), value == 17, "value == 17"); } pub fn read(self: @This(), comptime IntType: type, offset: u32) IntType { From d47575af6f6dee961ee222c7454e571a576f7160 Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Wed, 18 Jun 2025 11:17:57 -0300 Subject: [PATCH 35/41] [ulib][hwreg] Fix FieldPrinter printer --- .../system/ulib/hwreg/src/bitfields.zig | 58 ++++++++++--------- slipstream/system/ulib/hwreg/src/internal.zig | 42 ++++++++------ 2 files changed, 54 insertions(+), 46 deletions(-) diff --git a/slipstream/system/ulib/hwreg/src/bitfields.zig b/slipstream/system/ulib/hwreg/src/bitfields.zig index f694484..d4c2644 100644 --- a/slipstream/system/ulib/hwreg/src/bitfields.zig +++ b/slipstream/system/ulib/hwreg/src/bitfields.zig @@ -2789,43 +2789,47 @@ test "Print" { { var reg = PrintableTestReg.get().readFrom(&mmio_io); - //var call_count: u32 = 0; - const expected = [_][]const u8{ - "RsvdZ[31:31]: 0x1 (1)", - "field1[30:21]: 0x34c (844)", - "field2[20:12]: 0x072 (114)", - "RsvdZ[11:0]: 0xfff (4095)", - }; - - reg.print(struct { + var call_count: u32 = 0; + + const Printer = struct { + var call_count_ptr: *u32 = undefined; + const expected = [_][]const u8{ + "RsvdZ[31:31]: 0x1 (1)", + "field1[30:21]: 0x34c (844)", + "field2[20:12]: 0x072 (114)", + "RsvdZ[11:0]: 0xfff (4095)", + }; fn printFn(arg: []const u8) void { - // In a real test, we would check against expected[call_count] - std.debug.print("PrintableTestReg: {s}\n", .{arg}); + testing.expectEqualStrings(expected[call_count_ptr.*], arg) catch unreachable; + call_count_ptr.* += 1; } - }.printFn); + }; + Printer.call_count_ptr = &call_count; + reg.print(Printer.printFn); - //_ = call_count; - _ = expected; + try testing.expectEqual(Printer.expected.len, call_count); } { var reg = PrintableTestReg2.get().readFrom(&mmio_io); - //var call_count: u32 = 0; - const expected = [_][]const u8{ - "field1[30:21]: 0x34c (844)", - "field2[20:12]: 0x072 (114)", - "unknown set bits: 0x80000fff", - }; - - reg.print(struct { + var call_count: u32 = 0; + + const Printer = struct { + var call_count_ptr: *u32 = undefined; + const expected = [_][]const u8{ + "field1[30:21]: 0x34c (844)", + "field2[20:12]: 0x072 (114)", + "unknown set bits: 0x80000fff", + }; fn printFn(arg: []const u8) void { - // In a real test, we would check against expected[call_count] - std.debug.print("PrintableTestReg2: {s}\n", .{arg}); + testing.expectEqualStrings(expected[call_count_ptr.*], arg) catch unreachable; + call_count_ptr.* += 1; } - }.printFn); + }; + Printer.call_count_ptr = &call_count; + reg.print(Printer.printFn); - // _ = call_count; - _ = expected; + try testing.expectEqual(Printer.expected.len, call_count); } } diff --git a/slipstream/system/ulib/hwreg/src/internal.zig b/slipstream/system/ulib/hwreg/src/internal.zig index a3136d3..d7a70a9 100644 --- a/slipstream/system/ulib/hwreg/src/internal.zig +++ b/slipstream/system/ulib/hwreg/src/internal.zig @@ -43,13 +43,13 @@ pub fn unexpandedPred(comptime pred: bool, comptime id: comptime_int) bool { /// Field printer for debug output pub const FieldPrinter = struct { - name: ?[]const u8, + name: []const u8, bit_high_incl: u32, bit_low: u32, pub fn init() FieldPrinter { return FieldPrinter{ - .name = null, + .name = "", .bit_high_incl = 0, .bit_low = 0, }; @@ -63,21 +63,22 @@ pub const FieldPrinter = struct { }; } - /// Print the field name and extracted value in hex format + // Prints the field name, and the result of extracting the field from |value| in + // hex (with a left-padding of zeroes to a length matching the maximum number of + // nibbles needed to represent any value the field could take). pub fn print(self: FieldPrinter, value: u64, buf: []u8) []u8 { - if (self.name) |name| { - const field_value = (value >> @intCast(self.bit_low)) & computeMask(u64, self.bit_high_incl - self.bit_low + 1); - - //if (is_kernel) { - // Kernel version without padding - // _ = std.fmt.bufPrint(buf, "{s}[{d}:{d}]: 0x{x} ({d})", .{ name_, bit_high_incl_, bit_low_, val, val }) catch unreachable; - //} else { - // User space version with padding - const pad_len = (self.bit_high_incl - self.bit_low + 3) / 4; - return std.fmt.bufPrint(buf, "{s}[{d}:{d}]: 0x{x:0>[4]} ({[3]d})", .{ name, self.bit_high_incl, self.bit_low, field_value, pad_len }) catch unreachable; - //} - } - return buf; + const num_bits = self.bit_high_incl - self.bit_low + 1; + const mask = computeMask(u64, num_bits); + const field_value = (value >> @intCast(self.bit_low)) & mask; + + //if (is_kernel) { + // Kernel version without padding + // _ = std.fmt.bufPrint(buf, "{s}[{d}:{d}]: 0x{x} ({d})", .{ name_, bit_high_incl_, bit_low_, val, val }) catch unreachable; + //} else { + // User space version with padding + const pad_len = (num_bits + 3) / 4; + return std.fmt.bufPrint(buf, "{s}[{d}:{d}]: 0x{x:0>[4]} ({[3]d})", .{ self.name, self.bit_high_incl, self.bit_low, field_value, pad_len }) catch unreachable; + //} } }; @@ -148,7 +149,10 @@ pub fn RsvdZField(comptime RegType: type, comptime UnusedMarker: type, comptime }; } -/// Print register fields for debugging +// Implementation for RegisterBase::Print, see the documentation there. +// |reg_value| is the current value of the register. +// |fields_mask| is a bitmask with a bit set for each bit that has been defined +// in the register. pub fn printRegister(print_fn: anytype, fields: []FieldPrinter, num_fields: usize, reg_value: u64, fields_mask: u64, register_width_bytes: i32) void { var buf: [128]u8 = undefined; @@ -157,11 +161,11 @@ pub fn printRegister(print_fn: anytype, fields: []FieldPrinter, num_fields: usiz print_fn(fmt_buf); } - // Check for unknown bits + // Check if any unknown bits are set, and if so let the caller know const val = reg_value & ~fields_mask; if (val != 0) { const pad_len: usize = @intCast(@divExact(register_width_bytes * 8, 4)); - const fmt_buf = std.fmt.bufPrint(&buf, "unknown set bits: 0x{X:0>[1]}", .{ val, pad_len }) catch unreachable; + const fmt_buf = std.fmt.bufPrint(&buf, "unknown set bits: 0x{x:0>[1]}", .{ val, pad_len }) catch unreachable; print_fn(fmt_buf); } } From b7aa248824fa909c84ff2f4e9728fc1d9cfadcea Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Wed, 18 Jun 2025 11:50:36 -0300 Subject: [PATCH 36/41] [ulib][hwreg] Minor tweaks --- slipstream/system/ulib/hwreg/src/bitfields.zig | 7 +++---- slipstream/system/ulib/hwreg/src/internal.zig | 9 ++++++--- slipstream/system/ulib/hwreg/src/mmio.zig | 12 +++--------- slipstream/system/ulib/hwreg/src/root.zig | 4 ++++ 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/slipstream/system/ulib/hwreg/src/bitfields.zig b/slipstream/system/ulib/hwreg/src/bitfields.zig index d4c2644..01bea6f 100644 --- a/slipstream/system/ulib/hwreg/src/bitfields.zig +++ b/slipstream/system/ulib/hwreg/src/bitfields.zig @@ -2583,15 +2583,14 @@ fn TemplatedReg(comptime N: u32) type { test "Templated" { const TestMmio = struct { + const Self = @This(); fake_reg: u32, - pub fn read(self: *const @This(), comptime T: type, addr: u32) T { - _ = addr; + pub fn read(self: *const Self, comptime T: type, _: u32) T { return @intCast(self.fake_reg); } - pub fn write(self: *@This(), comptime T: type, value: T, addr: u32) void { - _ = addr; + pub fn write(self: *Self, comptime T: type, value: T, _: u32) void { self.fake_reg = @intCast(value); } }; diff --git a/slipstream/system/ulib/hwreg/src/internal.zig b/slipstream/system/ulib/hwreg/src/internal.zig index d7a70a9..841ac67 100644 --- a/slipstream/system/ulib/hwreg/src/internal.zig +++ b/slipstream/system/ulib/hwreg/src/internal.zig @@ -3,6 +3,9 @@ //! found in the LICENSE file. const std = @import("std"); +const slipstream = @import("slipstream/public"); + +const slipstreamAssert = slipstream.assert.slipstreamAssert; // Internal fn for checking the type and range of a subfield definition. pub fn subFieldCheck(comptime FieldType: type, comptime bit_high: u32, comptime bit_low: u32) void { @@ -193,16 +196,16 @@ fn visitEach(comptime f: anytype, v: anytype, args: anytype, comptime indices: [ const V = @TypeOf(v); comptime std.debug.assert(indices.len == @typeInfo(V).@"union".fields.len); + var visited_one = false; inline for (indices) |i| { const field = @typeInfo(V).@"union".fields[i]; if (@as(usize, @intFromEnum(v)) == i) { const selected = @field(v, field.name); @call(.auto, visit, .{ f, selected, args }); - return; + visited_one = true; } } - - unreachable; + slipstreamAssert(@src(), visited_one, "visited_one"); } /// Main visit function that handles both variant and non-variant types diff --git a/slipstream/system/ulib/hwreg/src/mmio.zig b/slipstream/system/ulib/hwreg/src/mmio.zig index b45b4e6..bfc2bf1 100644 --- a/slipstream/system/ulib/hwreg/src/mmio.zig +++ b/slipstream/system/ulib/hwreg/src/mmio.zig @@ -18,12 +18,6 @@ const internal = @import("internal.zig"); /// performed; that is no casting is performed between types and no scaling is applied to the /// offsets. pub fn RegisterMmioScaled(comptime ForcedAccessType: type) type { - comptime { - if (ForcedAccessType != void and !internal.isSupportedInt(ForcedAccessType)) { - @compileError("Unsupported type."); - } - } - return struct { const Self = @This(); @@ -35,7 +29,7 @@ pub fn RegisterMmioScaled(comptime ForcedAccessType: type) type { /// Write |val| to the |@sizeOf(IntType)| byte field located |offset| bytes from /// |base()|. - pub fn write(self: Self, comptime IntType: type, val: IntType, offset: u32) void { + pub fn write(self: *const Self, comptime IntType: type, val: IntType, offset: u32) void { const IoTypeForInt = IoType(IntType); comptime std.debug.assert(@sizeOf(IntType) <= @sizeOf(IoTypeForInt)); @@ -59,7 +53,7 @@ pub fn RegisterMmioScaled(comptime ForcedAccessType: type) type { /// Read the value of the |@sizeOf(IntType)| byte field located |offset| bytes from /// |base()|. - pub fn read(self: Self, comptime IntType: type, offset: u32) IntType { + pub fn read(self: *const Self, comptime IntType: type, offset: u32) IntType { const IoTypeForInt = IoType(IntType); comptime std.debug.assert(@sizeOf(IntType) <= @sizeOf(IoTypeForInt)); @@ -82,7 +76,7 @@ pub fn RegisterMmioScaled(comptime ForcedAccessType: type) type { } } - pub fn base(self: Self) usize { + pub fn base(self: *Self) usize { return @intFromPtr(self.mmio); } diff --git a/slipstream/system/ulib/hwreg/src/root.zig b/slipstream/system/ulib/hwreg/src/root.zig index 1a77d61..af803c8 100644 --- a/slipstream/system/ulib/hwreg/src/root.zig +++ b/slipstream/system/ulib/hwreg/src/root.zig @@ -4,8 +4,12 @@ pub const bitfields = @import("bitfields.zig"); pub const internal = @import("internal.zig"); +pub const mmio = @import("mmio.zig"); pub const Mock = @import("Mock.zig"); comptime { _ = bitfields; + _ = internal; + _ = mmio; + _ = Mock; } From a79b05c50bbe4c2f0662097fe456e5d110f2c061 Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Wed, 18 Jun 2025 11:51:05 -0300 Subject: [PATCH 37/41] [ulib][hwreg] Add pio support --- slipstream/system/ulib/hwreg/src/pio.zig | 151 ++++++++++++++++++++++ slipstream/system/ulib/hwreg/src/root.zig | 2 + 2 files changed, 153 insertions(+) create mode 100644 slipstream/system/ulib/hwreg/src/pio.zig diff --git a/slipstream/system/ulib/hwreg/src/pio.zig b/slipstream/system/ulib/hwreg/src/pio.zig new file mode 100644 index 0000000..67d3f59 --- /dev/null +++ b/slipstream/system/ulib/hwreg/src/pio.zig @@ -0,0 +1,151 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. + +const std = @import("std"); +const builtin = @import("builtin"); +const internal = @import("internal.zig"); +const mmio = @import("mmio.zig"); + +/// This is used for PIO via MMIO, where a 1-byte port offset is scaled +/// to correspond to a 4-byte MMIO address. +pub const RegisterMmioPio = mmio.RegisterMmioScaled(u32); + +/// This can be passed to readFrom and writeTo methods. The RegisterAddr +/// object passes u32 even though port addresses are only 16 bits. +/// This can either be used default-constructed where the RegisterAddr +/// contains the full I/O port address, or be constructed with a base +/// port address that is added to the value stored in the RegisterAddr. +pub const RegisterDirectPioX86 = struct { + const Self = @This(); + + base: u16 = 0, + + pub fn init() Self { + return Self{}; + } + + pub fn initWithBase(base: u16) Self { + return Self{ .base = base }; + } + + pub fn write(self: *Self, comptime IntType: type, value: IntType, port: u32) void { + comptime { + if (!internal.isSupportedInt(IntType)) { + @compileError("unsupported register access width"); + } + } + + if (comptime @sizeOf(IntType) == @sizeOf(u64)) { + // For 64-bit values, split into two 32-bit writes + self.write(u32, @as(u32, @truncate(value)), port); + self.write(u32, @as(u32, @truncate(value >> 32)), port + 1); + return; + } + + const p = self.adjustPort(port); + + switch (builtin.cpu.arch) { + .x86_64, .x86 => { + // The "a" constraint means the A register, so %al, %ax, or %eax, + // depending on the type of the value. The "N" constraint means an + // 8-bit immediate, so use that if it's constant and fits; otherwise + // the "d" constraint means the D register, i.e. %dx for u16. + asm volatile ("out %[v], %[p]" + : + : [v] "{al}" (value), + [p] "N{dx}" (p), + ); + }, + else => @compileError("Direct PIO is only supported on x86/x86_64"), + } + } + + pub fn read(self: *const Self, comptime IntType: type, port: u32) IntType { + comptime { + if (!internal.isSupportedInt(IntType)) { + @compileError("unsupported register access width"); + } + } + + if (comptime @sizeOf(IntType) == @sizeOf(u64)) { + // For 64-bit values, combine two 32-bit reads + const lo = self.read(u32, port); + const hi = self.read(u32, port + 1); + return (@as(IntType, hi) << 32) | @as(IntType, lo); + } + + const p = self.adjustPort(port); + var value: IntType = undefined; + + switch (builtin.cpu.arch) { + .x86_64, .x86 => { + // Same operands as above, except "=a" for output to the A register, + // and the opposite order in the assembly syntax. + asm volatile ("in %[p], %[v]" + : [v] "={al}" (value), + : [p] "N{dx}" (p), + ); + }, + else => @compileError("Direct PIO is only supported on x86/x86_64"), + } + + return value; + } + + fn adjustPort(self: Self, offset: u32) u16 { + const p = @as(u16, @truncate(offset)); + std.debug.assert(p == offset); // Ensure no truncation occurred + const adjusted = @as(u16, @truncate(self.base +% p)); + std.debug.assert(adjusted == self.base +% p); // Ensure no truncation occurred + return adjusted; + } +}; + +/// This can be default-constructed or constructed with a u16 argument to +/// do direct PIO; or constructed with a pointer argument to do PIO via MMIO, +/// where a 1-byte port offset is scaled to correspond to a 4-byte MMIO address. +pub const RegisterPio = union(enum) { + direct: RegisterDirectPioX86, + mmio: RegisterMmioPio, + + pub fn init() RegisterPio { + return RegisterPio{ .direct = RegisterDirectPioX86.init() }; + } + + pub fn initWithBase(base: u16) RegisterPio { + return RegisterPio{ .direct = RegisterDirectPioX86.initWithBase(base) }; + } + + pub fn initWithMmio(mmio_ptr: *volatile anyopaque) RegisterPio { + return RegisterPio{ .mmio = RegisterMmioPio.init(mmio_ptr) }; + } + + pub fn write(self: *RegisterPio, comptime IntType: type, value: IntType, port: u32) void { + switch (self.*) { + .direct => |direct| { + var mutable_direct = direct; + mutable_direct.write(IntType, value, port); + }, + .mmio => |mmio_pio| { + var mutable_mmio = mmio_pio; + mutable_mmio.write(IntType, value, port); + }, + } + } + + pub fn read(self: *const RegisterPio, comptime IntType: type, port: u32) IntType { + return switch (self.*) { + .direct => |direct| { + var mutable_direct = direct; + return mutable_direct.read(IntType, port); + }, + .mmio => |mmio_pio| { + var mutable_mmio = mmio_pio; + return mutable_mmio.read(IntType, port); + }, + }; + } +}; + +pub const RegisterDirectPio = if (builtin.cpu.arch == .x86_64 or builtin.cpu.arch == .x86) RegisterDirectPioX86 else RegisterMmioPio; diff --git a/slipstream/system/ulib/hwreg/src/root.zig b/slipstream/system/ulib/hwreg/src/root.zig index af803c8..e1c2aec 100644 --- a/slipstream/system/ulib/hwreg/src/root.zig +++ b/slipstream/system/ulib/hwreg/src/root.zig @@ -5,11 +5,13 @@ pub const bitfields = @import("bitfields.zig"); pub const internal = @import("internal.zig"); pub const mmio = @import("mmio.zig"); +pub const pio = @import("pio.zig"); pub const Mock = @import("Mock.zig"); comptime { _ = bitfields; _ = internal; _ = mmio; + _ = pio; _ = Mock; } From e861bdcfbd71469755eb79695fa6c399e5099d24 Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Wed, 18 Jun 2025 11:52:11 -0300 Subject: [PATCH 38/41] [ulib][uart] Fix dependencies paths --- slipstream/system/ulib/uart/build.zig.zon | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/slipstream/system/ulib/uart/build.zig.zon b/slipstream/system/ulib/uart/build.zig.zon index 4bb4a8b..821b435 100644 --- a/slipstream/system/ulib/uart/build.zig.zon +++ b/slipstream/system/ulib/uart/build.zig.zon @@ -5,16 +5,16 @@ .paths = .{""}, .dependencies = .{ .@"lib/arch" = .{ - .path = "../../../kernel/lib/arch", + .path = "../../../../slipstream/kernel/lib/arch", }, .@"sdk/zbi_format" = .{ .path = "../../../../sdk/lib/zbi-format", }, .@"ulib/mock_function" = .{ - .path = "../mock_function", + .path = "../../../../slipstream/system/ulib/mock_function", }, .@"ulib/hwreg" = .{ - .path = "../hwreg", + .path = "../../../../slipstream/system/ulib/hwreg", }, }, } From 3ee73bd514ee5996254ba4577be64dd8cc878c55 Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Wed, 18 Jun 2025 12:13:44 -0300 Subject: [PATCH 39/41] [ulib][uart] Refactor to use IoProviderFactory --- slipstream/system/ulib/uart/build.zig | 2 +- slipstream/system/ulib/uart/root.zig | 22 --- slipstream/system/ulib/uart/src/all.zig | 169 +++++++----------- slipstream/system/ulib/uart/src/mock.zig | 2 +- slipstream/system/ulib/uart/src/ns8250.zig | 15 +- slipstream/system/ulib/uart/src/null.zig | 2 +- slipstream/system/ulib/uart/src/pl011.zig | 6 +- slipstream/system/ulib/uart/src/root.zig | 25 +++ .../ulib/uart/{ => src}/test/driver_tests.zig | 112 +++++++----- .../uart/{ => src}/test/parsing_tests.zig | 12 +- slipstream/system/ulib/uart/src/uart.zig | 155 ++++++++++------ 11 files changed, 277 insertions(+), 245 deletions(-) delete mode 100644 slipstream/system/ulib/uart/root.zig create mode 100644 slipstream/system/ulib/uart/src/root.zig rename slipstream/system/ulib/uart/{ => src}/test/driver_tests.zig (72%) rename slipstream/system/ulib/uart/{ => src}/test/parsing_tests.zig (95%) diff --git a/slipstream/system/ulib/uart/build.zig b/slipstream/system/ulib/uart/build.zig index 51f7f60..4d4ab87 100644 --- a/slipstream/system/ulib/uart/build.zig +++ b/slipstream/system/ulib/uart/build.zig @@ -9,7 +9,7 @@ pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const mod = b.addModule("uart", .{ - .root_source_file = b.path("root.zig"), + .root_source_file = b.path("src/root.zig"), .target = target, .optimize = optimize, }); diff --git a/slipstream/system/ulib/uart/root.zig b/slipstream/system/ulib/uart/root.zig deleted file mode 100644 index a4f4efb..0000000 --- a/slipstream/system/ulib/uart/root.zig +++ /dev/null @@ -1,22 +0,0 @@ -//! Copyright 2025 The Drift Authors. All rights reserved. -//! Use of this source code is governed by a BSD-style license that can be -//! found in the LICENSE file. - -const uart = @import("src/uart.zig"); -const ns8250 = @import("src/ns8250.zig"); -const pl011 = @import("src/pl011.zig"); -const chars_from = @import("src/chars_from.zig"); -const all = @import("src/all.zig"); - -comptime { - _ = uart; - _ = ns8250; - _ = pl011; - _ = chars_from; - _ = all; -} - -test { - _ = @import("test/driver_tests.zig"); - _ = @import("test/parsing_tests.zig"); -} diff --git a/slipstream/system/ulib/uart/src/all.zig b/slipstream/system/ulib/uart/src/all.zig index 037517a..a84dff3 100644 --- a/slipstream/system/ulib/uart/src/all.zig +++ b/slipstream/system/ulib/uart/src/all.zig @@ -25,9 +25,9 @@ pub const DummyDriver = struct { pub const ConfigType = uart.StubConfig; pub const config_name: []const u8 = "dummy"; - pub const io_type: uart.IoRegisterType = .none; - pub const driver_type: u32 = 0; - pub const extra: u32 = 0; + pub const IoType: uart.IoRegisterType = .none; + //pub const driver_type: u32 = 0; + //pub const extra: u32 = 0; base: null_driver.Driver, @@ -166,27 +166,27 @@ pub fn Config(comptime UartDriver: type) type { // /// Returns a Config object if any supported driver provides a TryMatch static method // /// that can be invoked with the provided arguments. Otherwise null is returned. // /// The matching order is determined by the position in the list of drivers. - // pub fn match(args: anytype) ?Self { - // const ArgsType = @TypeOf(args); - - // // Try each driver type in the union - // inline for (@typeInfo(Driver).@"union".fields) |field| { - // if (field.type == void) continue; + pub fn match(args: anytype) ?Self { + const ArgsType = @TypeOf(args); - // if (comptime isMatchableDriver(field.type, ArgsType)) { - // if (field.type.tryMatch(args)) |config| { - // std.debug.print("attempted match to {s}", .{field.type.config_name}); - // std.debug.print(" OK\n", .{}); - // var result: Self = undefined; - // result.configs = @unionInit(ConfigVariant(Driver), field.name, config); - // return result; - // } - // std.debug.print("attempted match to {s}", .{field.type.config_name}); - // std.debug.print(" FAIL\n", .{}); - // } - // } - // return null; - // } + // Try each driver type in the union + inline for (@typeInfo(Driver).@"union".fields) |field| { + if (field.type == void) continue; + + if (comptime isMatchableDriver(field.type, ArgsType)) { + if (field.type.tryMatch(args)) |config| { + //std.debug.print("attempted match to {s}", .{field.type.config_name}); + //std.debug.print(" OK\n", .{}); + var result: Self = undefined; + result.configs = @unionInit(ConfigVariant(), field.name, config); + return result; + } + //std.debug.print("attempted match to {s}", .{field.type.config_name}); + //std.debug.print(" FAIL\n", .{}); + } + } + return null; + } // /// Returns an empty Config object if any supported driver provides a TrySelect static method // /// that succeeds when invoked with args. Otherwise null is returned. @@ -236,7 +236,7 @@ pub fn Config(comptime UartDriver: type) type { } /// Constructor from a KernelDriver instance - pub fn initFromKernelDriver(comptime T: type, comptime IoProvider: type, comptime Sync: type, kernel_driver: *uart.KernelDriver(T, IoProvider, Sync)) Self { + pub fn initFromKernelDriver(comptime T: type, comptime IoProvider: uart.IoProviderFactory, comptime Sync: type, kernel_driver: *uart.KernelDriver(T, IoProvider, Sync)) Self { const KernelDriverType = uart.KernelDriver(T, IoProvider, Sync); return initFromConfig(T, uart.Config(T){ .config = kernel_driver.getConfig(KernelDriverType.DefaultLockPolicy) }); } @@ -254,37 +254,39 @@ pub fn Config(comptime UartDriver: type) type { /// Instantiates the Driver with a configuration. /// Equivalent to the C++ MakeDriver template function. -// pub fn MakeDriver(comptime UartDriver: type, config: Config(UartDriver)) UartDriver { -// // Handle the main Driver union case -// var driver: UartDriver = undefined; - -// const visitor = struct { -// driver_ptr: *UartDriver, - -// const VisitorSelf = @This(); - -// pub fn visitFn(self: VisitorSelf, uart_config: anytype) void { -// const ConfigType = @TypeOf(uart_config); -// if (@hasField(ConfigType, "config")) { -// // Extract the underlying driver type from the config -// const underlying_driver = @TypeOf(uart_config.config); - -// // Find the corresponding field in the Driver union -// inline for (@typeInfo(Driver).@"union".fields) |field| { -// if (field.type != void and @hasDecl(field.type, "ConfigType") and -// field.type.ConfigType == underlying_driver) -// { -// const driver_instance = field.type.init(uart_config.config); -// self.driver_ptr.* = @unionInit(UartDriver, field.name, driver_instance); -// return; -// } -// } -// } -// } -// }{ .driver_ptr = &driver }; -// config.visitConst(visitor.visitFn, .{visitor}); -// return driver; -// } +pub fn makeDriver(comptime UartDriver: type, config: *const Config(UartDriver)) UartDriver { + // Handle the main Driver union case + var driver: UartDriver = undefined; + + const Visitor = struct { + driver_ptr: *UartDriver, + + const VisitorSelf = @This(); + + pub fn visitFn(uart_config: anytype, visitor_self: *VisitorSelf) void { + const ConfigType = @TypeOf(uart_config); + if (@hasField(ConfigType, "config")) { + // Extract the underlying driver type from the config + const underlying_driver = @TypeOf(uart_config.config); + + // Find the corresponding field in the Driver union + inline for (@typeInfo(Driver).@"union".fields) |field| { + if (field.type != void and @hasDecl(field.type, "ConfigType") and + field.type.ConfigType == underlying_driver) + { + const driver_instance = field.type.init(uart_config.config); + visitor_self.driver_ptr.* = @unionInit(UartDriver, field.name, driver_instance); + return; + } + } + } + } + }; + + var visitor = Visitor{ .driver_ptr = &driver }; + config.visitConst(Visitor.visitFn, .{&visitor}); + return driver; +} /// Extracts configuration from a Driver instance. /// Equivalent to the C++ GetConfig template function. @@ -307,19 +309,18 @@ pub fn Config(comptime UartDriver: type) type { /// KernelDriver is a variant across all the KernelDriver types. /// This is equivalent to the C++ template uart::all::KernelDriver class. -pub fn KernelDriver(comptime IoProviderType: type, comptime SyncPolicy: type, comptime UartDriver: type) type { +pub fn KernelDriver(comptime IoProvider: uart.IoProviderFactory, comptime SyncPolicy: type, comptime UartDriver: type) type { if (@typeInfo(UartDriver) != .@"union") { @compileError("UartDriver must be a union type"); } return struct { const Self = @This(); - pub const uart_type = UartDriver; /// Individual KernelDriver type for a specific UART driver fn OneDriver(comptime Uart: type) type { - return uart.KernelDriver(Uart, IoProviderType, SyncPolicy); + return uart.KernelDriver(Uart, IoProvider, SyncPolicy); } /// Generate the variant type containing all possible OneDriver types @@ -413,11 +414,8 @@ pub fn KernelDriver(comptime IoProviderType: type, comptime SyncPolicy: type, co pub fn visitFn(kernel_driver: anytype, visitor_self: *@This()) void { const DriverType = @TypeOf(kernel_driver); - if (DriverType != void and @hasDecl(DriverType, "getConfig")) { - const driver_config = kernel_driver.getConfig(DriverType.DefaultLockPolicy); - const uart_config = uart.Config(DriverType.UartType){ .config = driver_config }; - visitor_self.result_ptr.* = Config(UartDriver).initFromConfig(DriverType.UartType, uart_config); - } + const uart_config = uart.Config(DriverType.UartType){ .config = kernel_driver.getConfig(DriverType.DefaultLockPolicy) }; + visitor_self.result_ptr.* = Config(UartDriver).initFromConfig(DriverType.UartType, uart_config); } }; @@ -489,50 +487,5 @@ pub fn KernelDriver(comptime IoProviderType: type, comptime SyncPolicy: type, co }; self.visit(Visitor.visitFn, .{}); } - - // pub fn hardwareInit(self: *Self, comptime LockPolicy: type) void { - // const visitor = struct { - // pub fn visitFn(_: @This(), driver: anytype) void { - // if (@hasDecl(@TypeOf(driver.*), "hardwareInit")) { - // driver.hardwareInit(LockPolicy); - // } - // } - // }{}; - // self.visit(visitor.visitFn, .{visitor}); - // } - - // pub fn write(self: *Self, comptime LockPolicy: type, str: []const u8, waiter_args: anytype) !usize { - // var result: usize = 0; - // self.visit(struct { - // result_ptr: *usize, - // str: []const u8, - // waiter_args: @TypeOf(waiter_args), - - // const VisitorSelf = @This(); - - // pub fn visitFn(visitor_self: VisitorSelf, driver: anytype) void { - // if (@hasDecl(@TypeOf(driver.*), "write")) { - // visitor_self.result_ptr.* = driver.write(LockPolicy, visitor_self.str, visitor_self.waiter_args) catch 0; - // } - // } - // }{ .result_ptr = &result, .str = str, .waiter_args = waiter_args }.visitFn); - // return result; - // } - - // pub fn read(self: *Self, comptime LockPolicy: type) ?u8 { - // var result: ?u8 = null; - // self.visit(struct { - // result_ptr: *?u8, - - // const VisitorSelf = @This(); - - // pub fn visitFn(visitor_self: VisitorSelf, driver: anytype) void { - // if (@hasDecl(@TypeOf(driver.*), "read")) { - // visitor_self.result_ptr.* = driver.read(LockPolicy); - // } - // } - // }{ .result_ptr = &result }.visitFn); - // return result; - // } }; } diff --git a/slipstream/system/ulib/uart/src/mock.zig b/slipstream/system/ulib/uart/src/mock.zig index f703493..5b28f11 100644 --- a/slipstream/system/ulib/uart/src/mock.zig +++ b/slipstream/system/ulib/uart/src/mock.zig @@ -60,7 +60,7 @@ pub const Driver = struct { //pub const devicetree_bindings: [0][]const u8 = .{}; pub const config_name: []const u8 = "mock"; - //pub const io_type: uart.IoRegisterType = .mmio8; + pub const IoType: uart.IoRegisterType = .mmio8; //pub const driver_type: u32 = 0; //pub const extra: u32 = 0; diff --git a/slipstream/system/ulib/uart/src/ns8250.zig b/slipstream/system/ulib/uart/src/ns8250.zig index df87f7e..bc319fb 100644 --- a/slipstream/system/ulib/uart/src/ns8250.zig +++ b/slipstream/system/ulib/uart/src/ns8250.zig @@ -1019,6 +1019,8 @@ pub fn DriverImpl(comptime kdrv_extra: u32, comptime KdrvConfig: type, comptime const Self = @This(); const Base = uart.DriverBase(Self, kdrv_extra, KdrvConfig, io_reg_type, io_slots); + pub const IoType = Base.IoType; + pub const ConfigType = KdrvConfig; // Aliases to pick the correct FCR and IER register based on which type we are @@ -1038,7 +1040,14 @@ pub fn DriverImpl(comptime kdrv_extra: u32, comptime KdrvConfig: type, comptime else => "ns8250", }; - pub fn tryMatchString(string: []const u8) ?uart.Config(Self) { + pub fn tryMatch(args: anytype) ?uart.Config(Self) { + if (uart.isString(args)) { + return tryMatchString(args); + } + return null; + } + + fn tryMatchString(string: []const u8) ?uart.Config(Self) { if (kdrv_extra == driver_config.ZBI_KERNEL_DRIVER_I8250_PIO_UART) { if (std.mem.eql(u8, string, "legacy")) { return uart.Config(Self).initWithConfig(legacy_config); @@ -1313,7 +1322,7 @@ pub fn DriverImpl(comptime kdrv_extra: u32, comptime KdrvConfig: type, comptime } pub fn getConfig(self: *const Self) ConfigType { - return self.base.config; + return self.base.cfg; } pub fn getIoSlots(self: *const Self) uart.IoSlotType(io_reg_type) { @@ -1338,7 +1347,7 @@ pub const Dw8250Driver = DriverImpl(driver_config.ZBI_KERNEL_DRIVER_DW8250_UART, // uart::KernelDriver UartDriver API for a PXA style variant. pub const PxaDriver = DriverImpl(driver_config.ZBI_KERNEL_DRIVER_PXA_UART, driver_config.SimpleDriverConfig, uart.IoRegisterType.mmio32, getIoSlots(driver_config.ZBI_KERNEL_DRIVER_PXA_UART)); -const SimpleTestDriver = uart.KernelDriver(Mmio32Driver, mock.IoProvider(driver_config.SimpleDriverConfig, uart.IoRegisterType.mmio32), sync.UnsynchronizedPolicy); +const SimpleTestDriver = uart.KernelDriver(Mmio32Driver, mock.IoProvider, sync.UnsynchronizedPolicy); const test_config = driver_config.SimpleDriverConfig{ .mmio_phys = 0, diff --git a/slipstream/system/ulib/uart/src/null.zig b/slipstream/system/ulib/uart/src/null.zig index b9d9e77..627a3bb 100644 --- a/slipstream/system/ulib/uart/src/null.zig +++ b/slipstream/system/ulib/uart/src/null.zig @@ -19,7 +19,7 @@ pub const Driver = struct { pub const devicetree_bindings: [0][]const u8 = .{}; pub const config_name: []const u8 = "none"; - pub const io_type: uart.IoRegisterType = .none; + pub const IoType: uart.IoRegisterType = .none; pub const driver_type: u32 = 0; pub const extra: u32 = 0; diff --git a/slipstream/system/ulib/uart/src/pl011.zig b/slipstream/system/ulib/uart/src/pl011.zig index 47a3953..a2775a7 100644 --- a/slipstream/system/ulib/uart/src/pl011.zig +++ b/slipstream/system/ulib/uart/src/pl011.zig @@ -778,13 +778,13 @@ pub const io_slots: usize = 0x2C + @sizeOf(u16); pub const Driver = struct { const Self = @This(); const Base = uart.DriverBase(Self, driver_config.ZBI_KERNEL_DRIVER_PL011_UART, driver_config.SimpleDriverConfig, uart.IoRegisterType.mmio8, io_slots); + pub const IoType = Base.IoType; base: Base, pub const ConfigType = driver_config.SimpleDriverConfig; //pub const devicetree_bindings: []const []const u8 = &.{ "arm,primecell", "arm,pl011" }; //pub const config_name: []const u8 = "pl011"; - //pub const kIoType = uart.IoRegisterType.mmio8; pub fn tryMatchString(string: []const u8) ?uart.Config(Self) { if (std.mem.eql(u8, string, "qemu")) { @@ -812,7 +812,7 @@ pub const Driver = struct { } pub fn getConfig(self: *const Self) ConfigType { - return self.base.config; + return self.base.cfg; } pub fn getIoSlots(self: *const Self) usize { @@ -955,7 +955,7 @@ pub const Driver = struct { }; // Test driver and configuration -const SimpleTestDriver = uart.KernelDriver(Driver, mock.IoProvider(driver_config.SimpleDriverConfig, uart.IoRegisterType.mmio8), sync.UnsynchronizedPolicy); +const SimpleTestDriver = uart.KernelDriver(Driver, mock.IoProvider, sync.UnsynchronizedPolicy); const test_config = driver_config.SimpleDriverConfig{ .mmio_phys = 0, diff --git a/slipstream/system/ulib/uart/src/root.zig b/slipstream/system/ulib/uart/src/root.zig new file mode 100644 index 0000000..7535c55 --- /dev/null +++ b/slipstream/system/ulib/uart/src/root.zig @@ -0,0 +1,25 @@ +//! Copyright 2025 The Drift Authors. All rights reserved. +//! Use of this source code is governed by a BSD-style license that can be +//! found in the LICENSE file. + +pub const all = @import("all.zig"); +pub const sync = @import("sync.zig"); + +const uart = @import("uart.zig"); +pub const BasicIoProvider = uart.BasicIoProvider; +pub const BasicIoProviderStub = uart.BasicIoProviderStub; +pub const BasicIoProviderMmio = uart.BasicIoProviderMmio; +pub const BasicIoProviderPio = uart.BasicIoProviderPio; + +comptime { + _ = all; + _ = sync; +} + +test { + _ = @import("ns8250.zig"); + _ = @import("pl011.zig"); + _ = @import("chars_from.zig"); + _ = @import("test/driver_tests.zig"); + _ = @import("test/parsing_tests.zig"); +} diff --git a/slipstream/system/ulib/uart/test/driver_tests.zig b/slipstream/system/ulib/uart/src/test/driver_tests.zig similarity index 72% rename from slipstream/system/ulib/uart/test/driver_tests.zig rename to slipstream/system/ulib/uart/src/test/driver_tests.zig index 98b60f1..0e71fe1 100644 --- a/slipstream/system/ulib/uart/test/driver_tests.zig +++ b/slipstream/system/ulib/uart/src/test/driver_tests.zig @@ -5,12 +5,12 @@ const std = @import("std"); const zbi_format = @import("sdk/zbi_format"); -const uart = @import("../src/uart.zig"); -const ns8250 = @import("../src/ns8250.zig"); -const null_driver = @import("../src/null.zig"); -const mock = @import("../src/mock.zig"); -const sync = @import("../src/sync.zig"); -const all = @import("../src/all.zig"); +const uart = @import("../uart.zig"); +const ns8250 = @import("../ns8250.zig"); +const null_driver = @import("../null.zig"); +const mock = @import("../mock.zig"); +const sync = @import("../sync.zig"); +const all = @import("../all.zig"); const driver_config = zbi_format.driver_config; const testing = std.testing; @@ -36,14 +36,14 @@ test "uart nonblocking" { .expectWrite("world\r\n") .expectUnlock(); - const TestDriver = uart.KernelDriver(mock.Driver, mock.IoProvider(uart.StubConfig, uart.IoRegisterType.none), mock.SyncPolicy); + const TestDriver = uart.KernelDriver(mock.Driver, mock.IoProvider, mock.SyncPolicy); var driver = TestDriver.init(mock_uart); defer driver.deinit(); driver.mockInit(); driver.hardwareInit(mock.Locking); - try testing.expectEqual(@as(usize, 3), driver.write(mock.Locking, "hi!", .{})); - try testing.expectEqual(@as(usize, 12), driver.write(mock.Locking, "hello world\n", .{})); + try testing.expectEqual(3, driver.write(mock.Locking, "hi!", .{})); + try testing.expectEqual(12, driver.write(mock.Locking, "hello world\n", .{})); } test "uart lock policy" { @@ -63,15 +63,15 @@ test "uart lock policy" { .expectTxReady(true) .expectWrite("world\r\n"); - const TestDriver = uart.KernelDriver(mock.Driver, mock.IoProvider(uart.StubConfig, uart.IoRegisterType.none), mock.SyncPolicy); + const TestDriver = uart.KernelDriver(mock.Driver, mock.IoProvider, mock.SyncPolicy); var driver = TestDriver.init(mock_uart); defer driver.deinit(); driver.mockInit(); driver.hardwareInit(mock.Locking); // Just check that lock args are forwarded correctly. - try testing.expectEqual(@as(usize, 3), driver.write(mock.NoopLocking, "hi!", .{})); - try testing.expectEqual(@as(usize, 12), driver.write(mock.NoopLocking, "hello world\n", .{})); + try testing.expectEqual(3, driver.write(mock.NoopLocking, "hi!", .{})); + try testing.expectEqual(12, driver.write(mock.NoopLocking, "hello world\n", .{})); } test "uart blocking" { @@ -97,14 +97,14 @@ test "uart blocking" { .expectWrite("world\r\n") .expectUnlock(); - const TestDriver = uart.KernelDriver(mock.Driver, mock.IoProvider(uart.StubConfig, uart.IoRegisterType.none), mock.SyncPolicy); + const TestDriver = uart.KernelDriver(mock.Driver, mock.IoProvider, mock.SyncPolicy); var driver = TestDriver.init(mock_uart); defer driver.deinit(); driver.mockInit(); driver.hardwareInit(mock.Locking); - try testing.expectEqual(@as(usize, 3), driver.write(mock.Locking, "hi!", .{})); - try testing.expectEqual(@as(usize, 12), driver.write(mock.Locking, "hello world\n", .{})); + try testing.expectEqual(3, driver.write(mock.Locking, "hi!", .{})); + try testing.expectEqual(12, driver.write(mock.Locking, "hello world\n", .{})); } test "uart config" { @@ -178,9 +178,9 @@ test "uart config" { fn visitFn(config: anytype) void { const ConfigType = @TypeOf(config); if (ConfigType == uart.Config(ns8250.Mmio32Driver)) { - testing.expectEqual(@as(u64, expected_dcfg.mmio_phys), config.config.mmio_phys) catch unreachable; - testing.expectEqual(@as(u32, expected_dcfg.irq), config.config.irq) catch unreachable; - testing.expectEqual(@as(u32, expected_dcfg.flags), config.config.flags) catch unreachable; + testing.expectEqual(expected_dcfg.mmio_phys, config.config.mmio_phys) catch unreachable; + testing.expectEqual(expected_dcfg.irq, config.config.irq) catch unreachable; + testing.expectEqual(expected_dcfg.flags, config.config.flags) catch unreachable; } else { testing.expect(false) catch unreachable; // Unexpected configuration } @@ -194,7 +194,7 @@ test "uart config" { .irq = 3, }; - const TestKernelDriver = uart.KernelDriver(ns8250.PioDriver, mock.IoProvider(ns8250.PioDriver.ConfigType, uart.IoRegisterType.pio), sync.UnsynchronizedPolicy); + const TestKernelDriver = uart.KernelDriver(ns8250.PioDriver, mock.IoProvider, sync.UnsynchronizedPolicy); var driver = TestKernelDriver.init(pio_cfg); defer driver.deinit(); @@ -206,8 +206,8 @@ test "uart config" { fn visitFn(config: anytype) void { const ConfigType = @TypeOf(config); if (ConfigType == uart.Config(ns8250.PioDriver)) { - testing.expectEqual(@as(u16, expected_pio_cfg.base), config.config.base) catch unreachable; - testing.expectEqual(@as(u32, expected_pio_cfg.irq), config.config.irq) catch unreachable; + testing.expectEqual(expected_pio_cfg.base, config.config.base) catch unreachable; + testing.expectEqual(expected_pio_cfg.irq, config.config.irq) catch unreachable; } else { testing.expect(false) catch unreachable; // Unexpected configuration } @@ -230,9 +230,9 @@ test "uart config" { fn visitFn(config: anytype) void { const ConfigType = @TypeOf(config); if (ConfigType == uart.Config(ns8250.Mmio32Driver)) { - testing.expectEqual(@as(u64, expected_dcfg.mmio_phys), config.config.mmio_phys) catch unreachable; - testing.expectEqual(@as(u32, expected_dcfg.irq), config.config.irq) catch unreachable; - testing.expectEqual(@as(u32, expected_dcfg.flags), config.config.flags) catch unreachable; + testing.expectEqual(expected_dcfg.mmio_phys, config.config.mmio_phys) catch unreachable; + testing.expectEqual(expected_dcfg.irq, config.config.irq) catch unreachable; + testing.expectEqual(expected_dcfg.flags, config.config.flags) catch unreachable; } else { testing.expect(false) catch unreachable; // Unexpected configuration } @@ -249,19 +249,19 @@ test "uart config" { .irq = 3, }; - const TestKernelDriver1 = uart.KernelDriver(ns8250.PioDriver, mock.IoProvider(ns8250.PioDriver.ConfigType, uart.IoRegisterType.pio), sync.UnsynchronizedPolicy); + const TestKernelDriver1 = uart.KernelDriver(ns8250.PioDriver, mock.IoProvider, sync.UnsynchronizedPolicy); var driver1 = TestKernelDriver1.init(pio_cfg1); defer driver.deinit(); - const all_configs1 = all.Config(all.Driver).initFromKernelDriver(ns8250.PioDriver, mock.IoProvider(ns8250.PioDriver.ConfigType, uart.IoRegisterType.pio), sync.UnsynchronizedPolicy, &driver1); + const all_configs1 = all.Config(all.Driver).initFromKernelDriver(ns8250.PioDriver, mock.IoProvider, sync.UnsynchronizedPolicy, &driver1); all_configs1.visitConst(struct { const expected_pio_cfg = pio_cfg1; fn visitFn(config: anytype) void { const ConfigType = @TypeOf(config); if (ConfigType == uart.Config(ns8250.PioDriver)) { - testing.expectEqual(@as(u16, expected_pio_cfg.base), config.config.base) catch unreachable; - testing.expectEqual(@as(u32, expected_pio_cfg.irq), config.config.irq) catch unreachable; + testing.expectEqual(expected_pio_cfg.base, config.config.base) catch unreachable; + testing.expectEqual(expected_pio_cfg.irq, config.config.irq) catch unreachable; } else { testing.expect(false) catch unreachable; // Unexpected configuration } @@ -271,7 +271,7 @@ test "uart config" { } test "all config" { - const AllDriver = all.KernelDriver(mock.IoProvider(uart.StubConfig, uart.IoRegisterType.none), sync.UnsynchronizedPolicy, all.Driver); + const AllDriver = all.KernelDriver(mock.IoProvider, sync.UnsynchronizedPolicy, all.Driver); // Assignment var all_driver = AllDriver.init(); @@ -283,7 +283,7 @@ test "all config" { .irq = 3, }; - const TestKernelDriver = uart.KernelDriver(ns8250.PioDriver, mock.IoProvider(ns8250.PioDriver.ConfigType, uart.IoRegisterType.pio), sync.UnsynchronizedPolicy); + const TestKernelDriver = uart.KernelDriver(ns8250.PioDriver, mock.IoProvider, sync.UnsynchronizedPolicy); var driver = TestKernelDriver.init(pio_cfg); defer driver.deinit(); @@ -295,8 +295,8 @@ test "all config" { fn visitFn(config: anytype) void { const ConfigType = @TypeOf(config); if (ConfigType == uart.Config(ns8250.PioDriver)) { - testing.expectEqual(@as(u16, expected_pio_cfg.base), config.config.base) catch unreachable; - testing.expectEqual(@as(u32, expected_pio_cfg.irq), config.config.irq) catch unreachable; + testing.expectEqual(expected_pio_cfg.base, config.config.base) catch unreachable; + testing.expectEqual(expected_pio_cfg.irq, config.config.irq) catch unreachable; } else { testing.expect(false) catch unreachable; // Unexpected configuration } @@ -304,7 +304,7 @@ test "all config" { }.visitFn, .{}); // Constructor - var all_driver2 = all.KernelDriver(mock.IoProvider(uart.StubConfig, uart.IoRegisterType.none), sync.UnsynchronizedPolicy, all.Driver).initFromConfig(all_config); + var all_driver2 = all.KernelDriver(mock.IoProvider, sync.UnsynchronizedPolicy, all.Driver).initFromConfig(all_config); defer all_driver2.deinit(); all_driver2.visitConst(struct { @@ -313,8 +313,8 @@ test "all config" { const DriverType = @TypeOf(drv); if (DriverType.UartType == ns8250.PioDriver) { const config = drv.getConfig(DriverType.DefaultLockPolicy); - testing.expectEqual(@as(u16, expected_pio_cfg.base), config.base) catch unreachable; - testing.expectEqual(@as(u32, expected_pio_cfg.irq), config.irq) catch unreachable; + testing.expectEqual(expected_pio_cfg.base, config.base) catch unreachable; + testing.expectEqual(expected_pio_cfg.irq, config.irq) catch unreachable; } else { testing.expect(false) catch unreachable; // Unexpected driver type } @@ -323,19 +323,19 @@ test "all config" { } test "uart null driver" { - const TestDriver = uart.KernelDriver(null_driver.Driver, mock.IoProvider(uart.StubConfig, uart.IoRegisterType.none), sync.UnsynchronizedPolicy); + const TestDriver = uart.KernelDriver(null_driver.Driver, mock.IoProvider, sync.UnsynchronizedPolicy); var driver = TestDriver.init(uart.StubConfig{}); defer driver.deinit(); driver.hardwareInit(TestDriver.DefaultLockPolicy); - try testing.expectEqual(@as(usize, 3), driver.write(TestDriver.DefaultLockPolicy, "hi!", {})); - try testing.expectEqual(@as(usize, 12), driver.write(TestDriver.DefaultLockPolicy, "hello world\n", {})); - try testing.expectEqual(@as(?u8, null), driver.read(TestDriver.DefaultLockPolicy)); + try testing.expectEqual(3, driver.write(TestDriver.DefaultLockPolicy, "hi!", {})); + try testing.expectEqual(12, driver.write(TestDriver.DefaultLockPolicy, "hello world\n", {})); + try testing.expectEqual(null, driver.read(TestDriver.DefaultLockPolicy)); } test "uart all driver" { const AllConfig = all.Config(all.Driver); - const AllDriver = all.KernelDriver(mock.IoProvider(uart.StubConfig, uart.IoRegisterType.none), sync.UnsynchronizedPolicy, all.Driver); + const AllDriver = all.KernelDriver(mock.IoProvider, sync.UnsynchronizedPolicy, all.Driver); // Default constructed configuration should default to being the null_driver.Driver var driver = AllDriver.initFromConfig(AllConfig.init()); @@ -352,8 +352,8 @@ test "uart all driver" { } else { var mut_drv = drv; mut_drv.hardwareInit(DriverType.DefaultLockPolicy); - const write_result = mut_drv.write(DriverType.DefaultLockPolicy, "hi!", {}) catch 0; - testing.expectEqual(@as(usize, 3), write_result) catch unreachable; + const write_result = mut_drv.write(DriverType.DefaultLockPolicy, "hi!", {}); + testing.expectEqual(3, write_result) catch unreachable; } } }.visitFn, .{}); @@ -366,13 +366,33 @@ test "uart all driver" { fn visitFn(drv: anytype) void { const DriverType = @TypeOf(drv); if (DriverType == void) { - testing.expect(false) catch unreachable; // Unexpected driver type + try testing.expect(false); // Unexpected driver type } else { var mut_drv = drv; - const write_result = mut_drv.write(DriverType.DefaultLockPolicy, "hello world\n", {}) catch 0; - testing.expectEqual(@as(usize, 12), write_result) catch unreachable; + const write_result = mut_drv.write(DriverType.DefaultLockPolicy, "hello world\n", {}); + testing.expectEqual(12, write_result) catch unreachable; const read_result = mut_drv.read(DriverType.DefaultLockPolicy); - testing.expectEqual(@as(?u8, null), read_result) catch unreachable; + testing.expectEqual(null, read_result) catch unreachable; + } + } + }.visitFn, .{}); +} + +test "uart all driver match legacy" { + const all_config = all.Config(all.Driver).match("legacy"); + try testing.expect(all_config != null); + + const driver: all.KernelDriver(uart.BasicIoProvider, sync.UnsynchronizedPolicy, all.Driver) = all.KernelDriver(uart.BasicIoProvider, sync.UnsynchronizedPolicy, all.Driver).initFromUart(all.makeDriver(all.Driver, &all_config.?)); + + driver.visitConst(struct { + fn visitFn(drv: anytype) void { + const DriverType = @TypeOf(drv); + const config = drv.getConfig(DriverType.DefaultLockPolicy); + if (driver_config.SimplePioConfig == @TypeOf(config)) { + testing.expectEqual(0x3f8, config.base) catch unreachable; + testing.expectEqual(4, config.irq) catch unreachable; + } else { + testing.expect(false) catch unreachable; // Unexpected driver type } } }.visitFn, .{}); diff --git a/slipstream/system/ulib/uart/test/parsing_tests.zig b/slipstream/system/ulib/uart/src/test/parsing_tests.zig similarity index 95% rename from slipstream/system/ulib/uart/test/parsing_tests.zig rename to slipstream/system/ulib/uart/src/test/parsing_tests.zig index 08ea326..a18848f 100644 --- a/slipstream/system/ulib/uart/test/parsing_tests.zig +++ b/slipstream/system/ulib/uart/src/test/parsing_tests.zig @@ -4,9 +4,9 @@ const std = @import("std"); -const uart = @import("../src/uart.zig"); -const ns8250 = @import("../src/ns8250.zig"); -const parse = @import("../src/parse.zig"); +const uart = @import("../uart.zig"); +const ns8250 = @import("../ns8250.zig"); +const parse = @import("../parse.zig"); const testing = std.testing; @@ -240,7 +240,7 @@ test "parsing - two u64s" { test "ns8250 8-bit mmio driver parsing" { { - const driver_config = ns8250.Mmio8Driver.tryMatchString("ns8250-8bit,0xa,0xb"); + const driver_config = ns8250.Mmio8Driver.tryMatch("ns8250-8bit,0xa,0xb"); try testing.expect(driver_config != null); @@ -251,7 +251,7 @@ test "ns8250 8-bit mmio driver parsing" { } { - const driver_config = ns8250.Mmio8Driver.tryMatchString("ns8250-8bit,0xa,0xb,0xc"); + const driver_config = ns8250.Mmio8Driver.tryMatch("ns8250-8bit,0xa,0xb,0xc"); try testing.expect(driver_config != null); @@ -263,7 +263,7 @@ test "ns8250 8-bit mmio driver parsing" { } test "ns8250 legacy driver parsing" { - const driver_config = ns8250.PioDriver.tryMatchString("legacy"); + const driver_config = ns8250.PioDriver.tryMatch("legacy"); try testing.expect(driver_config != null); diff --git a/slipstream/system/ulib/uart/src/uart.zig b/slipstream/system/ulib/uart/src/uart.zig index 707a337..b6c87f9 100644 --- a/slipstream/system/ulib/uart/src/uart.zig +++ b/slipstream/system/ulib/uart/src/uart.zig @@ -3,7 +3,9 @@ //! found in the LICENSE file. const std = @import("std"); +const builtin = @import("builtin"); const zbi_format = @import("sdk/zbi_format"); +const hwreg = @import("ulib/hwreg"); const parse = @import("parse.zig"); const chars_from = @import("chars_from.zig"); @@ -135,12 +137,18 @@ pub const RxCallbackFn = *const fn (anytype, anytype) void; pub fn DriverBase(comptime Driver: type, comptime drvExtra: u32, comptime KdrvConfig: type, comptime IoRegType: IoRegisterType, comptime IoSlots: IoSlotType(IoRegType)) type { return struct { const Self = @This(); + pub const ConfigType = KdrvConfig; + + // No devicetree bindings by default. pub const devicetree_bindings: []const []const u8 = &.{}; - pub const io_type: IoRegType = IoRegType; + + // Register Io Type. + pub const IoType: IoRegisterType = IoRegType; + pub const extra: u32 = drvExtra; - config: ConfigType, + cfg: ConfigType, //pub fn tryMatch(header: anytype, payload: anytype) ?Config { // if (header.type == ZBI_TYPE_KERNEL_DRIVER and header.extra == extra and @@ -150,8 +158,15 @@ pub fn DriverBase(comptime Driver: type, comptime drvExtra: u32, comptime KdrvCo // return null; //} + pub fn tryMatch(args: anytype) ?Config(Driver) { + if (isString(args)) { + return tryMatchString(args); + } + return null; + } + pub fn tryMatchString(string: []const u8) ?Config(Driver) { - std.debug.print("TryMatch::BASE\n", .{}); + //std.debug.print("TryMatch::BASE\n", .{}); const config_name = Driver.config_name; if (string.len >= config_name.len and std.mem.eql(u8, string[0..config_name.len], config_name)) @@ -170,8 +185,8 @@ pub fn DriverBase(comptime Driver: type, comptime drvExtra: u32, comptime KdrvCo // return null; //} - pub fn initWithConfig(cfg: ConfigType) Self { - return Self{ .config = cfg }; + pub fn initWithConfig(config: ConfigType) Self { + return Self{ .cfg = config }; } pub fn initWithTaggedConfig(tagged_config: Config(Driver)) Self { @@ -185,41 +200,56 @@ pub fn DriverBase(comptime Driver: type, comptime drvExtra: u32, comptime KdrvCo } @compileError("IoSlots must be different from kDynamicIoSlot or ioSlots implementation must be provided in derived class."); } + + // Get the MMIO range for MMIO drivers + pub fn mmioRange(self: *const Self) struct { address: u64, size: usize } { + if (IoRegType == IoRegisterType.mmio32) { + // Each IoSlot represents 4 bytes. + return .{ + .address = self.cfg.mmio_phys, + .size = self.getIoSlots() * @sizeOf(u32), + }; + } else if (IoRegType == IoRegisterType.mmio8) { + return .{ + .address = self.cfg.mmio_phys, + .size = self.getIoSlots(), + }; + } + unreachable; + } }; } +/// Generic type representing an I/O provider factory function. +/// This type signature defines the interface for creating I/O providers +/// for different UART configurations and I/O register types. +pub const IoProviderFactory = fn (comptime ConfigType: type, comptime IoType: IoRegisterType) type; + pub fn BasicIoProvider(comptime ConfigType: type, comptime IoType: IoRegisterType) type { - _ = IoType; + if (ConfigType == zbi_format.driver_config.SimpleDriverConfig) { + return BasicIoProviderMmio(ConfigType, IoType); + } else if ((builtin.cpu.arch == .x86 or builtin.cpu.arch == .x86_64) and (ConfigType == zbi_format.driver_config.SimplePioConfig)) { + return BasicIoProviderPio(ConfigType, IoType); + } else { + return BasicIoProviderStub(ConfigType, IoRegisterType.none); + } +} + +// Specialization for Stub drivers, such as `null::Driver`. +pub fn BasicIoProviderStub(comptime ConfigType: type, comptime IoType: IoRegisterType) type { return struct { const Self = @This(); pub fn init(cfg: ConfigType, io_slots: usize) Self { + _ = IoType; _ = cfg; _ = io_slots; return Self{}; } - pub fn deinit(self: *Self) void { - _ = self; - } - - pub fn io(self: *Self) ?*anyopaque { - _ = self; - return null; - } - }; -} - -pub fn directMapMmio(phys: u64, size: usize) *volatile anyopaque { - _ = size; - return @ptrFromInt(phys); -} - -pub fn BasicIoProviderStub(comptime ConfigType: type) type { - return struct { - const Self = @This(); - - pub fn init(cfg: ConfigType, io_slots: usize) Self { + pub fn initWithBase(cfg: ConfigType, io_slots: usize, base: *volatile anyopaque) Self { + _ = IoType; + _ = base; _ = cfg; _ = io_slots; return Self{}; @@ -236,32 +266,36 @@ pub fn BasicIoProviderStub(comptime ConfigType: type) type { }; } -pub fn BasicIoProviderMmio(comptime IoType: IoRegisterType) type { +pub fn directMapMmio(phys: u64, size: usize) *volatile anyopaque { + _ = size; + return @ptrFromInt(phys); +} + +// The specialization used most commonly handles simple MMIO devices. +pub fn BasicIoProviderMmio(comptime ConfigType: type, comptime IoType: IoRegisterType) type { + std.debug.assert(ConfigType == zbi_format.driver_config.SimpleDriverConfig); return struct { const Self = @This(); io_reg: union(enum) { - //mmio: hwreg.RegisterMmio, - //mmio_scaled: hwreg.RegisterMmioScaled(u32), + mmio: hwreg.mmio.RegisterMmio, + mmio_scaled: hwreg.mmio.RegisterMmioScaled(u32), }, - pub fn init(cfg: zbi_format.SimpleDriverConfig, io_slots: usize) Self { + pub fn init(cfg: zbi_format.driver_config.SimpleDriverConfig, io_slots: usize) Self { return Self.initWithMapper(cfg, io_slots, directMapMmio); } - pub fn initWithMapper(cfg: zbi_format.SimpleDriverConfig, io_slots: usize, comptime mapMmio: fn (u64, usize) *volatile anyopaque) Self { - _ = cfg; - _ = io_slots; - _ = mapMmio; + pub fn initWithMapper(cfg: zbi_format.driver_config.SimpleDriverConfig, io_slots: usize, comptime mapMmio: fn (u64, usize) *volatile anyopaque) Self { switch (IoType) { .mmio8 => { return Self{ - //.io_reg = .{ .mmio = hwreg.RegisterMmio.init(mapMmio(cfg.mmio_phys, io_slots)) }, + .io_reg = .{ .mmio = hwreg.mmio.RegisterMmio.init(mapMmio(cfg.mmio_phys, io_slots)) }, }; }, .mmio32 => { return Self{ - //.io_reg = .{ .mmio_scaled = hwreg.RegisterMmioScaled(u32).init(mapMmio(cfg.mmio_phys, io_slots * 4)) }, + .io_reg = .{ .mmio_scaled = hwreg.mmio.RegisterMmioScaled(u32).init(mapMmio(cfg.mmio_phys, io_slots * 4)) }, }; }, else => { @@ -274,30 +308,31 @@ pub fn BasicIoProviderMmio(comptime IoType: IoRegisterType) type { _ = self; } - pub fn io(self: *Self) *@TypeOf(self.io_reg) { + pub fn getIo(self: *Self) *@TypeOf(self.io_reg) { return &self.io_reg; } }; } -pub fn BasicIoProviderPio(comptime IoType: IoRegisterType) type { - //if (builtin.cpu.arch != .x86_64 and builtin.cpu.arch != .x86) { - // @compileError("PIO only supported on x86"); - //} +// The specialization for devices using actual PIO only occurs on x86. +pub fn BasicIoProviderPio(comptime ConfigType: type, comptime IoType: IoRegisterType) type { + comptime { + std.debug.assert(ConfigType == zbi_format.driver_config.SimplePioConfig); + std.debug.assert(IoType == .pio); + if (builtin.cpu.arch != .x86_64 and builtin.cpu.arch != .x86) { + @compileError("PIO only supported on x86"); + } + } return struct { const Self = @This(); - //io_reg: hwreg.RegisterDirectPio, + io_reg: hwreg.pio.RegisterDirectPio, - pub fn init(cfg: zbi_format.SimplePioConfig, io_slots: u16) Self { - _ = cfg; - if (IoType != .pio) { - @compileError("Expected PIO IoType"); - } + pub fn init(cfg: zbi_format.driver_config.SimplePioConfig, io_slots: u16) Self { std.debug.assert(io_slots > 0); return Self{ - //.io_reg = hwreg.RegisterDirectPio.init(cfg.base), + .io_reg = hwreg.pio.RegisterDirectPio.initWithBase(cfg.base), }; } @@ -305,13 +340,13 @@ pub fn BasicIoProviderPio(comptime IoType: IoRegisterType) type { _ = self; } - //pub fn io(self: *Self) *hwreg.RegisterDirectPio { - // return &self.io_reg; - //} + pub fn getIo(self: *Self) *hwreg.pio.RegisterDirectPio { + return &self.io_reg; + } }; } -pub fn KernelDriver(comptime UartDriver: type, comptime IoProviderType: type, comptime SyncPolicy: type) type { +pub fn KernelDriver(comptime UartDriver: type, comptime IoProvider: IoProviderFactory, comptime SyncPolicy: type) type { return struct { const Self = @This(); @@ -326,6 +361,8 @@ pub fn KernelDriver(comptime UartDriver: type, comptime IoProviderType: type, co pub const UartType = UartDriver; pub const ConfigType = UartDriver.ConfigType; + const IoProviderType = IoProvider(UartType.ConfigType, UartType.IoType); + lock: SyncPolicy.Lock(Self), waiter: Waiter, uart: UartType, @@ -431,7 +468,7 @@ pub fn KernelDriver(comptime UartDriver: type, comptime IoProviderType: type, co self.uart.interrupt(IoProviderType, SyncPolicy.Lock(Self), Waiter, &self.io, &self.lock, &self.waiter, tx, tx_context, rx, rx_context); } - pub fn write(self: *Self, comptime LockPolicy: type, str: []const u8, waiter_args: anytype) !usize { + pub fn write(self: *Self, comptime LockPolicy: type, str: []const u8, waiter_args: anytype) usize { var chars = chars_from.CharsFrom(true).init(str); // Massage into u8 with \n -> CRLF. var it = chars.begin(); @@ -478,3 +515,13 @@ pub fn KernelDriver(comptime UartDriver: type, comptime IoProviderType: type, co } }; } + +pub fn isString(args: anytype) bool { + const info = @typeInfo(@TypeOf(args)); + return switch (info) { + .pointer => |ptr| (ptr.size == .slice and ptr.child == u8) or + (ptr.size == .one and @typeInfo(ptr.child) == .array and + @typeInfo(ptr.child).array.child == u8), + else => false, + }; +} From 2c0a2110007db92b399a78e23be340b69b69d289 Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Wed, 18 Jun 2025 15:47:45 -0300 Subject: [PATCH 40/41] [system][public] Refactor to avoid cyclic dependency --- slipstream/system/public/build.zig | 25 ++++++------------- slipstream/system/public/build.zig.zon | 3 --- .../system/public/slipstream/assert.zig | 19 ++++++++------ .../system/public/slipstream/internal.zig | 2 +- slipstream/system/public/slipstream/root.zig | 2 +- 5 files changed, 21 insertions(+), 30 deletions(-) diff --git a/slipstream/system/public/build.zig b/slipstream/system/public/build.zig index 71ccf62..0862131 100644 --- a/slipstream/system/public/build.zig +++ b/slipstream/system/public/build.zig @@ -18,23 +18,12 @@ pub fn build(b: *std.Build) void { .optimize = optimize, }); - if (isKernel(target.query)) { - const deps = [_]struct { name: []const u8, dep_name: []const u8, module_name: []const u8 }{ - .{ .name = "kernel", .dep_name = "kernel", .module_name = "kernel" }, - }; - - for (deps) |dep| { - const dep_module = b.dependency(dep.dep_name, .{}); - mod.addImport(dep.name, dep_module.module(dep.module_name)); - } - } + const host_test_step = b.step("test", "Run unit tests"); + const host_unit_tests = b.addTest(.{ + .root_module = mod, + .target = b.graph.host, + }); - //const host_test_step = b.step("test", "Run unit tests"); - //const host_unit_tests = b.addTest(.{ - // .root_module = mod, - // .target = b.graph.host, - //}); - // - //const run_host_unit_tests = b.addRunArtifact(host_unit_tests); - //host_test_step.dependOn(&run_host_unit_tests.step); + const run_host_unit_tests = b.addRunArtifact(host_unit_tests); + host_test_step.dependOn(&run_host_unit_tests.step); } diff --git a/slipstream/system/public/build.zig.zon b/slipstream/system/public/build.zig.zon index 3c01fd3..51627f9 100644 --- a/slipstream/system/public/build.zig.zon +++ b/slipstream/system/public/build.zig.zon @@ -3,7 +3,4 @@ .fingerprint = 0x3bb42e1dd601ba4b, .version = "0.0.1", .paths = .{""}, - .dependencies = .{ .kernel = .{ - .path = "../../kernel", - } }, } diff --git a/slipstream/system/public/slipstream/assert.zig b/slipstream/system/public/slipstream/assert.zig index a79f454..ed8197b 100644 --- a/slipstream/system/public/slipstream/assert.zig +++ b/slipstream/system/public/slipstream/assert.zig @@ -1,16 +1,21 @@ -//! Copyright 2018 The Fuchsia Authors. All rights reserved. +//! Copyright 2025 The Drift Authors. All rights reserved. //! Use of this source code is governed by a BSD-style license that can be //! found in the LICENSE file. const builtin = @import("builtin"); const std = @import("std"); +const internal = @import("internal.zig"); -pub fn isKernel(comptime target: std.Target) bool { - return target.abi == .none and target.os.tag == .freestanding; -} - -const Impl = if (isKernel(builtin.target)) @import("kernel").assert else @import("internal.zig"); +//fn isKernel(comptime target: std.Target) bool { +// return target.abi == .none and target.os.tag == .freestanding; +//} pub fn slipstreamAssert(comptime src: std.builtin.SourceLocation, x: bool, comptime expression: []const u8) void { - Impl.assert(src, x, expression); + // TODO (Herrera) : Add kernel assert + + //if (isKernel(builtin.target)) { + // assertFail(src.file, src.line, expression); + //} else { + internal.assert(src, x, expression); + //} } diff --git a/slipstream/system/public/slipstream/internal.zig b/slipstream/system/public/slipstream/internal.zig index 67a758f..dabd8e2 100644 --- a/slipstream/system/public/slipstream/internal.zig +++ b/slipstream/system/public/slipstream/internal.zig @@ -1,4 +1,4 @@ -//! Copyright 2018 The Fuchsia Authors. All rights reserved. +//! Copyright 2025 The Drift Authors. All rights reserved. //! Use of this source code is governed by a BSD-style license that can be //! found in the LICENSE file. diff --git a/slipstream/system/public/slipstream/root.zig b/slipstream/system/public/slipstream/root.zig index fc97cfe..0a0da88 100644 --- a/slipstream/system/public/slipstream/root.zig +++ b/slipstream/system/public/slipstream/root.zig @@ -1,4 +1,4 @@ -//! Copyright 2018 The Fuchsia Authors. All rights reserved. +//! Copyright 2025 The Drift Authors. All rights reserved. //! Use of this source code is governed by a BSD-style license that can be //! found in the LICENSE file. From 55915daafa5afe0804e2c4ff3b2d0c0c029f82c7 Mon Sep 17 00:00:00 2001 From: Bruno Herrera Date: Wed, 18 Jun 2025 15:51:47 -0300 Subject: [PATCH 41/41] [kernel] Rename threadInitEarly to initEarly --- slipstream/kernel/kernel/Thread.zig | 2 +- slipstream/kernel/top/main.zig | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/slipstream/kernel/kernel/Thread.zig b/slipstream/kernel/kernel/Thread.zig index b23387b..7eef5cc 100644 --- a/slipstream/kernel/kernel/Thread.zig +++ b/slipstream/kernel/kernel/Thread.zig @@ -27,7 +27,7 @@ pub fn getListLock() *SpinLock { /// Initialize threading system /// /// This function is called once, from kmain() -pub fn threadInitEarly() void { +pub fn initEarly() void { assert.debugAssert(@src(), arch.currCpuNum() == 0, "arch.currCpuNum() == 0"); // Initialize the thread list. This needs to be done manually now, since initial thread code diff --git a/slipstream/kernel/top/main.zig b/slipstream/kernel/top/main.zig index 6017659..5bef895 100644 --- a/slipstream/kernel/top/main.zig +++ b/slipstream/kernel/top/main.zig @@ -2,9 +2,10 @@ //! Use of this source code is governed by a BSD-style license that can be //! found in the LICENSE file. -const Init = @import("../lib/init/init.zig"); +const std = @import("std"); -const threadInitEarly = @import("../kernel/Thread.zig").threadInitEarly; +const Init = @import("../lib/init/init.zig"); +const Thread = @import("../kernel/Thread.zig"); // saved boot arguments from whoever loaded the system var lk_boot_args: [4]u64 = undefined; @@ -18,7 +19,7 @@ export fn lk_main(arg0: u64, arg1: u64, arg2: u64, arg3: u64) align(16) callconv lk_boot_args[3] = arg3; //Init.initLevelAll(.{ .primary_cpu = true }); - threadInitEarly(); + Thread.initEarly(); //Init.initLevelAll(.{ .secondary_cpus = true }); }