diff --git a/port/raspberrypi/rp2xxx/src/hal.zig b/port/raspberrypi/rp2xxx/src/hal.zig index 143aaba20..6d298e967 100644 --- a/port/raspberrypi/rp2xxx/src/hal.zig +++ b/port/raspberrypi/rp2xxx/src/hal.zig @@ -4,7 +4,6 @@ const microzig = @import("microzig"); const SIO = microzig.chip.peripherals.SIO; pub const adc = @import("hal/adc.zig"); -pub const atomic = @import("hal/atomic.zig"); pub const clocks = @import("hal/clocks.zig"); pub const dma = @import("hal/dma.zig"); pub const flash = @import("hal/flash.zig"); @@ -37,7 +36,15 @@ comptime { // HACK: tests can't access microzig. maybe there's a better way to do this. if (!builtin.is_test) { _ = image_def; + } +} +// On the RP2040, we need to import the `atomic.zig` file to export some global +// functions that are used by the atomic builtins. Other chips have hardware +// atomics, so we don't need to export those functions for them. + +comptime { + if (!builtin.is_test and compatibility.chip == .RP2040) { _ = @import("hal/atomic.zig"); } } diff --git a/port/raspberrypi/rp2xxx/src/hal/atomic.zig b/port/raspberrypi/rp2xxx/src/hal/atomic.zig index 67626bccd..f2c1ec995 100644 --- a/port/raspberrypi/rp2xxx/src/hal/atomic.zig +++ b/port/raspberrypi/rp2xxx/src/hal/atomic.zig @@ -1,3 +1,11 @@ +// Based on: https://github.com/ziglang/zig/blob/79460d4a3eef8eb927b02a7eda8bc9999a766672/lib/compiler_rt/atomics.zig +// and: https://github.com/raspberrypi/pico-sdk/blob/ee68c78d0afae2b69c03ae1a72bf5cc267a2d94c/src/rp2_common/pico_atomic/atomic.c + +//! Atomic operations for RP2040 +//! +//! These functions should not be called directly. Instead, use the Zig atomic +//! builtins or `std.atomic.Value`. + const std = @import("std"); const builtin = @import("builtin"); const microzig = @import("microzig"); @@ -15,6 +23,12 @@ inline fn atomic_unlock(critical_section: CriticalSection) void { atomic_spinlock.unlock_irq(critical_section); } +fn atomic_store(comptime T: type, ptr: *volatile T, val: T, _: i32) void { + const save = atomic_lock(); + defer atomic_unlock(save); + ptr.* = val; +} + fn atomic_load(comptime T: type, ptr: *volatile T, _: i32) T { const save = atomic_lock(); defer atomic_unlock(save); @@ -22,53 +36,180 @@ fn atomic_load(comptime T: type, ptr: *volatile T, _: i32) T { return val; } -fn atomic_rmw_and(comptime T: type, ptr: *volatile T, val: T, _: i32) T { +fn atomic_rmw(comptime T: type, ptr: *volatile T, val: T, _: i32, comptime op: std.builtin.AtomicRmwOp) T { const save = atomic_lock(); defer atomic_unlock(save); const tmp = ptr.*; - ptr.* = tmp & val; + + switch (op) { + .Xchg => ptr.* = val, + .Add => ptr.* += val, + .Sub => ptr.* -= val, + .And => ptr.* &= val, + .Or => ptr.* |= val, + .Xor => ptr.* ^= val, + .Nand => ptr.* = ~(ptr.* & val), + .Max => ptr.* = @max(ptr.*, val), + .Min => ptr.* = @min(ptr.*, val), + } + return tmp; } -fn atomic_rmw_or(comptime T: type, ptr: *volatile T, val: T, _: i32) T { +fn atomic_compare_exchange(comptime T: type, ptr: *volatile T, expected: *T, desired: T, _: i32, _: i32) bool { const save = atomic_lock(); defer atomic_unlock(save); - const tmp = ptr.*; - ptr.* = tmp | val; - return tmp; + const old_value = ptr.*; + + if (old_value == expected.*) { + ptr.* = desired; + return true; + } else { + expected.* = old_value; + return false; + } } -fn __atomic_fetch_and_4(ptr: *u32, val: u32, model: i32) callconv(.c) u32 { - return atomic_rmw_and(u32, ptr, val, model); +export fn __atomic_store_1(ptr: *u8, val: u8, model: i32) callconv(.c) void { + atomic_store(u8, ptr, val, model); } -fn __atomic_fetch_or_4(ptr: *u32, val: u32, model: i32) callconv(.c) u32 { - return atomic_rmw_or(u32, ptr, val, model); +export fn __atomic_store_2(ptr: *u16, val: u16, model: i32) callconv(.c) void { + atomic_store(u16, ptr, val, model); } -fn __atomic_load_4(src: *u32, model: i32) callconv(.c) u32 { - return atomic_load(u32, src, model); +export fn __atomic_store_4(ptr: *u32, val: u32, model: i32) callconv(.c) void { + atomic_store(u32, ptr, val, model); } -const linkage: std.builtin.GlobalLinkage = if (builtin.is_test) - .internal -else if (builtin.object_format == .c) - .strong -else - .weak; +export fn __atomic_load_1(ptr: *u8, model: i32) callconv(.c) u8 { + return atomic_load(u8, ptr, model); +} -const visibility: std.builtin.SymbolVisibility = .default; +export fn __atomic_load_2(ptr: *u16, model: i32) callconv(.c) u16 { + return atomic_load(u16, ptr, model); +} -// Based on: https://github.com/ziglang/zig/blob/79460d4a3eef8eb927b02a7eda8bc9999a766672/lib/compiler_rt/atomics.zig -// and: https://github.com/raspberrypi/pico-sdk/blob/ee68c78d0afae2b69c03ae1a72bf5cc267a2d94c/src/rp2_common/pico_atomic/atomic.c -// TODO: export all the rest atomics +export fn __atomic_load_4(ptr: *u32, model: i32) callconv(.c) u32 { + return atomic_load(u32, ptr, model); +} -comptime { - // This export should only happen for the RP2040 due to the ARMv6-M ISA used by the ARM Cortex-M0+. - if (chip == .RP2040) { - @export(&__atomic_fetch_and_4, .{ .name = "__atomic_fetch_and_4", .linkage = linkage, .visibility = visibility }); - @export(&__atomic_fetch_or_4, .{ .name = "__atomic_fetch_or_4", .linkage = linkage, .visibility = visibility }); +export fn __atomic_exchange_1(ptr: *u8, val: u8, model: i32) callconv(.c) u8 { + return atomic_rmw(u8, ptr, val, model, .Xchg); +} - @export(&__atomic_load_4, .{ .name = "__atomic_load_4", .linkage = linkage, .visibility = visibility }); - } +export fn __atomic_exchange_2(ptr: *u16, val: u16, model: i32) callconv(.c) u16 { + return atomic_rmw(u16, ptr, val, model, .Xchg); +} + +export fn __atomic_exchange_4(ptr: *u32, val: u32, model: i32) callconv(.c) u32 { + return atomic_rmw(u32, ptr, val, model, .Xchg); +} + +export fn __atomic_fetch_add_1(ptr: *u8, val: u8, model: i32) callconv(.c) u8 { + return atomic_rmw(u8, ptr, val, model, .Add); +} + +export fn __atomic_fetch_add_2(ptr: *u16, val: u16, model: i32) callconv(.c) u16 { + return atomic_rmw(u16, ptr, val, model, .Add); +} + +export fn __atomic_fetch_add_4(ptr: *u32, val: u32, model: i32) callconv(.c) u32 { + return atomic_rmw(u32, ptr, val, model, .Add); +} + +export fn __atomic_fetch_sub_1(ptr: *u8, val: u8, model: i32) callconv(.c) u8 { + return atomic_rmw(u8, ptr, val, model, .Sub); +} + +export fn __atomic_fetch_sub_2(ptr: *u16, val: u16, model: i32) callconv(.c) u16 { + return atomic_rmw(u16, ptr, val, model, .Sub); +} + +export fn __atomic_fetch_sub_4(ptr: *u32, val: u32, model: i32) callconv(.c) u32 { + return atomic_rmw(u32, ptr, val, model, .Sub); +} + +export fn __atomic_fetch_and_1(ptr: *u8, val: u8, model: i32) callconv(.c) u8 { + return atomic_rmw(u8, ptr, val, model, .And); +} + +export fn __atomic_fetch_and_2(ptr: *u16, val: u16, model: i32) callconv(.c) u16 { + return atomic_rmw(u16, ptr, val, model, .And); +} + +export fn __atomic_fetch_and_4(ptr: *u32, val: u32, model: i32) callconv(.c) u32 { + return atomic_rmw(u32, ptr, val, model, .And); +} + +export fn __atomic_fetch_or_1(ptr: *u8, val: u8, model: i32) callconv(.c) u8 { + return atomic_rmw(u8, ptr, val, model, .Or); +} + +export fn __atomic_fetch_or_2(ptr: *u16, val: u16, model: i32) callconv(.c) u16 { + return atomic_rmw(u16, ptr, val, model, .Or); +} + +export fn __atomic_fetch_or_4(ptr: *u32, val: u32, model: i32) callconv(.c) u32 { + return atomic_rmw(u32, ptr, val, model, .Or); +} + +export fn __atomic_fetch_xor_1(ptr: *u8, val: u8, model: i32) callconv(.c) u8 { + return atomic_rmw(u8, ptr, val, model, .Xor); +} + +export fn __atomic_fetch_xor_2(ptr: *u16, val: u16, model: i32) callconv(.c) u16 { + return atomic_rmw(u16, ptr, val, model, .Xor); +} + +export fn __atomic_fetch_xor_4(ptr: *u32, val: u32, model: i32) callconv(.c) u32 { + return atomic_rmw(u32, ptr, val, model, .Xor); +} + +export fn __atomic_fetch_nand_1(ptr: *u8, val: u8, model: i32) callconv(.c) u8 { + return atomic_rmw(u8, ptr, val, model, .Nand); +} + +export fn __atomic_fetch_nand_2(ptr: *u16, val: u16, model: i32) callconv(.c) u16 { + return atomic_rmw(u16, ptr, val, model, .Nand); +} + +export fn __atomic_fetch_nand_4(ptr: *u32, val: u32, model: i32) callconv(.c) u32 { + return atomic_rmw(u32, ptr, val, model, .Nand); +} + +export fn __atomic_fetch_max_1(ptr: *u8, val: u8, model: i32) callconv(.c) u8 { + return atomic_rmw(u8, ptr, val, model, .Max); +} + +export fn __atomic_fetch_max_2(ptr: *u16, val: u16, model: i32) callconv(.c) u16 { + return atomic_rmw(u16, ptr, val, model, .Max); +} + +export fn __atomic_fetch_max_4(ptr: *u32, val: u32, model: i32) callconv(.c) u32 { + return atomic_rmw(u32, ptr, val, model, .Max); +} + +export fn __atomic_fetch_min_1(ptr: *u8, val: u8, model: i32) callconv(.c) u8 { + return atomic_rmw(u8, ptr, val, model, .Min); +} + +export fn __atomic_fetch_min_2(ptr: *u16, val: u16, model: i32) callconv(.c) u16 { + return atomic_rmw(u16, ptr, val, model, .Min); +} + +export fn __atomic_fetch_min_4(ptr: *u32, val: u32, model: i32) callconv(.c) u32 { + return atomic_rmw(u32, ptr, val, model, .Min); +} + +export fn __atomic_compare_exchange_1(ptr: *u8, expected: *u8, desired: u8, success: i32, failure: i32) callconv(.c) bool { + return atomic_compare_exchange(u8, ptr, expected, desired, success, failure); +} + +export fn __atomic_compare_exchange_2(ptr: *u16, expected: *u16, desired: u16, success: i32, failure: i32) callconv(.c) bool { + return atomic_compare_exchange(u16, ptr, expected, desired, success, failure); +} + +export fn __atomic_compare_exchange_4(ptr: *u32, expected: *u32, desired: u32, success: i32, failure: i32) callconv(.c) bool { + return atomic_compare_exchange(u32, ptr, expected, desired, success, failure); }