diff --git a/examples/nordic/nrf5x/README.md b/examples/nordic/nrf5x/README.md index 8c640cd1a..5360fed9d 100644 --- a/examples/nordic/nrf5x/README.md +++ b/examples/nordic/nrf5x/README.md @@ -2,6 +2,23 @@ HALs and register definitions for nrf5x devices +## SoftDevice examples + +- `pca10040_softdevice_preflashed_blinky`: + app-only build for a target with SoftDevice-shifted flash/RAM. Use this when + SoftDevice is already flashed on the chip. +- `nrf52840_mdk_softdevice_merged_blinky`: + emits both app artifacts and a merged Intel HEX that includes the app image + and `s140` SoftDevice image. +- `nrf52840_dongle_softdevice_merged_blinky`: + same merged approach for the nRF52840 Dongle with `s140`. + +Build one SoftDevice example: + +```sh +zig build -Dexample=nrf52840_mdk_softdevice_merged_blinky +``` + ## Renode supports: - nrf52840 development kit diff --git a/examples/nordic/nrf5x/build.zig b/examples/nordic/nrf5x/build.zig index 8bd330893..936bddb9f 100644 --- a/examples/nordic/nrf5x/build.zig +++ b/examples/nordic/nrf5x/build.zig @@ -17,29 +17,88 @@ pub fn build(b: *std.Build) void { const pca10040 = mb.ports.nrf5x.boards.nordic.pca10040; const microbit_v1 = mb.ports.nrf5x.boards.bbc.microbit_v1; const microbit_v2 = mb.ports.nrf5x.boards.bbc.microbit_v2; + const pca10040_s132 = mb.ports.nrf5x.softdevice.boards.nordic.pca10040_s132; + const nrf52840_dongle_s140 = mb.ports.nrf5x.softdevice.boards.nordic.nrf52840_dongle_s140; + const nrf52840_mdk_s140 = mb.ports.nrf5x.softdevice.boards.nordic.nrf52840_mdk_s140; + + // Install SoftDevice hex files into zig-out/firmware/ for easy flashing. + const install_step = b.getInstallStep(); + const sd_files = mb.ports.nrf5x.softdevice.files; + inline for (.{ + .{ sd_files.s112, "s112_nrf52_7.2.0_softdevice.hex" }, + .{ sd_files.s113, "s113_nrf52_7.2.0_softdevice.hex" }, + .{ sd_files.s122, "s122_nrf52_8.0.0_softdevice.hex" }, + .{ sd_files.s132, "s132_nrf52_7.2.0_softdevice.hex" }, + .{ sd_files.s140, "s140_nrf52_7.2.0_softdevice.hex" }, + }) |entry| { + install_step.dependOn(&b.addInstallFileWithDir(entry[0], .{ .custom = "firmware" }, entry[1]).step); + } const available_examples = [_]Example{ - .{ .target = nrf52840_dongle, .name = "nrf52840_dongle_blinky", .file = "src/blinky.zig" }, - - .{ .target = nrf52840_mdk, .name = "nrf52840_mdk_blinky", .file = "src/blinky.zig" }, - .{ .target = nrf52840_mdk, .name = "nrf52840_mdk_uart", .file = "src/uart.zig" }, - .{ .target = nrf52840_mdk, .name = "nrf52840_mdk_i2c_bus_scan", .file = "src/i2c_bus_scan.zig" }, - .{ .target = nrf52840_mdk, .name = "nrf52840_mdk_i2c_accel", .file = "src/i2c_accel.zig" }, - .{ .target = nrf52840_mdk, .name = "nrf52840_mdk_i2c_hall_effect", .file = "src/i2c_hall_effect.zig" }, - .{ .target = nrf52840_mdk, .name = "nrf52840_mdk_i2c_position_sensor", .file = "src/i2c_position_sensor.zig" }, - .{ .target = nrf52840_mdk, .name = "nrf52840_mdk_i2c_temp", .file = "src/i2c_temp.zig" }, - .{ .target = nrf52840_mdk, .name = "nrf52840_mdk_rtt_log", .file = "src/rtt_log.zig" }, - .{ .target = nrf52840_mdk, .name = "nrf52840_mdk_semihosting", .file = "src/semihosting.zig" }, - .{ .target = nrf52840_mdk, .name = "nrf52840_mdk_spi_master", .file = "src/spi_master.zig" }, - - .{ .target = pca10040, .name = "pca10040_blinky", .file = "src/blinky.zig" }, - .{ .target = pca10040, .name = "pca10040_uart", .file = "src/uart.zig" }, - .{ .target = pca10040, .name = "pca10040_i2c_bus_scan", .file = "src/i2c_bus_scan.zig" }, - .{ .target = pca10040, .name = "pca10040_i2c_temp", .file = "src/i2c_temp.zig" }, - .{ .target = pca10040, .name = "pca10040_spi_master", .file = "src/spi_master.zig" }, - - .{ .target = microbit_v1, .name = "microbit_v1_display", .file = "src/microbit/display.zig" }, - .{ .target = microbit_v2, .name = "microbit_v2_display", .file = "src/microbit/display.zig" }, + .{ .target = nrf52840_dongle, .name = "nrf52840_dongle_blinky", .file = "src/blinky.zig", .softdevice_hex = null }, + + .{ .target = nrf52840_mdk, .name = "nrf52840_mdk_blinky", .file = "src/blinky.zig", .softdevice_hex = null }, + .{ .target = nrf52840_mdk, .name = "nrf52840_mdk_uart", .file = "src/uart.zig", .softdevice_hex = null }, + .{ .target = nrf52840_mdk, .name = "nrf52840_mdk_i2c_bus_scan", .file = "src/i2c_bus_scan.zig", .softdevice_hex = null }, + .{ .target = nrf52840_mdk, .name = "nrf52840_mdk_i2c_accel", .file = "src/i2c_accel.zig", .softdevice_hex = null }, + .{ .target = nrf52840_mdk, .name = "nrf52840_mdk_i2c_hall_effect", .file = "src/i2c_hall_effect.zig", .softdevice_hex = null }, + .{ .target = nrf52840_mdk, .name = "nrf52840_mdk_i2c_position_sensor", .file = "src/i2c_position_sensor.zig", .softdevice_hex = null }, + .{ .target = nrf52840_mdk, .name = "nrf52840_mdk_i2c_temp", .file = "src/i2c_temp.zig", .softdevice_hex = null }, + .{ .target = nrf52840_mdk, .name = "nrf52840_mdk_rtt_log", .file = "src/rtt_log.zig", .softdevice_hex = null }, + .{ .target = nrf52840_mdk, .name = "nrf52840_mdk_semihosting", .file = "src/semihosting.zig", .softdevice_hex = null }, + .{ .target = nrf52840_mdk, .name = "nrf52840_mdk_spi_master", .file = "src/spi_master.zig", .softdevice_hex = null }, + + // SoftDevice examples: the app code is identical — the difference is in + // how the SoftDevice binary gets onto the chip. "preflashed" assumes the + // SoftDevice is already on the device; "merged" produces a single hex + // containing both the SoftDevice and the app. + .{ + .target = pca10040_s132, + .name = "pca10040_softdevice_preflashed_blinky", + .file = "src/softdevice_preflashed_blinky.zig", + .softdevice_hex = null, + .is_softdevice = true, + }, + .{ + .target = nrf52840_mdk_s140, + .name = "nrf52840_mdk_softdevice_merged_blinky", + .file = "src/softdevice_merged_blinky.zig", + .softdevice_hex = mb.ports.nrf5x.softdevice.files.s140, + .is_softdevice = true, + }, + .{ + .target = nrf52840_dongle_s140, + .name = "nrf52840_dongle_softdevice_merged_blinky", + .file = "src/softdevice_merged_blinky.zig", + .softdevice_hex = mb.ports.nrf5x.softdevice.files.s140, + .is_softdevice = true, + }, + + // SoftDevice API examples for PCA10040 (nRF52832 + S132). + // The softdevice module is automatically wired via the HAL. + .{ + .target = pca10040_s132, + .name = "pca10040_softdevice_init", + .file = "src/softdevice_init.zig", + .softdevice_hex = null, + .is_softdevice = true, + }, + .{ + .target = pca10040_s132, + .name = "pca10040_softdevice_beacon", + .file = "src/softdevice_beacon.zig", + .softdevice_hex = null, + .is_softdevice = true, + }, + + .{ .target = pca10040, .name = "pca10040_blinky", .file = "src/blinky.zig", .softdevice_hex = null }, + .{ .target = pca10040, .name = "pca10040_uart", .file = "src/uart.zig", .softdevice_hex = null }, + .{ .target = pca10040, .name = "pca10040_i2c_bus_scan", .file = "src/i2c_bus_scan.zig", .softdevice_hex = null }, + .{ .target = pca10040, .name = "pca10040_i2c_temp", .file = "src/i2c_temp.zig", .softdevice_hex = null }, + .{ .target = pca10040, .name = "pca10040_spi_master", .file = "src/spi_master.zig", .softdevice_hex = null }, + + .{ .target = microbit_v1, .name = "microbit_v1_display", .file = "src/microbit/display.zig", .softdevice_hex = null }, + .{ .target = microbit_v2, .name = "microbit_v2_display", .file = "src/microbit/display.zig", .softdevice_hex = null }, }; for (available_examples) |example| { @@ -68,6 +127,27 @@ pub fn build(b: *std.Build) void { // For debugging, we also always install the firmware as an ELF file mb.install_firmware(fw, .{ .format = .elf }); + + // For preflashed SoftDevice targets, also emit a .hex so users can + // flash the app via nrfjprog or a J-Link probe. + if (example.softdevice_hex == null and example.is_softdevice) { + mb.install_firmware(fw, .{ .format = .hex }); + } + + if (example.softdevice_hex) |softdevice_hex| { + const app_hex = fw.get_emitted_bin(.hex); + const merged_hex = mb.ports.nrf5x.merge_hex( + app_hex, + softdevice_hex, + b.fmt("{s}_merged.hex", .{example.name}), + ); + const install_merged = b.addInstallFileWithDir( + merged_hex, + .{ .custom = "firmware" }, + b.fmt("{s}_merged.hex", .{example.name}), + ); + b.getInstallStep().dependOn(&install_merged.step); + } } } @@ -75,4 +155,6 @@ const Example = struct { target: *const microzig.Target, name: []const u8, file: []const u8, + softdevice_hex: ?std.Build.LazyPath, + is_softdevice: bool = false, }; diff --git a/examples/nordic/nrf5x/src/softdevice_beacon.zig b/examples/nordic/nrf5x/src/softdevice_beacon.zig new file mode 100644 index 000000000..592cb0ab3 --- /dev/null +++ b/examples/nordic/nrf5x/src/softdevice_beacon.zig @@ -0,0 +1,90 @@ +// BLE beacon example for PCA10040. +// +// Enables the SoftDevice, configures BLE advertising, and broadcasts a +// non-connectable beacon with a device name. LED1 blinks while advertising. +// LED4 is lit on fault or error. +const microzig = @import("microzig"); +const board = microzig.board; +const nrf = microzig.hal; +const sd = nrf.softdevice; +const time = nrf.time; + +const gap = sd.gap; +const ble = sd.ble; +const sdm = sd.sdm; + +pub fn main() void { + board.init(); + + // 1. Enable SoftDevice + const clock_cfg: sdm.ClockLfCfg = .{ + .source = .rc, + .rc_ctiv = 16, + .rc_temp_ctiv = 2, + .accuracy = .ppm_500, + }; + sdm.enable(&clock_cfg, fault_handler) catch return signal_error(); + + // 2. Enable BLE stack + var ram_base: u32 = 0x20002800; // must match SoftDevice profile ram_start + ble.enable(&ram_base) catch return signal_error(); + + // 3. Set device name + const name = "nRF52 Beacon"; + var perm = gap.ConnSecMode.open(); + gap.device_name_set(&perm, name, name.len) catch return signal_error(); + + // 4. Build advertising data: flags + complete local name + var adv_buf = build_adv_data(name); + + // 5. Configure advertising set + var adv_handle: u8 = 0xFF; // BLE_GAP_ADV_SET_HANDLE_NOT_SET + var adv_data: gap.AdvData = .{ + .adv_data = gap.Data.from_slice(&adv_buf), + }; + const adv_params: gap.AdvParams = .{ + .properties = .{ + .type_ = .nonconnectable_nonscannable_undirected, + }, + .interval = 160, // 100ms (units of 0.625ms) + .duration = 0, // advertise indefinitely + }; + gap.adv_set_configure(&adv_handle, &adv_data, &adv_params) catch return signal_error(); + + // 6. Start advertising + gap.adv_start(adv_handle, ble.conn_cfg_tag_default) catch return signal_error(); + + // 7. Blink LED1 while advertising + while (true) { + board.led1.toggle(); + time.sleep_ms(1000); + } +} + +fn build_adv_data(name: []const u8) [31]u8 { + var buf: [31]u8 = .{0} ** 31; + var pos: usize = 0; + + // AD structure: flags + buf[pos] = 2; // length + buf[pos + 1] = gap.ad_type.flags; + buf[pos + 2] = gap.adv_flag.le_only_general_disc_mode; + pos += 3; + + // AD structure: complete local name + const name_len: u8 = @intCast(name.len); + buf[pos] = name_len + 1; // length (type byte + name) + buf[pos + 1] = gap.ad_type.complete_local_name; + @memcpy(buf[pos + 2 .. pos + 2 + name_len], name); + + return buf; +} + +fn signal_error() void { + board.led4.put(board.led_active_state); + while (true) {} +} + +fn fault_handler(_: sdm.FaultId, _: u32, _: u32) callconv(.c) void { + signal_error(); +} diff --git a/examples/nordic/nrf5x/src/softdevice_init.zig b/examples/nordic/nrf5x/src/softdevice_init.zig new file mode 100644 index 000000000..b6cfb50d9 --- /dev/null +++ b/examples/nordic/nrf5x/src/softdevice_init.zig @@ -0,0 +1,38 @@ +// SoftDevice initialization example for PCA10040. +// +// Enables the SoftDevice BLE stack, then blinks LED1 to confirm it's running. +// LED4 is lit on fault. This demonstrates the minimal SoftDevice setup. +const microzig = @import("microzig"); +const board = microzig.board; +const nrf = microzig.hal; +const sd = nrf.softdevice; +const time = nrf.time; + +pub fn main() void { + board.init(); + + // Enable SoftDevice with default LF clock (internal RC oscillator). + const clock_cfg: sd.sdm.ClockLfCfg = .{ + .source = .rc, + .rc_ctiv = 16, // calibration interval (4s * 16 = 64s) + .rc_temp_ctiv = 2, // calibrate on temperature change every 2 intervals + .accuracy = .ppm_500, + }; + + sd.sdm.enable(&clock_cfg, fault_handler) catch { + // Signal error on LED4 + board.led4.put(board.led_active_state); + while (true) {} + }; + + // SoftDevice is running — blink LED1 + while (true) { + board.led1.toggle(); + time.sleep_ms(500); + } +} + +fn fault_handler(_: sd.sdm.FaultId, _: u32, _: u32) callconv(.c) void { + board.led4.put(board.led_active_state); + while (true) {} +} diff --git a/examples/nordic/nrf5x/src/softdevice_merged_blinky.zig b/examples/nordic/nrf5x/src/softdevice_merged_blinky.zig new file mode 100644 index 000000000..db654caae --- /dev/null +++ b/examples/nordic/nrf5x/src/softdevice_merged_blinky.zig @@ -0,0 +1,17 @@ +// SoftDevice "merged" example: the build produces a combined Intel HEX that +// contains both the SoftDevice binary and the application. Flash the +// *_merged.hex to program everything in one shot. The app code is identical +// to the preflashed variant — only the build configuration differs. +const microzig = @import("microzig"); +const board = microzig.board; +const nrf = microzig.hal; +const time = nrf.time; + +pub fn main() void { + board.init(); + + while (true) { + board.led1.toggle(); + time.sleep_ms(250); + } +} diff --git a/examples/nordic/nrf5x/src/softdevice_preflashed_blinky.zig b/examples/nordic/nrf5x/src/softdevice_preflashed_blinky.zig new file mode 100644 index 000000000..a8870b629 --- /dev/null +++ b/examples/nordic/nrf5x/src/softdevice_preflashed_blinky.zig @@ -0,0 +1,16 @@ +// SoftDevice "preflashed" example: assumes the SoftDevice is already on the +// chip. The build system adjusts the linker memory map so the application is +// placed after the SoftDevice region. Flash this .hex/.elf on its own. +const microzig = @import("microzig"); +const board = microzig.board; +const nrf = microzig.hal; +const time = nrf.time; + +pub fn main() void { + board.init(); + + while (true) { + board.led1.toggle(); + time.sleep_ms(250); + } +} diff --git a/port/nordic/nrf5x/README.md b/port/nordic/nrf5x/README.md index 8c640cd1a..a4a4b3622 100644 --- a/port/nordic/nrf5x/README.md +++ b/port/nordic/nrf5x/README.md @@ -2,6 +2,29 @@ HALs and register definitions for nrf5x devices +## SoftDevice integration + +The nRF5x port now exposes SoftDevice-aware targets under `mb.ports.nrf5x.softdevice`. + +Available profiles: + +- `s132` for nRF52832 +- `s140` for nRF52833, nRF52840 + +SoftDevice binaries are sourced from the official Nordic nRF5 SDK v17.1.0 +package via Zig package manager. + +Example target names: + +- `mb.ports.nrf5x.softdevice.boards.nordic.pca10040_s132` +- `mb.ports.nrf5x.softdevice.boards.nordic.nrf52840_dongle_s140` +- `mb.ports.nrf5x.softdevice.boards.nordic.nrf52840_mdk_s140` +- `mb.ports.nrf5x.softdevice.boards.bbc.microbit_v2_s140` + +You can merge app HEX + SoftDevice HEX with: + +- `mb.ports.nrf5x.merge_hex(app_hex, softdevice_hex, "output.hex")` + ## Renode supports: - nrf52840 development kit diff --git a/port/nordic/nrf5x/build.zig b/port/nordic/nrf5x/build.zig index f51c41796..ff3916355 100644 --- a/port/nordic/nrf5x/build.zig +++ b/port/nordic/nrf5x/build.zig @@ -3,6 +3,18 @@ const microzig = @import("microzig/build-internals"); const Self = @This(); +const SoftDeviceProfile = struct { + name: []const u8, + flash_start: u64, + ram_start: u64, + hex_path: std.Build.LazyPath, + /// Variant root file (e.g. "src/softdevice/s132.zig") — single source of + /// truth for which SoftDevice variant a target uses. + variant_root: std.Build.LazyPath, +}; + +dep: *std.Build.Dependency, + chips: struct { nrf51822: *const microzig.Target, nrf52832: *const microzig.Target, @@ -22,10 +34,38 @@ boards: struct { }, }, +softdevice: struct { + files: struct { + s112: std.Build.LazyPath, + s113: std.Build.LazyPath, + s122: std.Build.LazyPath, + s132: std.Build.LazyPath, + s140: std.Build.LazyPath, + }, + + chips: struct { + nrf52832_s132: *const microzig.Target, + nrf52833_s140: *const microzig.Target, + nrf52840_s140: *const microzig.Target, + }, + + boards: struct { + nordic: struct { + pca10040_s132: *const microzig.Target, + nrf52840_dongle_s140: *const microzig.Target, + nrf52840_mdk_s140: *const microzig.Target, + }, + bbc: struct { + microbit_v2_s140: *const microzig.Target, + }, + }, +}, + pub fn init(dep: *std.Build.Dependency) Self { const b = dep.builder; const nrfx = b.dependency("nrfx", .{}); + const softdevice_dep = b.dependency("nrf5-sdk", .{}); const hal: microzig.HardwareAbstractionLayer = .{ .root_source_file = b.path("src/hal.zig"), @@ -142,30 +182,70 @@ pub fn init(dep: *std.Build.Dependency) Self { .hal = hal, }; + const profile_s132: SoftDeviceProfile = .{ + .name = "s132-7.2.0", + .flash_start = 0x00026000, + .ram_start = 0x20002800, + .hex_path = softdevice_dep.path("components/softdevice/s132/hex/s132_nrf52_7.2.0_softdevice.hex"), + .variant_root = b.path("src/softdevice/s132.zig"), + }; + + const profile_s140: SoftDeviceProfile = .{ + .name = "s140-7.2.0", + .flash_start = 0x00027000, + .ram_start = 0x20002800, + .hex_path = softdevice_dep.path("components/softdevice/s140/hex/s140_nrf52_7.2.0_softdevice.hex"), + .variant_root = b.path("src/softdevice/s140.zig"), + }; + + const resolved_chip_nrf51822 = chip_nrf51822.derive(.{}); + const resolved_chip_nrf52832 = chip_nrf52832.derive(.{}); + const resolved_chip_nrf52833 = chip_nrf52833.derive(.{}); + const resolved_chip_nrf52840 = chip_nrf52840.derive(.{}); + + const resolved_chip_nrf52832_s132 = derive_with_softdevice_memory( + resolved_chip_nrf52832, + dep, + profile_s132, + ); + + const resolved_chip_nrf52833_s140 = derive_with_softdevice_memory( + resolved_chip_nrf52833, + dep, + profile_s140, + ); + + const resolved_chip_nrf52840_s140 = derive_with_softdevice_memory( + resolved_chip_nrf52840, + dep, + profile_s140, + ); + return .{ + .dep = dep, .chips = .{ - .nrf51822 = chip_nrf51822.derive(.{}), - .nrf52832 = chip_nrf52832.derive(.{}), - .nrf52833 = chip_nrf52833.derive(.{}), - .nrf52840 = chip_nrf52840.derive(.{}), + .nrf51822 = resolved_chip_nrf51822, + .nrf52832 = resolved_chip_nrf52832, + .nrf52833 = resolved_chip_nrf52833, + .nrf52840 = resolved_chip_nrf52840, }, .boards = .{ .nordic = .{ - .nrf52840_dongle = chip_nrf52840.derive(.{ + .nrf52840_dongle = resolved_chip_nrf52840.derive(.{ .board = .{ .name = "nRF52840 Dongle", .url = "https://www.nordicsemi.com/Products/Development-hardware/nrf52840-dongle", .root_source_file = b.path("src/boards/nrf52840-dongle.zig"), }, }), - .nrf52840_mdk = chip_nrf52840.derive(.{ + .nrf52840_mdk = resolved_chip_nrf52840.derive(.{ .board = .{ .name = "nRF52840 MDK USB Dongle", .url = "https://wiki.makerdiary.com/nrf52840-mdk-usb-dongle", .root_source_file = b.path("src/boards/nrf52840-mdk.zig"), }, }), - .pca10040 = chip_nrf52832.derive(.{ + .pca10040 = resolved_chip_nrf52832.derive(.{ .board = .{ .name = "PCA10040", .url = "https://www.nordicsemi.com/Products/Development-hardware/nRF52-DK", @@ -174,7 +254,7 @@ pub fn init(dep: *std.Build.Dependency) Self { }), }, .bbc = .{ - .microbit_v1 = chip_nrf51822.derive(.{ + .microbit_v1 = resolved_chip_nrf51822.derive(.{ .preferred_binary_format = .hex, .board = .{ .name = "micro:bit v1", @@ -182,7 +262,7 @@ pub fn init(dep: *std.Build.Dependency) Self { .root_source_file = b.path("src/boards/microbit.zig"), }, }), - .microbit_v2 = chip_nrf52833.derive(.{ + .microbit_v2 = resolved_chip_nrf52833.derive(.{ .preferred_binary_format = .hex, .board = .{ .name = "micro:bit v2", @@ -192,9 +272,134 @@ pub fn init(dep: *std.Build.Dependency) Self { }), }, }, + .softdevice = .{ + .files = .{ + .s112 = softdevice_dep.path("components/softdevice/s112/hex/s112_nrf52_7.2.0_softdevice.hex"), + .s113 = softdevice_dep.path("components/softdevice/s113/hex/s113_nrf52_7.2.0_softdevice.hex"), + .s122 = softdevice_dep.path("components/softdevice/s122/hex/s122_nrf52_8.0.0_softdevice.hex"), + .s132 = softdevice_dep.path("components/softdevice/s132/hex/s132_nrf52_7.2.0_softdevice.hex"), + .s140 = softdevice_dep.path("components/softdevice/s140/hex/s140_nrf52_7.2.0_softdevice.hex"), + }, + .chips = .{ + .nrf52832_s132 = resolved_chip_nrf52832_s132, + .nrf52833_s140 = resolved_chip_nrf52833_s140, + .nrf52840_s140 = resolved_chip_nrf52840_s140, + }, + .boards = .{ + .nordic = .{ + .pca10040_s132 = resolved_chip_nrf52832_s132.derive(.{ + .board = .{ + .name = "PCA10040 (SoftDevice s132 7.2.0)", + .url = "https://www.nordicsemi.com/Products/Development-hardware/nRF52-DK", + .root_source_file = b.path("src/boards/pca10040.zig"), + }, + }), + .nrf52840_dongle_s140 = resolved_chip_nrf52840_s140.derive(.{ + .board = .{ + .name = "nRF52840 Dongle (SoftDevice s140 7.2.0)", + .url = "https://www.nordicsemi.com/Products/Development-hardware/nrf52840-dongle", + .root_source_file = b.path("src/boards/nrf52840-dongle.zig"), + }, + }), + .nrf52840_mdk_s140 = resolved_chip_nrf52840_s140.derive(.{ + .board = .{ + .name = "nRF52840 MDK USB Dongle (SoftDevice s140 7.2.0)", + .url = "https://wiki.makerdiary.com/nrf52840-mdk-usb-dongle", + .root_source_file = b.path("src/boards/nrf52840-mdk.zig"), + }, + }), + }, + .bbc = .{ + .microbit_v2_s140 = resolved_chip_nrf52833_s140.derive(.{ + .preferred_binary_format = .hex, + .board = .{ + .name = "micro:bit v2 (SoftDevice s140 7.2.0)", + .url = "https://tech.microbit.org/hardware/2-2-revision", + .root_source_file = b.path("src/boards/microbit.zig"), + }, + }), + }, + }, + }, }; } +pub fn merge_hex(self: *const Self, lower_hex: std.Build.LazyPath, upper_hex: std.Build.LazyPath, output_name: []const u8) std.Build.LazyPath { + const run = self.dep.builder.addRunArtifact(self.dep.artifact("nrf5x-mergehex")); + run.addFileArg(lower_hex); + run.addFileArg(upper_hex); + return run.addOutputFileArg(output_name); +} + +fn derive_with_softdevice_memory( + base: *const microzig.Target, + dep: *std.Build.Dependency, + profile: SoftDeviceProfile, +) *const microzig.Target { + const b = dep.builder; + const allocator = b.allocator; + var filtered_regions = std.array_list.Managed(microzig.MemoryRegion).init(allocator); + + var found_flash = false; + var found_ram = false; + + for (base.chip.memory_regions) |region| { + if (region.tag == .flash) { + if (found_flash) continue; + var updated_region = region; + const end = region.offset + region.length; + if (profile.flash_start >= end) @panic("softdevice flash start is outside of flash memory"); + updated_region.length = end - profile.flash_start; + updated_region.offset = profile.flash_start; + filtered_regions.append(updated_region) catch @panic("OOM"); + found_flash = true; + continue; + } + + if (region.tag == .ram) { + if (found_ram) continue; + var updated_region = region; + const end = region.offset + region.length; + if (profile.ram_start >= end) @panic("softdevice ram start is outside of ram memory"); + updated_region.length = end - profile.ram_start; + updated_region.offset = profile.ram_start; + filtered_regions.append(updated_region) catch @panic("OOM"); + found_ram = true; + continue; + } + + filtered_regions.append(region) catch @panic("OOM"); + } + + if (!found_flash) @panic("target has no flash memory region"); + if (!found_ram) @panic("target has no ram memory region"); + + const regions = filtered_regions.toOwnedSlice() catch @panic("OOM"); + + const chip: microzig.Chip = .{ + .name = base.chip.name, + .url = base.chip.url, + .register_definition = base.chip.register_definition, + .memory_regions = regions, + .patch_files = base.chip.patch_files, + }; + + // Create a softdevice-aware HAL that re-exports the standard HAL plus + // the variant-specific SoftDevice module (single source of truth). + const sd_mod = b.createModule(.{ + .root_source_file = profile.variant_root, + }); + + const hal_sd: microzig.HardwareAbstractionLayer = .{ + .root_source_file = b.path("src/hal_softdevice.zig"), + .imports = allocator.dupe(std.Build.Module.Import, &.{ + .{ .name = "softdevice", .module = sd_mod }, + }) catch @panic("OOM"), + }; + + return base.derive(.{ .chip = chip, .hal = hal_sd }); +} + pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const target = b.standardTargetOptions(.{}); @@ -210,4 +415,14 @@ pub fn build(b: *std.Build) void { const unit_tests_run = b.addRunArtifact(unit_tests); const test_step = b.step("test", "Run platform agnostic unit tests"); test_step.dependOn(&unit_tests_run.step); + + const mergehex_exe = b.addExecutable(.{ + .name = "nrf5x-mergehex", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/tools/mergehex.zig"), + .target = b.graph.host, + .optimize = .ReleaseSafe, + }), + }); + b.installArtifact(mergehex_exe); } diff --git a/port/nordic/nrf5x/build.zig.zon b/port/nordic/nrf5x/build.zig.zon index 352a9973a..067d63d9d 100644 --- a/port/nordic/nrf5x/build.zig.zon +++ b/port/nordic/nrf5x/build.zig.zon @@ -4,6 +4,10 @@ .version = "0.0.0", .dependencies = .{ .@"microzig/build-internals" = .{ .path = "../../../build-internals" }, + .@"nrf5-sdk" = .{ + .url = "https://files.nordicsemi.com/artifactory/nRF5-SDK/external/nRF5_SDK_v17.x.x/nRF5_SDK_17.1.0_ddde560.zip", + .hash = "N-V-__8AADqJIyI1rzA6qzQ55YKnO79GgguMYmJWscpjDvAk", + }, .nrfx = .{ .url = "https://github.com/NordicSemiconductor/nrfx/archive/refs/tags/v3.11.0.tar.gz", .hash = "N-V-__8AABC-kgxbmC3Prd8fgisfbLs1giU34ofX1vz9bwaA", diff --git a/port/nordic/nrf5x/src/hal_softdevice.zig b/port/nordic/nrf5x/src/hal_softdevice.zig new file mode 100644 index 000000000..ae1e4a516 --- /dev/null +++ b/port/nordic/nrf5x/src/hal_softdevice.zig @@ -0,0 +1,25 @@ +// HAL wrapper for SoftDevice-enabled targets. +// +// Re-exports the standard nRF5x HAL and adds the variant-specific SoftDevice +// module. The build system provides the "softdevice" import pointing to the +// correct variant root (s112, s113, s122, s132, or s140). + +const hal = @import("hal.zig"); + +pub const softdevice = @import("softdevice"); + +pub const compatibility = hal.compatibility; +pub const clocks = hal.clocks; +pub const gpio = hal.gpio; +pub const i2c = hal.i2c; +pub const i2cdma = hal.i2cdma; +pub const spim = hal.spim; +pub const time = hal.time; +pub const uart = hal.uart; +pub const drivers = hal.drivers; + +pub const default_interrupts = hal.default_interrupts; + +pub fn init() void { + hal.init(); +} diff --git a/port/nordic/nrf5x/src/softdevice/ble.zig b/port/nordic/nrf5x/src/softdevice/ble.zig new file mode 100644 index 000000000..afbb5b648 --- /dev/null +++ b/port/nordic/nrf5x/src/softdevice/ble.zig @@ -0,0 +1,246 @@ +// BLE core - enable, event retrieval, UUID management, configuration. + +const svc = @import("svc.zig"); +const err = @import("err.zig"); +const gap = @import("gap.zig"); +const gattc = @import("gattc.zig"); +const gatts = @import("gatts.zig"); +const gatt = @import("gatt.zig"); +const l2cap = @import("l2cap.zig"); +const Error = err.Error; + +// SVC base = 0x60 +const SVC_BASE = 0x60; + +pub const conn_handle_invalid: u16 = 0xFFFF; +pub const conn_handle_all: u16 = 0xFFFE; +pub const conn_cfg_tag_default: u8 = 0; +pub const uuid_vs_count_default: u8 = 10; +pub const uuid_vs_count_max: u8 = 254; +pub const evt_ptr_alignment: u8 = 4; + +pub const UuidType = enum(u8) { + unknown = 0x00, + ble = 0x01, + vendor_begin = 0x02, + _, +}; + +pub const Uuid = extern struct { + uuid: u16, + type_: u8, +}; + +pub const Uuid128 = extern struct { + uuid128: [16]u8, +}; + +pub const UserMemType = enum(u8) { + invalid = 0x00, + gatts_queued_writes = 0x01, +}; + +pub const UserMemBlock = extern struct { + p_mem: ?[*]u8 = null, + len: u16 = 0, +}; + +pub const Version = extern struct { + version_number: u8, + company_id: u16, + subversion_number: u16, +}; + +pub const PaLnaCfg = packed struct(u8) { + enable: u1 = 0, + active_high: u1 = 0, + gpio_pin: u6 = 0, +}; + +pub const CommonOptPaLna = extern struct { + pa_cfg: PaLnaCfg = .{}, + lna_cfg: PaLnaCfg = .{}, + ppi_ch_id_set: u8 = 0, + ppi_ch_id_clr: u8 = 0, + gpiote_ch_id: u8 = 0, +}; + +pub const CommonOptConnEvtExt = extern struct { + flags: packed struct(u8) { + enable: u1 = 0, + _pad: u7 = 0, + } = .{}, +}; + +pub const CommonOptExtendedRcCal = extern struct { + flags: packed struct(u8) { + enable: u1 = 1, + _pad: u7 = 0, + } = .{}, +}; + +pub const CommonOpt = extern union { + pa_lna: CommonOptPaLna, + conn_evt_ext: CommonOptConnEvtExt, + extended_rc_cal: CommonOptExtendedRcCal, +}; + +pub const Opt = extern union { + common_opt: CommonOpt, + gap_opt: gap.Opt, + gattc_opt: gattc.Opt, +}; + +pub const ConnCfgGap = extern struct { + conn_count: u8 = 1, + event_length: u16 = 3, +}; + +pub const ConnCfg = extern struct { + conn_cfg_tag: u8, + params: extern union { + gap_conn_cfg: gap.ConnCfg, + gattc_conn_cfg: gattc.ConnCfg, + gatts_conn_cfg: gatts.ConnCfg, + gatt_conn_cfg: gatt.ConnCfg, + l2cap_conn_cfg: l2cap.ConnCfg, + }, +}; + +pub const CommonCfgVsUuid = extern struct { + vs_uuid_count: u8 = uuid_vs_count_default, +}; + +pub const CommonCfg = extern union { + vs_uuid_cfg: CommonCfgVsUuid, +}; + +pub const GapCfg = extern union { + role_count: gap.CfgRoleCount, + device_name: gap.CfgDeviceName, + ppcp_include_cfg: u8, + car_include_cfg: u8, +}; + +pub const GattsCfg = extern union { + service_changed: gatts.CfgServiceChanged, + attr_tab_size: gatts.CfgAttrTabSize, +}; + +pub const Cfg = extern union { + conn_cfg: ConnCfg, + common_cfg: CommonCfg, + gap_cfg: GapCfg, + gatts_cfg: GattsCfg, +}; + +// BLE Configuration IDs +pub const cfg_id = struct { + // Common + pub const vs_uuid: u32 = 0x01; + // Connection + pub const conn_gap: u32 = 0x20; + pub const conn_gattc: u32 = 0x21; + pub const conn_gatts: u32 = 0x22; + pub const conn_gatt: u32 = 0x23; + pub const conn_l2cap: u32 = 0x24; + // GAP + pub const gap_role_count: u32 = 0x40; + pub const gap_device_name: u32 = 0x41; + pub const gap_ppcp_incl: u32 = 0x42; + pub const gap_car_incl: u32 = 0x43; + // GATTS + pub const gatts_service_changed: u32 = 0xA0; + pub const gatts_attr_tab_size: u32 = 0xA1; +}; + +// BLE Option IDs +pub const opt_id = struct { + pub const common_pa_lna: u32 = 0x01; + pub const common_conn_evt_ext: u32 = 0x02; + pub const common_extended_rc_cal: u32 = 0x03; + pub const gap_ch_map: u32 = 0x20; + pub const gap_local_conn_latency: u32 = 0x21; + pub const gap_passkey: u32 = 0x22; + pub const gap_compat_mode_1: u32 = 0x23; + pub const gap_auth_payload_timeout: u32 = 0x24; + pub const gap_slave_latency_disable: u32 = 0x25; + pub const gattc_uuid_disc: u32 = 0x60; +}; + +// Event header +pub const EvtHdr = extern struct { + evt_id: u16, + evt_len: u16, +}; + +// Common event IDs +pub const EvtId = enum(u16) { + user_mem_request = 0x01, + user_mem_release = 0x02, + _, +}; + +// Standard BLE UUIDs +pub const uuid = struct { + pub const service_primary: u16 = 0x2800; + pub const service_secondary: u16 = 0x2801; + pub const service_include: u16 = 0x2802; + pub const characteristic: u16 = 0x2803; + pub const descriptor_char_ext_prop: u16 = 0x2900; + pub const descriptor_char_user_desc: u16 = 0x2901; + pub const descriptor_client_char_config: u16 = 0x2902; + pub const descriptor_server_char_config: u16 = 0x2903; + pub const gap: u16 = 0x1800; + pub const gatt_: u16 = 0x1801; + pub const gap_device_name: u16 = 0x2A00; + pub const gap_appearance: u16 = 0x2A01; + pub const gap_ppcp: u16 = 0x2A04; + pub const gatt_service_changed: u16 = 0x2A05; +}; + +// --- SVC functions --- + +pub fn enable(p_app_ram_base: *u32) Error!void { + return err.check(svc.svcall1(SVC_BASE + 0, @intFromPtr(p_app_ram_base))); +} + +pub fn cfg_set(cfg_id_val: u32, p_cfg: *const Cfg, app_ram_base: u32) Error!void { + return err.check(svc.svcall3(SVC_BASE + 9, cfg_id_val, @intFromPtr(p_cfg), app_ram_base)); +} + +pub fn evt_get(p_dest: ?[*]align(4) u8, p_len: *u16) Error!void { + return err.check(svc.svcall2(SVC_BASE + 1, @intFromPtr(p_dest), @intFromPtr(p_len))); +} + +pub fn uuid_vs_add(p_vs_uuid: *const Uuid128, p_uuid_type: *u8) Error!void { + return err.check(svc.svcall2(SVC_BASE + 2, @intFromPtr(p_vs_uuid), @intFromPtr(p_uuid_type))); +} + +pub fn uuid_vs_remove(p_uuid_type: *u8) Error!void { + return err.check(svc.svcall1(SVC_BASE + 10, @intFromPtr(p_uuid_type))); +} + +pub fn uuid_decode(uuid_le_len: u8, p_uuid_le: [*]const u8, p_uuid: *Uuid) Error!void { + return err.check(svc.svcall3(SVC_BASE + 3, uuid_le_len, @intFromPtr(p_uuid_le), @intFromPtr(p_uuid))); +} + +pub fn uuid_encode(p_uuid: *const Uuid, p_uuid_le_len: *u8, p_uuid_le: ?[*]u8) Error!void { + return err.check(svc.svcall3(SVC_BASE + 4, @intFromPtr(p_uuid), @intFromPtr(p_uuid_le_len), @intFromPtr(p_uuid_le))); +} + +pub fn version_get(p_version: *Version) Error!void { + return err.check(svc.svcall1(SVC_BASE + 5, @intFromPtr(p_version))); +} + +pub fn user_mem_reply(conn_handle: u16, p_block: ?*const UserMemBlock) Error!void { + return err.check(svc.svcall2(SVC_BASE + 6, conn_handle, @intFromPtr(p_block))); +} + +pub fn opt_set(opt_id_val: u32, p_opt: *const Opt) Error!void { + return err.check(svc.svcall2(SVC_BASE + 7, opt_id_val, @intFromPtr(p_opt))); +} + +pub fn opt_get(opt_id_val: u32, p_opt: *Opt) Error!void { + return err.check(svc.svcall2(SVC_BASE + 8, opt_id_val, @intFromPtr(p_opt))); +} diff --git a/port/nordic/nrf5x/src/softdevice/err.zig b/port/nordic/nrf5x/src/softdevice/err.zig new file mode 100644 index 000000000..86b04cf49 --- /dev/null +++ b/port/nordic/nrf5x/src/softdevice/err.zig @@ -0,0 +1,130 @@ +// Nordic SoftDevice error codes mapped to a Zig error set. + +pub const Error = error{ + SvcHandlerMissing, + SoftdeviceNotEnabled, + Internal, + NoMem, + NotFound, + NotSupported, + InvalidParam, + InvalidState, + InvalidLength, + InvalidFlags, + InvalidData, + DataSize, + Timeout, + Null, + Forbidden, + InvalidAddr, + Busy, + ConnCount, + Resources, + + // SDM errors (0x1000+) + LfclkSourceUnknown, + IncorrectInterruptConfiguration, + IncorrectClenr0, + + // SoC errors (0x2000+) + MutexAlreadyTaken, + NvicInterruptNotAvailable, + NvicInterruptPriorityNotAllowed, + NvicShouldNotReturn, + PowerModeUnknown, + PowerPofThresholdUnknown, + PowerOffShouldNotReturn, + RandNotEnoughValues, + PpiInvalidChannel, + PpiInvalidGroup, + + // BLE stack errors (0x3000+) + BleNotEnabled, + BleInvalidConnHandle, + BleInvalidAttrHandle, + BleInvalidAdvHandle, + BleInvalidRole, + BleBlockedByOtherLinks, + + // GAP errors (0x3200+) + GapUuidListMismatch, + GapDiscoverableWithWhitelist, + GapInvalidBleAddr, + GapWhitelistInUse, + GapDeviceIdentitiesInUse, + GapDeviceIdentitiesDuplicate, + + // GATTC errors (0x3300+) + GattcProcNotPermitted, + + // GATTS errors (0x3400+) + GattsInvalidAttrType, + GattsSysAttrMissing, + + Unknown, +}; + +pub fn check(ret: u32) Error!void { + if (ret == 0) return; + return from_code(ret); +} + +pub fn from_code(code: u32) Error { + return switch (code) { + 0x01 => Error.SvcHandlerMissing, + 0x02 => Error.SoftdeviceNotEnabled, + 0x03 => Error.Internal, + 0x04 => Error.NoMem, + 0x05 => Error.NotFound, + 0x06 => Error.NotSupported, + 0x07 => Error.InvalidParam, + 0x08 => Error.InvalidState, + 0x09 => Error.InvalidLength, + 0x0A => Error.InvalidFlags, + 0x0B => Error.InvalidData, + 0x0C => Error.DataSize, + 0x0D => Error.Timeout, + 0x0E => Error.Null, + 0x0F => Error.Forbidden, + 0x10 => Error.InvalidAddr, + 0x11 => Error.Busy, + 0x12 => Error.ConnCount, + 0x13 => Error.Resources, + + 0x1000 => Error.LfclkSourceUnknown, + 0x1001 => Error.IncorrectInterruptConfiguration, + 0x1002 => Error.IncorrectClenr0, + + 0x2000 => Error.MutexAlreadyTaken, + 0x2001 => Error.NvicInterruptNotAvailable, + 0x2002 => Error.NvicInterruptPriorityNotAllowed, + 0x2003 => Error.NvicShouldNotReturn, + 0x2004 => Error.PowerModeUnknown, + 0x2005 => Error.PowerPofThresholdUnknown, + 0x2006 => Error.PowerOffShouldNotReturn, + 0x2007 => Error.RandNotEnoughValues, + 0x2008 => Error.PpiInvalidChannel, + 0x2009 => Error.PpiInvalidGroup, + + 0x3001 => Error.BleNotEnabled, + 0x3002 => Error.BleInvalidConnHandle, + 0x3003 => Error.BleInvalidAttrHandle, + 0x3004 => Error.BleInvalidAdvHandle, + 0x3005 => Error.BleInvalidRole, + 0x3006 => Error.BleBlockedByOtherLinks, + + 0x3200 => Error.GapUuidListMismatch, + 0x3201 => Error.GapDiscoverableWithWhitelist, + 0x3202 => Error.GapInvalidBleAddr, + 0x3203 => Error.GapWhitelistInUse, + 0x3204 => Error.GapDeviceIdentitiesInUse, + 0x3205 => Error.GapDeviceIdentitiesDuplicate, + + 0x3300 => Error.GattcProcNotPermitted, + + 0x3400 => Error.GattsInvalidAttrType, + 0x3401 => Error.GattsSysAttrMissing, + + else => Error.Unknown, + }; +} diff --git a/port/nordic/nrf5x/src/softdevice/gap.zig b/port/nordic/nrf5x/src/softdevice/gap.zig new file mode 100644 index 000000000..fdc8a62e4 --- /dev/null +++ b/port/nordic/nrf5x/src/softdevice/gap.zig @@ -0,0 +1,880 @@ +// BLE GAP (Generic Access Profile) - advertising, connections, scanning, security. +// +// Types are defined at file scope (shared across all SoftDevice variants). +// Functions are variant-parameterized via Api(). + +const Self = @This(); +const svc = @import("svc.zig"); +const err = @import("err.zig"); +const Variant = @import("variant.zig").Variant; +const Error = err.Error; + +pub const addr_len = 6; +pub const sec_key_len = 16; +pub const sec_rand_len = 8; +pub const passkey_len = 6; +pub const lesc_p256_pk_len = 64; +pub const lesc_dhkey_len = 32; +pub const channel_count = 40; +pub const whitelist_addr_max_count = 8; +pub const device_identities_max_count = 8; +pub const devname_max_len = 248; +pub const adv_set_data_size_max: u16 = 31; +pub const adv_set_data_size_extended_max_supported: u16 = 255; + +pub const Role = enum(u8) { + invalid = 0x0, + periph = 0x1, + central = 0x2, +}; + +pub const AddrType = enum(u7) { + public = 0x00, + random_static = 0x01, + random_private_resolvable = 0x02, + random_private_non_resolvable = 0x03, + anonymous = 0x7F, + _, +}; + +pub const Addr = extern struct { + flags: packed struct(u8) { + addr_id_peer: u1 = 0, + addr_type: AddrType = .public, + } = .{}, + addr: [addr_len]u8 = .{0} ** addr_len, +}; + +pub const ConnParams = extern struct { + min_conn_interval: u16, + max_conn_interval: u16, + slave_latency: u16, + conn_sup_timeout: u16, +}; + +pub const ConnSecMode = packed struct(u8) { + sm: u4 = 0, + lv: u4 = 0, + + pub fn no_access() ConnSecMode { + return .{ .sm = 0, .lv = 0 }; + } + pub fn open() ConnSecMode { + return .{ .sm = 1, .lv = 1 }; + } + pub fn enc_no_mitm() ConnSecMode { + return .{ .sm = 1, .lv = 2 }; + } + pub fn enc_with_mitm() ConnSecMode { + return .{ .sm = 1, .lv = 3 }; + } + pub fn lesc_enc_with_mitm() ConnSecMode { + return .{ .sm = 1, .lv = 4 }; + } + pub fn signed_no_mitm() ConnSecMode { + return .{ .sm = 2, .lv = 1 }; + } + pub fn signed_with_mitm() ConnSecMode { + return .{ .sm = 2, .lv = 2 }; + } +}; + +pub const ConnSec = extern struct { + sec_mode: ConnSecMode, + encr_key_size: u8, +}; + +pub const Irk = extern struct { + irk: [sec_key_len]u8, +}; + +pub const ChannelMask = [5]u8; + +pub const AdvProperties = extern struct { + type_: AdvType, + flags: packed struct(u8) { + anonymous: u1 = 0, + include_tx_power: u1 = 0, + _pad: u6 = 0, + } = .{}, +}; + +pub const AdvType = enum(u8) { + connectable_scannable_undirected = 0x01, + connectable_nonscannable_directed_high_duty_cycle = 0x02, + connectable_nonscannable_directed = 0x03, + nonconnectable_scannable_undirected = 0x04, + nonconnectable_nonscannable_undirected = 0x05, + extended_connectable_nonscannable_undirected = 0x06, + extended_connectable_nonscannable_directed = 0x07, + extended_nonconnectable_scannable_undirected = 0x08, + extended_nonconnectable_scannable_directed = 0x09, + extended_nonconnectable_nonscannable_undirected = 0x0A, + extended_nonconnectable_nonscannable_directed = 0x0B, + _, +}; + +pub const AdvFilterPolicy = enum(u8) { + any = 0x00, + filter_scanreq = 0x01, + filter_connreq = 0x02, + filter_both = 0x03, +}; + +pub const AdvParams = extern struct { + properties: AdvProperties, + p_peer_addr: ?*const Addr = null, + interval: u32, + duration: u16, + max_adv_evts: u8 = 0, + channel_mask: ChannelMask = .{0} ** 5, + filter_policy: AdvFilterPolicy = .any, + primary_phy: Phy = .@"1mbps", + secondary_phy: Phy = .@"1mbps", + adv_flags: packed struct(u8) { + set_id: u4 = 0, + scan_req_notification: u1 = 0, + _pad: u3 = 0, + } = .{}, +}; + +pub const AdvData = extern struct { + adv_data: Data = .{}, + scan_rsp_data: Data = .{}, +}; + +pub const Data = extern struct { + p_data: ?[*]u8 = null, + len: u16 = 0, + + pub fn from_slice(slice: []u8) Data { + return .{ .p_data = slice.ptr, .len = @intCast(slice.len) }; + } +}; + +pub const Phy = enum(u8) { + auto = 0x00, + @"1mbps" = 0x01, + @"2mbps" = 0x02, + coded = 0x04, + not_set = 0xFF, + _, +}; + +pub const Phys = extern struct { + tx_phys: u8, + rx_phys: u8, +}; + +pub const ScanParams = extern struct { + flags: packed struct(u8) { + extended: u1 = 0, + report_incomplete_evts: u1 = 0, + active: u1 = 0, + filter_policy: u2 = 0, + _pad: u3 = 0, + } = .{}, + scan_phys: u8 = 0x01, + interval: u16, + window: u16, + timeout: u16 = 0, + channel_mask: ChannelMask = .{0} ** 5, +}; + +pub const PrivacyMode = enum(u8) { + off = 0x00, + device_privacy = 0x01, + network_privacy = 0x02, +}; + +pub const PrivacyParams = extern struct { + privacy_mode: PrivacyMode = .off, + private_addr_type: AddrType = .random_private_resolvable, + private_addr_cycle_s: u16 = 900, + p_device_irk: ?*Irk = null, +}; + +pub const SecKdist = packed struct(u8) { + enc: u1 = 0, + id: u1 = 0, + sign: u1 = 0, + link: u1 = 0, + _pad: u4 = 0, +}; + +pub const IoCaps = enum(u3) { + display_only = 0, + display_yesno = 1, + keyboard_only = 2, + none = 3, + keyboard_display = 4, +}; + +pub const SecParams = extern struct { + flags: packed struct(u8) { + bond: u1 = 0, + mitm: u1 = 0, + lesc: u1 = 0, + keypress: u1 = 0, + io_caps: IoCaps = .none, + oob: u1 = 0, + }, + min_key_size: u8 = 7, + max_key_size: u8 = 16, + kdist_own: SecKdist = .{}, + kdist_peer: SecKdist = .{}, +}; + +pub const EncInfo = extern struct { + ltk: [sec_key_len]u8, + flags: packed struct(u8) { + lesc: u1 = 0, + auth: u1 = 0, + ltk_len: u6 = 0, + }, +}; + +pub const MasterId = extern struct { + ediv: u16, + rand: [sec_rand_len]u8, +}; + +pub const SignInfo = extern struct { + csrk: [sec_key_len]u8, +}; + +pub const LescP256Pk = extern struct { + pk: [lesc_p256_pk_len]u8, +}; + +pub const LescDhkey = extern struct { + key: [lesc_dhkey_len]u8, +}; + +pub const LescOobData = extern struct { + addr: Addr, + r: [sec_key_len]u8, + c: [sec_key_len]u8, +}; + +pub const EncKey = extern struct { + enc_info: EncInfo, + master_id: MasterId, +}; + +pub const IdKey = extern struct { + id_info: Irk, + id_addr_info: Addr, +}; + +pub const SecKeys = extern struct { + p_enc_key: ?*EncKey = null, + p_id_key: ?*IdKey = null, + p_sign_key: ?*SignInfo = null, + p_pk: ?*LescP256Pk = null, +}; + +pub const SecKeyset = extern struct { + keys_own: SecKeys = .{}, + keys_peer: SecKeys = .{}, +}; + +pub const DataLengthParams = extern struct { + max_tx_octets: u16 = 0, + max_rx_octets: u16 = 0, + max_tx_time_us: u16 = 0, + max_rx_time_us: u16 = 0, +}; + +pub const DataLengthLimitation = extern struct { + tx_payload_limited_octets: u16, + rx_payload_limited_octets: u16, + tx_rx_time_limited_us: u16, +}; + +pub const ConnCfg = extern struct { + conn_count: u8 = 1, + event_length: u16 = 3, +}; + +pub const ConnEvtTrigger = extern struct { + ppi_ch_id: u8, + task_endpoint: u32, + conn_evt_counter_start: u16, + period_in_events: u16, +}; + +pub const AdvReportType = packed struct(u16) { + connectable: u1 = 0, + scannable: u1 = 0, + directed: u1 = 0, + scan_response: u1 = 0, + extended_pdu: u1 = 0, + status: u2 = 0, + _reserved: u9 = 0, +}; + +pub const TxPowerRole = enum(u8) { + adv = 1, + scan_init = 2, + conn = 3, +}; + +pub const CfgRoleCount = extern struct { + adv_set_count: u8 = 1, + periph_role_count: u8 = 1, + central_role_count: u8 = 3, + central_sec_count: u8 = 1, + qos_channel_survey_role_available: u8 = 0, +}; + +pub const CfgDeviceName = extern struct { + write_perm: ConnSecMode = ConnSecMode.no_access(), + vloc: u8 = 0x01, + p_value: ?[*]u8 = null, + current_len: u16 = 0, + max_len: u16 = 31, +}; + +pub const Cfg = extern union { + role_count: CfgRoleCount, + device_name: CfgDeviceName, + ppcp_include_cfg: u8, + car_include_cfg: u8, +}; + +// --- GAP Event structures --- + +pub const EvtConnected = extern struct { + peer_addr: Addr, + role: Role, + conn_params: ConnParams, + adv_handle: u8, + adv_data: AdvData, +}; + +pub const EvtDisconnected = extern struct { + reason: u8, +}; + +pub const EvtConnParamUpdate = extern struct { + conn_params: ConnParams, +}; + +pub const EvtSecParamsRequest = extern struct { + peer_params: SecParams, +}; + +pub const EvtSecInfoRequest = extern struct { + peer_addr: Addr, + master_id: MasterId, + enc_info: u1, + id_info: u1, + sign_info: u1, +}; + +pub const EvtPasskeyDisplay = extern struct { + passkey: [passkey_len]u8, + match_request: u8, +}; + +pub const EvtAuthKeyRequest = extern struct { + key_type: u8, +}; + +pub const EvtLescDhkeyRequest = extern struct { + p_pk_peer: *LescP256Pk, + oobd_req: u8, +}; + +pub const EvtAuthStatus = extern struct { + auth_status: u8, + error_src: u8, + bonded: u8, + lesc: u8, + sm1_levels: packed struct(u8) { + sec_mode_1_lv1: u1 = 0, + sec_mode_1_lv2: u1 = 0, + sec_mode_1_lv3: u1 = 0, + sec_mode_1_lv4: u1 = 0, + _pad: u4 = 0, + }, + sm2_levels: packed struct(u8) { + sec_mode_2_lv1: u1 = 0, + sec_mode_2_lv2: u1 = 0, + _pad: u6 = 0, + }, + kdist_own: SecKdist, + kdist_peer: SecKdist, +}; + +pub const EvtConnSecUpdate = extern struct { + conn_sec: ConnSec, +}; + +pub const EvtTimeout = extern struct { + src: TimeoutSrc, +}; + +pub const TimeoutSrc = enum(u8) { + scan = 0x01, + conn = 0x02, + auth_payload = 0x03, +}; + +pub const EvtRssiChanged = extern struct { + rssi: i8, + ch_index: u8, +}; + +pub const EvtAdvReport = extern struct { + type_: AdvReportType, + peer_addr: Addr, + direct_addr: Addr, + primary_phy: Phy, + secondary_phy: Phy, + tx_power: i8, + rssi: i8, + ch_index: u8, + set_id: u8, + data_id: u16 align(1), + data: Data, + aux_pointer: AuxPointer, +}; + +pub const AuxPointer = extern struct { + aux_offset: u16, + aux_phy: Phy, +}; + +pub const EvtSecRequest = extern struct { + bond: u8, + mitm: u8, + lesc: u8, + keypress: u8, +}; + +pub const EvtConnParamUpdateRequest = extern struct { + conn_params: ConnParams, +}; + +pub const EvtPhyUpdateRequest = extern struct { + peer_preferred_phys: Phys, +}; + +pub const EvtPhyUpdate = extern struct { + status: u8, + tx_phy: Phy, + rx_phy: Phy, +}; + +pub const EvtDataLengthUpdateRequest = extern struct { + peer_params: DataLengthParams, +}; + +pub const EvtDataLengthUpdate = extern struct { + effective_params: DataLengthParams, +}; + +pub const EvtQosChannelSurveyReport = extern struct { + channel_energy: [channel_count]i8, +}; + +pub const EvtAdvSetTerminated = extern struct { + reason: u8, + adv_handle: u8, + num_completed_adv_events: u8, + adv_data: AdvData, +}; + +pub const EvtScanReqReport = extern struct { + adv_handle: u8, + rssi: i8, + peer_addr: Addr, +}; + +pub const EvtKeyPressed = extern struct { + kp_not: u8, +}; + +// --- GAP Option types --- + +pub const OptChMap = extern struct { + conn_handle: u16, + ch_map: ChannelMask, +}; + +pub const OptLocalConnLatency = extern struct { + conn_handle: u16, + requested_latency: u16, + p_actual_latency: ?*u16, +}; + +pub const OptPasskey = extern struct { + p_passkey: ?*const [passkey_len]u8, +}; + +pub const OptCompatMode1 = extern struct { + enable: u8, +}; + +pub const OptAuthPayloadTimeout = extern struct { + conn_handle: u16, + auth_payload_timeout: u16, +}; + +pub const OptSlaveLatencyDisable = extern struct { + conn_handle: u16, + disable: u8, +}; + +pub const Opt = extern union { + ch_map: OptChMap, + local_conn_latency: OptLocalConnLatency, + passkey: OptPasskey, + compat_mode_1: OptCompatMode1, + auth_payload_timeout: OptAuthPayloadTimeout, + slave_latency_disable: OptSlaveLatencyDisable, +}; + +// --- GAP Event IDs --- + +pub const EvtId = enum(u16) { + connected = 0x10, + disconnected = 0x11, + conn_param_update = 0x12, + sec_params_request = 0x13, + sec_info_request = 0x14, + passkey_display = 0x15, + key_pressed = 0x16, + auth_key_request = 0x17, + lesc_dhkey_request = 0x18, + auth_status = 0x19, + conn_sec_update = 0x1A, + timeout = 0x1B, + rssi_changed = 0x1C, + adv_report = 0x1D, + sec_request = 0x1E, + conn_param_update_request = 0x1F, + scan_req_report = 0x20, + phy_update_request = 0x21, + phy_update = 0x22, + data_length_update_request = 0x23, + data_length_update = 0x24, + qos_channel_survey_report = 0x25, + adv_set_terminated = 0x26, + _, +}; + +// --- Advertising Data Type constants --- + +pub const ad_type = struct { + pub const flags: u8 = 0x01; + pub const @"16bit_service_uuid_more_available": u8 = 0x02; + pub const @"16bit_service_uuid_complete": u8 = 0x03; + pub const @"32bit_service_uuid_more_available": u8 = 0x04; + pub const @"32bit_service_uuid_complete": u8 = 0x05; + pub const @"128bit_service_uuid_more_available": u8 = 0x06; + pub const @"128bit_service_uuid_complete": u8 = 0x07; + pub const short_local_name: u8 = 0x08; + pub const complete_local_name: u8 = 0x09; + pub const tx_power_level: u8 = 0x0A; + pub const appearance: u8 = 0x19; + pub const manufacturer_specific_data: u8 = 0xFF; +}; + +pub const adv_flag = struct { + pub const le_limited_disc_mode: u8 = 0x01; + pub const le_general_disc_mode: u8 = 0x02; + pub const br_edr_not_supported: u8 = 0x04; + pub const le_only_limited_disc_mode: u8 = 0x01 | 0x04; + pub const le_only_general_disc_mode: u8 = 0x02 | 0x04; +}; + +// --- Variant-parameterized SVC function API --- + +pub fn Api(comptime v: Variant) type { + return struct { + // Type re-exports (shared across all variants, not duplicated) + pub const addr_len = Self.addr_len; + pub const sec_key_len = Self.sec_key_len; + pub const sec_rand_len = Self.sec_rand_len; + pub const passkey_len = Self.passkey_len; + pub const lesc_p256_pk_len = Self.lesc_p256_pk_len; + pub const lesc_dhkey_len = Self.lesc_dhkey_len; + pub const channel_count = Self.channel_count; + pub const whitelist_addr_max_count = Self.whitelist_addr_max_count; + pub const device_identities_max_count = Self.device_identities_max_count; + pub const devname_max_len = Self.devname_max_len; + pub const adv_set_data_size_max = Self.adv_set_data_size_max; + pub const adv_set_data_size_extended_max_supported = Self.adv_set_data_size_extended_max_supported; + + pub const Role = Self.Role; + pub const AddrType = Self.AddrType; + pub const Addr = Self.Addr; + pub const ConnParams = Self.ConnParams; + pub const ConnSecMode = Self.ConnSecMode; + pub const ConnSec = Self.ConnSec; + pub const Irk = Self.Irk; + pub const ChannelMask = Self.ChannelMask; + pub const AdvProperties = Self.AdvProperties; + pub const AdvType = Self.AdvType; + pub const AdvFilterPolicy = Self.AdvFilterPolicy; + pub const AdvParams = Self.AdvParams; + pub const AdvData = Self.AdvData; + pub const Data = Self.Data; + pub const Phy = Self.Phy; + pub const Phys = Self.Phys; + pub const ScanParams = Self.ScanParams; + pub const PrivacyMode = Self.PrivacyMode; + pub const PrivacyParams = Self.PrivacyParams; + pub const SecKdist = Self.SecKdist; + pub const IoCaps = Self.IoCaps; + pub const SecParams = Self.SecParams; + pub const EncInfo = Self.EncInfo; + pub const MasterId = Self.MasterId; + pub const SignInfo = Self.SignInfo; + pub const LescP256Pk = Self.LescP256Pk; + pub const LescDhkey = Self.LescDhkey; + pub const LescOobData = Self.LescOobData; + pub const EncKey = Self.EncKey; + pub const IdKey = Self.IdKey; + pub const SecKeys = Self.SecKeys; + pub const SecKeyset = Self.SecKeyset; + pub const DataLengthParams = Self.DataLengthParams; + pub const DataLengthLimitation = Self.DataLengthLimitation; + pub const ConnCfg = Self.ConnCfg; + pub const ConnEvtTrigger = Self.ConnEvtTrigger; + pub const AdvReportType = Self.AdvReportType; + pub const TxPowerRole = Self.TxPowerRole; + pub const CfgRoleCount = Self.CfgRoleCount; + pub const CfgDeviceName = Self.CfgDeviceName; + pub const Cfg = Self.Cfg; + + pub const EvtConnected = Self.EvtConnected; + pub const EvtDisconnected = Self.EvtDisconnected; + pub const EvtConnParamUpdate = Self.EvtConnParamUpdate; + pub const EvtSecParamsRequest = Self.EvtSecParamsRequest; + pub const EvtSecInfoRequest = Self.EvtSecInfoRequest; + pub const EvtPasskeyDisplay = Self.EvtPasskeyDisplay; + pub const EvtAuthKeyRequest = Self.EvtAuthKeyRequest; + pub const EvtLescDhkeyRequest = Self.EvtLescDhkeyRequest; + pub const EvtAuthStatus = Self.EvtAuthStatus; + pub const EvtConnSecUpdate = Self.EvtConnSecUpdate; + pub const EvtTimeout = Self.EvtTimeout; + pub const TimeoutSrc = Self.TimeoutSrc; + pub const EvtRssiChanged = Self.EvtRssiChanged; + pub const EvtAdvReport = Self.EvtAdvReport; + pub const AuxPointer = Self.AuxPointer; + pub const EvtSecRequest = Self.EvtSecRequest; + pub const EvtConnParamUpdateRequest = Self.EvtConnParamUpdateRequest; + pub const EvtPhyUpdateRequest = Self.EvtPhyUpdateRequest; + pub const EvtPhyUpdate = Self.EvtPhyUpdate; + pub const EvtDataLengthUpdateRequest = Self.EvtDataLengthUpdateRequest; + pub const EvtDataLengthUpdate = Self.EvtDataLengthUpdate; + pub const EvtQosChannelSurveyReport = Self.EvtQosChannelSurveyReport; + pub const EvtAdvSetTerminated = Self.EvtAdvSetTerminated; + pub const EvtScanReqReport = Self.EvtScanReqReport; + pub const EvtKeyPressed = Self.EvtKeyPressed; + + pub const OptChMap = Self.OptChMap; + pub const OptLocalConnLatency = Self.OptLocalConnLatency; + pub const OptPasskey = Self.OptPasskey; + pub const OptCompatMode1 = Self.OptCompatMode1; + pub const OptAuthPayloadTimeout = Self.OptAuthPayloadTimeout; + pub const OptSlaveLatencyDisable = Self.OptSlaveLatencyDisable; + pub const Opt = Self.Opt; + + pub const EvtId = Self.EvtId; + pub const ad_type = Self.ad_type; + pub const adv_flag = Self.adv_flag; + + // --- SVC functions --- + + pub fn addr_set(p_addr: *const Self.Addr) Error!void { + return err.check(svc.svcall1(v.gap_svc(.addr_set), @intFromPtr(p_addr))); + } + + pub fn addr_get(p_addr: *Self.Addr) Error!void { + return err.check(svc.svcall1(v.gap_svc(.addr_get), @intFromPtr(p_addr))); + } + + pub fn whitelist_set(pp_wl_addrs: [*]const *const Self.Addr, len: u8) Error!void { + return err.check(svc.svcall2(v.gap_svc(.whitelist_set), @intFromPtr(pp_wl_addrs), len)); + } + + pub fn device_identities_set(pp_id_keys: [*]const *const Self.Addr, pp_local_irks: ?[*]const *const Self.Irk, len: u8) Error!void { + return err.check(svc.svcall3(v.gap_svc(.device_identities_set), @intFromPtr(pp_id_keys), @intFromPtr(pp_local_irks), len)); + } + + pub fn privacy_set(p_privacy_params: *const Self.PrivacyParams) Error!void { + return err.check(svc.svcall1(v.gap_svc(.privacy_set), @intFromPtr(p_privacy_params))); + } + + pub fn privacy_get(p_privacy_params: *Self.PrivacyParams) Error!void { + return err.check(svc.svcall1(v.gap_svc(.privacy_get), @intFromPtr(p_privacy_params))); + } + + pub fn adv_set_configure(p_adv_handle: *u8, p_adv_data: ?*const Self.AdvData, p_adv_params: ?*const Self.AdvParams) Error!void { + return err.check(svc.svcall3(v.gap_svc(.adv_set_configure), @intFromPtr(p_adv_handle), @intFromPtr(p_adv_data), @intFromPtr(p_adv_params))); + } + + pub fn adv_start(adv_handle: u8, conn_cfg_tag: u8) Error!void { + return err.check(svc.svcall2(v.gap_svc(.adv_start), adv_handle, conn_cfg_tag)); + } + + pub fn adv_stop(adv_handle: u8) Error!void { + return err.check(svc.svcall1(v.gap_svc(.adv_stop), adv_handle)); + } + + pub fn conn_param_update(conn_handle: u16, p_conn_params: *const Self.ConnParams) Error!void { + return err.check(svc.svcall2(v.gap_svc(.conn_param_update), conn_handle, @intFromPtr(p_conn_params))); + } + + pub fn disconnect(conn_handle: u16, hci_status_code: u8) Error!void { + return err.check(svc.svcall2(v.gap_svc(.disconnect), conn_handle, hci_status_code)); + } + + pub fn tx_power_set(role: Self.TxPowerRole, handle: u16, tx_power: i8) Error!void { + return err.check(svc.svcall3(v.gap_svc(.tx_power_set), @intFromEnum(role), handle, @as(usize, @bitCast(@as(isize, tx_power))))); + } + + pub fn appearance_set(appearance: u16) Error!void { + return err.check(svc.svcall1(v.gap_svc(.appearance_set), appearance)); + } + + pub fn appearance_get(p_appearance: *u16) Error!void { + return err.check(svc.svcall1(v.gap_svc(.appearance_get), @intFromPtr(p_appearance))); + } + + pub fn ppcp_set(p_conn_params: *const Self.ConnParams) Error!void { + return err.check(svc.svcall1(v.gap_svc(.ppcp_set), @intFromPtr(p_conn_params))); + } + + pub fn ppcp_get(p_conn_params: *Self.ConnParams) Error!void { + return err.check(svc.svcall1(v.gap_svc(.ppcp_get), @intFromPtr(p_conn_params))); + } + + pub fn device_name_set(p_write_perm: *const Self.ConnSecMode, p_dev_name: [*]const u8, len: u16) Error!void { + return err.check(svc.svcall3(v.gap_svc(.device_name_set), @intFromPtr(p_write_perm), @intFromPtr(p_dev_name), len)); + } + + pub fn device_name_get(p_dev_name: ?[*]u8, p_len: *u16) Error!void { + return err.check(svc.svcall2(v.gap_svc(.device_name_get), @intFromPtr(p_dev_name), @intFromPtr(p_len))); + } + + pub fn authenticate(conn_handle: u16, p_sec_params: ?*const Self.SecParams) Error!void { + return err.check(svc.svcall2(v.gap_svc(.authenticate), conn_handle, @intFromPtr(p_sec_params))); + } + + pub fn sec_params_reply(conn_handle: u16, sec_status: u8, p_sec_params: ?*const Self.SecParams, p_sec_keyset: ?*Self.SecKeyset) Error!void { + return err.check(svc.svcall4(v.gap_svc(.sec_params_reply), conn_handle, sec_status, @intFromPtr(p_sec_params), @intFromPtr(p_sec_keyset))); + } + + pub fn auth_key_reply(conn_handle: u16, key_type: u8, p_key: ?[*]const u8) Error!void { + return err.check(svc.svcall3(v.gap_svc(.auth_key_reply), conn_handle, key_type, @intFromPtr(p_key))); + } + + pub fn lesc_dhkey_reply(conn_handle: u16, p_dhkey: *const Self.LescDhkey) Error!void { + return err.check(svc.svcall2(v.gap_svc(.lesc_dhkey_reply), conn_handle, @intFromPtr(p_dhkey))); + } + + pub fn keypress_notify(conn_handle: u16, kp_not: u8) Error!void { + return err.check(svc.svcall2(v.gap_svc(.keypress_notify), conn_handle, kp_not)); + } + + pub fn lesc_oob_data_get(conn_handle: u16, p_pk_own: *const Self.LescP256Pk, p_oobd_own: *Self.LescOobData) Error!void { + return err.check(svc.svcall3(v.gap_svc(.lesc_oob_data_get), conn_handle, @intFromPtr(p_pk_own), @intFromPtr(p_oobd_own))); + } + + pub fn lesc_oob_data_set(conn_handle: u16, p_oobd_own: ?*const Self.LescOobData, p_oobd_peer: ?*const Self.LescOobData) Error!void { + return err.check(svc.svcall3(v.gap_svc(.lesc_oob_data_set), conn_handle, @intFromPtr(p_oobd_own), @intFromPtr(p_oobd_peer))); + } + + /// Central-only: encrypt a connection as the central device. + pub fn encrypt(conn_handle: u16, p_master_id: *const Self.MasterId, p_enc_info: *const Self.EncInfo) Error!void { + if (!v.has_central()) @compileError("encrypt requires central role (S132/S140)"); + return err.check(svc.svcall3(v.gap_svc(.encrypt), conn_handle, @intFromPtr(p_master_id), @intFromPtr(p_enc_info))); + } + + pub fn sec_info_reply(conn_handle: u16, p_enc_info: ?*const Self.EncInfo, p_id_info: ?*const Self.Irk, p_sign_info: ?*const Self.SignInfo) Error!void { + return err.check(svc.svcall4(v.gap_svc(.sec_info_reply), conn_handle, @intFromPtr(p_enc_info), @intFromPtr(p_id_info), @intFromPtr(p_sign_info))); + } + + pub fn conn_sec_get(conn_handle: u16, p_conn_sec: *Self.ConnSec) Error!void { + return err.check(svc.svcall2(v.gap_svc(.conn_sec_get), conn_handle, @intFromPtr(p_conn_sec))); + } + + pub fn rssi_start(conn_handle: u16, threshold_dbm: u8, skip_count: u8) Error!void { + return err.check(svc.svcall3(v.gap_svc(.rssi_start), conn_handle, threshold_dbm, skip_count)); + } + + pub fn rssi_stop(conn_handle: u16) Error!void { + return err.check(svc.svcall1(v.gap_svc(.rssi_stop), conn_handle)); + } + + /// Observer/central-only: start scanning for advertising packets. + pub fn scan_start(p_scan_params: *const Self.ScanParams, p_adv_report_buffer: *const Self.Data) Error!void { + if (!v.has_observer()) @compileError("scan_start requires observer role (S113/S132/S140)"); + return err.check(svc.svcall2(v.gap_svc(.scan_start), @intFromPtr(p_scan_params), @intFromPtr(p_adv_report_buffer))); + } + + /// Observer/central-only: stop scanning. + pub fn scan_stop() Error!void { + if (!v.has_observer()) @compileError("scan_stop requires observer role (S113/S132/S140)"); + return err.check(svc.svcall0(v.gap_svc(.scan_stop))); + } + + /// Central-only: initiate a connection to a peer. + pub fn connect(p_peer_addr: *const Self.Addr, p_scan_params: *const Self.ScanParams, p_conn_params: *const Self.ConnParams, conn_cfg_tag: u8) Error!void { + if (!v.has_central()) @compileError("connect requires central role (S132/S140)"); + return err.check(svc.svcall4(v.gap_svc(.connect), @intFromPtr(p_peer_addr), @intFromPtr(p_scan_params), @intFromPtr(p_conn_params), conn_cfg_tag)); + } + + /// Central-only: cancel a pending connection attempt. + pub fn connect_cancel() Error!void { + if (!v.has_central()) @compileError("connect_cancel requires central role (S132/S140)"); + return err.check(svc.svcall0(v.gap_svc(.connect_cancel))); + } + + pub fn rssi_get(conn_handle: u16, p_rssi: *i8, p_ch_index: ?*u8) Error!void { + return err.check(svc.svcall3(v.gap_svc(.rssi_get), conn_handle, @intFromPtr(p_rssi), @intFromPtr(p_ch_index))); + } + + pub fn phy_update(conn_handle: u16, p_gap_phys: *const Self.Phys) Error!void { + return err.check(svc.svcall2(v.gap_svc(.phy_update), conn_handle, @intFromPtr(p_gap_phys))); + } + + pub fn data_length_update(conn_handle: u16, p_dl_params: ?*const Self.DataLengthParams, p_dl_limitation: ?*Self.DataLengthLimitation) Error!void { + return err.check(svc.svcall3(v.gap_svc(.data_length_update), conn_handle, @intFromPtr(p_dl_params), @intFromPtr(p_dl_limitation))); + } + + /// Start QoS channel survey (S122/S132/S140 only). + pub fn qos_channel_survey_start(interval_us: u32) Error!void { + if (!v.has_qos_survey()) @compileError("qos_channel_survey_start not available on " ++ @tagName(v)); + return err.check(svc.svcall1(v.gap_svc(.qos_channel_survey_start), interval_us)); + } + + /// Stop QoS channel survey (S122/S132/S140 only). + pub fn qos_channel_survey_stop() Error!void { + if (!v.has_qos_survey()) @compileError("qos_channel_survey_stop not available on " ++ @tagName(v)); + return err.check(svc.svcall0(v.gap_svc(.qos_channel_survey_stop))); + } + + pub fn adv_addr_get(adv_handle: u8, p_addr: *Self.Addr) Error!void { + return err.check(svc.svcall2(v.gap_svc(.adv_addr_get), adv_handle, @intFromPtr(p_addr))); + } + + pub fn next_conn_evt_counter_get(conn_handle: u16, p_counter: *u16) Error!void { + return err.check(svc.svcall2(v.gap_svc(.next_conn_evt_counter_get), conn_handle, @intFromPtr(p_counter))); + } + + pub fn conn_evt_trigger_start(conn_handle: u16, p_params: *const Self.ConnEvtTrigger) Error!void { + return err.check(svc.svcall2(v.gap_svc(.conn_evt_trigger_start), conn_handle, @intFromPtr(p_params))); + } + + pub fn conn_evt_trigger_stop(conn_handle: u16) Error!void { + return err.check(svc.svcall1(v.gap_svc(.conn_evt_trigger_stop), conn_handle)); + } + }; +} diff --git a/port/nordic/nrf5x/src/softdevice/gatt.zig b/port/nordic/nrf5x/src/softdevice/gatt.zig new file mode 100644 index 000000000..5511c8fe8 --- /dev/null +++ b/port/nordic/nrf5x/src/softdevice/gatt.zig @@ -0,0 +1,107 @@ +// GATT common types and constants shared between GATTC and GATTS. + +pub const att_mtu_default: u16 = 23; +pub const handle_invalid: u16 = 0x0000; +pub const handle_start: u16 = 0x0001; +pub const handle_end: u16 = 0xFFFF; + +pub const TimeoutSrc = enum(u8) { + protocol = 0x00, +}; + +pub const WriteOp = enum(u8) { + invalid = 0x00, + write_req = 0x01, + write_cmd = 0x02, + sign_write_cmd = 0x03, + prep_write_req = 0x04, + exec_write_req = 0x05, +}; + +pub const ExecWriteFlag = enum(u8) { + prepared_cancel = 0x00, + prepared_write = 0x01, +}; + +pub const HvxType = enum(u8) { + invalid = 0x00, + notification = 0x01, + indication = 0x02, +}; + +pub const ConnCfg = extern struct { + att_mtu: u16, +}; + +pub const CharProps = packed struct(u8) { + broadcast: u1 = 0, + read: u1 = 0, + write_wo_resp: u1 = 0, + write: u1 = 0, + notify: u1 = 0, + indicate: u1 = 0, + auth_signed_wr: u1 = 0, + _pad: u1 = 0, +}; + +pub const CharExtProps = packed struct(u8) { + reliable_wr: u1 = 0, + wr_aux: u1 = 0, + _pad: u6 = 0, +}; + +pub const StatusCode = enum(u16) { + success = 0x0000, + unknown = 0x0001, + atterr_invalid = 0x0100, + atterr_invalid_handle = 0x0101, + atterr_read_not_permitted = 0x0102, + atterr_write_not_permitted = 0x0103, + atterr_invalid_pdu = 0x0104, + atterr_insuf_authentication = 0x0105, + atterr_request_not_supported = 0x0106, + atterr_invalid_offset = 0x0107, + atterr_insuf_authorization = 0x0108, + atterr_prepare_queue_full = 0x0109, + atterr_attribute_not_found = 0x010A, + atterr_attribute_not_long = 0x010B, + atterr_insuf_enc_key_size = 0x010C, + atterr_invalid_att_val_length = 0x010D, + atterr_unlikely_error = 0x010E, + atterr_insuf_encryption = 0x010F, + atterr_unsupported_group_type = 0x0110, + atterr_insuf_resources = 0x0111, + _, +}; + +pub const CpfFormat = enum(u8) { + rfu = 0x00, + boolean = 0x01, + two_bit = 0x02, + nibble = 0x03, + uint8 = 0x04, + uint12 = 0x05, + uint16 = 0x06, + uint24 = 0x07, + uint32 = 0x08, + uint48 = 0x09, + uint64 = 0x0A, + uint128 = 0x0B, + sint8 = 0x0C, + sint12 = 0x0D, + sint16 = 0x0E, + sint24 = 0x0F, + sint32 = 0x10, + sint48 = 0x11, + sint64 = 0x12, + sint128 = 0x13, + float32 = 0x14, + float64 = 0x15, + sfloat = 0x16, + float_ = 0x17, + duint16 = 0x18, + utf8s = 0x19, + utf16s = 0x1A, + struct_ = 0x1B, + _, +}; diff --git a/port/nordic/nrf5x/src/softdevice/gattc.zig b/port/nordic/nrf5x/src/softdevice/gattc.zig new file mode 100644 index 000000000..b681e419e --- /dev/null +++ b/port/nordic/nrf5x/src/softdevice/gattc.zig @@ -0,0 +1,212 @@ +// BLE GATT Client - service/characteristic discovery, read, write, and notifications. + +const svc = @import("svc.zig"); +const err = @import("err.zig"); +const gatt = @import("gatt.zig"); +const Error = err.Error; + +// SVC base = 0x9B +const SVC_BASE = 0x9B; + +pub const Uuid = extern struct { + uuid: u16, + type_: u8, +}; + +pub const Uuid128 = extern struct { + uuid128: [16]u8, +}; + +pub const HandleRange = extern struct { + start_handle: u16, + end_handle: u16, +}; + +pub const Service = extern struct { + uuid: Uuid, + handle_range: HandleRange, +}; + +pub const Include = extern struct { + handle: u16, + included_srvc: Service, +}; + +pub const Char = extern struct { + uuid: Uuid, + char_props: gatt.CharProps, + char_ext_props_flags: packed struct(u8) { + char_ext_props: u1 = 0, + _pad: u7 = 0, + } = .{}, + handle_decl: u16, + handle_value: u16, +}; + +pub const Desc = extern struct { + handle: u16, + uuid: Uuid, +}; + +pub const WriteParams = extern struct { + write_op: gatt.WriteOp, + flags: u8 = 0, + handle: u16, + offset: u16 = 0, + len: u16, + p_value: [*]const u8, +}; + +pub const AttrInfo16 = extern struct { + handle: u16, + uuid: Uuid, +}; + +pub const AttrInfo128 = extern struct { + handle: u16, + uuid: Uuid128, +}; + +pub const AttrInfoFormat = enum(u8) { + @"16bit" = 1, + @"128bit" = 2, +}; + +pub const ConnCfg = extern struct { + write_cmd_tx_queue_size: u8 = 1, +}; + +pub const OptUuidDisc = extern struct { + auto_add_vs_enable_flags: packed struct(u8) { + auto_add_vs_enable: u1 = 0, + _pad: u7 = 0, + } = .{}, +}; + +pub const Opt = extern union { + uuid_disc: OptUuidDisc, +}; + +// --- Event structures --- + +pub const EvtPrimSrvcDiscRsp = extern struct { + count: u16, + services: [1]Service, +}; + +pub const EvtRelDiscRsp = extern struct { + count: u16, + includes: [1]Include, +}; + +pub const EvtCharDiscRsp = extern struct { + count: u16, + chars: [1]Char, +}; + +pub const EvtDescDiscRsp = extern struct { + count: u16, + descs: [1]Desc, +}; + +pub const EvtReadRsp = extern struct { + handle: u16, + offset: u16, + len: u16, + data: [1]u8, +}; + +pub const EvtCharValsReadRsp = extern struct { + len: u16, + values: [1]u8, +}; + +pub const EvtWriteRsp = extern struct { + handle: u16, + write_op: u8, + offset: u16, + len: u16, + data: [1]u8, +}; + +pub const EvtHvx = extern struct { + handle: u16, + type_: gatt.HvxType, + len: u16, + data: [1]u8, +}; + +pub const EvtExchangeMtuRsp = extern struct { + server_rx_mtu: u16, +}; + +pub const EvtTimeout = extern struct { + src: u8, +}; + +pub const EvtWriteCmdTxComplete = extern struct { + count: u8, +}; + +pub const EvtId = enum(u16) { + prim_srvc_disc_rsp = 0x30, + rel_disc_rsp = 0x31, + char_disc_rsp = 0x32, + desc_disc_rsp = 0x33, + attr_info_disc_rsp = 0x34, + char_val_by_uuid_read_rsp = 0x35, + read_rsp = 0x36, + char_vals_read_rsp = 0x37, + write_rsp = 0x38, + hvx = 0x39, + exchange_mtu_rsp = 0x3A, + timeout = 0x3B, + write_cmd_tx_complete = 0x3C, + _, +}; + +// --- SVC functions --- + +pub fn primary_services_discover(conn_handle: u16, start_handle: u16, p_srvc_uuid: ?*const Uuid) Error!void { + return err.check(svc.svcall3(SVC_BASE + 0, conn_handle, start_handle, @intFromPtr(p_srvc_uuid))); +} + +pub fn relationships_discover(conn_handle: u16, p_handle_range: *const HandleRange) Error!void { + return err.check(svc.svcall2(SVC_BASE + 1, conn_handle, @intFromPtr(p_handle_range))); +} + +pub fn characteristics_discover(conn_handle: u16, p_handle_range: *const HandleRange) Error!void { + return err.check(svc.svcall2(SVC_BASE + 2, conn_handle, @intFromPtr(p_handle_range))); +} + +pub fn descriptors_discover(conn_handle: u16, p_handle_range: *const HandleRange) Error!void { + return err.check(svc.svcall2(SVC_BASE + 3, conn_handle, @intFromPtr(p_handle_range))); +} + +pub fn attr_info_discover(conn_handle: u16, p_handle_range: *const HandleRange) Error!void { + return err.check(svc.svcall2(SVC_BASE + 4, conn_handle, @intFromPtr(p_handle_range))); +} + +pub fn char_value_by_uuid_read(conn_handle: u16, p_uuid: *const Uuid, p_handle_range: *const HandleRange) Error!void { + return err.check(svc.svcall3(SVC_BASE + 5, conn_handle, @intFromPtr(p_uuid), @intFromPtr(p_handle_range))); +} + +pub fn read(conn_handle: u16, handle: u16, offset: u16) Error!void { + return err.check(svc.svcall3(SVC_BASE + 6, conn_handle, handle, offset)); +} + +pub fn char_values_read(conn_handle: u16, p_handles: [*]const u16, handle_count: u16) Error!void { + return err.check(svc.svcall3(SVC_BASE + 7, conn_handle, @intFromPtr(p_handles), handle_count)); +} + +pub fn write(conn_handle: u16, p_write_params: *const WriteParams) Error!void { + return err.check(svc.svcall2(SVC_BASE + 8, conn_handle, @intFromPtr(p_write_params))); +} + +pub fn hv_confirm(conn_handle: u16, handle: u16) Error!void { + return err.check(svc.svcall2(SVC_BASE + 9, conn_handle, handle)); +} + +pub fn exchange_mtu_request(conn_handle: u16, client_rx_mtu: u16) Error!void { + return err.check(svc.svcall2(SVC_BASE + 10, conn_handle, client_rx_mtu)); +} diff --git a/port/nordic/nrf5x/src/softdevice/gatts.zig b/port/nordic/nrf5x/src/softdevice/gatts.zig new file mode 100644 index 000000000..f83e1df8c --- /dev/null +++ b/port/nordic/nrf5x/src/softdevice/gatts.zig @@ -0,0 +1,255 @@ +// BLE GATT Server - service/characteristic management, notifications, indications. + +const svc = @import("svc.zig"); +const err = @import("err.zig"); +const gatt = @import("gatt.zig"); +const gap = @import("gap.zig"); +const Error = err.Error; + +// SVC base = 0xA8 +const SVC_BASE = 0xA8; + +pub const Uuid = extern struct { + uuid: u16, + type_: u8, +}; + +pub const SrvcType = enum(u8) { + invalid = 0x00, + primary = 0x01, + secondary = 0x02, +}; + +pub const Vloc = enum(u2) { + invalid = 0, + stack = 1, + user = 2, +}; + +pub const AuthorizeType = enum(u8) { + invalid = 0x00, + read = 0x01, + write = 0x02, +}; + +pub const AttrMd = extern struct { + read_perm: gap.ConnSecMode = gap.ConnSecMode.open(), + write_perm: gap.ConnSecMode = gap.ConnSecMode.open(), + flags: packed struct(u8) { + vlen: u1 = 0, + vloc: Vloc = .stack, + rd_auth: u1 = 0, + wr_auth: u1 = 0, + _pad: u3 = 0, + } = .{}, +}; + +pub const Attr = extern struct { + p_uuid: *const Uuid, + p_attr_md: *const AttrMd, + init_len: u16, + init_offs: u16 = 0, + max_len: u16, + p_value: ?[*]u8 = null, +}; + +pub const Value = extern struct { + len: u16, + offset: u16 = 0, + p_value: ?[*]u8 = null, +}; + +pub const CharPf = extern struct { + format: gatt.CpfFormat, + exponent: i8 = 0, + unit: u16 = 0, + name_space: u8 = 0, + desc: u16 = 0, +}; + +pub const CharMd = extern struct { + char_props: gatt.CharProps = .{}, + char_ext_props: gatt.CharExtProps = .{}, + p_char_user_desc: ?[*]const u8 = null, + char_user_desc_max_size: u16 = 0, + char_user_desc_size: u16 = 0, + p_char_pf: ?*const CharPf = null, + p_user_desc_md: ?*const AttrMd = null, + p_cccd_md: ?*const AttrMd = null, + p_sccd_md: ?*const AttrMd = null, +}; + +pub const CharHandles = extern struct { + value_handle: u16 = 0, + user_desc_handle: u16 = 0, + cccd_handle: u16 = 0, + sccd_handle: u16 = 0, +}; + +pub const HvxParams = extern struct { + handle: u16, + type_: gatt.HvxType, + offset: u16 = 0, + p_len: *u16, + p_data: ?[*]const u8 = null, +}; + +pub const AuthorizeParams = extern struct { + gatt_status: gatt.StatusCode = .success, + flags: packed struct(u8) { + update: u1 = 0, + _pad: u7 = 0, + } = .{}, + offset: u16 = 0, + len: u16 = 0, + p_data: ?[*]const u8 = null, +}; + +pub const RwAuthorizeReplyParams = extern struct { + type_: AuthorizeType, + params: extern union { + read: AuthorizeParams, + write: AuthorizeParams, + }, +}; + +pub const ConnCfg = extern struct { + hvn_tx_queue_size: u8 = 1, +}; + +pub const CfgServiceChanged = extern struct { + flags: packed struct(u8) { + service_changed: u1 = 1, + _pad: u7 = 0, + } = .{}, +}; + +pub const CfgAttrTabSize = extern struct { + attr_tab_size: u32 = 1408, +}; + +pub const Cfg = extern union { + service_changed: CfgServiceChanged, + attr_tab_size: CfgAttrTabSize, +}; + +pub const SysAttrFlag = struct { + pub const sys_srvcs: u32 = 1 << 0; + pub const usr_srvcs: u32 = 1 << 1; +}; + +// --- Event structures --- + +pub const EvtWrite = extern struct { + handle: u16, + uuid: Uuid, + op: u8, + auth_required: u8, + offset: u16, + len: u16, + data: [1]u8, +}; + +pub const EvtRead = extern struct { + handle: u16, + uuid: Uuid, + offset: u16, +}; + +pub const EvtRwAuthorizeRequest = extern struct { + type_: AuthorizeType, + request: extern union { + read: EvtRead, + write: EvtWrite, + }, +}; + +pub const EvtSysAttrMissing = extern struct { + hint: u8, +}; + +pub const EvtHvc = extern struct { + handle: u16, +}; + +pub const EvtExchangeMtuRequest = extern struct { + client_rx_mtu: u16, +}; + +pub const EvtTimeout = extern struct { + src: u8, +}; + +pub const EvtHvnTxComplete = extern struct { + count: u8, +}; + +pub const EvtId = enum(u16) { + write = 0x50, + rw_authorize_request = 0x52, + sys_attr_missing = 0x53, + hvc = 0x54, + sc_confirm = 0x55, + exchange_mtu_request = 0x56, + timeout = 0x57, + hvn_tx_complete = 0x58, + _, +}; + +// --- SVC functions --- + +pub fn service_add(type_: SrvcType, p_uuid: *const Uuid, p_handle: *u16) Error!void { + return err.check(svc.svcall3(SVC_BASE + 6, @intFromEnum(type_), @intFromPtr(p_uuid), @intFromPtr(p_handle))); +} + +pub fn include_add(service_handle: u16, inc_srvc_handle: u16, p_include_handle: *u16) Error!void { + return err.check(svc.svcall3(SVC_BASE + 7, service_handle, inc_srvc_handle, @intFromPtr(p_include_handle))); +} + +pub fn characteristic_add(service_handle: u16, p_char_md: *const CharMd, p_attr_char_value: *const Attr, p_handles: *CharHandles) Error!void { + return err.check(svc.svcall4(SVC_BASE + 1, service_handle, @intFromPtr(p_char_md), @intFromPtr(p_attr_char_value), @intFromPtr(p_handles))); +} + +pub fn descriptor_add(char_handle: u16, p_attr: *const Attr, p_handle: *u16) Error!void { + return err.check(svc.svcall3(SVC_BASE + 2, char_handle, @intFromPtr(p_attr), @intFromPtr(p_handle))); +} + +pub fn value_set(conn_handle: u16, handle: u16, p_value: *Value) Error!void { + return err.check(svc.svcall3(SVC_BASE + 3, conn_handle, handle, @intFromPtr(p_value))); +} + +pub fn value_get(conn_handle: u16, handle: u16, p_value: *Value) Error!void { + return err.check(svc.svcall3(SVC_BASE + 4, conn_handle, handle, @intFromPtr(p_value))); +} + +pub fn hvx(conn_handle: u16, p_hvx_params: *HvxParams) Error!void { + return err.check(svc.svcall2(SVC_BASE + 5, conn_handle, @intFromPtr(p_hvx_params))); +} + +pub fn service_changed(conn_handle: u16, start_handle: u16, end_handle: u16) Error!void { + return err.check(svc.svcall3(SVC_BASE + 0, conn_handle, start_handle, end_handle)); +} + +pub fn rw_authorize_reply(conn_handle: u16, p_rw_authorize_reply_params: *const RwAuthorizeReplyParams) Error!void { + return err.check(svc.svcall2(SVC_BASE + 8, conn_handle, @intFromPtr(p_rw_authorize_reply_params))); +} + +pub fn sys_attr_set(conn_handle: u16, p_sys_attr_data: ?[*]const u8, len: u16, flags: u32) Error!void { + return err.check(svc.svcall4(SVC_BASE + 9, conn_handle, @intFromPtr(p_sys_attr_data), len, flags)); +} + +pub fn sys_attr_get(conn_handle: u16, p_sys_attr_data: ?[*]u8, p_len: *u16, flags: u32) Error!void { + return err.check(svc.svcall4(SVC_BASE + 10, conn_handle, @intFromPtr(p_sys_attr_data), @intFromPtr(p_len), flags)); +} + +pub fn initial_user_handle_get(p_handle: *u16) Error!void { + return err.check(svc.svcall1(SVC_BASE + 11, @intFromPtr(p_handle))); +} + +pub fn attr_get(handle: u16, p_uuid: ?*Uuid, p_md: ?*AttrMd) Error!void { + return err.check(svc.svcall3(SVC_BASE + 12, handle, @intFromPtr(p_uuid), @intFromPtr(p_md))); +} + +pub fn exchange_mtu_reply(conn_handle: u16, server_rx_mtu: u16) Error!void { + return err.check(svc.svcall2(SVC_BASE + 13, conn_handle, server_rx_mtu)); +} diff --git a/port/nordic/nrf5x/src/softdevice/hci.zig b/port/nordic/nrf5x/src/softdevice/hci.zig new file mode 100644 index 000000000..c55e9b680 --- /dev/null +++ b/port/nordic/nrf5x/src/softdevice/hci.zig @@ -0,0 +1,33 @@ +// BLE HCI (Host Controller Interface) status codes. + +pub const StatusCode = enum(u8) { + success = 0x00, + unknown_btle_command = 0x01, + unknown_connection_identifier = 0x02, + authentication_failure = 0x05, + pin_or_key_missing = 0x06, + memory_capacity_exceeded = 0x07, + connection_timeout = 0x08, + command_disallowed = 0x0C, + invalid_btle_command_parameters = 0x12, + remote_user_terminated_connection = 0x13, + remote_dev_termination_due_to_low_resources = 0x14, + remote_dev_termination_due_to_power_off = 0x15, + local_host_terminated_connection = 0x16, + unsupported_remote_feature = 0x1A, + invalid_lmp_parameters = 0x1E, + unspecified_error = 0x1F, + lmp_response_timeout = 0x22, + lmp_error_transaction_collision = 0x23, + lmp_pdu_not_allowed = 0x24, + instant_passed = 0x28, + pairing_with_unit_key_unsupported = 0x29, + different_transaction_collision = 0x2A, + parameter_out_of_mandatory_range = 0x30, + controller_busy = 0x3A, + conn_interval_unacceptable = 0x3B, + directed_advertiser_timeout = 0x3C, + conn_terminated_due_to_mic_failure = 0x3D, + conn_failed_to_be_established = 0x3E, + _, +}; diff --git a/port/nordic/nrf5x/src/softdevice/l2cap.zig b/port/nordic/nrf5x/src/softdevice/l2cap.zig new file mode 100644 index 000000000..31f326155 --- /dev/null +++ b/port/nordic/nrf5x/src/softdevice/l2cap.zig @@ -0,0 +1,128 @@ +// BLE L2CAP (Logical Link Control and Adaptation Protocol). + +const svc = @import("svc.zig"); +const err = @import("err.zig"); +const gap = @import("gap.zig"); +const Error = err.Error; + +// SVC base = 0xB8 +const SVC_BASE = 0xB8; + +pub const ch_count_max = 64; +pub const mtu_min: u16 = 23; +pub const mps_min: u16 = 23; +pub const cid_invalid: u16 = 0x0000; +pub const credits_default: u16 = 1; + +pub const ChSetupRefusedSrc = enum(u8) { + local = 0x01, + remote = 0x02, +}; + +pub const ChStatusCode = enum(u16) { + success = 0x0000, + le_psm_not_supported = 0x0002, + no_resources = 0x0004, + insuf_authentication = 0x0005, + insuf_authorization = 0x0006, + insuf_enc_key_size = 0x0007, + insuf_enc = 0x0008, + invalid_src_cid = 0x0009, + src_cid_already_allocated = 0x000A, + unacceptable_params = 0x000B, + _, +}; + +pub const ChRxParams = extern struct { + rx_mtu: u16, + rx_mps: u16, + sdu_buf: gap.Data = .{}, +}; + +pub const ChSetupParams = extern struct { + rx_params: ChRxParams, + le_psm: u16, + status: ChStatusCode = .success, +}; + +pub const ChTxParams = extern struct { + tx_mtu: u16, + peer_mps: u16, + tx_mps: u16, + credits: u16, +}; + +pub const ConnCfg = extern struct { + rx_mps: u16 = 0, + tx_mps: u16 = 0, + rx_queue_size: u8 = 0, + tx_queue_size: u8 = 0, + ch_count: u8 = 0, +}; + +// --- Event structures --- + +pub const EvtChSetupRequest = extern struct { + tx_params: ChTxParams, + le_psm: u16, +}; + +pub const EvtChSetupRefused = extern struct { + source: ChSetupRefusedSrc, + status: ChStatusCode, +}; + +pub const EvtChSetup = extern struct { + tx_params: ChTxParams, +}; + +pub const EvtChSduBufReleased = extern struct { + sdu_buf: gap.Data, +}; + +pub const EvtChCredit = extern struct { + credits: u16, +}; + +pub const EvtChRx = extern struct { + sdu_len: u16, + sdu_buf: gap.Data, +}; + +pub const EvtChTx = extern struct { + sdu_buf: gap.Data, +}; + +pub const EvtId = enum(u16) { + ch_setup_request = 0x70, + ch_setup_refused = 0x71, + ch_setup = 0x72, + ch_released = 0x73, + ch_sdu_buf_released = 0x74, + ch_credit = 0x75, + ch_rx = 0x76, + ch_tx = 0x77, + _, +}; + +// --- SVC functions --- + +pub fn ch_setup(conn_handle: u16, p_local_cid: *u16, p_params: *const ChSetupParams) Error!void { + return err.check(svc.svcall3(SVC_BASE + 0, conn_handle, @intFromPtr(p_local_cid), @intFromPtr(p_params))); +} + +pub fn ch_release(conn_handle: u16, local_cid: u16) Error!void { + return err.check(svc.svcall2(SVC_BASE + 1, conn_handle, local_cid)); +} + +pub fn ch_rx(conn_handle: u16, local_cid: u16, p_sdu_buf: *const gap.Data) Error!void { + return err.check(svc.svcall3(SVC_BASE + 2, conn_handle, local_cid, @intFromPtr(p_sdu_buf))); +} + +pub fn ch_tx(conn_handle: u16, local_cid: u16, p_sdu_buf: *const gap.Data) Error!void { + return err.check(svc.svcall3(SVC_BASE + 3, conn_handle, local_cid, @intFromPtr(p_sdu_buf))); +} + +pub fn ch_flow_control(conn_handle: u16, local_cid: u16, credits: u16, p_credits: ?*u16) Error!void { + return err.check(svc.svcall4(SVC_BASE + 4, conn_handle, local_cid, credits, @intFromPtr(p_credits))); +} diff --git a/port/nordic/nrf5x/src/softdevice/s112.zig b/port/nordic/nrf5x/src/softdevice/s112.zig new file mode 100644 index 000000000..75cc842e0 --- /dev/null +++ b/port/nordic/nrf5x/src/softdevice/s112.zig @@ -0,0 +1,16 @@ +// SoftDevice S112 — peripheral only. +// Minimal BLE stack for nRF52810, nRF52811, nRF52832. + +pub const variant = @import("variant.zig").Variant.s112; +pub const gap = @import("gap.zig").Api(.s112); +pub const ble = @import("ble.zig"); +pub const soc = @import("soc.zig"); +pub const gattc = @import("gattc.zig"); +pub const gatts = @import("gatts.zig"); +pub const sdm = @import("sdm.zig"); +pub const err = @import("err.zig"); +pub const gatt = @import("gatt.zig"); +pub const hci = @import("hci.zig"); +pub const svc = @import("svc.zig"); + +pub const Error = err.Error; diff --git a/port/nordic/nrf5x/src/softdevice/s113.zig b/port/nordic/nrf5x/src/softdevice/s113.zig new file mode 100644 index 000000000..186766513 --- /dev/null +++ b/port/nordic/nrf5x/src/softdevice/s113.zig @@ -0,0 +1,16 @@ +// SoftDevice S113 — peripheral + observer (scanning). +// BLE stack for nRF52810, nRF52811, nRF52832, nRF52840. + +pub const variant = @import("variant.zig").Variant.s113; +pub const gap = @import("gap.zig").Api(.s113); +pub const ble = @import("ble.zig"); +pub const soc = @import("soc.zig"); +pub const gattc = @import("gattc.zig"); +pub const gatts = @import("gatts.zig"); +pub const sdm = @import("sdm.zig"); +pub const err = @import("err.zig"); +pub const gatt = @import("gatt.zig"); +pub const hci = @import("hci.zig"); +pub const svc = @import("svc.zig"); + +pub const Error = err.Error; diff --git a/port/nordic/nrf5x/src/softdevice/s122.zig b/port/nordic/nrf5x/src/softdevice/s122.zig new file mode 100644 index 000000000..480c7515c --- /dev/null +++ b/port/nordic/nrf5x/src/softdevice/s122.zig @@ -0,0 +1,17 @@ +// SoftDevice S122 — peripheral (mesh-focused). +// Different GAP SVC offsets from S112/S113/S132/S140. +// BLE stack for nRF52820, nRF52833, nRF52840. + +pub const variant = @import("variant.zig").Variant.s122; +pub const gap = @import("gap.zig").Api(.s122); +pub const ble = @import("ble.zig"); +pub const soc = @import("soc.zig"); +pub const gattc = @import("gattc.zig"); +pub const gatts = @import("gatts.zig"); +pub const sdm = @import("sdm.zig"); +pub const err = @import("err.zig"); +pub const gatt = @import("gatt.zig"); +pub const hci = @import("hci.zig"); +pub const svc = @import("svc.zig"); + +pub const Error = err.Error; diff --git a/port/nordic/nrf5x/src/softdevice/s132.zig b/port/nordic/nrf5x/src/softdevice/s132.zig new file mode 100644 index 000000000..08bc0d8bd --- /dev/null +++ b/port/nordic/nrf5x/src/softdevice/s132.zig @@ -0,0 +1,17 @@ +// SoftDevice S132 — peripheral + central. +// BLE stack for nRF52832. + +pub const variant = @import("variant.zig").Variant.s132; +pub const gap = @import("gap.zig").Api(.s132); +pub const ble = @import("ble.zig"); +pub const soc = @import("soc.zig"); +pub const l2cap = @import("l2cap.zig"); +pub const gattc = @import("gattc.zig"); +pub const gatts = @import("gatts.zig"); +pub const sdm = @import("sdm.zig"); +pub const err = @import("err.zig"); +pub const gatt = @import("gatt.zig"); +pub const hci = @import("hci.zig"); +pub const svc = @import("svc.zig"); + +pub const Error = err.Error; diff --git a/port/nordic/nrf5x/src/softdevice/s140.zig b/port/nordic/nrf5x/src/softdevice/s140.zig new file mode 100644 index 000000000..44a4843b4 --- /dev/null +++ b/port/nordic/nrf5x/src/softdevice/s140.zig @@ -0,0 +1,17 @@ +// SoftDevice S140 — peripheral + central + observer + extended advertising. +// Full-featured BLE stack for nRF52840, nRF52833, nRF52820. + +pub const variant = @import("variant.zig").Variant.s140; +pub const gap = @import("gap.zig").Api(.s140); +pub const ble = @import("ble.zig"); +pub const soc = @import("soc.zig"); +pub const l2cap = @import("l2cap.zig"); +pub const gattc = @import("gattc.zig"); +pub const gatts = @import("gatts.zig"); +pub const sdm = @import("sdm.zig"); +pub const err = @import("err.zig"); +pub const gatt = @import("gatt.zig"); +pub const hci = @import("hci.zig"); +pub const svc = @import("svc.zig"); + +pub const Error = err.Error; diff --git a/port/nordic/nrf5x/src/softdevice/sdm.zig b/port/nordic/nrf5x/src/softdevice/sdm.zig new file mode 100644 index 000000000..5c45685d3 --- /dev/null +++ b/port/nordic/nrf5x/src/softdevice/sdm.zig @@ -0,0 +1,69 @@ +// SoftDevice Manager (SDM) - enable, disable, and query the SoftDevice. + +const svc = @import("svc.zig"); +const err = @import("err.zig"); +const Error = err.Error; + +// SVC numbers (SDM_SVC_BASE = 0x10) +const SD_SOFTDEVICE_ENABLE = 0x10; +const SD_SOFTDEVICE_DISABLE = 0x11; +const SD_SOFTDEVICE_IS_ENABLED = 0x12; +const SD_SOFTDEVICE_VECTOR_TABLE_BASE_SET = 0x13; + +pub const ClockLfSrc = enum(u8) { + rc = 0, + xtal = 1, + synth = 2, +}; + +pub const ClockLfAccuracy = enum(u8) { + ppm_250 = 0, + ppm_500 = 1, + ppm_150 = 2, + ppm_100 = 3, + ppm_75 = 4, + ppm_50 = 5, + ppm_30 = 6, + ppm_20 = 7, + ppm_10 = 8, + ppm_5 = 9, + ppm_2 = 10, + ppm_1 = 11, +}; + +pub const ClockLfCfg = extern struct { + source: ClockLfSrc, + rc_ctiv: u8, + rc_temp_ctiv: u8, + accuracy: ClockLfAccuracy, +}; + +pub const FaultId = enum(u32) { + sd_assert = 0x00000001, + app_memacc = 0x00001001, + _, +}; + +pub const FaultHandler = *const fn (id: FaultId, pc: u32, info: u32) callconv(.c) void; + +pub fn enable(cfg: ?*const ClockLfCfg, handler: FaultHandler) Error!void { + return err.check(svc.svcall2( + SD_SOFTDEVICE_ENABLE, + if (cfg) |p| @intFromPtr(p) else 0, + @intFromPtr(handler), + )); +} + +pub fn disable() Error!void { + return err.check(svc.svcall0(SD_SOFTDEVICE_DISABLE)); +} + +pub fn is_enabled() Error!bool { + var result: u8 = 0; + try err.check(svc.svcall1(SD_SOFTDEVICE_IS_ENABLED, @intFromPtr(&result))); + return result != 0; +} + +pub fn set_vector_table_base(address: u32) Error!void { + return err.check(svc.svcall1(SD_SOFTDEVICE_VECTOR_TABLE_BASE_SET, address)); +} diff --git a/port/nordic/nrf5x/src/softdevice/soc.zig b/port/nordic/nrf5x/src/softdevice/soc.zig new file mode 100644 index 000000000..a7ff94e0f --- /dev/null +++ b/port/nordic/nrf5x/src/softdevice/soc.zig @@ -0,0 +1,356 @@ +// System on Chip (SoC) library - flash, power, clock, RNG, encryption, radio timeslot. + +const svc = @import("svc.zig"); +const err = @import("err.zig"); +const Error = err.Error; + +// SVC bases +const SOC_SVC_BASE = 0x20; +const SOC_SVC_BASE_NOT_AVAILABLE = 0x2C; + +pub const ecb_key_length = 16; +pub const ecb_cleartext_length = 16; +pub const ecb_ciphertext_length = 16; + +pub const PowerMode = enum(u8) { + const_lat = 0, + low_pwr = 1, +}; + +pub const PowerThreshold = enum(u8) { + v17 = 4, + v21 = 5, + v23 = 6, + v25 = 7, + v27 = 8, + v28 = 15, + _, +}; + +pub const PowerThresholdVddh = enum(u8) { + v27 = 0, + v28 = 1, + v29 = 2, + v30 = 3, + v31 = 4, + v32 = 5, + v33 = 6, + v34 = 7, + v35 = 8, + v36 = 9, + v37 = 10, + v38 = 11, + v39 = 12, + v40 = 13, + v41 = 14, + v42 = 15, +}; + +pub const DcdcMode = enum(u8) { + disable = 0, + enable = 1, +}; + +pub const RadioNotificationDistance = enum(u8) { + none = 0, + @"800us" = 1, + @"1740us" = 2, + @"2680us" = 3, + @"3620us" = 4, + @"4560us" = 5, + @"5500us" = 6, +}; + +pub const RadioNotificationType = enum(u8) { + none = 0, + int_on_active = 1, + int_on_inactive = 2, + int_on_both = 3, +}; + +pub const RadioHfclkCfg = enum(u8) { + xtal_guaranteed = 0, + no_guarantee = 1, +}; + +pub const RadioPriority = enum(u8) { + high = 0, + normal = 1, +}; + +pub const RadioRequestType = enum(u8) { + earliest = 0, + normal = 1, +}; + +pub const RadioSignalCallbackAction = enum(u8) { + none = 0, + extend = 1, + end_ = 2, + request_and_end = 3, +}; + +pub const RadioCallbackSignalType = enum(u8) { + start = 0, + timer0 = 1, + radio = 2, + extend_failed = 3, + extend_succeeded = 4, +}; + +pub const RadioRequestEarliest = extern struct { + hfclk: RadioHfclkCfg, + priority: RadioPriority, + length_us: u32, + timeout_us: u32, +}; + +pub const RadioRequestNormal = extern struct { + hfclk: RadioHfclkCfg, + priority: RadioPriority, + distance_us: u32, + length_us: u32, +}; + +pub const RadioRequest = extern struct { + request_type: RadioRequestType, + params: extern union { + earliest: RadioRequestEarliest, + normal: RadioRequestNormal, + }, +}; + +pub const RadioSignalCallbackReturnParam = extern struct { + callback_action: RadioSignalCallbackAction, + params: extern union { + request: RadioRequest, + extend_us: u32, + }, +}; + +pub const RadioSignalCallback = *const fn (signal_type: RadioCallbackSignalType) callconv(.c) *RadioSignalCallbackReturnParam; + +pub const EcbData = extern struct { + key: [ecb_key_length]u8, + cleartext: [ecb_cleartext_length]u8, + ciphertext: [ecb_ciphertext_length]u8, +}; + +pub const EcbDataBlock = extern struct { + p_key: *const [ecb_key_length]u8, + p_cleartext: *const [ecb_cleartext_length]u8, + p_ciphertext: *[ecb_ciphertext_length]u8, +}; + +pub const SocEvt = enum(u32) { + hfclk_started = 0, + power_failure_warning = 1, + flash_operation_success = 2, + flash_operation_error = 3, + radio_blocked = 4, + radio_canceled = 5, + radio_signal_callback_invalid_return = 6, + radio_session_idle = 7, + radio_session_closed = 8, + power_usb_power_ready = 9, + power_usb_detected = 10, + power_usb_removed = 11, + _, +}; + +// --- PPI functions (available when SD is disabled too) --- + +pub fn ppi_channel_enable_get(p_channel_enable: *u32) Error!void { + return err.check(svc.svcall1(SOC_SVC_BASE + 0, @intFromPtr(p_channel_enable))); +} + +pub fn ppi_channel_enable_set(channel_enable_set_msk: u32) Error!void { + return err.check(svc.svcall1(SOC_SVC_BASE + 1, channel_enable_set_msk)); +} + +pub fn ppi_channel_enable_clr(channel_enable_clr_msk: u32) Error!void { + return err.check(svc.svcall1(SOC_SVC_BASE + 2, channel_enable_clr_msk)); +} + +pub fn ppi_channel_assign(channel_num: u8, evt_endpoint: u32, task_endpoint: u32) Error!void { + return err.check(svc.svcall3(SOC_SVC_BASE + 3, channel_num, evt_endpoint, task_endpoint)); +} + +pub fn ppi_group_task_enable(group_num: u8) Error!void { + return err.check(svc.svcall1(SOC_SVC_BASE + 4, group_num)); +} + +pub fn ppi_group_task_disable(group_num: u8) Error!void { + return err.check(svc.svcall1(SOC_SVC_BASE + 5, group_num)); +} + +pub fn ppi_group_assign(group_num: u8, channel_msk: u32) Error!void { + return err.check(svc.svcall2(SOC_SVC_BASE + 6, group_num, channel_msk)); +} + +pub fn ppi_group_get(group_num: u8, p_channel_msk: *u32) Error!void { + return err.check(svc.svcall2(SOC_SVC_BASE + 7, group_num, @intFromPtr(p_channel_msk))); +} + +// --- Flash functions --- + +pub fn flash_page_erase(page_number: u32) Error!void { + return err.check(svc.svcall1(SOC_SVC_BASE + 8, page_number)); +} + +pub fn flash_write(p_dst: *u32, p_src: *const u32, size: u32) Error!void { + return err.check(svc.svcall3(SOC_SVC_BASE + 9, @intFromPtr(p_dst), @intFromPtr(p_src), size)); +} + +// --- Functions requiring SoftDevice to be enabled --- + +pub fn mutex_new(p_mutex: *u8) Error!void { + return err.check(svc.svcall1(SOC_SVC_BASE_NOT_AVAILABLE + 0, @intFromPtr(p_mutex))); +} + +pub fn mutex_acquire(p_mutex: *u8) Error!bool { + const ret = svc.svcall1(SOC_SVC_BASE_NOT_AVAILABLE + 1, @intFromPtr(p_mutex)); + if (ret == 0) return true; // acquired + if (ret == 0x2000) return false; // already taken + return err.from_code(ret); +} + +pub fn mutex_release(p_mutex: *u8) Error!void { + return err.check(svc.svcall1(SOC_SVC_BASE_NOT_AVAILABLE + 2, @intFromPtr(p_mutex))); +} + +pub fn rand_application_pool_capacity_get(p_pool_capacity: *u8) Error!void { + return err.check(svc.svcall1(SOC_SVC_BASE_NOT_AVAILABLE + 3, @intFromPtr(p_pool_capacity))); +} + +pub fn rand_application_bytes_available_get(p_bytes_available: *u8) Error!void { + return err.check(svc.svcall1(SOC_SVC_BASE_NOT_AVAILABLE + 4, @intFromPtr(p_bytes_available))); +} + +pub fn rand_application_vector_get(p_buff: [*]u8, length: u8) Error!void { + return err.check(svc.svcall2(SOC_SVC_BASE_NOT_AVAILABLE + 5, @intFromPtr(p_buff), length)); +} + +pub fn power_mode_set(power_mode: PowerMode) Error!void { + return err.check(svc.svcall1(SOC_SVC_BASE_NOT_AVAILABLE + 6, @intFromEnum(power_mode))); +} + +pub fn power_system_off() Error!void { + return err.check(svc.svcall0(SOC_SVC_BASE_NOT_AVAILABLE + 7)); +} + +pub fn power_reset_reason_get(p_reset_reason: *u32) Error!void { + return err.check(svc.svcall1(SOC_SVC_BASE_NOT_AVAILABLE + 8, @intFromPtr(p_reset_reason))); +} + +pub fn power_reset_reason_clr(reset_reason_clr_msk: u32) Error!void { + return err.check(svc.svcall1(SOC_SVC_BASE_NOT_AVAILABLE + 9, reset_reason_clr_msk)); +} + +pub fn power_pof_enable(pof_enable: bool) Error!void { + return err.check(svc.svcall1(SOC_SVC_BASE_NOT_AVAILABLE + 10, @intFromBool(pof_enable))); +} + +pub fn power_pof_threshold_set(threshold: PowerThreshold) Error!void { + return err.check(svc.svcall1(SOC_SVC_BASE_NOT_AVAILABLE + 11, @intFromEnum(threshold))); +} + +pub fn power_pof_thresholdvddh_set(threshold: PowerThresholdVddh) Error!void { + return err.check(svc.svcall1(SOC_SVC_BASE_NOT_AVAILABLE + 12, @intFromEnum(threshold))); +} + +pub fn power_ram_power_set(index: u8, ram_powerset: u32) Error!void { + return err.check(svc.svcall2(SOC_SVC_BASE_NOT_AVAILABLE + 13, index, ram_powerset)); +} + +pub fn power_ram_power_clr(index: u8, ram_powerclr: u32) Error!void { + return err.check(svc.svcall2(SOC_SVC_BASE_NOT_AVAILABLE + 14, index, ram_powerclr)); +} + +pub fn power_ram_power_get(index: u8, p_ram_power: *u32) Error!void { + return err.check(svc.svcall2(SOC_SVC_BASE_NOT_AVAILABLE + 15, index, @intFromPtr(p_ram_power))); +} + +pub fn power_gpregret_set(gpregret_id: u32, gpregret_msk: u32) Error!void { + return err.check(svc.svcall2(SOC_SVC_BASE_NOT_AVAILABLE + 16, gpregret_id, gpregret_msk)); +} + +pub fn power_gpregret_clr(gpregret_id: u32, gpregret_msk: u32) Error!void { + return err.check(svc.svcall2(SOC_SVC_BASE_NOT_AVAILABLE + 17, gpregret_id, gpregret_msk)); +} + +pub fn power_gpregret_get(gpregret_id: u32, p_gpregret: *u32) Error!void { + return err.check(svc.svcall2(SOC_SVC_BASE_NOT_AVAILABLE + 18, gpregret_id, @intFromPtr(p_gpregret))); +} + +pub fn power_dcdc_mode_set(dcdc_mode: DcdcMode) Error!void { + return err.check(svc.svcall1(SOC_SVC_BASE_NOT_AVAILABLE + 19, @intFromEnum(dcdc_mode))); +} + +pub fn power_dcdc0_mode_set(dcdc_mode: DcdcMode) Error!void { + return err.check(svc.svcall1(SOC_SVC_BASE_NOT_AVAILABLE + 20, @intFromEnum(dcdc_mode))); +} + +pub fn app_evt_wait() Error!void { + return err.check(svc.svcall0(SOC_SVC_BASE_NOT_AVAILABLE + 21)); +} + +pub fn clock_hfclk_request() Error!void { + return err.check(svc.svcall0(SOC_SVC_BASE_NOT_AVAILABLE + 22)); +} + +pub fn clock_hfclk_release() Error!void { + return err.check(svc.svcall0(SOC_SVC_BASE_NOT_AVAILABLE + 23)); +} + +pub fn clock_hfclk_is_running(p_is_running: *u32) Error!void { + return err.check(svc.svcall1(SOC_SVC_BASE_NOT_AVAILABLE + 24, @intFromPtr(p_is_running))); +} + +pub fn radio_notification_cfg_set(type_: RadioNotificationType, distance: RadioNotificationDistance) Error!void { + return err.check(svc.svcall2(SOC_SVC_BASE_NOT_AVAILABLE + 25, @intFromEnum(type_), @intFromEnum(distance))); +} + +pub fn ecb_block_encrypt(p_ecb_data: *EcbData) Error!void { + return err.check(svc.svcall1(SOC_SVC_BASE_NOT_AVAILABLE + 26, @intFromPtr(p_ecb_data))); +} + +pub fn ecb_blocks_encrypt(ecb_block_count: u8, p_data_blocks: [*]EcbDataBlock) Error!void { + return err.check(svc.svcall2(SOC_SVC_BASE_NOT_AVAILABLE + 27, ecb_block_count, @intFromPtr(p_data_blocks))); +} + +pub fn radio_session_open(p_radio_signal_callback: RadioSignalCallback) Error!void { + return err.check(svc.svcall1(SOC_SVC_BASE_NOT_AVAILABLE + 28, @intFromPtr(p_radio_signal_callback))); +} + +pub fn radio_session_close() Error!void { + return err.check(svc.svcall0(SOC_SVC_BASE_NOT_AVAILABLE + 29)); +} + +pub fn radio_request(p_request: *const RadioRequest) Error!void { + return err.check(svc.svcall1(SOC_SVC_BASE_NOT_AVAILABLE + 30, @intFromPtr(p_request))); +} + +pub fn evt_get(p_evt_id: *u32) Error!void { + return err.check(svc.svcall1(SOC_SVC_BASE_NOT_AVAILABLE + 31, @intFromPtr(p_evt_id))); +} + +pub fn temp_get(p_temp: *i32) Error!void { + return err.check(svc.svcall1(SOC_SVC_BASE_NOT_AVAILABLE + 32, @intFromPtr(p_temp))); +} + +pub fn power_usbpwrrdy_enable(enable: bool) Error!void { + return err.check(svc.svcall1(SOC_SVC_BASE_NOT_AVAILABLE + 33, @intFromBool(enable))); +} + +pub fn power_usbdetected_enable(enable: bool) Error!void { + return err.check(svc.svcall1(SOC_SVC_BASE_NOT_AVAILABLE + 34, @intFromBool(enable))); +} + +pub fn power_usbremoved_enable(enable: bool) Error!void { + return err.check(svc.svcall1(SOC_SVC_BASE_NOT_AVAILABLE + 35, @intFromBool(enable))); +} + +pub fn power_usbregstatus_get(p_usbregstatus: *u32) Error!void { + return err.check(svc.svcall1(SOC_SVC_BASE_NOT_AVAILABLE + 36, @intFromPtr(p_usbregstatus))); +} diff --git a/port/nordic/nrf5x/src/softdevice/svc.zig b/port/nordic/nrf5x/src/softdevice/svc.zig new file mode 100644 index 000000000..bdce77517 --- /dev/null +++ b/port/nordic/nrf5x/src/softdevice/svc.zig @@ -0,0 +1,52 @@ +// SVC (Supervisor Call) mechanism for Nordic SoftDevice. +// +// The SoftDevice uses SVC instructions to dispatch function calls from +// application code. Parameters are passed in r0-r3 per ARM calling +// convention. The SVC number is encoded in the instruction immediate. + +const std = @import("std"); + +fn comptime_num(comptime num: u32) []const u8 { + return std.fmt.comptimePrint("{d}", .{num}); +} + +pub inline fn svcall0(comptime num: u32) u32 { + return asm volatile ("svc " ++ comptime_num(num) + : [ret] "={r0}" (-> u32), + : + : .{ .memory = true }); +} + +pub inline fn svcall1(comptime num: u32, p0: usize) u32 { + return asm volatile ("svc " ++ comptime_num(num) + : [ret] "={r0}" (-> u32), + : [p0] "{r0}" (p0), + : .{ .memory = true }); +} + +pub inline fn svcall2(comptime num: u32, p0: usize, p1: usize) u32 { + return asm volatile ("svc " ++ comptime_num(num) + : [ret] "={r0}" (-> u32), + : [p0] "{r0}" (p0), + [p1] "{r1}" (p1), + : .{ .memory = true }); +} + +pub inline fn svcall3(comptime num: u32, p0: usize, p1: usize, p2: usize) u32 { + return asm volatile ("svc " ++ comptime_num(num) + : [ret] "={r0}" (-> u32), + : [p0] "{r0}" (p0), + [p1] "{r1}" (p1), + [p2] "{r2}" (p2), + : .{ .memory = true }); +} + +pub inline fn svcall4(comptime num: u32, p0: usize, p1: usize, p2: usize, p3: usize) u32 { + return asm volatile ("svc " ++ comptime_num(num) + : [ret] "={r0}" (-> u32), + : [p0] "{r0}" (p0), + [p1] "{r1}" (p1), + [p2] "{r2}" (p2), + [p3] "{r3}" (p3), + : .{ .memory = true }); +} diff --git a/port/nordic/nrf5x/src/softdevice/variant.zig b/port/nordic/nrf5x/src/softdevice/variant.zig new file mode 100644 index 000000000..4fae2291c --- /dev/null +++ b/port/nordic/nrf5x/src/softdevice/variant.zig @@ -0,0 +1,115 @@ +// SoftDevice variant definitions — feature flags and SVC offset tables. +// +// S112/S113/S132/S140 share identical SVC offsets for common GAP functions. +// S122 has different offsets because it omits central/observer functions, +// causing subsequent entries to pack into lower positions. + +pub const Variant = enum { + s112, + s113, + s122, + s132, + s140, + + /// Central role support (initiating connections, encrypting as central). + pub fn has_central(comptime self: Variant) bool { + return self == .s132 or self == .s140; + } + + /// Observer role support (scanning). + pub fn has_observer(comptime self: Variant) bool { + return self == .s113 or self == .s132 or self == .s140; + } + + /// L2CAP Connection-Oriented Channels. + pub fn has_l2cap_coc(comptime self: Variant) bool { + return self == .s132 or self == .s140; + } + + /// QoS channel survey. + pub fn has_qos_survey(comptime self: Variant) bool { + return self == .s122 or self == .s132 or self == .s140; + } + + /// Resolve a GAP function to its full SVC number. + pub fn gap_svc(comptime self: Variant, comptime func: GapFunc) u32 { + const base: u32 = 0x6C; + if (self == .s122) { + return base + s122_gap_offsets[@intFromEnum(func)]; + } + return base + @intFromEnum(func); + } +}; + +/// GAP SVC function identifiers. Enum values match the standard +/// (S112/S113/S132/S140) offsets from GAP SVC base (0x6C). +pub const GapFunc = enum(u8) { + addr_set = 0, + addr_get = 1, + whitelist_set = 2, + device_identities_set = 3, + privacy_set = 4, + privacy_get = 5, + adv_set_configure = 6, + adv_start = 7, + adv_stop = 8, + conn_param_update = 9, + disconnect = 10, + tx_power_set = 11, + appearance_set = 12, + appearance_get = 13, + ppcp_set = 14, + ppcp_get = 15, + device_name_set = 16, + device_name_get = 17, + authenticate = 18, + sec_params_reply = 19, + auth_key_reply = 20, + lesc_dhkey_reply = 21, + keypress_notify = 22, + lesc_oob_data_get = 23, + lesc_oob_data_set = 24, + encrypt = 25, + sec_info_reply = 26, + conn_sec_get = 27, + rssi_start = 28, + rssi_stop = 29, + scan_start = 30, + scan_stop = 31, + connect = 32, + connect_cancel = 33, + rssi_get = 34, + phy_update = 35, + data_length_update = 36, + qos_channel_survey_start = 37, + qos_channel_survey_stop = 38, + adv_addr_get = 39, + next_conn_evt_counter_get = 40, + conn_evt_trigger_start = 41, + conn_evt_trigger_stop = 42, +}; + +/// S122 GAP SVC offsets. Functions absent on S122 (encrypt, scan_start/stop, +/// connect/cancel) are omitted, causing all subsequent functions to shift +/// into lower positions. +const s122_gap_offsets: [43]u8 = blk: { + var t: [43]u8 = .{0} ** 43; + // 0–24: identical to standard offsets + for (0..25) |i| t[i] = @intCast(i); + // 25 onward: skip encrypt (std 25), scan_start/stop (std 30-31), + // connect/cancel (std 32-33) + t[@intFromEnum(GapFunc.sec_info_reply)] = 25; + t[@intFromEnum(GapFunc.conn_sec_get)] = 26; + t[@intFromEnum(GapFunc.rssi_start)] = 27; + t[@intFromEnum(GapFunc.rssi_stop)] = 28; + t[@intFromEnum(GapFunc.rssi_get)] = 29; + t[@intFromEnum(GapFunc.phy_update)] = 30; + t[@intFromEnum(GapFunc.data_length_update)] = 31; + t[@intFromEnum(GapFunc.qos_channel_survey_start)] = 32; + t[@intFromEnum(GapFunc.qos_channel_survey_stop)] = 33; + t[@intFromEnum(GapFunc.adv_addr_get)] = 34; + t[@intFromEnum(GapFunc.next_conn_evt_counter_get)] = 35; + t[@intFromEnum(GapFunc.conn_evt_trigger_start)] = 36; + t[@intFromEnum(GapFunc.conn_evt_trigger_stop)] = 37; + break :blk t; +}; diff --git a/port/nordic/nrf5x/src/tools/mergehex.zig b/port/nordic/nrf5x/src/tools/mergehex.zig new file mode 100644 index 000000000..5af83e013 --- /dev/null +++ b/port/nordic/nrf5x/src/tools/mergehex.zig @@ -0,0 +1,75 @@ +const std = @import("std"); + +pub fn main() !u8 { + const allocator = std.heap.page_allocator; + + const args = try std.process.argsAlloc(allocator); + defer std.process.argsFree(allocator, args); + + if (args.len != 4) { + std.log.err("usage: nrf5x-mergehex ", .{}); + return 1; + } + + try merge_hex_files(allocator, args[1], args[2], args[3]); + return 0; +} + +fn merge_hex_files(allocator: std.mem.Allocator, lower_path: []const u8, upper_path: []const u8, out_path: []const u8) !void { + const lower_data = try read_file(allocator, lower_path); + defer allocator.free(lower_data); + + const upper_data = try read_file(allocator, upper_path); + defer allocator.free(upper_data); + + const out_file = try std.fs.cwd().createFile(out_path, .{}); + defer out_file.close(); + + var out_write_buf: [4096]u8 = undefined; + var writer = out_file.writer(&out_write_buf); + + try write_without_eof(&writer.interface, lower_data); + const upper_has_eof = try write_all_lines(&writer.interface, upper_data); + + if (!upper_has_eof) { + try writer.interface.writeAll(":00000001FF\n"); + } + + try writer.interface.flush(); +} + +fn read_file(allocator: std.mem.Allocator, path: []const u8) ![]u8 { + const file = try std.fs.cwd().openFile(path, .{}); + defer file.close(); + + const stat = try file.stat(); + return try file.readToEndAlloc(allocator, stat.size + 1); +} + +fn write_without_eof(writer: *std.Io.Writer, data: []const u8) !void { + var lines = std.mem.splitScalar(u8, data, '\n'); + while (lines.next()) |line_with_cr| { + const line = std.mem.trimRight(u8, line_with_cr, "\r"); + if (line.len == 0) continue; + if (std.mem.eql(u8, line, ":00000001FF")) continue; + try writer.print("{s}\n", .{line}); + } +} + +fn write_all_lines(writer: *std.Io.Writer, data: []const u8) !bool { + var eof_seen = false; + var lines = std.mem.splitScalar(u8, data, '\n'); + + while (lines.next()) |line_with_cr| { + const line = std.mem.trimRight(u8, line_with_cr, "\r"); + if (line.len == 0) continue; + + if (std.mem.eql(u8, line, ":00000001FF")) { + eof_seen = true; + } + + try writer.print("{s}\n", .{line}); + } + + return eof_seen; +}