diff --git a/.gitignore b/.gitignore index 5cdbea3bfc..8b4520baf0 100644 --- a/.gitignore +++ b/.gitignore @@ -62,3 +62,4 @@ compliance.xml tools/provision/*.hex tools/provision/*.bin +tools/provision/certificate.json diff --git a/CMakeLists.txt b/CMakeLists.txt index d2e0d438d7..f392ed44ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,34 @@ # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # +# Sidewalk-specific nrf7002eb2 overlays live under boards/shields/nrf7002eb2/. +# Do not map state-notifier-time-sync to &led1 in board overlays; add it here only +# when the nrf7002eb2 shield is not used, and drop the alias from the shield overlay +# when it is. +if(DEFINED BOARD AND BOARD MATCHES "^nrf54l15dk_nrf54l15_cpuapp(_ns)?$") + set(nrf7002eb2_shield_dir ${CMAKE_CURRENT_LIST_DIR}/boards/shields/nrf7002eb2) + set(nrf7002eb2_shield_selected FALSE) + + if(DEFINED SHIELD) + string(REPLACE " " ";" shield_list "${SHIELD}") + foreach(shield_name IN LISTS shield_list) + if(shield_name MATCHES "^nrf7002eb2") + set(nrf7002eb2_shield_selected TRUE) + set(shield_overlay ${nrf7002eb2_shield_dir}/boards/${BOARD}.overlay) + if(EXISTS "${shield_overlay}") + list(APPEND EXTRA_DTC_OVERLAY_FILE "${shield_overlay}") + endif() + break() + endif() + endforeach() + endif() + + if(NOT nrf7002eb2_shield_selected) + list(APPEND EXTRA_DTC_OVERLAY_FILE + "${nrf7002eb2_shield_dir}/nrf54l15_state_notifier_time_sync.overlay") + endif() +endif() + if(CONFIG_SIDEWALK_BUILD) if (CONFIG_SIDEWALK_USE_PREBUILT_LIBRARIES) add_subdirectory(lib) diff --git a/Kconfig b/Kconfig index 5674ecb313..16f78e4e6a 100644 --- a/Kconfig +++ b/Kconfig @@ -233,6 +233,8 @@ config SIDEWALK_USE_PREBUILT_LIBRARIES Building from sources requires additional dependencies, not released with nRF Connect SDK. +rsource "subsys/nrf70_location/Kconfig" + rsource "Kconfig.dependencies" rsource "utils/Kconfig" rsource "boards/shields/*/Kconfig.shield" diff --git a/boards/shields/nrf7002eb2/boards/nrf54l15dk_nrf54l15_cpuapp.overlay b/boards/shields/nrf7002eb2/boards/nrf54l15dk_nrf54l15_cpuapp.overlay new file mode 100644 index 0000000000..23bf0ef60f --- /dev/null +++ b/boards/shields/nrf7002eb2/boards/nrf54l15dk_nrf54l15_cpuapp.overlay @@ -0,0 +1,7 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "../nrf54l15.overlay" diff --git a/boards/shields/nrf7002eb2/boards/nrf54l15dk_nrf54l15_cpuapp_ns.overlay b/boards/shields/nrf7002eb2/boards/nrf54l15dk_nrf54l15_cpuapp_ns.overlay new file mode 100644 index 0000000000..23bf0ef60f --- /dev/null +++ b/boards/shields/nrf7002eb2/boards/nrf54l15dk_nrf54l15_cpuapp_ns.overlay @@ -0,0 +1,7 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "../nrf54l15.overlay" diff --git a/boards/shields/nrf7002eb2/nrf54l15.overlay b/boards/shields/nrf7002eb2/nrf54l15.overlay new file mode 100644 index 0000000000..a00b1409c1 --- /dev/null +++ b/boards/shields/nrf7002eb2/nrf54l15.overlay @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/ { + aliases { + /delete-property/ state-notifier-time-sync; + }; +}; diff --git a/boards/shields/nrf7002eb2/nrf54l15_state_notifier_time_sync.overlay b/boards/shields/nrf7002eb2/nrf54l15_state_notifier_time_sync.overlay new file mode 100644 index 0000000000..ffbd13c4d1 --- /dev/null +++ b/boards/shields/nrf7002eb2/nrf54l15_state_notifier_time_sync.overlay @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/ { + aliases { + state-notifier-time-sync = &led1; + }; +}; diff --git a/samples/sid_end_device/boards/nrf54l15dk_nrf54l15_cpuapp.overlay b/samples/sid_end_device/boards/nrf54l15dk_nrf54l15_cpuapp.overlay index a35eebbd54..9c0c63c0a3 100644 --- a/samples/sid_end_device/boards/nrf54l15dk_nrf54l15_cpuapp.overlay +++ b/samples/sid_end_device/boards/nrf54l15dk_nrf54l15_cpuapp.overlay @@ -7,11 +7,9 @@ /{ aliases { state-notifier-connected = &led0; - state-notifier-time-sync = &led1; state-notifier-registered = &led2; state-notifier-working = &led3; }; - }; &cpuapp_rram { diff --git a/samples/sid_end_device/boards/nrf54l15dk_nrf54l15_cpuapp_ns.overlay b/samples/sid_end_device/boards/nrf54l15dk_nrf54l15_cpuapp_ns.overlay index a649cf85b5..175b3fc461 100644 --- a/samples/sid_end_device/boards/nrf54l15dk_nrf54l15_cpuapp_ns.overlay +++ b/samples/sid_end_device/boards/nrf54l15dk_nrf54l15_cpuapp_ns.overlay @@ -7,7 +7,6 @@ /{ aliases { state-notifier-connected = &led0; - state-notifier-time-sync = &led1; state-notifier-registered = &led2; state-notifier-working = &led3; }; diff --git a/samples/sid_end_device/sample.yaml b/samples/sid_end_device/sample.yaml index e3bdcb6679..fa40ce6d95 100644 --- a/samples/sid_end_device/sample.yaml +++ b/samples/sid_end_device/sample.yaml @@ -157,6 +157,18 @@ tests: - cli - lr1110 + sample.sidewalk.dut.nrf7002eb2: + platform_exclude: + - nrf54l15dk/nrf54l15/cpuapp/ns + extra_args: + - OVERLAY_CONFIG="overlay-dut.conf" + - SHIELD=nrf7002eb2 + extra_configs: + - CONFIG_SIDEWALK_APPLICATION_NAME="dut.nrf7002" + tags: + - cli + - nrf70 + sample.sidewalk.dut.nrf_sidewalk_eb: platform_exclude: - nrf54l15dk/nrf54l15/cpuapp/ns diff --git a/samples/sid_end_device/src/sidewalk_events.c b/samples/sid_end_device/src/sidewalk_events.c index 976ef23010..8434b9b4fb 100644 --- a/samples/sid_end_device/src/sidewalk_events.c +++ b/samples/sid_end_device/src/sidewalk_events.c @@ -107,7 +107,7 @@ void sidewalk_event_platform_init(sidewalk_ctx_t *sid, void *ctx) void sidewalk_event_autostart(sidewalk_ctx_t *sid, void *ctx) { if (sid->handle != NULL) { - LOG_INF("Sidewlak is already running"); + LOG_INF("Sidewalk is already running"); return; } if (app_mfg_cfg_is_empty()) { diff --git a/subsys/CMakeLists.txt b/subsys/CMakeLists.txt index 14204d282f..2a06eb9d10 100644 --- a/subsys/CMakeLists.txt +++ b/subsys/CMakeLists.txt @@ -6,6 +6,7 @@ add_subdirectory(sal) + if (CONFIG_SIDEWALK_USE_PREBUILT_LIBRARIES) add_subdirectory(app_utils) add_subdirectory(semtech) @@ -15,4 +16,7 @@ if (CONFIG_SIDEWALK_USE_PREBUILT_LIBRARIES) endif() endif() +add_subdirectory_ifdef(CONFIG_SIDEWALK_WIFI_LOCATION_NRF70 nrf70_location) + + add_subdirectory_ifdef(CONFIG_SIDEWALK_DEMO_PARSER demo) diff --git a/subsys/nrf70_location/CMakeLists.txt b/subsys/nrf70_location/CMakeLists.txt new file mode 100644 index 0000000000..87ec74b01c --- /dev/null +++ b/subsys/nrf70_location/CMakeLists.txt @@ -0,0 +1,29 @@ +# +# Copyright (c) 2026 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +zephyr_library_named(sid_pal_wifi_nrf70_impl) + +target_sources(sid_pal_wifi_nrf70_impl PRIVATE + nrf70_wifi_scan.c + sid_pal_wifi_nrf70_impl.c +) + +if(NOT CONFIG_SIDEWALK_SUBGHZ_RADIO_LR1110) + target_sources(sid_pal_wifi_nrf70_impl PRIVATE + sid_pal_gnss_stub.c + ) + target_link_libraries(sid_pal_wifi_nrf70_impl PRIVATE + sid_pal_gnss_ifc + ) +endif() + +target_include_directories(sid_pal_wifi_nrf70_impl PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} +) + +target_link_libraries(sid_pal_wifi_nrf70_impl PRIVATE + sid_pal_wifi_ifc +) diff --git a/subsys/nrf70_location/Kconfig b/subsys/nrf70_location/Kconfig new file mode 100644 index 0000000000..e5ff97e425 --- /dev/null +++ b/subsys/nrf70_location/Kconfig @@ -0,0 +1,65 @@ +# +# Copyright (c) 2026 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +config SIDEWALK_WIFI_LOCATION_NRF70 + bool "nRF70 Wi-Fi location PAL" + default y if SHIELD_NRF7002EB2 + select NET_MGMT + select NET_MGMT_EVENT + select NET_MGMT_EVENT_INFO + select WIFI + select NETWORKING + select WIFI_NRF70 + help + Wi-Fi PAL for Sidewalk location using nRF70 net_mgmt scan. + +if SIDEWALK_WIFI_LOCATION_NRF70 + +choice NRF70_OPER_MODES + default NRF70_SCAN_ONLY +endchoice + +config SIDEWALK_WIFI_LOCATION_NRF70_SCAN_TIMEOUT_MS + int "nRF70 Wi-Fi scan timeout (ms)" + default 20000 + range 1000 60000 + help + Backstop timeout for a single nRF70 Wi-Fi scan. + +choice SIDEWALK_WIFI_LOCATION_NRF70_SCAN_BAND_FILTER + prompt "nRF70 Wi-Fi location scan band filter" + default SIDEWALK_WIFI_LOCATION_NRF70_SCAN_BAND_2_4GHZ if SHIELD_NRF7002EB2 + default SIDEWALK_WIFI_LOCATION_NRF70_SCAN_BAND_2_4_AND_5_GHZ + help + Select which Wi-Fi frequency bands the nRF70 location scan covers. + +config SIDEWALK_WIFI_LOCATION_NRF70_SCAN_BAND_2_4GHZ + bool "2.4 GHz only" + +config SIDEWALK_WIFI_LOCATION_NRF70_SCAN_BAND_2_4_AND_5_GHZ + bool "2.4 and 5 GHz" + +config SIDEWALK_WIFI_LOCATION_NRF70_SCAN_BAND_ALL + bool "All regulatory bands (2.4/5/6 GHz)" + +endchoice + +choice SIDEWALK_WIFI_LOCATION_NRF70_SCAN_TYPE + prompt "nRF70 Wi-Fi location scan type" + default SIDEWALK_WIFI_LOCATION_NRF70_SCAN_PASSIVE if SHIELD_NRF7002EB2 + default SIDEWALK_WIFI_LOCATION_NRF70_SCAN_ACTIVE + help + Scan type hint passed to the nRF70 driver. + +config SIDEWALK_WIFI_LOCATION_NRF70_SCAN_ACTIVE + bool "Active" + +config SIDEWALK_WIFI_LOCATION_NRF70_SCAN_PASSIVE + bool "Passive" + +endchoice + +endif # SIDEWALK_WIFI_LOCATION_NRF70 diff --git a/subsys/nrf70_location/nrf70_wifi_scan.c b/subsys/nrf70_location/nrf70_wifi_scan.c new file mode 100644 index 0000000000..5b29232039 --- /dev/null +++ b/subsys/nrf70_location/nrf70_wifi_scan.c @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "nrf70_wifi_scan.h" + +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(nrf70_wifi_scan, CONFIG_SIDEWALK_LOG_LEVEL); + +#define WIFI_SCAN_EVENTS (NET_EVENT_WIFI_SCAN_RESULT | NET_EVENT_WIFI_SCAN_DONE) + +static struct net_mgmt_event_callback scan_cb; +static bool cb_registered; +static nrf70_wifi_scan_done_cb_t scan_done_cb; + +static struct { + struct nrf70_wifi_scan_ap *results; + size_t max_results; + size_t stored; + size_t total; + int status; +} scan_ctx; + +static atomic_t scan_done = ATOMIC_INIT(0); + +static struct net_if *wifi_iface(void) +{ + struct net_if *iface = net_if_get_first_wifi(); + + return iface ? iface : net_if_get_default(); +} + +/* Insert an AP into the results array, keeping it sorted by descending RSSI and + * capped at max_results (weakest dropped). */ +static void scan_result_insert(int8_t rssi, const uint8_t *mac, uint8_t mac_len) +{ + struct nrf70_wifi_scan_ap *r = scan_ctx.results; + size_t max = scan_ctx.max_results; + size_t pos; + + if (!r || max == 0) { + return; + } + + if (scan_ctx.stored < max) { + pos = scan_ctx.stored++; + } else if (rssi > r[max - 1].rssi) { + pos = max - 1; + } else { + return; + } + + for (; pos > 0 && r[pos - 1].rssi < rssi; pos--) { + r[pos] = r[pos - 1]; + } + + memset(&r[pos], 0, sizeof(r[pos])); + r[pos].rssi = rssi; + r[pos].mac_len = MIN(mac_len, (uint8_t)WIFI_MAC_ADDR_LEN); + memcpy(r[pos].mac, mac, r[pos].mac_len); +} + +static void scan_result_handle(struct net_mgmt_event_callback *cb) +{ + const struct wifi_scan_result *entry = (const struct wifi_scan_result *)cb->info; + + scan_ctx.total++; + scan_result_insert(entry->rssi, entry->mac, entry->mac_length); +} + +static void scan_done_handle(struct net_mgmt_event_callback *cb) +{ + const struct wifi_status *status = (const struct wifi_status *)cb->info; + + scan_ctx.status = status->status; + atomic_set(&scan_done, 1); + + if (scan_done_cb) { + scan_done_cb(); + } +} + +static void scan_mgmt_event_handler(struct net_mgmt_event_callback *cb, uint64_t mgmt_event, + struct net_if *iface) +{ + ARG_UNUSED(iface); + + switch (mgmt_event) { + case NET_EVENT_WIFI_SCAN_RESULT: + scan_result_handle(cb); + break; + case NET_EVENT_WIFI_SCAN_DONE: + scan_done_handle(cb); + break; + default: + break; + } +} + +int nrf70_wifi_scan_start(struct nrf70_wifi_scan_ap *results, size_t max_results, + nrf70_wifi_scan_done_cb_t on_done) +{ + struct net_if *iface = wifi_iface(); + int ret; + + if (!results || max_results == 0) { + return -EINVAL; + } + if (!iface) { + return -ENODEV; + } + if (!net_if_is_admin_up(iface)) { + return -ENETDOWN; + } + + scan_ctx.results = results; + scan_ctx.max_results = max_results; + scan_ctx.stored = 0; + scan_ctx.total = 0; + scan_ctx.status = 0; + scan_done_cb = on_done; + atomic_set(&scan_done, 0); + + net_mgmt_init_event_callback(&scan_cb, scan_mgmt_event_handler, WIFI_SCAN_EVENTS); + net_mgmt_add_event_callback(&scan_cb); + cb_registered = true; + + struct wifi_scan_params params = { +#if defined(CONFIG_SIDEWALK_WIFI_LOCATION_NRF70_SCAN_ACTIVE) + .scan_type = WIFI_SCAN_TYPE_ACTIVE, +#elif defined(CONFIG_SIDEWALK_WIFI_LOCATION_NRF70_SCAN_PASSIVE) + .scan_type = WIFI_SCAN_TYPE_PASSIVE, +#endif +#if defined(CONFIG_SIDEWALK_WIFI_LOCATION_NRF70_SCAN_BAND_2_4GHZ) + .bands = BIT(WIFI_FREQ_BAND_2_4_GHZ), +#elif defined(CONFIG_SIDEWALK_WIFI_LOCATION_NRF70_SCAN_BAND_2_4_AND_5_GHZ) + .bands = BIT(WIFI_FREQ_BAND_2_4_GHZ) | BIT(WIFI_FREQ_BAND_5_GHZ), +#endif + }; + + ret = net_mgmt(NET_REQUEST_WIFI_SCAN, iface, ¶ms, sizeof(params)); + if (ret) { + nrf70_wifi_scan_abort(); + return ret; + } + + return 0; +} + +int nrf70_wifi_scan_get_result(size_t *stored, size_t *total, int *status) +{ + if (!cb_registered) { + return -EINVAL; + } + if (!atomic_get(&scan_done)) { + return 0; + } + + net_mgmt_del_event_callback(&scan_cb); + cb_registered = false; + + if (stored) { + *stored = scan_ctx.stored; + } + if (total) { + *total = scan_ctx.total; + } + if (status) { + *status = scan_ctx.status; + } + + return 1; +} + +void nrf70_wifi_scan_abort(void) +{ + /* Zephyr's Wi-Fi mgmt API has no scan-cancel request, so a scan already + * running in the driver cannot be stopped; it will run to completion in + * the background. All we can do is stop listening for its results and + * release the collection context. */ + if (cb_registered) { + net_mgmt_del_event_callback(&scan_cb); + cb_registered = false; + } + scan_ctx.results = NULL; + scan_ctx.max_results = 0; + atomic_set(&scan_done, 0); +} diff --git a/subsys/nrf70_location/nrf70_wifi_scan.h b/subsys/nrf70_location/nrf70_wifi_scan.h new file mode 100644 index 0000000000..a4494afcd8 --- /dev/null +++ b/subsys/nrf70_location/nrf70_wifi_scan.h @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef NRF70_WIFI_SCAN_H +#define NRF70_WIFI_SCAN_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * A single access point discovered by a net_mgmt Wi-Fi scan. + * + * Only the fields the Sidewalk Wi-Fi PAL payload actually carries (BSSID and + * signal strength) are kept; SSID/channel/security are intentionally dropped to + * keep the collection buffer small. + */ +struct nrf70_wifi_scan_ap { + int8_t rssi; + uint8_t mac[WIFI_MAC_ADDR_LEN]; + uint8_t mac_len; +}; + +/** + * Completion callback, invoked once from the net_mgmt event thread when the + * scan finishes. It must be lightweight (e.g. post an event); the actual + * results are read later with nrf70_wifi_scan_get_result(). + */ +typedef void (*nrf70_wifi_scan_done_cb_t)(void); + +/** + * Start an asynchronous net_mgmt Wi-Fi scan. + * + * Returns immediately after handing the request to the driver. Results stream + * in on the net_mgmt event thread and are kept as the @p max_results strongest + * access points (descending RSSI) in @p results. When the scan finishes, + * @p on_done is called (from the net_mgmt thread) so the caller can wake up and + * collect the results with nrf70_wifi_scan_get_result(). + * + * Only one scan may be in flight at a time. @p results must stay valid until + * the scan completes or nrf70_wifi_scan_abort() is called. + * + * @param[out] results Array to keep the strongest APs (must be non-NULL). + * @param[in] max_results Capacity of @p results. + * @param[in] on_done Completion callback (may be NULL). + * + * @return 0 on success, negative errno on failure. + */ +int nrf70_wifi_scan_start(struct nrf70_wifi_scan_ap *results, size_t max_results, + nrf70_wifi_scan_done_cb_t on_done); + +/** + * Collect the results of a scan started by nrf70_wifi_scan_start(). + * + * Intended to be called after the completion callback has fired. + * + * @param[out] stored Number of APs kept in the results array. May be NULL. + * @param[out] total Total number of APs reported by the driver. May be NULL. + * @param[out] status Driver scan status (0 = success). Valid when done. May be NULL. + * + * @return 1 if the scan has completed, 0 if still running, negative errno on error. + */ +int nrf70_wifi_scan_get_result(size_t *stored, size_t *total, int *status); + +/** + * Stop tracking an in-flight scan and release the net_mgmt callback. + * + * NOTE: Zephyr's Wi-Fi mgmt API has no scan-cancel request, so a scan already + * issued to the driver cannot be stopped in hardware - it keeps running to + * completion in the background. This only detaches the results listener and + * frees the collection context, so the caller may reuse its results buffer. + */ +void nrf70_wifi_scan_abort(void); + +#ifdef __cplusplus +} +#endif + +#endif /* NRF70_WIFI_SCAN_H */ diff --git a/subsys/nrf70_location/sid_pal_gnss_stub.c b/subsys/nrf70_location/sid_pal_gnss_stub.c new file mode 100644 index 0000000000..5660bf89bc --- /dev/null +++ b/subsys/nrf70_location/sid_pal_gnss_stub.c @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include + +sid_error_t sid_pal_gnss_init(struct sid_pal_gnss_config *config) +{ + ARG_UNUSED(config); + return SID_ERROR_NOSUPPORT; +} + +sid_error_t sid_pal_gnss_process_event(uint8_t event_id) +{ + ARG_UNUSED(event_id); + return SID_ERROR_NOSUPPORT; +} + +sid_error_t sid_pal_gnss_schedule_scan(uint32_t scan_delay_s) +{ + ARG_UNUSED(scan_delay_s); + return SID_ERROR_NOSUPPORT; +} + +sid_error_t sid_pal_gnss_cancel_scan(void) +{ + return SID_ERROR_NOSUPPORT; +} + +sid_error_t sid_pal_gnss_get_scan_payload(struct sid_pal_gnss_payload *gnss_scan_group) +{ + ARG_UNUSED(gnss_scan_group); + return SID_ERROR_NOSUPPORT; +} + +sid_error_t sid_pal_gnss_alm_demod_start(void) +{ + return SID_ERROR_NOSUPPORT; +} + +sid_error_t sid_pal_gnss_deinit(void) +{ + return SID_ERROR_NOSUPPORT; +} diff --git a/subsys/nrf70_location/sid_pal_wifi_nrf70_impl.c b/subsys/nrf70_location/sid_pal_wifi_nrf70_impl.c new file mode 100644 index 0000000000..64869298ff --- /dev/null +++ b/subsys/nrf70_location/sid_pal_wifi_nrf70_impl.c @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include + +#include +#include +#include +#include + +#include "nrf70_wifi_scan.h" + +LOG_MODULE_REGISTER(sid_pal_wifi_nrf70, CONFIG_SIDEWALK_LOG_LEVEL); + +#define WIFI_SCAN_TIMEOUT_MS CONFIG_SIDEWALK_WIFI_LOCATION_NRF70_SCAN_TIMEOUT_MS + +static struct sid_pal_wifi_config wifi_config; + +/* Access points kept for the last/ongoing scan, capped at the payload size. */ +static struct nrf70_wifi_scan_ap scan_aps[SID_WIFI_MAX_RESULTS]; + +/* Results of the last completed scan, consumed by get_scan_payload(). */ +static struct sid_pal_wifi_payload last_payload; + +static bool scan_busy; /* a scan request is in progress (delayed or running) */ +static bool scan_started; /* the net_mgmt scan has actually been issued */ +static int64_t scan_deadline; + +/* Ask the library to call sid_pal_wifi_process_event() after delay_ms. + * Safe to call from any thread: on_wifi_event posts to the Sidewalk event + * queue. */ +static void request_process_event(uint32_t delay_ms) +{ + if (wifi_config.on_wifi_event) { + wifi_config.on_wifi_event(wifi_config.ctx, SID_PAL_WIFI_INTERNAL, delay_ms); + } +} + +/* Scanner completion callback - runs on the net_mgmt event thread. */ +static void scan_done_wake(void) +{ + request_process_event(0); +} + +/* Issue the net_mgmt scan and arm the backstop timeout. Runs on the library + * thread (schedule_scan or, for a delayed scan, process_event). */ +static sid_error_t start_scan_now(void) +{ + int ret = nrf70_wifi_scan_start(scan_aps, ARRAY_SIZE(scan_aps), scan_done_wake); + + if (ret) { + LOG_ERR("nRF70 Wi-Fi PAL: failed to start scan (%d)", ret); + return SID_ERROR_IO_ERROR; + } + + scan_started = true; + scan_deadline = k_uptime_get() + WIFI_SCAN_TIMEOUT_MS; + + /* Backstop only: real completion is signalled by scan_done_wake(). */ + request_process_event(WIFI_SCAN_TIMEOUT_MS); + return SID_ERROR_NONE; +} + +static void finish_scan(size_t stored, int status) +{ + memset(&last_payload, 0, sizeof(last_payload)); + + if (status != 0) { + LOG_ERR("nRF70 Wi-Fi scan failed (status %d)", status); + } else if (stored == 0) { + LOG_WRN("nRF70 Wi-Fi scan found no access points"); + } else { + /* scan_aps is already sorted by descending RSSI and capped at + * SID_WIFI_MAX_RESULTS by the scanner. */ + uint8_t n = (uint8_t)MIN(stored, (size_t)SID_WIFI_MAX_RESULTS); + + for (uint8_t i = 0; i < n; i++) { + /* PAL contract stores RSSI as unsigned. */ + last_payload.results[i].rssi = (uint8_t)scan_aps[i].rssi; + memcpy(last_payload.results[i].mac, scan_aps[i].mac, + SID_WIFI_MAC_ADDRESS_LENGTH); + } + last_payload.nbr_results = n; + LOG_INF("nRF70 Wi-Fi PAL: collected %u AP(s)", n); + } + + scan_busy = false; + scan_started = false; + + if (wifi_config.on_wifi_event) { + wifi_config.on_wifi_event(wifi_config.ctx, SID_PAL_WIFI_SCAN_COMPLETE, 0); + } +} + +/* --------------------------------------------------------------------------- + * Wi-Fi PAL + * ------------------------------------------------------------------------- */ + +sid_error_t sid_pal_wifi_init(struct sid_pal_wifi_config *config) +{ + if (!config) { + return SID_ERROR_NULL_POINTER; + } + + wifi_config = *config; + + LOG_INF("nRF70 Wi-Fi PAL initialized"); + return SID_ERROR_NONE; +} + +sid_error_t sid_pal_wifi_deinit(void) +{ + nrf70_wifi_scan_abort(); + scan_busy = false; + scan_started = false; + memset(&wifi_config, 0, sizeof(wifi_config)); + return SID_ERROR_NONE; +} + +sid_error_t sid_pal_wifi_process_event(uint8_t event_id) +{ + ARG_UNUSED(event_id); + + if (!scan_busy) { + return SID_ERROR_NONE; + } + + if (!scan_started) { + /* The requested scan delay has elapsed; start the scan now. */ + if (start_scan_now() != SID_ERROR_NONE) { + finish_scan(0, -EIO); + } + return SID_ERROR_NONE; + } + + size_t stored = 0; + size_t total = 0; + int status = 0; + + int ret = nrf70_wifi_scan_get_result(&stored, &total, &status); + + if (ret == 0) { + /* Woken but scan not done yet: only act if the backstop timeout + * has elapsed, otherwise wait for the real completion wake. */ + if (k_uptime_get() >= scan_deadline) { + LOG_ERR("nRF70 Wi-Fi scan timed out"); + nrf70_wifi_scan_abort(); + finish_scan(0, -ETIMEDOUT); + } + return SID_ERROR_NONE; + } + + if (ret < 0) { + finish_scan(0, ret); + return SID_ERROR_NONE; + } + + LOG_INF("nRF70 Wi-Fi PAL: scan complete (%u of %u AP(s))", + (unsigned)stored, (unsigned)total); + finish_scan(stored, status); + return SID_ERROR_NONE; +} + +sid_error_t sid_pal_wifi_schedule_scan(uint32_t scan_delay_s) +{ + if (!wifi_config.on_wifi_event) { + return SID_ERROR_UNINITIALIZED; + } + + if (scan_busy) { + LOG_WRN("nRF70 Wi-Fi PAL: scan already in progress"); + return SID_ERROR_BUSY; + } + + scan_busy = true; + scan_started = false; + + if (scan_delay_s == 0) { + sid_error_t err = start_scan_now(); + + if (err != SID_ERROR_NONE) { + scan_busy = false; + return err; + } + LOG_INF("nRF70 Wi-Fi PAL: scan scheduled"); + } else { + /* Defer the scan: ask the library to call process_event() after + * the requested delay, where start_scan_now() will run. */ + LOG_INF("nRF70 Wi-Fi PAL: scan scheduled in %u s", scan_delay_s); + request_process_event(scan_delay_s * 1000U); + } + + return SID_ERROR_NONE; +} + +sid_error_t sid_pal_wifi_cancel_scan(void) +{ + /* Best-effort cancel. A scan that has not started yet (a deferred scan + * still waiting for its delay to elapse) is cancelled cleanly. A scan + * already running in the driver cannot be stopped in hardware (Zephyr + * has no scan-cancel request); we just detach the listener so its + * eventual completion is ignored and the PAL is freed for a new scan. */ + if (scan_busy) { + nrf70_wifi_scan_abort(); + scan_busy = false; + scan_started = false; + } + return SID_ERROR_NONE; +} + +sid_error_t sid_pal_wifi_get_scan_payload(struct sid_pal_wifi_payload *wifi_scan_result) +{ + if (!wifi_scan_result) { + return SID_ERROR_NULL_POINTER; + } + + if (last_payload.nbr_results == 0) { + return SID_ERROR_INSUFFICIENT_RESULTS; + } + + *wifi_scan_result = last_payload; + + LOG_INF("nRF70 Wi-Fi PAL: reporting %u AP(s) to Sidewalk", + wifi_scan_result->nbr_results); + return SID_ERROR_NONE; +} diff --git a/subsys/sal/sid_pal/include/bt_app_callbacks.h b/subsys/sal/sid_pal/include/bt_app_callbacks.h index 13d4812ca5..2d7f4aa51d 100644 --- a/subsys/sal/sid_pal/include/bt_app_callbacks.h +++ b/subsys/sal/sid_pal/include/bt_app_callbacks.h @@ -25,7 +25,7 @@ extern "C" { bool sid_ble_bt_attr_is_SMP(const struct bt_gatt_attr *attr); /** - * @brief check if attr is for one of Sidewlak services + * @brief check if attr is for one of Sidewalk services * * @param attr * @return true if attr is diff --git a/subsys/semtech/CMakeLists.txt b/subsys/semtech/CMakeLists.txt index 56b9675e5e..2c9e923e3f 100644 --- a/subsys/semtech/CMakeLists.txt +++ b/subsys/semtech/CMakeLists.txt @@ -67,7 +67,11 @@ target_compile_definitions(smtc_lbm INTERFACE ) add_subdirectory_ifdef(CONFIG_SIDEWALK_SUBGHZ_RADIO_SX126X sx126x) -add_subdirectory_ifdef(CONFIG_SIDEWALK_SUBGHZ_RADIO_SX126X sx126x_location) + +if(CONFIG_SIDEWALK_SUBGHZ_RADIO_SX126X AND NOT CONFIG_SIDEWALK_WIFI_LOCATION_NRF70) + add_subdirectory(sx126x_location) +endif() + add_subdirectory_ifdef(CONFIG_SIDEWALK_SUBGHZ_RADIO_LR1110 lr11xx) add_subdirectory_ifdef(CONFIG_SIDEWALK_SUBGHZ_RADIO_LR1110 lr1110) add_subdirectory_ifdef(CONFIG_SIDEWALK_SUBGHZ_RADIO_LR1110 lr1110_location) @@ -78,9 +82,13 @@ if(CONFIG_SIDEWALK_SUBGHZ_RADIO_SX126X) target_link_libraries(sidewalk_radio INTERFACE "-Wl,--start-group" sid_pal_radio_sx126x_impl - sid_pal_radio_sx126x_location_impl "-Wl,--end-group" ) + if(NOT CONFIG_SIDEWALK_WIFI_LOCATION_NRF70) + target_link_libraries(sidewalk_radio INTERFACE + sid_pal_radio_sx126x_location_impl + ) + endif() endif() if(CONFIG_SIDEWALK_SUBGHZ_RADIO_LR1110) diff --git a/subsys/semtech/lr1110_location/CMakeLists.txt b/subsys/semtech/lr1110_location/CMakeLists.txt index 9dea1e0662..35d385f5dc 100644 --- a/subsys/semtech/lr1110_location/CMakeLists.txt +++ b/subsys/semtech/lr1110_location/CMakeLists.txt @@ -9,6 +9,12 @@ add_library(sid_pal_radio_lr11xx_location_impl STATIC smtc_modem_impl.c ) +if(NOT CONFIG_SIDEWALK_WIFI_LOCATION_NRF70) + target_sources(sid_pal_radio_lr11xx_location_impl PRIVATE + sid_pal_wifi_lr1110_impl.c + ) +endif() + target_include_directories(sid_pal_radio_lr11xx_location_impl PRIVATE ${SEMTECH_COMMON_INCLUDES} ${LR11XX_COMMON_INCLUDES} diff --git a/subsys/semtech/lr1110_location/lr1110_location_priv.h b/subsys/semtech/lr1110_location/lr1110_location_priv.h new file mode 100644 index 0000000000..96e2dbea5f --- /dev/null +++ b/subsys/semtech/lr1110_location/lr1110_location_priv.h @@ -0,0 +1,33 @@ +/* + * Copyright 2025 Amazon.com, Inc. or its affiliates. All rights reserved. + * + * AMAZON PROPRIETARY/CONFIDENTIAL + */ + +#ifndef LR1110_LOCATION_PRIV_H +#define LR1110_LOCATION_PRIV_H + +#include +#include +#include +#include +#include + +struct lr11xx_location_configuration { + struct sid_pal_gnss_config gnss_cfg; + struct sid_pal_wifi_config wifi_cfg; + bool is_init; + bool is_busy; + lr11xx_gnss_wifi_config_t *init_config; + smtc_modem_gnss_event_data_scan_done_t gnss_scan_done_data; + smtc_modem_wifi_event_data_scan_done_t wifi_scan_done_data; + bool radio_init_on_loc; +}; + +extern struct lr11xx_location_configuration location_state; + +sid_error_t sid_pal_common_location_init(void); +sid_error_t sid_pal_common_location_deinit(void); +sid_error_t sid_pal_common_process_event(void); + +#endif /* LR1110_LOCATION_PRIV_H */ diff --git a/subsys/semtech/lr1110_location/sid_pal_gnss_wifi_impl.c b/subsys/semtech/lr1110_location/sid_pal_gnss_wifi_impl.c index 7aaa69dd5c..8e53995674 100644 --- a/subsys/semtech/lr1110_location/sid_pal_gnss_wifi_impl.c +++ b/subsys/semtech/lr1110_location/sid_pal_gnss_wifi_impl.c @@ -30,26 +30,17 @@ #include #include +#include "lr1110_location_priv.h" + #define LBM_STACK_ID 0 #define NAV3_TLV_HEADER_SIZE 3 #define SEMTECH_GNSS_NG_TAG 0x53 enum LOCATION_EVENT_TYPE { - EVENT_TYPE_LBM, -}; - -struct lr11xx_location_configuration { - struct sid_pal_gnss_config gnss_cfg; - struct sid_pal_wifi_config wifi_cfg; - bool is_init; - bool is_busy; - lr11xx_gnss_wifi_config_t *init_config; - smtc_modem_gnss_event_data_scan_done_t gnss_scan_done_data; - smtc_modem_wifi_event_data_scan_done_t wifi_scan_done_data; - bool radio_init_on_loc; + EVENT_TYPE_LBM, }; -static struct lr11xx_location_configuration location_state = {0}; +struct lr11xx_location_configuration location_state; static uint8_t grp_token = 0; void rotate_scan_grp_token() { @@ -227,12 +218,7 @@ sid_error_t sid_pal_gnss_init(struct sid_pal_gnss_config *config) { sid_error_t sid_pal_gnss_process_event(uint8_t event_id) { - return sid_pal_common_process_event(); -} - -sid_error_t sid_pal_wifi_process_event(uint8_t event_id) -{ - return sid_pal_common_process_event(); + return sid_pal_common_process_event(); } sid_error_t sid_pal_gnss_alm_demod_start() { @@ -314,54 +300,3 @@ sid_error_t sid_pal_gnss_get_scan_payload(struct sid_pal_gnss_payload *gnss_scan return SID_ERROR_NONE; } - -// END OF GNSS SPECIFIC PAL IMPLEMENTATION - -// WIFI SPECIFIC PAL IMPLEMENTATION - -sid_error_t sid_pal_wifi_init(struct sid_pal_wifi_config *config) { - location_state.wifi_cfg = *config; - return sid_pal_common_location_init(); -} - -sid_error_t sid_pal_wifi_deinit(){ - sid_pal_common_location_deinit(); - return SID_ERROR_NONE; -} - -sid_error_t sid_pal_wifi_schedule_scan(uint32_t scan_delay_s) { - if(location_state.is_busy) { - SID_PAL_LOG_INFO("Location modem is busy, cannot schedule scan"); - return SID_ERROR_BUSY; - } - - if(!location_state.is_init) { - SID_PAL_LOG_INFO("PAL is uninitialized"); - return SID_ERROR_UNINITIALIZED; - } - - location_state.is_busy = true; - smtc_modem_wifi_scan(LBM_STACK_ID, scan_delay_s); - sid_pal_common_process_event(); - - return SID_ERROR_NONE; -} - -sid_error_t sid_pal_wifi_cancel_scan() { - return SID_ERROR_NOSUPPORT; -} - -sid_error_t sid_pal_wifi_get_scan_payload(struct sid_pal_wifi_payload *wifi_scan_payload) { - uint8_t ap_idx = location_state.wifi_scan_done_data.nbr_results; - if(ap_idx < 1) { - return SID_ERROR_INSUFFICIENT_RESULTS; - } - for(uint8_t i = 0; i < location_state.wifi_scan_done_data.nbr_results; i++) { - wifi_scan_payload->results[i].rssi = location_state.wifi_scan_done_data.results[i].rssi; - memcpy(&(wifi_scan_payload->results[i].mac), location_state.wifi_scan_done_data.results[i].mac_address, SID_WIFI_MAC_ADDRESS_LENGTH); - } - - wifi_scan_payload->nbr_results = location_state.wifi_scan_done_data.nbr_results; - return SID_ERROR_NONE; -} -// END OF WIFI PAL IMPLEMENTATION diff --git a/subsys/semtech/lr1110_location/sid_pal_wifi_lr1110_impl.c b/subsys/semtech/lr1110_location/sid_pal_wifi_lr1110_impl.c new file mode 100644 index 0000000000..66e8ff880f --- /dev/null +++ b/subsys/semtech/lr1110_location/sid_pal_wifi_lr1110_impl.c @@ -0,0 +1,76 @@ +/* + * Copyright 2025 Amazon.com, Inc. or its affiliates. All rights reserved. + * + * AMAZON PROPRIETARY/CONFIDENTIAL + */ + +#include +#include +#include +#include +#include + +#include "lr1110_location_priv.h" + +#define LBM_STACK_ID 0 + +sid_error_t sid_pal_wifi_init(struct sid_pal_wifi_config *config) +{ + location_state.wifi_cfg = *config; + return sid_pal_common_location_init(); +} + +sid_error_t sid_pal_wifi_process_event(uint8_t event_id) +{ + return sid_pal_common_process_event(); +} + +sid_error_t sid_pal_wifi_deinit(void) +{ + sid_pal_common_location_deinit(); + return SID_ERROR_NONE; +} + +sid_error_t sid_pal_wifi_schedule_scan(uint32_t scan_delay_s) +{ + if (location_state.is_busy) { + SID_PAL_LOG_INFO("Location modem is busy, cannot schedule scan"); + return SID_ERROR_BUSY; + } + + if (!location_state.is_init) { + SID_PAL_LOG_INFO("PAL is uninitialized"); + return SID_ERROR_UNINITIALIZED; + } + + location_state.is_busy = true; + smtc_modem_wifi_scan(LBM_STACK_ID, scan_delay_s); + sid_pal_common_process_event(); + + return SID_ERROR_NONE; +} + +sid_error_t sid_pal_wifi_cancel_scan(void) +{ + return SID_ERROR_NOSUPPORT; +} + +sid_error_t sid_pal_wifi_get_scan_payload(struct sid_pal_wifi_payload *wifi_scan_payload) +{ + uint8_t ap_idx = location_state.wifi_scan_done_data.nbr_results; + + if (ap_idx < 1) { + return SID_ERROR_INSUFFICIENT_RESULTS; + } + + for (uint8_t i = 0; i < location_state.wifi_scan_done_data.nbr_results; i++) { + wifi_scan_payload->results[i].rssi = + location_state.wifi_scan_done_data.results[i].rssi; + memcpy(&(wifi_scan_payload->results[i].mac), + location_state.wifi_scan_done_data.results[i].mac_address, + SID_WIFI_MAC_ADDRESS_LENGTH); + } + + wifi_scan_payload->nbr_results = location_state.wifi_scan_done_data.nbr_results; + return SID_ERROR_NONE; +} diff --git a/utils/include/sidewalk_version.h b/utils/include/sidewalk_version.h index 5b574d2d81..d041a4e248 100644 --- a/utils/include/sidewalk_version.h +++ b/utils/include/sidewalk_version.h @@ -11,9 +11,12 @@ #ifdef CONFIG_DT_HAS_SEMTECH_LR1110_ENABLED #define SHIELD_NAME "Semtech lr1110" -#elif defined(CONFIG_DT_HAS_SEMTECH_SX1262_ENABLED) | \ +#elif defined(CONFIG_DT_HAS_SEMTECH_SX1262_ENABLED) || \ defined(CONFIG_DT_HAS_SEMTECH_SX1262_NEW_ENABLED) #define SHIELD_NAME "Semtech sx1262" +#elif defined(CONFIG_DT_HAS_NORDIC_NRF7002_SPI_ENABLED) || \ + defined(CONFIG_DT_HAS_NORDIC_NRF7002_QSPI_ENABLED) +#define SHIELD_NAME "nRF7002 EB-II" #endif #ifndef SHIELD_NAME diff --git a/west.yml b/west.yml index 1060a56833..564a360146 100644 --- a/west.yml +++ b/west.yml @@ -21,6 +21,7 @@ manifest: - hal_nordic - mbedtls - mcuboot + - nrf_wifi - nrfxlib - oberon-psa-crypto - qcbor