Summary
With CONFIG_OPENTHREAD_ZEPHYR_BORDER_ROUTER=y and CONFIG_OPENTHREAD_TREL=n, the Zephyr/NCS build fails to link:
ld.bfd: ... openthread_platform.a(trel.c.obj):
in function `socket_received_cb':
trel.c:(.text.socket_received_cb+0x40):
undefined reference to `otPlatTrelHandleReceived'
The reason is that zephyr/modules/openthread/platform/trel.c is gated on CONFIG_OPENTHREAD_ZEPHYR_BORDER_ROUTER, not on CONFIG_OPENTHREAD_TREL. So the TREL platform glue compiles even when TREL itself is disabled, and references otPlatTrelHandleReceived which is only emitted by the OT core when OPENTHREAD_TREL=y.
This blocks any attempt to use the BR feature group without TREL, which is exactly the configuration we need (see companion DevZone post devzone-trel-mle-wedge.md — TREL=y deadlocks the OT thread on NCS v3.3.0 + OT pin a03011cf7).
Environment
- nRF Connect SDK: v3.3.0
- Zephyr: bundled v4.3.99 from the NCS workspace
- OpenThread:
OPENTHREAD_SOURCES=y, upstream pina03011cf7(DevZone #127950 prevents using the prebuilt Nordic library here) - Board:
nrf54lm20dk/nrf54lm20a/cpuapp(also reproduces onnrf5340dk/nrf5340/cpuapp)
Failing Kconfig (minimal):
CONFIG_OPENTHREAD=y CONFIG_OPENTHREAD_SOURCES=y CONFIG_OPENTHREAD_ZEPHYR_BORDER_ROUTER=y CONFIG_OPENTHREAD_TREL=n
Root cause
In zephyr/modules/openthread/CMakeLists.txt (or the equivalent NCS overlay) the openthread_platform library unconditionally adds platform/trel.c whenever OPENTHREAD_ZEPHYR_BORDER_ROUTER is set:
zephyr_library_sources_ifdef(CONFIG_OPENTHREAD_ZEPHYR_BORDER_ROUTER ... platform/trel.c ... )
platform/trel.c calls otPlatTrelHandleReceived() from its receive callback, but that symbol is only emitted by OT core when OPENTHREAD_TREL=y.
Suggested fix
Tighten the gate:
-zephyr_library_sources_ifdef(CONFIG_OPENTHREAD_ZEPHYR_BORDER_ROUTER - platform/trel.c -) +zephyr_library_sources_ifdef(CONFIG_OPENTHREAD_TREL + platform/trel.c +)
trel.c belongs to the TREL platform implementation; gating it on OPENTHREAD_TREL is semantically correct and resolves the link error.
A second small change is helpful but optional: in zephyr/subsys/net/l2/openthread/openthread_border_router.c, openthread_start_border_router_services() calls trel_plat_init() unconditionally. With the CMake fix above, trel_plat_init is no longer compiled, so the wrapper now also fails to link. Either:
- gate the call on
IS_ENABLED(CONFIG_OPENTHREAD_TREL), or - provide a weak no-op
trel_plat_initreturningOT_ERROR_NONEin the platform glue when TREL is disabled.
Workaround we are running
In the application's CMakeLists.txt:
if(CONFIG_OPENTHREAD_ZEPHYR_BORDER_ROUTER)
target_sources(openthread_platform PRIVATE
${ZEPHYR_BASE}/modules/openthread/platform/infra_if.c
${ZEPHYR_BASE}/modules/openthread/platform/udp.c
${ZEPHYR_BASE}/modules/openthread/platform/mdns_socket.c
${ZEPHYR_BASE}/modules/openthread/platform/border_agent.c
${ZEPHYR_BASE}/modules/openthread/platform/dhcp6_pd.c
)
if(CONFIG_OPENTHREAD_TREL)
target_sources(openthread_platform PRIVATE
${ZEPHYR_BASE}/modules/openthread/platform/trel.c
)
endif()
endif()
Plus an app-side stub for the missing-when-TREL=n symbols:
#if !defined(CONFIG_OPENTHREAD_TREL) void otPlatTrelHandleReceived(otInstance *aInstance, uint8_t *aBuffer, uint16_t aLength, const otSockAddr *aSenderAddr) { ... } otError trel_plat_init(otInstance *instance, struct net_if *ail_iface) { return OT_ERROR_NONE; } #endif
This is a cosmetic, app-local workaround for what should be a one-line CMake fix upstream.
Reproducer
A minimal app with the Kconfig snippet above and any OPENTHREAD_ZEPHYR_BORDER_ROUTER=y configuration will fail to link in stock NCS v3.3.0.