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/* 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; +} 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/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 89b6d10..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"); @@ -181,12 +182,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 +274,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/spin_lock.zig b/slipstream/kernel/arch/x86/spin_lock.zig index 7ddb270..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("../../lib/arch/x86/intrin.zig"); const ArchSpinLock = @import("../../kernel/arch/SpinLock.zig"); inline fn archSpinLockCore(lock: *ArchSpinLock, val: u32) void { diff --git a/slipstream/kernel/arch/x86/start.S b/slipstream/kernel/arch/x86/start.S index c36ff4a..5811a50 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 @@ -21,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 @@ -197,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 @@ -232,7 +246,15 @@ 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 + + // Move it into place. + mov %rax, %gs:SX_TLS_STACK_GUARD_OFFSET + // 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/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({ diff --git a/slipstream/kernel/build.zig b/slipstream/kernel/build.zig index 7aa5dff..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); @@ -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, }); @@ -41,9 +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 = "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 82b8890..e4795de 100644 --- a/slipstream/kernel/build.zig.zon +++ b/slipstream/kernel/build.zig.zon @@ -7,14 +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", + }, + .@"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 734c352..7eef5cc 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); @@ -25,8 +27,8 @@ pub fn getListLock() *SpinLock { /// Initialize threading system /// /// This function is called once, from kmain() -pub fn threadInitEarly() void { - assert.debug_assert(@src(), arch.currCpuNum() == 0); +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 // manipulates the list before global constructors are run. 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 bd7340d..e8b8d12 100644 --- a/slipstream/kernel/kernel/assert.zig +++ b/slipstream/kernel/kernel/assert.zig @@ -3,20 +3,24 @@ //! found in the LICENSE file. const std = @import("std"); +const builtin = @import("builtin"); + const debug = @import("../top/debug.zig"); -const build_options = @import("build_options"); + /// 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 +28,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 +41,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/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 new file mode 100644 index 0000000..d54f9af --- /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("lib/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/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; 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..c95fcd0 --- /dev/null +++ b/slipstream/kernel/lib/arch/src/root.zig @@ -0,0 +1,13 @@ +//! 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)); + +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/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 diff --git a/slipstream/kernel/root.zig b/slipstream/kernel/root.zig index df04f0b..fd3ba59 100644 --- a/slipstream/kernel/root.zig +++ b/slipstream/kernel/root.zig @@ -2,11 +2,14 @@ //! 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"); 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 +17,5 @@ comptime { _ = faults; _ = top; _ = mp; + _ = platform; } 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)) { 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 }); } diff --git a/slipstream/system/public/build.zig b/slipstream/system/public/build.zig new file mode 100644 index 0000000..0862131 --- /dev/null +++ b/slipstream/system/public/build.zig @@ -0,0 +1,29 @@ +//! 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, + }); + + 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..51627f9 --- /dev/null +++ b/slipstream/system/public/build.zig.zon @@ -0,0 +1,6 @@ +.{ + .name = .public, + .fingerprint = 0x3bb42e1dd601ba4b, + .version = "0.0.1", + .paths = .{""}, +} diff --git a/slipstream/system/public/slipstream/assert.zig b/slipstream/system/public/slipstream/assert.zig new file mode 100644 index 0000000..ed8197b --- /dev/null +++ b/slipstream/system/public/slipstream/assert.zig @@ -0,0 +1,21 @@ +//! 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"); + +//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 { + // 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 new file mode 100644 index 0000000..dabd8e2 --- /dev/null +++ b/slipstream/system/public/slipstream/internal.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. + +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..0a0da88 --- /dev/null +++ b/slipstream/system/public/slipstream/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 assert = @import("assert.zig"); + +comptime { + _ = assert; +} 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"); +} diff --git a/slipstream/system/ulib/hwreg/build.zig b/slipstream/system/ulib/hwreg/build.zig new file mode 100644 index 0000000..256cea4 --- /dev/null +++ b/slipstream/system/ulib/hwreg/build.zig @@ -0,0 +1,38 @@ +//! 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 = "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" }, + }; + + 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/hwreg/build.zig.zon b/slipstream/system/ulib/hwreg/build.zig.zon new file mode 100644 index 0000000..4a2d23c --- /dev/null +++ b/slipstream/system/ulib/hwreg/build.zig.zon @@ -0,0 +1,17 @@ +.{ + .name = .hwreg, + .fingerprint = 0x5a18493e51a3d0f, + .version = "0.0.1", + .paths = .{""}, + .dependencies = .{ + .@"slipstream/public" = .{ + .path = "../../../../slipstream/system/public", + }, + .@"ulib/mmio-ptr" = .{ + .path = "../../../../slipstream/system/ulib/mmio-ptr", + }, + .@"ulib/mock_function" = .{ + .path = "../../../../slipstream/system/ulib/mock_function", + }, + }, +} diff --git a/slipstream/system/ulib/hwreg/src/Mock.zig b/slipstream/system/ulib/hwreg/src/Mock.zig new file mode 100644 index 0000000..f5dbd5d --- /dev/null +++ b/slipstream/system/ulib/hwreg/src/Mock.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 Mock = @This(); + +const std = @import("std"); +const mock_function = @import("ulib/mock_function"); + +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. + +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 { + const Self = @This(); + mock: *Mock, + + pub fn write(self: *const Self, 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: *const Self, 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 { + const Self = @This(); + pub fn write(_: *const Self, 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(_: *const Self, 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/bitfields.zig b/slipstream/system/ulib/hwreg/src/bitfields.zig new file mode 100644 index 0000000..01bea6f --- /dev/null +++ b/slipstream/system/ulib/hwreg/src/bitfields.zig @@ -0,0 +1,2917 @@ +//! 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 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. +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"); + } + } + } + + const has_printer = PrinterState == EnablePrinter; + + return struct { + const Self = @This(); + + pub const SelfType = DerivedType; + pub const ValueType = IntType; + pub const PrinterEnabled = has_printer; + + params: internal.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) SelfType { + self.reg_value = value; + return SelfType{ .base = self.* }; + } + + 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 SelfType{ .base = 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 SelfType{ .base = 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)); + } + + /// 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)); + } + + pub fn forEachField(self: *Self, comptime callback: anytype) void { + if (!has_printer) { + @compileError("Pass 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 = 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()); + } + } + + 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 getParams(self: *Self) *internal.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(); + + reg_addr: u32, + + pub fn init(reg_addr: u32) Self { + return Self{ .reg_addr = reg_addr }; + } + + /// 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.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.base.setRegAddr(self.reg_addr); + _ = reg.base.setRegValue(value); + return reg; + } + + pub fn addr(self: Self) u32 { + return self.reg_addr; + } + }; +} + +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, 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: *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 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)); + } + }; +} + +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); +} + +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: *const ParentType) ParentType.ValueType { + if (unshifted) { + return BitfieldRefConst(ParentType.ValueType).initUnshifted(parent.base.regValuePtrConst(), bit_high, bit_low).get(); + } else { + return BitfieldRefConst(ParentType.ValueType).init(parent.base.regValuePtrConst(), 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: *const ParentType) EnumType { + const raw_value = BitfieldRefConst(ParentType.ValueType).init(parent.base.regValuePtrConst(), 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: *const ParentType) FieldType { + if (unshifted) { + return BitfieldRefConst(FieldType).initUnshifted(&@field(parent, name), bit_high, bit_low).get(); + } else { + return BitfieldRefConst(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: *const ParentType) EnumType { + const raw_value = BitfieldRefConst(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; + + field: IntType = 0, + + // 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 firstBit(self: *const Self) IntType { + return FirstBit.get(self); + } + + pub fn setFirstBit(self: *Self, value: IntType) *Self { + FirstBit.set(self, value); + return self; + } + + pub fn midBit(self: *const Self) IntType { + return MidBit.get(self); + } + + pub fn setMidBit(self: *Self, value: IntType) *Self { + MidBit.set(self, value); + return self; + } + + pub fn lastBit(self: *const 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, + + // 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"); + + pub fn wholeLength(self: *const Self) IntType { + return WholeLength.get(self); + } + + pub fn setWholeLength(self: *Self, value: IntType) *Self { + WholeLength.set(self, value); + return self; + } + + pub fn singleBit(self: *const Self) IntType { + return SingleBit.get(self); + } + + pub fn setSingleBit(self: *Self, value: IntType) *Self { + SingleBit.set(self, value); + return self; + } + + pub fn range1(self: *const Self) IntType { + return Range1.get(self); + } + + pub fn setRange1(self: *Self, value: IntType) *Self { + Range1.set(self, value); + return self; + } + + pub fn range2(self: *const Self) IntType { + return Range2.get(self); + } + + pub fn setRange2(self: *Self, value: IntType) *Self { + Range2.set(self, value); + return self; + } + }; +} + +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: *const Self) EnumWholeRange { + return WholeLength.get(self); + } + + pub fn setWholeLength(self: *Self, value: EnumWholeRange) *Self { + WholeLength.set(self, value); + return self; + } + + pub fn singleBit(self: *const Self) EnumBit { + return SingleBit.get(self); + } + + pub fn setSingleBit(self: *Self, value: EnumBit) *Self { + SingleBit.set(self, value); + return self; + } + + pub fn range1(self: *const Self) EnumRange { + return Range1.get(self); + } + + pub fn setRange1(self: *Self, value: EnumRange) *Self { + Range1.set(self, value); + return self; + } + + pub fn range2(self: *const 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 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); + } + }; + + 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(); + defer mock.deinit(); + + _ = 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); + } + }; + + 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); + } + + pub fn address(self: *const Self) u32 { + return Address.get(@constCast(self)); + } + + pub fn setAddress(self: *Self, value: u32) *Self { + Address.set(self, value); + return self; + } + + pub fn isPrefetchable(self: *const Self) u32 { + return IsPrefetchable.get(@constCast(self)); + } + + pub fn setIsPrefetchable(self: *Self, value: u32) *Self { + IsPrefetchable.set(self, value); + return self; + } + + pub fn is64Bit(self: *const Self) u32 { + return Is64Bit.get(@constCast(self)); + } + + pub fn setIs64Bit(self: *Self, value: u32) *Self { + Is64Bit.set(self, value); + return self; + } + + pub fn isIoSpace(self: *const Self) u32 { + return IsIoSpace.get(@constCast(self)); + } + + pub fn setIsIoSpace(self: *Self, value: u32) *Self { + IsIoSpace.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) u32 { + return self.base.regValue(); + } +}; + +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); + } + + // 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 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); + } +} + +const ConstexprArithmeticTestReg = struct { + const Self = @This(); + pub const ValueType = u32; + + base: RegisterBase(Self, u32, void) = .{}, + + const kTen: u32 = 10; + + // 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"); + + pub fn init() Self { + return Self{ + .base = .{}, + }; + } + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(0); + } + + pub fn field1(self: *const Self) u32 { + return Field1.get(@constCast(self)); + } + + pub fn setField1(self: *Self, value: u32) *Self { + Field1.set(self, value); + return self; + } + + pub fn field2(self: *const Self) u32 { + return Field2.get(@constCast(self)); + } + + pub fn setField2(self: *Self, value: u32) *Self { + Field2.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; + } +}; + +test "BitsAsConstexprArithmeticExpressions" { + var fake_reg: u32 = 1 << 31; + var mmio_reg = mmio.RegisterMmio.init(&fake_reg); + + var reg = ConstexprArithmeticTestReg.get().readFrom(&mmio_reg); + _ = reg.setField1(1); + _ = reg.setField2(0xabcd); + _ = reg.writeTo(&mmio_reg); +} + +const ConditionalFieldTestRegEnum = enum { + kA, + kB, + kC, +}; + +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; + + base: ParentType = .{}, + + const kA = condition == ConditionalFieldTestRegEnum.kA; + const kB = condition == ConditionalFieldTestRegEnum.kB; + const kC = condition == ConditionalFieldTestRegEnum.kC; + + // 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; + + // 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; + + // 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; + + // Unconditional, common field + pub const Common = DefField(Self, 2, 0, "common"); + + 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 get() RegisterAddr(Self) { + return RegisterAddr(Self).init(0); + } + + pub fn a7(self: *Self) ValueType { + comptime std.debug.assert(kA); + return A7.get(self); + } + + pub fn setA7(self: *Self, value: ValueType) *Self { + comptime std.debug.assert(kA); + A7.set(self, value); + return self; + } + + pub fn a6_4(self: *Self) ValueType { + comptime std.debug.assert(kA); + return A6_4.get(self); + } + + pub fn setA6_4(self: *Self, value: ValueType) *Self { + comptime std.debug.assert(kA); + A6_4.set(self, value); + return self; + } + + pub fn b7_5(self: *Self) ValueType { + comptime std.debug.assert(kB); + return B7_5.get(self); + } + + pub fn setB7_5(self: *Self, value: ValueType) *Self { + comptime std.debug.assert(kB); + B7_5.set(self, value); + return self; + } + + pub fn c7_4(self: *Self) Rsvp { + comptime std.debug.assert(kC); + return C7_4.get(self); + } + + pub fn setC7_4(self: *Self, value: Rsvp) *Self { + comptime std.debug.assert(kC); + C7_4.set(self, value); + return self; + } + + pub fn c3(self: *Self) ValueType { + comptime std.debug.assert(kC); + return C3.get(self); + } + + pub fn setC3(self: *Self, value: ValueType) *Self { + comptime std.debug.assert(kC); + C3.set(self, value); + return self; + } + + pub fn common(self: *Self) ValueType { + return Common.get(self); + } + + pub fn setCommon(self: *Self, value: ValueType) *Self { + Common.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 regValue(self: *const Self) ValueType { + return self.base.regValue(); + } + }; +} + +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; + + base: ParentType = .{}, + + const Enum = enum(u8) { + kA = 0b00, + kB = 0b11, + }; + + 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 init() Self { + var self = Self{ .base = .{} }; + A.init(&self); + B.init(&self); + C.init(&self); + D.init(&self); + return self; + } + + pub fn get() RegisterAddr(Self) { + return RegisterAddr(Self).init(0); + } + + pub fn a(self: *Self) ValueType { + return A.get(self); + } + + pub fn setA(self: *Self, value: ValueType) *Self { + A.set(self, value); + return self; + } + + pub fn b(self: *Self) ValueType { + return B.get(self); + } + + pub fn setB(self: *Self, value: ValueType) *Self { + B.set(self, value); + return self; + } + + pub fn c(self: *Self) ValueType { + return C.get(self); + } + + pub fn setC(self: *Self, value: ValueType) *Self { + C.set(self, value); + return self; + } + + pub fn d(self: *Self) Enum { + return D.get(self); + } + + pub fn setD(self: *Self, value: Enum) *Self { + D.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 regValue(self: *const Self) ValueType { + return self.base.regValue(); + } + }; +} + +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; + + base: ParentType = .{}, + + //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 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 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 xmit(self: *Self) ValueType { + return Xmit.get(self); + } + + pub fn setXmit(self: *Self, value: ValueType) *Self { + Xmit.set(self, value); + return self; + } + + pub fn a(self: *Self) ValueType { + return A.get(self); + } + + pub fn setA(self: *Self, value: ValueType) *Self { + A.set(self, value); + return self; + } + + pub fn b(self: *Self) ValueType { + return B.get(self); + } + + pub fn setB(self: *Self, value: ValueType) *Self { + B.set(self, value); + return self; + } + + pub fn c(self: *Self) ValueType { + return C.get(self); + } + + pub fn setC(self: *Self, value: ValueType) *Self { + C.set(self, value); + return self; + } + + pub fn d(self: *Self) ValueType { + return D.get(self); + } + + pub fn setD(self: *Self, value: ValueType) *Self { + D.set(self, value); + return self; + } + + pub fn e(self: *Self) ValueType { + return E.get(self); + } + + pub fn setE(self: *Self, value: ValueType) *Self { + E.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 regValue(self: *const Self) ValueType { + return self.base.regValue(); + } + }; +} + +test "ConditionalFields" { + { + const RegA = ConditionalFieldTestReg(ConditionalFieldTestRegEnum.kA); + + var fake_reg: u8 = 0xff; + var mmio_reg = mmio.RegisterMmio.init(&fake_reg); + + 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()); + } + + { + const RegB = ConditionalFieldTestReg(ConditionalFieldTestRegEnum.kB); + + var fake_reg: u8 = 0xff; + var mmio_reg = mmio.RegisterMmio.init(&fake_reg); + + 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()); + } + + { + const RegC = ConditionalFieldTestReg(ConditionalFieldTestRegEnum.kC); + + var fake_reg: u16 = 0xffff; + var mmio_reg = mmio.RegisterMmio.init(&fake_reg); + + 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()); + } + + { + const Reg = ConditionalFieldsWithSameNameTestReg(true); + + var fake_reg: u16 = 0xffff; + var mmio_reg = mmio.RegisterMmio.init(&fake_reg); + + 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); + } + + { + const Reg = ConditionalFieldsWithSameNameTestReg(false); + + var fake_reg: u16 = 0xffff; + var mmio_reg = mmio.RegisterMmio.init(&fake_reg); + + 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); + } + + // Check conditionals work as expected, depending on the condition, the right + // bit should be set. + // 14 -> false + // 12 -> true + { + 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); + } + + { + 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); + } + + { + 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); + } + + { + 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); + } +} + +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" { + const TestMmio = struct { + const Self = @This(); + fake_reg: u32, + + pub fn read(self: *const Self, comptime T: type, _: u32) T { + return @intCast(self.fake_reg); + } + + pub fn write(self: *Self, comptime T: type, value: T, _: u32) void { + self.fake_reg = @intCast(value); + } + }; + + var mmio_io = TestMmio{ .fake_reg = 0xffff_ffff }; + + { + var reg = TemplatedReg(0).get().readFrom(&mmio_io); + try testing.expect(reg.head() == 0b1); + } + + { + var reg = TemplatedReg(4).get().readFrom(&mmio_io); + try testing.expect(reg.head() == 0b11111); + } + + { + var reg = TemplatedReg(31).get().readFrom(&mmio_io); + try testing.expect(reg.head() == 0xffff_ffff); + } + + { + var reg = TemplatedReg(1).get().readFrom(&mmio_io); + _ = reg.setHead(0); + _ = reg.writeTo(&mmio_io); + try testing.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_io = TestMmio{ .fake_reg = kInitVal }; + + { + var reg = PrintableTestReg.get().readFrom(&mmio_io); + 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 { + testing.expectEqualStrings(expected[call_count_ptr.*], arg) catch unreachable; + call_count_ptr.* += 1; + } + }; + Printer.call_count_ptr = &call_count; + reg.print(Printer.printFn); + + try testing.expectEqual(Printer.expected.len, call_count); + } + + { + var reg = PrintableTestReg2.get().readFrom(&mmio_io); + 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 { + testing.expectEqualStrings(expected[call_count_ptr.*], arg) catch unreachable; + call_count_ptr.* += 1; + } + }; + Printer.call_count_ptr = &call_count; + reg.print(Printer.printFn); + + try testing.expectEqual(Printer.expected.len, call_count); + } +} + +test "Variant" { + const FakeIo = struct { + pub fn write(self: @This(), comptime IntType: type, value: IntType, offset: u32) void { + _ = self; + _ = offset; + slipstreamAssert(@src(), value == 17, "value == 17"); + } + + pub fn read(self: @This(), comptime IntType: type, offset: u32) IntType { + _ = self; + _ = offset; + return 23; + } + }; + + const TestRegForVariantIo = struct { + const Self = @This(); + pub const ValueType = u64; + const ParentType = RegisterBase(Self, ValueType, void); + pub const printer_enabled: bool = ParentType.PrinterEnabled; + + 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{ .base = .{} }; + ValueField.init(&self); + 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 setRegValue(self: *Self, value: u64) *Self { + return self.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 std.testing.expect(TestRegForVariantIo.ValueField.get(®) == 23); + _ = TestRegForVariantIo.ValueField.set(®, 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 std.testing.expect(TestRegForVariantIo.ValueField.get(®) == 17); + reg.setRegAddr(1); + 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 new file mode 100644 index 0000000..841ac67 --- /dev/null +++ b/slipstream/system/ulib/hwreg/src/internal.zig @@ -0,0 +1,274 @@ +//! 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 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 { + 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) { + 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 = "", + .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, + }; + } + + // 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 { + 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; + //} + } +}; + +/// 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; + } + } + }; +} + +// 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; + + for (0..num_fields) |i| { + const fmt_buf = fields[i].print(reg_value, &buf); + print_fn(fmt_buf); + } + + // 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; + 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); + + 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 }); + visited_one = true; + } + } + slipstreamAssert(@src(), visited_one, "visited_one"); +} + +/// 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/mmio.zig b/slipstream/system/ulib/hwreg/src/mmio.zig new file mode 100644 index 0000000..bfc2bf1 --- /dev/null +++ b/slipstream/system/ulib/hwreg/src/mmio.zig @@ -0,0 +1,96 @@ +//! 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 mmio_ptr = @import("ulib/mmio-ptr"); + +const internal = @import("internal.zig"); + +/// 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 { + return struct { + const Self = @This(); + + mmio: [*]volatile u8 = undefined, + + pub fn init(mmio: *volatile anyopaque) Self { + return Self{ .mmio = initPtr(mmio) }; + } + + /// Write |val| to the |@sizeOf(IntType)| byte field located |offset| bytes from + /// |base()|. + 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)); + + 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 + /// |base()|. + pub fn read(self: *const Self, comptime IntType: type, offset: u32) IntType { + const IoTypeForInt = IoType(IntType); + comptime std.debug.assert(@sizeOf(IntType) <= @sizeOf(IoTypeForInt)); + + 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 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); + } + }; +} + +pub const RegisterMmio = RegisterMmioScaled(void); 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 new file mode 100644 index 0000000..e1c2aec --- /dev/null +++ b/slipstream/system/ulib/hwreg/src/root.zig @@ -0,0 +1,17 @@ +//! 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"); +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; +} 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); +} 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..d66ac6e --- /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 unreachable; + 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(); +} diff --git a/slipstream/system/ulib/uart/build.zig b/slipstream/system/ulib/uart/build.zig new file mode 100644 index 0000000..4d4ab87 --- /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("src/root.zig"), + .target = target, + .optimize = optimize, + }); + + const deps = [_]struct { name: []const u8, dep_name: []const u8, module_name: []const u8 }{ + .{ .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| { + 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..821b435 --- /dev/null +++ b/slipstream/system/ulib/uart/build.zig.zon @@ -0,0 +1,20 @@ +.{ + .name = .uart, + .fingerprint = 0x7ed180f4050587a0, + .version = "0.0.1", + .paths = .{""}, + .dependencies = .{ + .@"lib/arch" = .{ + .path = "../../../../slipstream/kernel/lib/arch", + }, + .@"sdk/zbi_format" = .{ + .path = "../../../../sdk/lib/zbi-format", + }, + .@"ulib/mock_function" = .{ + .path = "../../../../slipstream/system/ulib/mock_function", + }, + .@"ulib/hwreg" = .{ + .path = "../../../../slipstream/system/ulib/hwreg", + }, + }, +} diff --git a/slipstream/system/ulib/uart/src/all.zig b/slipstream/system/ulib/uart/src/all.zig new file mode 100644 index 0000000..a84dff3 --- /dev/null +++ b/slipstream/system/ulib/uart/src/all.zig @@ -0,0 +1,491 @@ +//! 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 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 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 +/// 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 IoType: uart.IoRegisterType = .none; + //pub const driver_type: u32 = 0; + //pub const extra: u32 = 0; + + 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); + } + + // 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); + } + + // 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 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(self: *const Self) ConfigType { + return self.base.getConfig(); + } + + 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 WithAllDrivers = 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, +}; + +// 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 + 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 { + if (@typeInfo(UartDriver) != .@"union") { + @compileError("UartDriver must be a union type"); + } + + return struct { + const Self = @This(); + + /// 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 { + // 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 { + 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(), 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()), + }; + } + + /// 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"); + } + + /// 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: 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) }); + } + + // 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: *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. +// 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 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, IoProvider, 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 @Type(std.builtin.Type{ + .@"union" = std.builtin.Type.Union{ + .layout = .auto, + .tag_type = union_info.tag_type, + .fields = &fields, + .decls = &[_]std.builtin.Type.Declaration{}, + }, + }); + } + + variant: OneDriverVariant(), + + /// 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; + } + } + } + } + }; + var visitor = Visitor{ .self_ptr = &self }; + config.visitConst(Visitor.visitFn, .{&visitor}); + return self; + } + + /// 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; + } + } + } + }; + var visitor = Visitor{ .self_ptr = &self }; + hwreg_internal.visit(Visitor.visitFn, uart_driver, .{&visitor}); + + return self; + } + + /// 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); + const uart_config = uart.Config(DriverType.UartType){ .config = kernel_driver.getConfig(DriverType.DefaultLockPolicy) }; + visitor_self.result_ptr.* = Config(UartDriver).initFromConfig(DriverType.UartType, uart_config); + } + }; + + var visitor = Visitor{ .result_ptr = &result }; + self.visitConst(Visitor.visitFn, .{&visitor}); + return result; + } + + /// 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); + } + + /// 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; + } + } + } + }; + var visitor = Visitor{ .result_ptr = &result }; + self.visit(Visitor.visitFn, .{&visitor}); + + // Set to moved-from state + self.variant = undefined; + + return result; + } + + /// 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; + } + + /// 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, .{}); + } + }; +} 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..a800be6 --- /dev/null +++ b/slipstream/system/ulib/uart/src/interrupt.zig @@ -0,0 +1,92 @@ +//! 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 ReaderCtx: type, comptime DisablerCtx: type) type { + const ReaderCallbackFn = *const fn (*ReaderCtx) u8; + const DisablerCallbackFn = *const fn (*DisablerCtx) void; + + return struct { + lock: *Lock, + reader: ReaderCallbackFn, + disabler: DisablerCallbackFn, + reader_context: *ReaderCtx, + disabler_context: *DisablerCtx, + + const Self = @This(); + + 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(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_context); + } + + /// 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 DisablerCtx: type) type { + const DisablerCallbackFn = *const fn (*DisablerCtx) void; + + return struct { + lock: *Lock, + waiter: *Waiter, + disabler: DisablerCallbackFn, + disabler_context: *DisablerCtx = undefined, + + const Self = @This(); + + pub fn init(lock: *Lock, waiter: *Waiter, disabler: DisablerCallbackFn, disabler_context: *DisablerCtx) Self { + return .{ + .lock = lock, + .waiter = waiter, + .disabler = disabler, + .disabler_context = disabler_context, + }; + } + + /// 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(self.disabler_context); + } + + /// 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..5b28f11 --- /dev/null +++ b/slipstream/system/ulib/uart/src/mock.zig @@ -0,0 +1,395 @@ +//! 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 hwreg = @import("ulib/hwreg"); +const mock_function = @import("ulib/mock_function"); + +const uart = @import("uart.zig"); + +// 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 { + return struct { + const Self = @This(); + + io: hwreg.Mock, + + pub fn init(_: anytype, _: anytype) Self { + _ = Config; + _ = IoRegisterType; + 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; + + //pub const devicetree_bindings: [0][]const u8 = .{}; + pub const config_name: []const u8 = "mock"; + pub const IoType: 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, + + 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(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(); + } + + 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 getConfig(_: *const Self) ConfigType { + return ConfigType{}; + } + + 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 { + _ = 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 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, comptime LocalIoProviderType: type, io: *LocalIoProviderType) 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, enable: bool) void { + _ = io; + _ = enable; + _ = self.mock.call(.{Expected{ .tx_enable = ExpectedTxEnable{} }}); + } +}; + +/// 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 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(); + } + } + }; +} + +/// Mock Lock for testing synchronization +pub const Lock = struct { + const Self = @This(); + + mock: *mock_function.MockFunction(Driver.ExpectedResult, &[_]type{Driver.Expected}), + + pub fn init() Self { + return Self{ + .mock = undefined, + }; + } + + pub fn driverInit(self: *Self, driver: *Driver) void { + self.mock = &driver.mock; + } + + pub fn lock(self: *Self) void { + _ = self.mock.call(.{Driver.Expected{ .lock = Driver.ExpectedLock{ .unlock = false } }}); + } + + pub fn unlock(self: *Self) void { + _ = self.mock.call(.{Driver.Expected{ .lock = Driver.ExpectedLock{ .unlock = true } }}); + } + + pub fn assertHeld(self: *Self) void { + _ = self.mock.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}), + + pub fn init() Self { + return Self{ + .mock = undefined, + }; + } + + pub fn driverInit(self: *Self, driver: *Driver) void { + self.mock = &driver.mock; + } + + pub fn wait(self: *Self, comptime GuardType: type, guard: *GuardType, enableTxInterrupt: anytype, args: anytype) void { + _ = guard; + _ = args; + + 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(); + } + } +}; + +/// 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..bc319fb --- /dev/null +++ b/slipstream/system/ulib/uart/src/ns8250.zig @@ -0,0 +1,1473 @@ +//! 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 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; + +// 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: *const Self) ValueType { + return Data.get(@constCast(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: *const Self) ValueType { + return Data.get(@constCast(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: *const Self) ValueType { + return DmaRequestEnable.get(@constCast(self)); + } + + pub fn setDmaRequestEnable(self: *Self, value: ValueType) *Self { + DmaRequestEnable.set(self, value); + return self; + } + + pub fn uartEnable(self: *const Self) ValueType { + return UartEnable.get(@constCast(self)); + } + + pub fn setUartEnable(self: *Self, value: ValueType) *Self { + UartEnable.set(self, value); + return self; + } + + pub fn nrzCodingEnable(self: *const Self) ValueType { + return NrzCodingEnable.get(@constCast(self)); + } + + pub fn setNrzCodingEnable(self: *Self, value: ValueType) *Self { + NrzCodingEnable.set(self, value); + return self; + } + + pub fn receiverTimeOut(self: *const Self) ValueType { + return ReceiverTimeOut.get(@constCast(self)); + } + + pub fn setReceiverTimeOut(self: *Self, value: ValueType) *Self { + ReceiverTimeOut.set(self, value); + return self; + } + + pub fn modemStatus(self: *const Self) ValueType { + return ModemStatus.get(@constCast(self)); + } + + pub fn setModemStatus(self: *Self, value: ValueType) *Self { + ModemStatus.set(self, value); + return self; + } + + pub fn lineStatus(self: *const Self) ValueType { + return LineStatus.get(@constCast(self)); + } + + pub fn setLineStatus(self: *Self, value: ValueType) *Self { + LineStatus.set(self, value); + return self; + } + + pub fn txEmpty(self: *const Self) ValueType { + return TxEmpty.get(@constCast(self)); + } + + pub fn setTxEmpty(self: *Self, value: ValueType) *Self { + TxEmpty.set(self, value); + return self; + } + + pub fn rxAvailable(self: *const Self) ValueType { + return RxAvailable.get(@constCast(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: *const Self) ValueType { + return FifosEnabled.get(@constCast(self)); + } + + pub fn extendedFifoEnabled(self: *const Self) ValueType { + return ExtendedFifoEnabled.get(self); + } + + pub fn interruptId(self: *const 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: *const Self) ValueType { + return ReceiverTrigger.get(self); + } + + pub fn setReceiverTrigger(self: *Self, value: ValueType) *Self { + ReceiverTrigger.set(self, value); + return self; + } + + pub fn peripheralBus32bit(self: *const Self) ValueType { + return PeripheralBus32bit.get(self); + } + + pub fn setPeripheralBus32bit(self: *Self, value: ValueType) *Self { + PeripheralBus32bit.set(self, value); + return self; + } + + pub fn trailingBytes(self: *const Self) ValueType { + return TrailingBytes.get(self); + } + + pub fn setTrailingBytes(self: *Self, value: ValueType) *Self { + TrailingBytes.set(self, value); + return self; + } + + pub fn transmitTrigger(self: *const Self) ValueType { + if (is_pxa) { + return TransmitTriggerPxa.get(self); + } else { + return TransmitTriggerDw8250.get(self); + } + } + + 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: *const Self) ValueType { + return ExtendedFifoEnable.get(self); + } + + pub fn setExtendedFifoEnable(self: *Self, value: ValueType) *Self { + ExtendedFifoEnable.set(self, value); + return self; + } + + pub fn dmaMode(self: *const Self) ValueType { + return DmaMode.get(self); + } + + pub fn setDmaMode(self: *Self, value: ValueType) *Self { + DmaMode.set(self, value); + return self; + } + + pub fn txFifoReset(self: *const Self) ValueType { + return TxFifoReset.get(self); + } + + pub fn setTxFifoReset(self: *Self, value: ValueType) *Self { + TxFifoReset.set(self, value); + return self; + } + + pub fn rxFifoReset(self: *const Self) ValueType { + return RxFifoReset.get(self); + } + + pub fn setRxFifoReset(self: *Self, value: ValueType) *Self { + RxFifoReset.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 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: *const Self) ValueType { + return DivisorLatchAccess.get(self); + } + + pub fn setDivisorLatchAccess(self: *Self, value: ValueType) *Self { + DivisorLatchAccess.set(self, value); + return self; + } + + pub fn breakControl(self: *const Self) ValueType { + return BreakControl.get(self); + } + + pub fn setBreakControl(self: *Self, value: ValueType) *Self { + BreakControl.set(self, value); + return self; + } + + pub fn stickParity(self: *const Self) ValueType { + return StickParity.get(self); + } + + pub fn setStickParity(self: *Self, value: ValueType) *Self { + StickParity.set(self, value); + return self; + } + + pub fn evenParity(self: *const Self) ValueType { + return EvenParity.get(self); + } + + pub fn setEvenParity(self: *Self, value: ValueType) *Self { + EvenParity.set(self, value); + return self; + } + + pub fn parityEnable(self: *const Self) ValueType { + return ParityEnable.get(self); + } + + pub fn setParityEnable(self: *Self, value: ValueType) *Self { + ParityEnable.set(self, value); + return self; + } + + pub fn stopBits(self: *const Self) ValueType { + return StopBits.get(self); + } + + pub fn setStopBits(self: *Self, value: ValueType) *Self { + StopBits.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 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: *const Self) ValueType { + return AutomaticFlowControlEnable.get(self); + } + + pub fn setAutomaticFlowControlEnable(self: *Self, value: ValueType) *Self { + AutomaticFlowControlEnable.set(self, value); + return self; + } + + pub fn loop(self: *const Self) ValueType { + return Loop.get(self); + } + + pub fn setLoop(self: *Self, value: ValueType) *Self { + Loop.set(self, value); + return self; + } + + pub fn auxiliaryOut2(self: *const Self) ValueType { + return AuxiliaryOut2.get(self); + } + + pub fn setAuxiliaryOut2(self: *Self, value: ValueType) *Self { + AuxiliaryOut2.set(self, value); + return self; + } + + pub fn auxiliaryOut1(self: *const Self) ValueType { + return AuxiliaryOut1.get(self); + } + + pub fn setAuxiliaryOut1(self: *Self, value: ValueType) *Self { + AuxiliaryOut1.set(self, value); + return self; + } + + pub fn requestToSend(self: *const Self) ValueType { + return RequestToSend.get(self); + } + + pub fn setRequestToSend(self: *Self, value: ValueType) *Self { + RequestToSend.set(self, value); + return self; + } + + pub fn dataTerminalReady(self: *const 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: *const Self) ValueType { + return ErrorInRxFifo.get(self); + } + + pub fn txEmpty(self: *const Self) ValueType { + return TxEmpty.get(self); + } + + pub fn txRegisterEmpty(self: *const Self) ValueType { + return TxRegisterEmpty.get(self); + } + + pub fn breakInterrupt(self: *const Self) ValueType { + return BreakInterrupt.get(self); + } + + pub fn framingError(self: *const Self) ValueType { + return FramingError.get(self); + } + + pub fn parityError(self: *const Self) ValueType { + return ParityError.get(self); + } + + pub fn overrunError(self: *const Self) ValueType { + return OverrunError.get(self); + } + + pub fn dataReady(self: *const 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: *const Self) ValueType { + return DataCarrierDetect.get(self); + } + + pub fn ringIndicator(self: *const Self) ValueType { + return RingIndicator.get(self); + } + + pub fn dataSetReady(self: *const Self) ValueType { + return DataSetReady.get(self); + } + + pub fn clearToSend(self: *const Self) ValueType { + return ClearToSend.get(self); + } + + pub fn deltaDataCarrierDetect(self: *const Self) ValueType { + return DeltaDataCarrierDetect.get(self); + } + + pub fn trailingEdgeRingIndicator(self: *const Self) ValueType { + return TrailingEdgeRingIndicator.get(self); + } + + pub fn deltaDataSetReady(self: *const Self) ValueType { + return DeltaDataSetReady.get(self); + } + + pub fn deltaClearToSend(self: *const 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: *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 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: *const 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: *const 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: *const Self) ValueType { + return ReceiveFifoFull.get(self); + } + + pub fn receiveFifoNotEmpty(self: *const Self) ValueType { + return ReceiveFifoNotEmpty.get(self); + } + + pub fn transmitFifoEmpty(self: *const Self) ValueType { + return TransmitFifoEmpty.get(self); + } + + pub fn transmitFifoNotFull(self: *const Self) ValueType { + return TransmitFifoNotFull.get(self); + } + + pub fn uartBusy(self: *const 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, 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; + } + 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 IoType = Base.IoType; + + 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 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); + } + } + return Base.tryMatchString(string); + } + + 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 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) { + // 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).setRxFifoReset(1).setTxFifoReset(1); + _ = fcr.setReceiverTrigger(0); // Trigger at 1 byte in the rx fifo. + _ = fcr.setTransmitTrigger(0); // 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.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.setTransmitTrigger(0); + } else { + _ = fcr.setExtendedFifoEnable(1); + } + _ = 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.setDataTerminalReady(1).setRequestToSend(1).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).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, _: 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; + 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, comptime IoProviderType: type, io: *IoProviderType) ?u8 { + 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, comptime IoProviderType: type, io: *IoProviderType, enable: bool) void { + var ier = Self.InterruptEnableRegister.get().readFrom(io.getIo()); + _ = ier.setTxEmpty(if (enable) 1 else 0).writeTo(io.getIo()); + } + + 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()); + } + + 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(context); + } + // 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(context); + } + } + + 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) { + 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 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, 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(_: anytype) u8 { + return @intCast(RxBufferRegister.get().readFrom(io.getIo()).data()); + } + }; + + const DisableContext = struct { + driver: *DriverImpl, + should_drain_rx: *bool, + + fn callback(ctx: *@This()) void { + // If the buffer is full, disable the receive interrupt instead and + // exit the loop + ctx.driver.enableRxInterrupt(IoProviderType, ctx.io_prov, false); + ctx.should_drain_rx.* = false; + } + }; + + const disableContext = DisableContext{ .should_drain_rx = &should_drain_rx }; + const RxInterruptType = uart.RxInterrupt( + LockType, + ReadCharContext, + DisableContext, + ); + + var rxIrq = RxInterruptType.init( + lock, + ReadCharContext.callback, + {}, + disableContext.callback, + &disableContext, + ); + rx(&rxIrq, rxContext); + lsr = LineStatusRegister.get().readFrom(io.getIo()); + } + } + } + + pub fn getConfig(self: *const Self) ConfigType { + return self.base.cfg; + } + + 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, 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.hardwareInit(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.hardwareInit(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.hardwareInit(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.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 new file mode 100644 index 0000000..627a3bb --- /dev/null +++ b/slipstream/system/ulib/uart/src/null.zig @@ -0,0 +1,154 @@ +//! 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("sdk/zbi_format"); + +const uart = @import("uart.zig"); + +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 IoType: uart.IoRegisterType = .none; + pub const driver_type: u32 = 0; + pub const extra: u32 = 0; + + pub fn tryMatchString(str: []const u8) ?uart.Config(Driver) { + if (std.mem.eql(u8, str, config_name)) { + return uart.Config(Driver).init(); + } + return null; + } + + 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 { + _ = 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 hardwareInit(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..60e2c7d --- /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("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. +/// +/// 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/pl011.zig b/slipstream/system/ulib/uart/src/pl011.zig new file mode 100644 index 0000000..a2775a7 --- /dev/null +++ b/slipstream/system/ulib/uart/src/pl011.zig @@ -0,0 +1,1276 @@ +//! 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("sdk/zbi_format"); +const hwreg = @import("ulib/hwreg"); + +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; +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.IrqFlags{ + .level_triggered = true, + .polarity_high = true, + }).toInt(), +}; + +/// 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); + 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 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 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.cfg; + } + + pub fn getIoSlots(self: *const Self) usize { + return self.base.getIoSlots(); + } + + /// Initialize the UART driver + 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. + 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: anytype) 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, 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; + 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: *@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: *@This()) void { + // If the buffer is full, disable the receive interrupt instead + // and stop checking. + ctx.driver.enableRxInterrupt(IoProviderType, ctx.io_prov, false); + ctx.full_ptr.* = true; + } + }; + + 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(&rxIrq, rxContext); + } + } + + if (misr.tx() != 0) { + const DisableContext = struct { + driver: *Driver, + io_prov: *IoProviderType, + + fn callback(ctx: *@This()) void { + ctx.driver.enableTxInterrupt(IoProviderType, ctx.io_prov, false); + } + }; + + const TxInterruptType = uart_interrupt.TxInterrupt(LockType, WaiterType, DisableContext); + var disableContext = DisableContext{ .driver = self, .io_prov = io }; + var txIrq = TxInterruptType.init( + lock, + waiter, + DisableContext.callback, + &disableContext, + ); + tx(&txIrq, txContext); + } + } +}; + +// Test driver and configuration +const SimpleTestDriver = uart.KernelDriver(Driver, mock.IoProvider, 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.hardwareInit(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.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).?); +} + +// 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.hardwareInit(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.hardwareInit(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 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" { + 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.initWithTag(UnsyncronizedLock, 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.initWithTag(UnsyncronizedLock, 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.initWithTag(UnsyncronizedLock, 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.initWithTag(UnsyncronizedLock, 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/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/src/sync.zig b/slipstream/system/ulib/uart/src/sync.zig new file mode 100644 index 0000000..12d8582 --- /dev/null +++ b/slipstream/system/ulib/uart/src/sync.zig @@ -0,0 +1,76 @@ +//! 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 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. +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 { + return struct { + pub const Self = @This(); + pub fn init() Self { + _ = MemberOf; + 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 { + return struct { + pub const Self = @This(); + pub fn initWithTag(comptime LockType: type, lock: *LockType, comptime _: std.builtin.SourceLocation) Self { + _ = LockPolicy; + _ = lock; + return .{}; + } + pub fn deinit(_: *Self) void {} + }; + } + + /// 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 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; + _ = enableTxInterrupt; + _ = 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/test/driver_tests.zig b/slipstream/system/ulib/uart/src/test/driver_tests.zig new file mode 100644 index 0000000..0e71fe1 --- /dev/null +++ b/slipstream/system/ulib/uart/src/test/driver_tests.zig @@ -0,0 +1,399 @@ +//! 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("sdk/zbi_format"); + +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; + +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, mock.SyncPolicy); + var driver = TestDriver.init(mock_uart); + defer driver.deinit(); + + driver.mockInit(); + driver.hardwareInit(mock.Locking); + try testing.expectEqual(3, driver.write(mock.Locking, "hi!", .{})); + try testing.expectEqual(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, 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(3, driver.write(mock.NoopLocking, "hi!", .{})); + try testing.expectEqual(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, mock.SyncPolicy); + var driver = TestDriver.init(mock_uart); + defer driver.deinit(); + + driver.mockInit(); + driver.hardwareInit(mock.Locking); + try testing.expectEqual(3, driver.write(mock.Locking, "hi!", .{})); + try testing.expectEqual(12, driver.write(mock.Locking, "hello world\n", .{})); +} + +test "uart config" { + var all_config: all.Config(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); + } + + // 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(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 + } + } + }.visitFn, .{}); + + // From kernel driver + const pio_cfg = driver_config.SimplePioConfig{ + .base = 0x3f8, + .reserved = 0, + .irq = 3, + }; + + const TestKernelDriver = uart.KernelDriver(ns8250.PioDriver, mock.IoProvider, sync.UnsynchronizedPolicy); + var driver = TestKernelDriver.init(pio_cfg); + defer driver.deinit(); + + var all_configs = all.Config(all.Driver).initFromUart(ns8250.PioDriver, driver.takeUart(TestKernelDriver.DefaultLockPolicy)); + + // 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(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 + } + } + }.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(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 + } + } + }.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, sync.UnsynchronizedPolicy); + var driver1 = TestKernelDriver1.init(pio_cfg1); + defer driver.deinit(); + + 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(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 + } + } + }.visitFn, .{}); + } +} + +test "all config" { + const AllDriver = all.KernelDriver(mock.IoProvider, 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, 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(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 + } + } + }.visitFn, .{}); + + // Constructor + var all_driver2 = all.KernelDriver(mock.IoProvider, 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(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 + } + } + }.visitFn, .{}); +} + +test "uart null driver" { + 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(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, 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!", {}); + testing.expectEqual(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) { + try testing.expect(false); // Unexpected driver type + } else { + var mut_drv = drv; + 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(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/src/test/parsing_tests.zig b/slipstream/system/ulib/uart/src/test/parsing_tests.zig new file mode 100644 index 0000000..a18848f --- /dev/null +++ b/slipstream/system/ulib/uart/src/test/parsing_tests.zig @@ -0,0 +1,276 @@ +//! 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 ns8250 = @import("../ns8250.zig"); +const parse = @import("../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.tryMatch("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.tryMatch("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.tryMatch("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); +} diff --git a/slipstream/system/ulib/uart/src/uart.zig b/slipstream/system/ulib/uart/src/uart.zig new file mode 100644 index 0000000..b6c87f9 --- /dev/null +++ b/slipstream/system/ulib/uart/src/uart.zig @@ -0,0 +1,527 @@ +//! 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 zbi_format = @import("sdk/zbi_format"); +const hwreg = @import("ulib/hwreg"); + +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; + +// 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 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 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 = &.{}; + + // Register Io Type. + pub const IoType: IoRegisterType = IoRegType; + + pub const extra: u32 = drvExtra; + + cfg: 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)) { + // return Config.init(@ptrCast(*const config_type, payload).*); + // } + // 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", .{}); + 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(KdrvConfig, 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 initWithConfig(config: ConfigType) Self { + return Self{ .cfg = config }; + } + + pub fn initWithTaggedConfig(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."); + } + + // 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 { + 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 initWithBase(cfg: ConfigType, io_slots: usize, base: *volatile anyopaque) Self { + _ = IoType; + _ = base; + _ = 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); +} + +// 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.mmio.RegisterMmio, + mmio_scaled: hwreg.mmio.RegisterMmioScaled(u32), + }, + + 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.driver_config.SimpleDriverConfig, io_slots: usize, comptime mapMmio: fn (u64, usize) *volatile anyopaque) Self { + switch (IoType) { + .mmio8 => { + return Self{ + .io_reg = .{ .mmio = hwreg.mmio.RegisterMmio.init(mapMmio(cfg.mmio_phys, io_slots)) }, + }; + }, + .mmio32 => { + return Self{ + .io_reg = .{ .mmio_scaled = hwreg.mmio.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 getIo(self: *Self) *@TypeOf(self.io_reg) { + return &self.io_reg; + } + }; +} + +// 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.pio.RegisterDirectPio, + + pub fn init(cfg: zbi_format.driver_config.SimplePioConfig, io_slots: u16) Self { + std.debug.assert(io_slots > 0); + return Self{ + .io_reg = hwreg.pio.RegisterDirectPio.initWithBase(cfg.base), + }; + } + + pub fn deinit(self: *Self) void { + _ = self; + } + + pub fn getIo(self: *Self) *hwreg.pio.RegisterDirectPio { + return &self.io_reg; + } + }; +} + +pub fn KernelDriver(comptime UartDriver: type, comptime IoProvider: IoProviderFactory, 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; + + const IoProviderType = IoProvider(UartType.ConfigType, UartType.IoType); + + 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: anytype) Self { + var self = Self{ + .lock = SyncPolicy.Lock(Self).init(), + .waiter = Waiter.init(), + .uart = UartType.init(args), + .io = undefined, + }; + self.io = IoProviderType.init(self.uart.getConfig(), self.uart.getIoSlots()); + + 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.driverInit(&self.uart); + self.waiter.driverInit(&self.uart); + } + } + + 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 { + var guard = SyncPolicy.Guard(LockPolicy).initWithTag(Lock(Self), @constCast(&self.lock), @src()); + 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 { + 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 { + var guard = SyncPolicy.Guard(LockPolicy).initWithTag(Lock(Self), @constCast(&self.lock), @src()); + defer guard.deinit(); + + return self.uart.getConfig(); + } + + // 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 hardwareInit(self: *Self, comptime LockPolicy: type) void { + var guard = SyncPolicy.Guard(LockPolicy).initWithTag(Lock(Self), &self.lock, @src()); + defer guard.deinit(); + + self.uart.hardwareInit(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).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).initWithTag(Lock(Self), &self.lock, @src()); + defer guard.deinit(); + + self.uart.initInterrupt(IoProviderType, &self.io, enableInterruptCallback, context); + } + + 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, 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 { + var chars = chars_from.CharsFrom(true).init(str); // Massage into u8 with \n -> CRLF. + var it = chars.begin(); + + var guard = SyncPolicy.Guard(LockPolicy).initWithTag(Lock(Self), &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(LockPolicy), &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).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).initWithTag(Lock(Self), &self.lock, @src()); + defer guard.deinit(); + + self.uart.enableRxInterrupt(IoProviderType, &self.io, true); + } + }; +} + +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, + }; +}