UltraWideLock v0.4.0

Porting to a new chipset#

How the UWB engine moves to a new chipset, what it costs, and how to prove a port did not change the validated target's code.

The primary target is the DWM3001CDK on NCS v3.3.0 (apps/dwm3001cdk-lock): reader, Matter node and Thread MTD in one nRF52833 image, hardware-validated to an approach unlock and a live Apple Home tile. The nRF5340 DK (apps/nrf5340dk-lock/) is the NFC target and carries the largest build, so the regression check below uses it; its Nordic-binary path is hardware-validated end to end, while the source-stack default has a firmware CI build and protocol host tests but awaits the full phone checklist. A third port is ESP32-S3 on ESP-IDF, ports/esp32/.

A complete Matter/credential lock must also meet the cross-port five-fabric, Thread-dataset, selective-removal, SRP and last-fabric cleanup rules in PORTING.md, externally visible contracts even when an ESP32 or nRF5340 delegates to CHIP.

1. The contract#

The ranging engine's whole platform requirement is two headers in modules/ultrawidelock_port/include/:

FunctionMeaning
ultrawidelock_malloc / ultrawidelock_calloc / ultrawidelock_freeheap
ultrawidelock_uptime_usmonotonic microseconds since boot
ultrawidelock_uptime_msmonotonic milliseconds since boot
ultrawidelock_sleep_msrelinquish the CPU for at least N ms
ultrawidelock_sleep_usshort busy-wait, microseconds (deca_sleep)
ultrawidelock_cycle_get_32free-running counter, RX-arm latency probe
ultrawidelock_mutex_init/lock/unlockblocking mutex (credential reader trust store; three lines per backend)
  • ultrawidelock_log.h: LOG_ERR/WRN/INF/DBG, the hexdump variants, LOG_MODULE_REGISTER/DECLARE, and ultrawidelock_printf.

Both select a backend from __ZEPHYR__, ESP_PLATFORM, or ULTRAWIDELOCK_PORT_HOST, and #error if none is defined. Two further headers in modules/ultrawidelock_uwb/src/facade/, ultrawidelock_bytes.h (endian-neutral load/store) and ultrawidelock_util.h (MIN/MAX/ARRAY_SIZE/IS_ENABLED), are pure code with no platform content, shared by every target including Zephyr.

Deliberately not in the contract: work queues, timers and init hooks. They appear only in uwb_rxdiag.c, uwb_selftest.c, ultrawidelock_logfmt.c, ultrawidelock_logquiet.c and ultrawidelock_shell.c, all Zephyr-only and in no port's source list. The k_work / k_sem / k_poll surface in dw3000_spi.c and dw3000_hw.c is excluded too: every port supplies its own backend for those two files. Adding any of it would multiply the port surface for code that never runs on the ranging path.

2. What a port costs#

The cost axis is the RTOS, not the chipset. Under Zephyr the SPI and GPIO layers are devicetree-abstracted, so a new Zephyr-supported SoC needs no C.

TierWorkTargetsEffort
0. Board fileDevicetree overlay, roughly 50 lines. No C.nRF52840, nRF54L15, EFR32MG24, STM32WB55, any Zephyr SoC with SPI + GPIO IRQHours
1. New RTOSA branch in ultrawidelock_port.h + ultrawidelock_log.h (about 55 lines), plus a DW3000 SPI/GPIO/IRQ backend (about 350 lines)ESP-IDF (done), Pico SDK (RP2350), STM32Cube + FreeRTOS, bare metal2 to 4 days
2. New UWB siliconA new driver under the ultrawidelock_uwb_facade.h seam. Not a port.Anything that is not DW3xxxWeeks, gated on driver availability

Effort figures other than ESP-IDF are estimates from line counts, not completed ports.

Scope of the Tier 0 claim. It applies to the UWB engine module, SoC-neutral apart from one guarded block: the nRF5340 HFCLK boost in ultrawidelock_uwb_facade.c. It does not cover the full Matter door-lock product: scripts/nrf5340dk-build.sh pins nrf5340dk/nrf5340/cpuapp and drives a sysbuild with a separate ipc_radio network-core image, so moving the whole application to a single-core part such as nRF52840 is a sysbuild and Matter-transport exercise well beyond a devicetree overlay.

The ESP-IDF port's target-specific surface for the ranging engine is ports/esp32/components/ultrawidelock_uwb/port/: dw3000_spi.c (169), dw3000_hw.c (180) and ultrawidelock_wrap_stubs.c (21), with no Zephyr compatibility layer.

Beyond the engine. A complete lock also needs the credential-auth reader from modules/ultrawidelock_cred: two more per-platform seams, both small, both with ESP-IDF worked examples:

  • a BLE transport implementing ultrawidelock_ble.h (the NimBLE backend is ports/esp32/components/ultrawidelock_ble/ultrawidelock_ble_esp32.c);
  • a storage backend for the ultrawidelock_prov trust store (the NVS one is ports/esp32/components/ultrawidelock_reader/ultrawidelock_prov_nvs.c).

Portable crypto calls ultrawidelock_prim.h. Target builds bind it once to ultrawidelock_prim_psa.c over the framework's PSA implementation; host tests bind it to a deterministic double. Modules do not call the framework provider directly.

3. Build tiers#

  • Full Aliro UWB: CONFIG_ULTRAWIDELOCK_UWB=y, ULTRAWIDELOCK_UWB_RESPONDER=y, ULTRAWIDELOCK_CRED=y.
  • Bring-up only: CONFIG_ULTRAWIDELOCK_UWB=y alone compiles just uwb_min.c.
  • No UWB (NFC-only): CONFIG_ULTRAWIDELOCK_UWB=n. The whole module is wrapped in if(CONFIG_ULTRAWIDELOCK_UWB) and contributes nothing; every external call site in integrations/nrfconnect-door-lock/patches/custom_impl-uwb.patch is #ifdef CONFIG_ULTRAWIDELOCK_CRED, so the build links clean with no UWB silicon present.

Aliro makes NFC mandatory and BLE and UWB optional, so an NFC-only lock is a legitimate certified device, and this tier drops the DWM3000EVB from the bill of materials. The NFC path needs the credential-auth layer, not modules/ultrawidelock_uwb.

Crypto seam#

ccc_kdf.h needs one AES-ECB primitive. ccc_crypto_prim.c adapts that CCC contract to ultrawidelock_aes_ecb_encrypt(), including both 128-bit and 256-bit keys. Every target links exactly one primitive provider, with no UWB-specific crypto-backend selector. Zephyr uses nrf_security or Mbed TLS PSA, and ESP-IDF and FreeRTOS use their Mbed TLS PSA providers.

SoC-specific seams#

ultrawidelock_uwb_facade.c boosts the nRF5340 app-core HFCLK to 128 MHz for the DW3000 SPI bus, guarded by CONFIG_SOC_NRF5340_CPUAPP. Other SoCs clock their SPI controller independently, so it compiles to a no-op. A new target needs an equivalent only if its SPI clock is divided at boot.

STS seam#

The CCC STS substitution rides a compile-time seam, modules/ultrawidelock_uwb/include/uwb_seam.h. Four decadriver entry points carry engine behaviour a caller must not skip, so every call site in the module goes through a helper instead of <deca_device_api.h>:

HelperSupplied byReplacesCarries
ultrawidelock_uwb_arm_rxccc_shim_rx.cdwt_rxenableprograms the CCC key/IV for the slot
ultrawidelock_uwb_set_sts_ivccc_shim_wrap.cdwt_configurestsivsubstitutes the CCC STS-V per frame
ultrawidelock_uwb_set_callbacksuwb_rxdiag.cdwt_setcallbacksinserts the Pre-POLL shim
ultrawidelock_uwb_configure_phyuwb_rxdiag.cdwt_configuretraces the PHY configuration

Below the CONFIG_ULTRAWIDELOCK_CRED tier there is no engine to reach and each helper inlines to the plain decadriver call. The ESP32 port omits uwb_rxdiag.c, which is k_work-based, and supplies the last two from port/ultrawidelock_seam_stubs.c.

This replaced a -Wl,--wrap=dwt_* link-time interposer: the seam is plain C, needs no linker feature, and a non-GNU toolchain is no longer a porting problem. make seam (the uwb-seam gate in make check) now enforces what the linker guaranteed structurally: it scans tracked sources for a call reaching past the seam and carries a --self-test proving it can fail. Keep that mechanical: a site that bypasses the seam is silent on the bench, because the radio still arms and ranging still runs, and only the unlock never happens.

4. Verifying a port did not change the validated target#

Porting touches shared code, so the nRF5340 image must be shown unaffected. The check is per-object: whole-image numbers are dominated by the Matter application and hide a small engine change.

sh
SIZE=<zephyr-sdk>/arm-zephyr-eabi/bin/arm-zephyr-eabi-size
D=build/nrf5340dk/matter-aliro-door-lock-app/modules/ultrawidelock_uwb/CMakeFiles/ultrawidelock_uwb.dir/src

make nrf-build                                    # before the change
find $D -name '*.obj' | sort | xargs $SIZE > /tmp/before.txt
# ... make the change ...
make nrf-build
find $D -name '*.obj' | sort | xargs $SIZE > /tmp/after.txt
diff /tmp/before.txt /tmp/after.txt                # must be empty for a pure refactor

Repeat for the vendored DW3000 objects under build/nrf5340dk/matter-aliro-door-lock-app/modules/ultrawidelock_dw3000/ if deps/dw3000 was touched, then run make check.

A byte-identical size table proves codegen is unchanged, not that the port works. Only a bench run against a phone does that.

5. Vendored DW3000 and the printk alias#

deps/dw3000 is vendor source with one local addition: DIAG tracing in deca_compat.c, deca_interface.c and dw3000_device.c (about 18 printk call sites, gated on CONFIG_ULTRAWIDELOCK_PRETTY_SHELL). To keep those files to a one-line include change rather than rewritten vendor call sites, ultrawidelock_log.h aliases printk on non-Zephyr targets. That alias exists only for this and should go with the DIAG tracing.

deca_port.c is shared by all ports and uses ultrawidelock_sleep_ms / ultrawidelock_sleep_us; dw3000_spi.c, dw3000_hw.c and dw3000_spi_trace.c are Zephyr-specific and each port replaces them.

6. Known-blocked targets#

Blocked on driver availability, not on engineering:

  • NXP SR150: NDA/production access only.
  • QM33 / QM35 as UWB silicon: the public qm35-sdk on GitLab is clonable without an NDA, but it ships FiRa ranging and 360° AoA and does not mention Aliro. The Nordic and Qorvo Aliro reference application is distributed to "early technology adopters" on request; the terms are not public. Whether the public SDK exposes the STS key injection this engine needs is unverified. Its licence agreement was not read and may restrict redistribution.