From 5a9f0fabd33c76144b4bfed76f661226e926c18a Mon Sep 17 00:00:00 2001 From: Kacper Radoszewski Date: Thu, 27 Nov 2025 08:57:56 +0100 Subject: [PATCH 01/47] [nrf noup] include: net: add TLS_DTLS_FRAG_EXT to NCS extensions nrf-squash! [nrf noup] include: net: add NCS extensions The TLS_DTLS_FRAG_EXT socket option is used to enable and disable the DTLS fragmentation extension. Signed-off-by: Kacper Radoszewski --- include/zephyr/net/socket_ncs.h | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/include/zephyr/net/socket_ncs.h b/include/zephyr/net/socket_ncs.h index 6a77d6c41f13..7c8bae645f6c 100644 --- a/include/zephyr/net/socket_ncs.h +++ b/include/zephyr/net/socket_ncs.h @@ -69,6 +69,27 @@ extern "C" { #define TLS_DTLS_HANDSHAKE_STATUS_FULL 0 #define TLS_DTLS_HANDSHAKE_STATUS_CACHED 1 +/** Socket option to enable the DTLS fragmentation extension. + * Accepted values for the option are: @ref DTLS_FRAG_EXT_DISABLED, + * @ref DTLS_FRAG_EXT_512_ENABLED, @ref DTLS_FRAG_EXT_1024_ENABLED. + */ +#define TLS_DTLS_FRAG_EXT (NET_SOCKET_NCS_BASE + 22) + +/** Disabled - The DTLS fragmentation extension is not included in the Client Hello. */ +#define DTLS_FRAG_EXT_DISABLED 0 +/** Enabled - The DTLS fragmentation extension is included in the Client Hello with the fragment + * size of 512 bytes. + * + * @note The user data size in send requests also becomes limited to a maximum of 512 bytes. + */ +#define DTLS_FRAG_EXT_512_ENABLED 1 +/** Enabled - The DTLS fragmentation extension is included in the Client Hello with the fragment + * size of 1024 bytes. + * + * @note The user data size in send requests also becomes limited to a maximum of 1024 bytes. + */ +#define DTLS_FRAG_EXT_1024_ENABLED 2 + /* NCS specific socket options */ /** sockopt: enable sending data as part of exceptional events */ From 90b9f49129aa620734f4286858b12ac766215cfa Mon Sep 17 00:00:00 2001 From: Chaitanya Tata Date: Wed, 19 Nov 2025 11:36:53 +0530 Subject: [PATCH 02/47] [nrf fromlist] boards: shields: Add nRF7002 EB-II shield This is primarily intended for providing Wi-Fi capabilities for the nRF54L Series hosts using SPI. Upstream PR #: 100121 Signed-off-by: Chaitanya Tata --- boards/shields/nrf7002eb2/Kconfig.shield | 14 +++ .../boards/nrf54l15dk_nrf54l15_cpuapp.overlay | 54 +++++++++++ .../nrf54lm20dk_nrf54lm20a_cpuapp.overlay | 89 ++++++++++++++++++ boards/shields/nrf7002eb2/doc/index.rst | 72 ++++++++++++++ boards/shields/nrf7002eb2/doc/nrf7002eb2.jpg | Bin 0 -> 49527 bytes boards/shields/nrf7002eb2/nrf7002eb2.overlay | 24 +++++ .../nrf7002eb2/nrf7002eb2_coex.overlay | 15 +++ .../shields/nrf7002eb2/nrf7002eb2_common.dtsi | 20 ++++ .../nrf7002eb2/nrf7002eb2_common_5g.dtsi | 12 +++ .../nrf7002eb2/nrf7002eb2_gpio_pins_1.dtsi | 25 +++++ .../nrf7002eb2/nrf7002eb2_gpio_pins_2.dtsi | 15 +++ .../nrf7002eb2/nrf7002eb2_nrf7000.overlay | 24 +++++ .../nrf7002eb2/nrf7002eb2_nrf7001.overlay | 23 +++++ boards/shields/nrf7002eb2/shield.yml | 26 +++++ 14 files changed, 413 insertions(+) create mode 100644 boards/shields/nrf7002eb2/Kconfig.shield create mode 100644 boards/shields/nrf7002eb2/boards/nrf54l15dk_nrf54l15_cpuapp.overlay create mode 100644 boards/shields/nrf7002eb2/boards/nrf54lm20dk_nrf54lm20a_cpuapp.overlay create mode 100644 boards/shields/nrf7002eb2/doc/index.rst create mode 100644 boards/shields/nrf7002eb2/doc/nrf7002eb2.jpg create mode 100644 boards/shields/nrf7002eb2/nrf7002eb2.overlay create mode 100644 boards/shields/nrf7002eb2/nrf7002eb2_coex.overlay create mode 100644 boards/shields/nrf7002eb2/nrf7002eb2_common.dtsi create mode 100644 boards/shields/nrf7002eb2/nrf7002eb2_common_5g.dtsi create mode 100644 boards/shields/nrf7002eb2/nrf7002eb2_gpio_pins_1.dtsi create mode 100644 boards/shields/nrf7002eb2/nrf7002eb2_gpio_pins_2.dtsi create mode 100644 boards/shields/nrf7002eb2/nrf7002eb2_nrf7000.overlay create mode 100644 boards/shields/nrf7002eb2/nrf7002eb2_nrf7001.overlay create mode 100644 boards/shields/nrf7002eb2/shield.yml diff --git a/boards/shields/nrf7002eb2/Kconfig.shield b/boards/shields/nrf7002eb2/Kconfig.shield new file mode 100644 index 000000000000..3a6309b39148 --- /dev/null +++ b/boards/shields/nrf7002eb2/Kconfig.shield @@ -0,0 +1,14 @@ +# Copyright (c) 2025 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +config SHIELD_NRF7002EB2 + def_bool $(shields_list_contains,nrf7002eb2) + +config SHIELD_NRF7002EB2_NRF7001 + def_bool $(shields_list_contains,nrf7002eb2_nrf7001) + +config SHIELD_NRF7002EB2_NRF7000 + def_bool $(shields_list_contains,nrf7002eb2_nrf7000) + +config SHIELD_NRF7002EB2_COEX + def_bool $(shields_list_contains,nrf7002eb2_coex) 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 000000000000..912d806b5f47 --- /dev/null +++ b/boards/shields/nrf7002eb2/boards/nrf54l15dk_nrf54l15_cpuapp.overlay @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "../nrf7002eb2_gpio_pins_1.dtsi" + +/ { + chosen { + zephyr,wifi = &wlan0; + zephyr,console = &uart30; + zephyr,shell-uart = &uart30; + zephyr,uart-mcumgr = &uart30; + zephyr,bt-mon-uart = &uart30; + zephyr,bt-c2h-uart = &uart30; + }; +}; + +&pinctrl { + spi22_default: spi22_default { + group1 { + psels = , + , + ; + bias-pull-down; + }; + }; + + spi22_sleep: spi22_sleep { + group1 { + psels = , + , + ; + bias-pull-down; + low-power-enable; + }; + }; +}; + +&spi22 { + cs-gpios = <&gpio1 10 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&spi22_default>; + pinctrl-1 = <&spi22_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&uart20 { + status = "disabled"; +}; + +&uart30 { + status = "okay"; +}; diff --git a/boards/shields/nrf7002eb2/boards/nrf54lm20dk_nrf54lm20a_cpuapp.overlay b/boards/shields/nrf7002eb2/boards/nrf54lm20dk_nrf54lm20a_cpuapp.overlay new file mode 100644 index 000000000000..dcc759345bad --- /dev/null +++ b/boards/shields/nrf7002eb2/boards/nrf54lm20dk_nrf54lm20a_cpuapp.overlay @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "../nrf7002eb2_gpio_pins_2.dtsi" + +/ { + chosen { + zephyr,wifi = &wlan0; + zephyr,console = &uart30; + zephyr,shell-uart = &uart30; + zephyr,uart-mcumgr = &uart30; + zephyr,bt-mon-uart = &uart30; + zephyr,bt-c2h-uart = &uart30; + }; + + buttons { + /delete-node/ button_3; + }; + + aliases { + /delete-property/ sw3; + }; +}; + +&gpio3 { + status = "okay"; +}; + +&pinctrl { + spi22_default: spi22_default { + group1 { + psels = , + , + ; + bias-pull-down; + }; + }; + + spi22_sleep: spi22_sleep { + group1 { + psels = , + , + ; + bias-pull-down; + low-power-enable; + }; + }; + + uart30_default: uart30_default { + group1 { + psels = ; + }; + + group2 { + psels = ; + bias-pull-up; + }; + }; + + uart30_sleep: uart30_sleep { + group1 { + psels = , + ; + low-power-enable; + }; + }; +}; + +&spi22 { + status = "okay"; + cs-gpios = <&gpio3 2 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&spi22_default>; + pinctrl-1 = <&spi22_sleep>; + pinctrl-names = "default", "sleep"; +}; + +/* uart20 has pin conflicts with EB-II shield hence disabling that + * and enabling uart30 as console port. + */ +&uart20 { + status = "disabled"; +}; + +&uart30 { + status = "okay"; +}; diff --git a/boards/shields/nrf7002eb2/doc/index.rst b/boards/shields/nrf7002eb2/doc/index.rst new file mode 100644 index 000000000000..6fe033c21221 --- /dev/null +++ b/boards/shields/nrf7002eb2/doc/index.rst @@ -0,0 +1,72 @@ +.. _nrf7002eb2: + +nRF7002 EB II +############# + +Overview +******** + +The nRF7002 EB II is a versatile evaluation kit in the form of a thumbstick shield which connects to +compatible Nordic host boards using the Nordic edge-connector. + +The nRF7002 EB II unlocks low-power Wi-Fi 6 capabilities for your host device. It supports dual-band Wi-Fi +2.4GHz and 5GHz, and is based on the nRF7002 SoC. The shield also supports nRF7001 and nRF7000 SoCs +through variant overlays. +Seamlessly connect to Wi-Fi networks and leverage Wi-Fi-based locationing, enabling advanced +features such as SSID sniffing of local Wi-Fi hubs. + +.. figure:: nrf7002eb2.jpg + :alt: nRF7002 EB II + :align: center + + nRF7002 EB II + +Requirements +************ + +The nRF7002 EB II board is designed to fit straight into a Nordic edge-connector and uses SPI as the +communication interface. Any host board that supports the Nordic edge-connector can be used with +the nRF7002 EB II. + +Prerequisites +------------- + +The nRF70 driver requires firmware binary blobs for Wi-Fi operation. Run the command +below to retrieve those files. + +.. code-block:: console + + west update + west blobs fetch nrf_wifi + +Usage +***** + +The shield can be used in any application by setting ``--shield nrf7002eb2`` when invoking ``west build``. + +Shield Variants +*************** + +The nRF7002 EB II has several variants to support different nRF70 SoCs and features: + +- ``nrf7002eb2``: The default variant using the nRF7002 SoC. +- ``nrf7002eb2_nrf7001``: Variant using the nRF7001 SoC. +- ``nrf7002eb2_nrf7000``: Variant using the nRF7000 SoC. +- ``nrf7002eb2_coex``: Variant which includes the COEX pins. These pins are not routed to the + edge-connector on some boards, like earlier revisions of the Thingy53 than v1.0.0. + +SR Co-existence +*************** + +The nRF7002 EB II supports SR co-existence provided the host board supports it. The SR co-existence +pins are connected to the host board's GPIO pins. + +Two Kconfig options are available to enable SR co-existence: + +- :kconfig:option:`CONFIG_NRF70_SR_COEX`: Enables SR co-existence. +- :kconfig:option:`CONFIG_NRF70_SR_COEX_RF_SWITCH`: Control SR side RF switch. + +References +********** + +- `Developing with nRF7002 EB II `_ diff --git a/boards/shields/nrf7002eb2/doc/nrf7002eb2.jpg b/boards/shields/nrf7002eb2/doc/nrf7002eb2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..458093f1662041123b3afae0d83a1aa9e8564ec3 GIT binary patch literal 49527 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 z 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 bi4kLg0)r;$UqfF0JMo17SNgvAYwCXh^nayi literal 0 HcmV?d00001 diff --git a/boards/shields/nrf7002eb2/nrf7002eb2.overlay b/boards/shields/nrf7002eb2/nrf7002eb2.overlay new file mode 100644 index 000000000000..304814fa3040 --- /dev/null +++ b/boards/shields/nrf7002eb2/nrf7002eb2.overlay @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + chosen { + zephyr,wifi = &wlan0; + }; +}; + +&wifi_spi { + status = "okay"; + + nrf70: nrf7002-spi@0 { + compatible = "nordic,nrf7002-spi"; + status = "okay"; + + /* Include common nRF70 overlays */ + #include "nrf7002eb2_common.dtsi" + #include "nrf7002eb2_common_5g.dtsi" + }; +}; diff --git a/boards/shields/nrf7002eb2/nrf7002eb2_coex.overlay b/boards/shields/nrf7002eb2/nrf7002eb2_coex.overlay new file mode 100644 index 000000000000..36f352bc6a5e --- /dev/null +++ b/boards/shields/nrf7002eb2/nrf7002eb2_coex.overlay @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + nrf_radio_coex: coex { + compatible = "nordic,nrf7002-coex"; + status = "okay"; + status0-gpios = <&gpio0 3 GPIO_ACTIVE_HIGH>; + req-gpios = <&gpio0 4 GPIO_ACTIVE_HIGH>; + grant-gpios = <&gpio1 7 (GPIO_PULL_DOWN | GPIO_ACTIVE_LOW)>; + }; +}; diff --git a/boards/shields/nrf7002eb2/nrf7002eb2_common.dtsi b/boards/shields/nrf7002eb2/nrf7002eb2_common.dtsi new file mode 100644 index 000000000000..efe0703ac9dd --- /dev/null +++ b/boards/shields/nrf7002eb2/nrf7002eb2_common.dtsi @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include + +/* Common assignments for nRF70 EB-II shield */ +reg = <0>; +spi-max-frequency = ; + +/* Maximum TX power limits for 2.4 GHz */ +wifi-max-tx-pwr-2g-dsss = <21>; +wifi-max-tx-pwr-2g-mcs0 = <16>; +wifi-max-tx-pwr-2g-mcs7 = <16>; + +/* List of interfaces */ +wlan0: wlan0 { + compatible = "nordic,wlan"; +}; diff --git a/boards/shields/nrf7002eb2/nrf7002eb2_common_5g.dtsi b/boards/shields/nrf7002eb2/nrf7002eb2_common_5g.dtsi new file mode 100644 index 000000000000..05a472a32bd5 --- /dev/null +++ b/boards/shields/nrf7002eb2/nrf7002eb2_common_5g.dtsi @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor + * + * SPDX-License-Identifier: Apache-2.0 + */ + +wifi-max-tx-pwr-5g-low-mcs0 = <13>; +wifi-max-tx-pwr-5g-low-mcs7 = <13>; +wifi-max-tx-pwr-5g-mid-mcs0 = <13>; +wifi-max-tx-pwr-5g-mid-mcs7 = <13>; +wifi-max-tx-pwr-5g-high-mcs0 = <12>; +wifi-max-tx-pwr-5g-high-mcs7 = <12>; diff --git a/boards/shields/nrf7002eb2/nrf7002eb2_gpio_pins_1.dtsi b/boards/shields/nrf7002eb2/nrf7002eb2_gpio_pins_1.dtsi new file mode 100644 index 000000000000..95ad1ed44221 --- /dev/null +++ b/boards/shields/nrf7002eb2/nrf7002eb2_gpio_pins_1.dtsi @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + nrf_radio_coex: coex { + compatible = "nordic,nrf7002-coex"; + status = "disabled"; + status0-gpios = <&gpio1 8 GPIO_ACTIVE_HIGH>; + req-gpios = <&gpio1 9 GPIO_ACTIVE_HIGH>; + grant-gpios = <&gpio1 12 (GPIO_PULL_DOWN | GPIO_ACTIVE_LOW)>; + }; +}; + +&nrf70 { + iovdd-ctrl-gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>; + bucken-gpios = <&gpio1 4 GPIO_ACTIVE_HIGH>; + host-irq-gpios = <&gpio1 14 GPIO_ACTIVE_HIGH>; +}; + +&gpio1 { + status = "okay"; +}; diff --git a/boards/shields/nrf7002eb2/nrf7002eb2_gpio_pins_2.dtsi b/boards/shields/nrf7002eb2/nrf7002eb2_gpio_pins_2.dtsi new file mode 100644 index 000000000000..dc59273a0314 --- /dev/null +++ b/boards/shields/nrf7002eb2/nrf7002eb2_gpio_pins_2.dtsi @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&nrf70 { + iovdd-ctrl-gpios = <&gpio1 13 GPIO_ACTIVE_HIGH>; + bucken-gpios = <&gpio1 4 GPIO_ACTIVE_HIGH>; + host-irq-gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>; +}; + +&gpio1 { + status = "okay"; +}; diff --git a/boards/shields/nrf7002eb2/nrf7002eb2_nrf7000.overlay b/boards/shields/nrf7002eb2/nrf7002eb2_nrf7000.overlay new file mode 100644 index 000000000000..22b36e7b27fc --- /dev/null +++ b/boards/shields/nrf7002eb2/nrf7002eb2_nrf7000.overlay @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + chosen { + zephyr,wifi = &wlan0; + }; +}; + +&wifi_spi { + status = "okay"; + + nrf70: nrf7000-spi@0 { + compatible = "nordic,nrf7000-spi"; + status = "okay"; + + /* Include common nRF70 overlays */ + #include "nrf7002eb2_common.dtsi" + #include "nrf7002eb2_common_5g.dtsi" + }; +}; diff --git a/boards/shields/nrf7002eb2/nrf7002eb2_nrf7001.overlay b/boards/shields/nrf7002eb2/nrf7002eb2_nrf7001.overlay new file mode 100644 index 000000000000..9a9243063a98 --- /dev/null +++ b/boards/shields/nrf7002eb2/nrf7002eb2_nrf7001.overlay @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + chosen { + zephyr,wifi = &wlan0; + }; +}; + +&wifi_spi { + status = "okay"; + + nrf70: nrf7001-spi@0 { + compatible = "nordic,nrf7001-spi"; + status = "okay"; + + /* Include common nRF70 overlays */ + #include "nrf7002eb2_common.dtsi" + }; +}; diff --git a/boards/shields/nrf7002eb2/shield.yml b/boards/shields/nrf7002eb2/shield.yml new file mode 100644 index 000000000000..60349b0a3cb7 --- /dev/null +++ b/boards/shields/nrf7002eb2/shield.yml @@ -0,0 +1,26 @@ +# @Kconfig.shield + +shields: + - name: nrf7002eb2 + full_name: nRF7002 EB-II Shield + vendor: nordic + supported_features: + - wifi + + - name: nrf7002eb2_nrf7001 + full_name: nRF7002 EB-II Shield (nRF7001) + vendor: nordic + supported_features: + - wifi + + - name: nrf7002eb2_nrf7000 + full_name: nRF7002 EB-II Shield (nRF7000) + vendor: nordic + supported_features: + - wifi + + - name: nrf7002eb2_coex + full_name: nRF7002 EB-II Shield (SR Co-Existence) + vendor: nordic + supported_features: + - wifi From 046863e5c096ce64492c7ae627c46df9d2cb2c2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20B=C3=B8e?= Date: Wed, 19 Nov 2025 09:59:40 +0100 Subject: [PATCH 03/47] [nrf fromtree] soc: nordic: ironside: Add counter service MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add counter service. Signed-off-by: Sebastian Bøe (cherry picked from commit f324a1540f57c7ea229fb047c542de80c4149aea) --- soc/nordic/ironside/CMakeLists.txt | 1 + soc/nordic/ironside/Kconfig | 6 + soc/nordic/ironside/counter.c | 80 ++++++++++ .../ironside/include/nrf_ironside/counter.h | 143 ++++++++++++++++++ 4 files changed, 230 insertions(+) create mode 100644 soc/nordic/ironside/counter.c create mode 100644 soc/nordic/ironside/include/nrf_ironside/counter.h diff --git a/soc/nordic/ironside/CMakeLists.txt b/soc/nordic/ironside/CMakeLists.txt index ea7f68207a89..f1ae2114f2ec 100644 --- a/soc/nordic/ironside/CMakeLists.txt +++ b/soc/nordic/ironside/CMakeLists.txt @@ -10,3 +10,4 @@ zephyr_library_sources_ifdef(CONFIG_NRF_IRONSIDE_BOOTMODE_SERVICE bootmode.c) zephyr_library_sources_ifdef(CONFIG_NRF_IRONSIDE_TDD_SERVICE tdd.c) zephyr_library_sources_ifdef(CONFIG_NRF_IRONSIDE_UPDATE_SERVICE update.c) zephyr_library_sources_ifdef(CONFIG_NRF_IRONSIDE_DVFS_SERVICE dvfs.c) +zephyr_library_sources_ifdef(CONFIG_NRF_IRONSIDE_COUNTER_SERVICE counter.c) diff --git a/soc/nordic/ironside/Kconfig b/soc/nordic/ironside/Kconfig index 32c6ef38165f..ff1d01f99e81 100644 --- a/soc/nordic/ironside/Kconfig +++ b/soc/nordic/ironside/Kconfig @@ -69,6 +69,12 @@ config NRF_IRONSIDE_DVFS_SERVICE help Service used to handle DVFS operating point requests. +config NRF_IRONSIDE_COUNTER_SERVICE + bool "IronSide counter service" + select NRF_IRONSIDE_CALL + help + Service used to manage secure monotonic counters. + if NRF_IRONSIDE_DVFS_SERVICE config NRF_IRONSIDE_ABB_STATUSANA_CHECK_MAX_ATTEMPTS diff --git a/soc/nordic/ironside/counter.c b/soc/nordic/ironside/counter.c new file mode 100644 index 000000000000..b0c80ea1bd50 --- /dev/null +++ b/soc/nordic/ironside/counter.c @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +int ironside_counter_set(enum ironside_counter counter_id, uint32_t value) +{ + int err; + struct ironside_call_buf *const buf = ironside_call_alloc(); + + buf->id = IRONSIDE_CALL_ID_COUNTER_SET_V1; + buf->args[IRONSIDE_COUNTER_SET_SERVICE_COUNTER_ID_IDX] = (uint32_t)counter_id; + buf->args[IRONSIDE_COUNTER_SET_SERVICE_VALUE_IDX] = value; + + ironside_call_dispatch(buf); + + if (buf->status == IRONSIDE_CALL_STATUS_RSP_SUCCESS) { + err = buf->args[IRONSIDE_COUNTER_SET_SERVICE_RETCODE_IDX]; + } else { + err = buf->status; + } + + ironside_call_release(buf); + + return err; +} + +int ironside_counter_get(enum ironside_counter counter_id, uint32_t *value) +{ + int err; + struct ironside_call_buf *buf; + + if (value == NULL) { + return -IRONSIDE_COUNTER_ERROR_INVALID_PARAM; + } + + buf = ironside_call_alloc(); + + buf->id = IRONSIDE_CALL_ID_COUNTER_GET_V1; + buf->args[IRONSIDE_COUNTER_GET_SERVICE_COUNTER_ID_IDX] = (uint32_t)counter_id; + + ironside_call_dispatch(buf); + + if (buf->status == IRONSIDE_CALL_STATUS_RSP_SUCCESS) { + err = buf->args[IRONSIDE_COUNTER_GET_SERVICE_RETCODE_IDX]; + if (err == 0) { + *value = buf->args[IRONSIDE_COUNTER_GET_SERVICE_VALUE_IDX]; + } + } else { + err = buf->status; + } + + ironside_call_release(buf); + + return err; +} + +int ironside_counter_lock(enum ironside_counter counter_id) +{ + int err; + struct ironside_call_buf *const buf = ironside_call_alloc(); + + buf->id = IRONSIDE_CALL_ID_COUNTER_LOCK_V1; + buf->args[IRONSIDE_COUNTER_LOCK_SERVICE_COUNTER_ID_IDX] = (uint32_t)counter_id; + + ironside_call_dispatch(buf); + + if (buf->status == IRONSIDE_CALL_STATUS_RSP_SUCCESS) { + err = buf->args[IRONSIDE_COUNTER_LOCK_SERVICE_RETCODE_IDX]; + } else { + err = buf->status; + } + + ironside_call_release(buf); + + return err; +} diff --git a/soc/nordic/ironside/include/nrf_ironside/counter.h b/soc/nordic/ironside/include/nrf_ironside/counter.h new file mode 100644 index 000000000000..ca8f23fd264b --- /dev/null +++ b/soc/nordic/ironside/include/nrf_ironside/counter.h @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_SOC_NORDIC_IRONSIDE_INCLUDE_NRF_IRONSIDE_COUNTER_H_ +#define ZEPHYR_SOC_NORDIC_IRONSIDE_INCLUDE_NRF_IRONSIDE_COUNTER_H_ + +#include +#include +#include + +/** + * @name Counter service error codes. + * @{ + */ + +/** Counter value is lower than current value (monotonic violation). */ +#define IRONSIDE_COUNTER_ERROR_TOO_LOW (1) +/** Invalid counter ID. */ +#define IRONSIDE_COUNTER_ERROR_INVALID_ID (2) +/** Counter is locked and cannot be modified. */ +#define IRONSIDE_COUNTER_ERROR_LOCKED (3) +/** Invalid parameter. */ +#define IRONSIDE_COUNTER_ERROR_INVALID_PARAM (4) +/** Storage operation failed. */ +#define IRONSIDE_COUNTER_ERROR_STORAGE_FAILURE (5) + +/** + * @} + */ + +/** Maximum value for a counter */ +#define IRONSIDE_COUNTER_MAX_VALUE UINT32_MAX + +/** Number of counters */ +#define IRONSIDE_COUNTER_NUM 4 + +/** + * @brief Counter identifiers. + */ +enum ironside_counter { + IRONSIDE_COUNTER_0 = 0, + IRONSIDE_COUNTER_1, + IRONSIDE_COUNTER_2, + IRONSIDE_COUNTER_3 +}; + +/* IronSide call identifiers with implicit versions. */ +#define IRONSIDE_CALL_ID_COUNTER_SET_V1 6 +#define IRONSIDE_CALL_ID_COUNTER_GET_V1 7 +#define IRONSIDE_CALL_ID_COUNTER_LOCK_V1 8 + +enum { + IRONSIDE_COUNTER_SET_SERVICE_COUNTER_ID_IDX, + IRONSIDE_COUNTER_SET_SERVICE_VALUE_IDX, + /* The last enum value is reserved for the number of arguments */ + IRONSIDE_COUNTER_SET_NUM_ARGS +}; + +enum { + IRONSIDE_COUNTER_GET_SERVICE_COUNTER_ID_IDX, + /* The last enum value is reserved for the number of arguments */ + IRONSIDE_COUNTER_GET_NUM_ARGS +}; + +enum { + IRONSIDE_COUNTER_LOCK_SERVICE_COUNTER_ID_IDX, + /* The last enum value is reserved for the number of arguments */ + IRONSIDE_COUNTER_LOCK_NUM_ARGS +}; + +/* Index of the return code within the service buffer. */ +#define IRONSIDE_COUNTER_SET_SERVICE_RETCODE_IDX (0) +#define IRONSIDE_COUNTER_GET_SERVICE_RETCODE_IDX (0) +#define IRONSIDE_COUNTER_LOCK_SERVICE_RETCODE_IDX (0) + +/* Index of the value within the GET response buffer. */ +#define IRONSIDE_COUNTER_GET_SERVICE_VALUE_IDX (1) + +BUILD_ASSERT(IRONSIDE_COUNTER_SET_NUM_ARGS <= NRF_IRONSIDE_CALL_NUM_ARGS); +BUILD_ASSERT(IRONSIDE_COUNTER_GET_NUM_ARGS <= NRF_IRONSIDE_CALL_NUM_ARGS); +BUILD_ASSERT(IRONSIDE_COUNTER_LOCK_NUM_ARGS <= NRF_IRONSIDE_CALL_NUM_ARGS); + +/** + * @brief Set a counter value. + * + * This sets the specified counter to the given value. The counter is monotonic, + * so the new value must be greater than or equal to the current value. + * If the counter is locked, the operation will fail. + * + * @note Counters are automatically initialized to 0 during the first boot in LCS ROT. + * The monotonic constraint applies to all subsequent writes. + * + * @param counter_id Counter identifier. + * @param value New counter value. + * + * @retval 0 on success. + * @retval -IRONSIDE_COUNTER_ERROR_INVALID_ID if counter_id is invalid. + * @retval -IRONSIDE_COUNTER_ERROR_TOO_LOW if value is lower than current value. + * @retval -IRONSIDE_COUNTER_ERROR_LOCKED if counter is locked. + * @retval -IRONSIDE_COUNTER_ERROR_STORAGE_FAILURE if storage operation failed. + * @retval Positive error status if reported by IronSide call (see error codes in @ref call.h). + */ +int ironside_counter_set(enum ironside_counter counter_id, uint32_t value); + +/** + * @brief Get a counter value. + * + * This retrieves the current value of the specified counter. + * + * @note Counters are automatically initialized to 0 during the first boot in LCS ROT, + * so this function will always succeed for valid counter IDs. + * + * @param counter_id Counter identifier. + * @param value Pointer to store the counter value. + * + * @retval 0 on success. + * @retval -IRONSIDE_COUNTER_ERROR_INVALID_ID if counter_id is invalid. + * @retval -IRONSIDE_COUNTER_ERROR_INVALID_PARAM if value is NULL. + * @retval -IRONSIDE_COUNTER_ERROR_STORAGE_FAILURE if storage operation failed. + * @retval Positive error status if reported by IronSide call (see error codes in @ref call.h). + */ +int ironside_counter_get(enum ironside_counter counter_id, uint32_t *value); + +/** + * @brief Lock a counter for the current boot. + * + * This locks the specified counter, preventing any further modifications until the next reboot. + * The lock state is not persistent and will be cleared on reboot. + * + * @note The intended use case is for a bootloader to lock a counter before transferring control + * to the next boot stage, preventing that image from modifying the counter value. + * + * @param counter_id Counter identifier. + * + * @retval 0 on success. + * @retval -IRONSIDE_COUNTER_ERROR_INVALID_ID if counter_id is invalid. + * @retval Positive error status if reported by IronSide call (see error codes in @ref call.h). + */ +int ironside_counter_lock(enum ironside_counter counter_id); + +#endif /* ZEPHYR_SOC_NORDIC_IRONSIDE_INCLUDE_NRF_IRONSIDE_COUNTER_H_ */ From 2134a01f94c6cd591e21bd0651e94ad0ad2fc500 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20B=C3=B8e?= Date: Thu, 27 Nov 2025 08:57:55 +0100 Subject: [PATCH 04/47] [nrf fromtree] soc: ironside: counter_service: 2 compilation errors fixed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 2 compilation errors fixed in counter service. Signed-off-by: Sebastian Bøe (cherry picked from commit a053b971442d0cabddf4a8e70e8f16eeef0a7781) --- soc/nordic/ironside/counter.c | 1 + soc/nordic/ironside/include/nrf_ironside/counter.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/soc/nordic/ironside/counter.c b/soc/nordic/ironside/counter.c index b0c80ea1bd50..b4506621851d 100644 --- a/soc/nordic/ironside/counter.c +++ b/soc/nordic/ironside/counter.c @@ -3,6 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +#include #include #include diff --git a/soc/nordic/ironside/include/nrf_ironside/counter.h b/soc/nordic/ironside/include/nrf_ironside/counter.h index ca8f23fd264b..37866c43a684 100644 --- a/soc/nordic/ironside/include/nrf_ironside/counter.h +++ b/soc/nordic/ironside/include/nrf_ironside/counter.h @@ -7,7 +7,7 @@ #define ZEPHYR_SOC_NORDIC_IRONSIDE_INCLUDE_NRF_IRONSIDE_COUNTER_H_ #include -#include +#include #include /** From 8d388bb6ccd2e70d8e92975a8d945acd265acaa3 Mon Sep 17 00:00:00 2001 From: Georgios Vasilakis Date: Tue, 30 Sep 2025 10:48:18 +0200 Subject: [PATCH 05/47] [nrf noup] soc: nordic: Support TF-M for poweroff Enable going to system off when the TF-M API is available. This calls the relevant TF-M platform API to enter system off mode using TF-M. This is a noup because the TF-M service for system off is currently located in sdk-nrf only. This is meant to be a short lived commit, work is already ongoing to upstream an official TF-M system off solution that can be be added to upstream Zephyr. Signed-off-by: Georgios Vasilakis --- soc/nordic/common/poweroff.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/soc/nordic/common/poweroff.c b/soc/nordic/common/poweroff.c index ebce3aae77cd..f659d31997f2 100644 --- a/soc/nordic/common/poweroff.c +++ b/soc/nordic/common/poweroff.c @@ -7,7 +7,9 @@ #include #include -#if defined(CONFIG_SOC_SERIES_NRF51X) || defined(CONFIG_SOC_SERIES_NRF52X) +#if defined(CONFIG_TFM_NRF_SYSTEM_OFF_SERVICE) +#include "tfm_platform_api.h" +#elif defined(CONFIG_SOC_SERIES_NRF51X) || defined(CONFIG_SOC_SERIES_NRF52X) #include #elif defined(CONFIG_SOC_SERIES_NRF54HX) #include @@ -30,6 +32,10 @@ void z_sys_poweroff(void) { +#if defined(CONFIG_TFM_NRF_SYSTEM_OFF_SERVICE) + tfm_platform_system_off(); +#else + #if defined(CONFIG_HAS_NORDIC_RAM_CTRL) uint8_t *ram_start; size_t ram_size; @@ -78,5 +84,7 @@ void z_sys_poweroff(void) nrf_regulators_system_off(NRF_REGULATORS); #endif +#endif /* CONFIG_TFM_NRF_SYSTEM_OFF_SERVICE */ + CODE_UNREACHABLE; } From c9a873ba578b982c093dfbe1e9c4371cd51dffc7 Mon Sep 17 00:00:00 2001 From: Jamie McCrae Date: Fri, 21 Nov 2025 08:26:32 +0000 Subject: [PATCH 06/47] [nrf fromtree] mgmt: mcumgr: grp: img_mgmt: Fix detecting where a slot resides Fixes an issue introduced in commit 32615695ad1b96670be8f2a38a8a9fd27e2d8ae5 which wrongly did not check what the residing device was on before determining if a slot was part of a partition area Signed-off-by: Jamie McCrae (cherry picked from commit ae2b4a44dc0d675aa90d62b46a52d04374ba8419) --- subsys/dfu/img_util/flash_img.c | 8 +++++++- subsys/mgmt/mcumgr/grp/img_mgmt/src/img_mgmt.c | 8 +++++++- tests/subsys/dfu/img_util/src/main.c | 7 +++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/subsys/dfu/img_util/flash_img.c b/subsys/dfu/img_util/flash_img.c index 24e95a8dc1a9..152aaabe17ae 100644 --- a/subsys/dfu/img_util/flash_img.c +++ b/subsys/dfu/img_util/flash_img.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -23,11 +24,16 @@ LOG_MODULE_REGISTER(flash_img, CONFIG_IMG_MANAGER_LOG_LEVEL); #include #endif +#define FIXED_PARTITION_GET_FLASH_NODE(node_id) \ + COND_CODE_1(DT_NODE_HAS_COMPAT(DT_PARENT(node_id), fixed_subpartitions), \ + (DT_PARENT(DT_GPARENT(node_id))), (DT_GPARENT(node_id))) + #define FIXED_PARTITION_IS_RUNNING_APP_PARTITION(label) \ + DT_SAME_NODE(FIXED_PARTITION_GET_FLASH_NODE(DT_CHOSEN(zephyr_code_partition)), \ + FIXED_PARTITION_GET_FLASH_NODE(DT_NODELABEL(label))) && \ (FIXED_PARTITION_OFFSET(label) <= CONFIG_FLASH_LOAD_OFFSET && \ FIXED_PARTITION_OFFSET(label) + FIXED_PARTITION_SIZE(label) > CONFIG_FLASH_LOAD_OFFSET) -#include #if defined(CONFIG_TRUSTED_EXECUTION_NONSECURE) && (CONFIG_TFM_MCUBOOT_IMAGE_NUMBER == 2) #define UPLOAD_FLASH_AREA_LABEL slot1_ns_partition #else diff --git a/subsys/mgmt/mcumgr/grp/img_mgmt/src/img_mgmt.c b/subsys/mgmt/mcumgr/grp/img_mgmt/src/img_mgmt.c index a2bf006b9474..5e52a9fa5c15 100644 --- a/subsys/mgmt/mcumgr/grp/img_mgmt/src/img_mgmt.c +++ b/subsys/mgmt/mcumgr/grp/img_mgmt/src/img_mgmt.c @@ -1,6 +1,6 @@ /* * Copyright (c) 2018-2021 mcumgr authors - * Copyright (c) 2022-2024 Nordic Semiconductor ASA + * Copyright (c) 2022-2025 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ @@ -48,7 +48,13 @@ to be able to figure out application running slot. #endif +#define FIXED_PARTITION_GET_FLASH_NODE(node_id) \ + COND_CODE_1(DT_NODE_HAS_COMPAT(DT_PARENT(node_id), fixed_subpartitions), \ + (DT_PARENT(DT_GPARENT(node_id))), (DT_GPARENT(node_id))) + #define FIXED_PARTITION_IS_RUNNING_APP_PARTITION(label) \ + DT_SAME_NODE(FIXED_PARTITION_GET_FLASH_NODE(DT_CHOSEN(zephyr_code_partition)), \ + FIXED_PARTITION_GET_FLASH_NODE(DT_NODELABEL(label))) && \ (FIXED_PARTITION_OFFSET(label) <= CONFIG_FLASH_LOAD_OFFSET && \ FIXED_PARTITION_OFFSET(label) + FIXED_PARTITION_SIZE(label) > CONFIG_FLASH_LOAD_OFFSET) diff --git a/tests/subsys/dfu/img_util/src/main.c b/tests/subsys/dfu/img_util/src/main.c index 33956be6251e..013498f49bd3 100644 --- a/tests/subsys/dfu/img_util/src/main.c +++ b/tests/subsys/dfu/img_util/src/main.c @@ -6,13 +6,20 @@ */ #include +#include #include #include #define SLOT0_PARTITION slot0_partition #define SLOT1_PARTITION slot1_partition +#define FIXED_PARTITION_GET_FLASH_NODE(node_id) \ + COND_CODE_1(DT_NODE_HAS_COMPAT(DT_PARENT(node_id), fixed_subpartitions), \ + (DT_PARENT(DT_GPARENT(node_id))), (DT_GPARENT(node_id))) + #define FIXED_PARTITION_IS_RUNNING_APP_PARTITION(label) \ + DT_SAME_NODE(FIXED_PARTITION_GET_FLASH_NODE(DT_CHOSEN(zephyr_code_partition)), \ + FIXED_PARTITION_GET_FLASH_NODE(DT_NODELABEL(label))) && \ (FIXED_PARTITION_OFFSET(label) <= CONFIG_FLASH_LOAD_OFFSET && \ FIXED_PARTITION_OFFSET(label) + FIXED_PARTITION_SIZE(label) > CONFIG_FLASH_LOAD_OFFSET) From 9354d134ff28dfabd0b3e39baa1dbbec3456f060 Mon Sep 17 00:00:00 2001 From: Tomasz Chyrowicz Date: Fri, 21 Nov 2025 12:05:44 +0100 Subject: [PATCH 07/47] [nrf fromlist] flash_map: Add a macro to fetch controller ID Add a macro that allows to get the node identifier of the flash controller the area/partition resides on. Upstream PR #: 99800 Signed-off-by: Tomasz Chyrowicz --- include/zephyr/storage/flash_map.h | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/include/zephyr/storage/flash_map.h b/include/zephyr/storage/flash_map.h index 9dc6bd91438f..558a0a0b7e72 100644 --- a/include/zephyr/storage/flash_map.h +++ b/include/zephyr/storage/flash_map.h @@ -19,7 +19,7 @@ * * @defgroup flash_area_api flash area Interface * @since 1.11 - * @version 1.0.0 + * @version 1.1.0 * @ingroup storage_apis * @{ */ @@ -469,6 +469,32 @@ uint8_t flash_area_erased_val(const struct flash_area *fa); (DT_MTD_FROM_FIXED_SUBPARTITION(node)), \ (DT_MTD_FROM_FIXED_PARTITION(node)))) +/** + * Get the node identifier of the flash controller the area/partition resides on + * + * @param label DTS node label of a partition + * + * @return Pointer to a device. + */ +#define FIXED_PARTITION_MTD(label) \ + COND_CODE_1( \ + DT_FIXED_SUBPARTITION_EXISTS(DT_NODELABEL(label)), \ + (DT_MTD_FROM_FIXED_SUBPARTITION(DT_NODELABEL(label))), \ + (DT_MTD_FROM_FIXED_PARTITION(DT_NODELABEL(label)))) + +/** + * Get the node identifier of the flash controller the area/partition resides on + * + * @param node DTS node of a partition + * + * @return Pointer to a device. + */ +#define FIXED_PARTITION_NODE_MTD(node) \ + COND_CODE_1( \ + DT_FIXED_SUBPARTITION_EXISTS(node), \ + (DT_MTD_FROM_FIXED_SUBPARTITION(node)), \ + (DT_MTD_FROM_FIXED_PARTITION(node))) + /** * Get pointer to flash_area object by partition label * From 67f2c2fb8c7de6cd64f30517f55d71336b797fe3 Mon Sep 17 00:00:00 2001 From: Tomasz Chyrowicz Date: Thu, 20 Nov 2025 15:18:09 +0100 Subject: [PATCH 08/47] [nrf fromlist] soc: Use absolute address in active partition Use absolute addresses while determining a running application partition. Upstream PR #: 99800 Signed-off-by: Tomasz Chyrowicz --- soc/nordic/nrf54h/soc.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/soc/nordic/nrf54h/soc.c b/soc/nordic/nrf54h/soc.c index c291767d0906..7886689dbb8d 100644 --- a/soc/nordic/nrf54h/soc.c +++ b/soc/nordic/nrf54h/soc.c @@ -41,14 +41,20 @@ LOG_MODULE_REGISTER(soc, CONFIG_SOC_LOG_LEVEL); DT_REG_ADDR(COND_CODE_1(DT_FIXED_SUBPARTITION_EXISTS(DT_NODELABEL(label)), \ (DT_GPARENT(DT_PARENT(DT_NODELABEL(label)))), \ (DT_GPARENT(DT_NODELABEL(label)))))) +#define FIXED_PARTITION_NODE_MTD(node) \ + COND_CODE_1( \ + DT_FIXED_SUBPARTITION_EXISTS(node), \ + (DT_MTD_FROM_FIXED_SUBPARTITION(node)), \ + (DT_MTD_FROM_FIXED_PARTITION(node))) #ifdef CONFIG_USE_DT_CODE_PARTITION #define FLASH_LOAD_OFFSET DT_REG_ADDR(DT_CHOSEN(zephyr_code_partition)) #elif defined(CONFIG_FLASH_LOAD_OFFSET) #define FLASH_LOAD_OFFSET CONFIG_FLASH_LOAD_OFFSET #endif - -#define PARTITION_IS_RUNNING_APP_PARTITION(label) \ +#define FIXED_PARTITION_IS_RUNNING_APP_PARTITION(label) \ + DT_SAME_NODE(FIXED_PARTITION_NODE_MTD(DT_CHOSEN(zephyr_code_partition)), \ + FIXED_PARTITION_NODE_MTD(DT_NODELABEL(label))) && \ (DT_REG_ADDR(DT_NODELABEL(label)) <= FLASH_LOAD_OFFSET && \ DT_REG_ADDR(DT_NODELABEL(label)) + DT_REG_SIZE(DT_NODELABEL(label)) > FLASH_LOAD_OFFSET) @@ -210,7 +216,7 @@ void soc_late_init_hook(void) void *radiocore_address = NULL; #if DT_NODE_EXISTS(DT_NODELABEL(cpurad_slot1_partition)) - if (PARTITION_IS_RUNNING_APP_PARTITION(cpuapp_slot1_partition)) { + if (FIXED_PARTITION_IS_RUNNING_APP_PARTITION(cpuapp_slot1_partition)) { radiocore_address = (void *)(FIXED_PARTITION_ADDRESS(cpurad_slot1_partition) + CONFIG_ROM_START_OFFSET); } else { From e7eb4253db07dd54db40b7e397377a0e1613f657 Mon Sep 17 00:00:00 2001 From: Tomasz Chyrowicz Date: Thu, 20 Nov 2025 15:19:09 +0100 Subject: [PATCH 09/47] [nrf fromlist] img_util: Use absolute address in active partition Use absolute addresses while determining a running application partition. Upstream PR #: 99800 Signed-off-by: Tomasz Chyrowicz --- subsys/dfu/img_util/flash_img.c | 8 ++------ tests/subsys/dfu/img_util/src/main.c | 8 ++------ 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/subsys/dfu/img_util/flash_img.c b/subsys/dfu/img_util/flash_img.c index 152aaabe17ae..d3c9749401b2 100644 --- a/subsys/dfu/img_util/flash_img.c +++ b/subsys/dfu/img_util/flash_img.c @@ -24,13 +24,9 @@ LOG_MODULE_REGISTER(flash_img, CONFIG_IMG_MANAGER_LOG_LEVEL); #include #endif -#define FIXED_PARTITION_GET_FLASH_NODE(node_id) \ - COND_CODE_1(DT_NODE_HAS_COMPAT(DT_PARENT(node_id), fixed_subpartitions), \ - (DT_PARENT(DT_GPARENT(node_id))), (DT_GPARENT(node_id))) - #define FIXED_PARTITION_IS_RUNNING_APP_PARTITION(label) \ - DT_SAME_NODE(FIXED_PARTITION_GET_FLASH_NODE(DT_CHOSEN(zephyr_code_partition)), \ - FIXED_PARTITION_GET_FLASH_NODE(DT_NODELABEL(label))) && \ + DT_SAME_NODE(FIXED_PARTITION_NODE_MTD(DT_CHOSEN(zephyr_code_partition)), \ + FIXED_PARTITION_MTD(label)) && \ (FIXED_PARTITION_OFFSET(label) <= CONFIG_FLASH_LOAD_OFFSET && \ FIXED_PARTITION_OFFSET(label) + FIXED_PARTITION_SIZE(label) > CONFIG_FLASH_LOAD_OFFSET) diff --git a/tests/subsys/dfu/img_util/src/main.c b/tests/subsys/dfu/img_util/src/main.c index 013498f49bd3..c0b5d96daeb1 100644 --- a/tests/subsys/dfu/img_util/src/main.c +++ b/tests/subsys/dfu/img_util/src/main.c @@ -13,13 +13,9 @@ #define SLOT0_PARTITION slot0_partition #define SLOT1_PARTITION slot1_partition -#define FIXED_PARTITION_GET_FLASH_NODE(node_id) \ - COND_CODE_1(DT_NODE_HAS_COMPAT(DT_PARENT(node_id), fixed_subpartitions), \ - (DT_PARENT(DT_GPARENT(node_id))), (DT_GPARENT(node_id))) - #define FIXED_PARTITION_IS_RUNNING_APP_PARTITION(label) \ - DT_SAME_NODE(FIXED_PARTITION_GET_FLASH_NODE(DT_CHOSEN(zephyr_code_partition)), \ - FIXED_PARTITION_GET_FLASH_NODE(DT_NODELABEL(label))) && \ + DT_SAME_NODE(FIXED_PARTITION_NODE_MTD(DT_CHOSEN(zephyr_code_partition)), \ + FIXED_PARTITION_MTD(label)) && \ (FIXED_PARTITION_OFFSET(label) <= CONFIG_FLASH_LOAD_OFFSET && \ FIXED_PARTITION_OFFSET(label) + FIXED_PARTITION_SIZE(label) > CONFIG_FLASH_LOAD_OFFSET) From 81e9044a00d6a87e254a5e53ce1a241873faeb73 Mon Sep 17 00:00:00 2001 From: Tomasz Chyrowicz Date: Thu, 20 Nov 2025 15:20:23 +0100 Subject: [PATCH 10/47] [nrf fromlist] img_mgmt: Use absolute address in active partition Use absolute addresses while determining a running application partition. Upstream PR #: 99800 Signed-off-by: Tomasz Chyrowicz --- subsys/mgmt/mcumgr/grp/img_mgmt/src/img_mgmt.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/subsys/mgmt/mcumgr/grp/img_mgmt/src/img_mgmt.c b/subsys/mgmt/mcumgr/grp/img_mgmt/src/img_mgmt.c index 5e52a9fa5c15..97fc430e374a 100644 --- a/subsys/mgmt/mcumgr/grp/img_mgmt/src/img_mgmt.c +++ b/subsys/mgmt/mcumgr/grp/img_mgmt/src/img_mgmt.c @@ -48,13 +48,9 @@ to be able to figure out application running slot. #endif -#define FIXED_PARTITION_GET_FLASH_NODE(node_id) \ - COND_CODE_1(DT_NODE_HAS_COMPAT(DT_PARENT(node_id), fixed_subpartitions), \ - (DT_PARENT(DT_GPARENT(node_id))), (DT_GPARENT(node_id))) - #define FIXED_PARTITION_IS_RUNNING_APP_PARTITION(label) \ - DT_SAME_NODE(FIXED_PARTITION_GET_FLASH_NODE(DT_CHOSEN(zephyr_code_partition)), \ - FIXED_PARTITION_GET_FLASH_NODE(DT_NODELABEL(label))) && \ + DT_SAME_NODE(FIXED_PARTITION_NODE_MTD(DT_CHOSEN(zephyr_code_partition)), \ + FIXED_PARTITION_MTD(label)) && \ (FIXED_PARTITION_OFFSET(label) <= CONFIG_FLASH_LOAD_OFFSET && \ FIXED_PARTITION_OFFSET(label) + FIXED_PARTITION_SIZE(label) > CONFIG_FLASH_LOAD_OFFSET) From a88ded230ab70cba8668e1c3ab0ac36e6bc4cc1e Mon Sep 17 00:00:00 2001 From: Pieter De Gendt Date: Thu, 4 Sep 2025 11:33:06 +0200 Subject: [PATCH 11/47] [nrf fromtree] modules: hostap: Support bgscan Add configuration options for background scanning (bgscan) in wpa_supplicant. Signed-off-by: Pieter De Gendt (cherry picked from commit 93c4dbd2e0a58ddec27a2ca3beee53e97c62ea65) --- include/zephyr/net/wifi_mgmt.h | 43 ++++++++++++++++++++++ modules/hostap/CMakeLists.txt | 20 +++++++++++ modules/hostap/Kconfig | 30 ++++++++++++++++ modules/hostap/src/supp_api.c | 65 ++++++++++++++++++++++++++++++++++ modules/hostap/src/supp_api.h | 11 ++++++ modules/hostap/src/supp_main.c | 3 ++ subsys/net/l2/wifi/wifi_mgmt.c | 25 +++++++++++++ 7 files changed, 197 insertions(+) diff --git a/include/zephyr/net/wifi_mgmt.h b/include/zephyr/net/wifi_mgmt.h index 703f634209d2..817a4fcd6fb6 100644 --- a/include/zephyr/net/wifi_mgmt.h +++ b/include/zephyr/net/wifi_mgmt.h @@ -137,6 +137,8 @@ enum net_request_wifi_cmd { NET_REQUEST_WIFI_CMD_AP_WPS_CONFIG, /** Configure BSS maximum idle period */ NET_REQUEST_WIFI_CMD_BSS_MAX_IDLE_PERIOD, + /** Configure background scanning */ + NET_REQUEST_WIFI_CMD_BGSCAN, /** @cond INTERNAL_HIDDEN */ NET_REQUEST_WIFI_CMD_MAX /** @endcond */ @@ -332,6 +334,11 @@ NET_MGMT_DEFINE_REQUEST_HANDLER(NET_REQUEST_WIFI_NEIGHBOR_REP_COMPLETE); NET_MGMT_DEFINE_REQUEST_HANDLER(NET_REQUEST_WIFI_BSS_MAX_IDLE_PERIOD); +#define NET_REQUEST_WIFI_BGSCAN \ + (NET_WIFI_BASE | NET_REQUEST_WIFI_CMD_BGSCAN) + +NET_MGMT_DEFINE_REQUEST_HANDLER(NET_REQUEST_WIFI_BGSCAN); + /** @cond INTERNAL_HIDDEN */ enum { @@ -1403,6 +1410,32 @@ enum wifi_sap_iface_state { WIFI_SAP_IFACE_ENABLED }; +#if defined(CONFIG_WIFI_NM_WPA_SUPPLICANT_BGSCAN) || defined(__DOXYGEN__) +/** @brief Wi-Fi background scan implementation */ +enum wifi_bgscan_type { + /** None, background scan is disabled */ + WIFI_BGSCAN_NONE = 0, + /** Simple, periodic scan based on signal strength */ + WIFI_BGSCAN_SIMPLE, + /** Learn channels used by the network (experimental) */ + WIFI_BGSCAN_LEARN, +}; + +/** @brief Wi-Fi background scan parameters */ +struct wifi_bgscan_params { + /** The type of background scanning */ + enum wifi_bgscan_type type; + /** Short scan interval in seconds */ + uint16_t short_interval; + /** Long scan interval in seconds */ + uint16_t long_interval; + /** Signal strength threshold in dBm */ + int8_t rssi_threshold; + /** Number of BSS Transition Management (BTM) queries */ + uint16_t btm_queries; +}; +#endif + /* Extended Capabilities */ enum wifi_ext_capab { WIFI_EXT_CAPAB_20_40_COEX = 0, @@ -1742,6 +1775,16 @@ struct wifi_mgmt_ops { */ int (*set_bss_max_idle_period)(const struct device *dev, unsigned short bss_max_idle_period); +#if defined(CONFIG_WIFI_NM_WPA_SUPPLICANT_BGSCAN) || defined(__DOXYGEN__) + /** Configure background scanning + * + * @param dev Pointer to the device structure for the driver instance. + * @param params Background scanning configuration parameters + * + * @return 0 if ok, < 0 if error + */ + int (*set_bgscan)(const struct device *dev, struct wifi_bgscan_params *params); +#endif }; /** Wi-Fi management offload API */ diff --git a/modules/hostap/CMakeLists.txt b/modules/hostap/CMakeLists.txt index f3233bb2cfbb..106d31b8cca4 100644 --- a/modules/hostap/CMakeLists.txt +++ b/modules/hostap/CMakeLists.txt @@ -78,6 +78,16 @@ zephyr_library_compile_definitions_ifdef(CONFIG_WIFI_NM_WPA_SUPPLICANT_WNM CONFIG_WNM ) +zephyr_library_compile_definitions_ifdef(CONFIG_WIFI_NM_WPA_SUPPLICANT_BGSCAN + CONFIG_BGSCAN +) +zephyr_library_compile_definitions_ifdef(CONFIG_WIFI_NM_WPA_SUPPLICANT_BGSCAN_SIMPLE + CONFIG_BGSCAN_SIMPLE +) +zephyr_library_compile_definitions_ifdef(CONFIG_WIFI_NM_WPA_SUPPLICANT_BGSCAN_LEARN + CONFIG_BGSCAN_LEARN +) + zephyr_library_include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/src ${HOSTAP_BASE}/ @@ -156,6 +166,16 @@ zephyr_library_sources_ifdef(CONFIG_WIFI_NM_WPA_SUPPLICANT_WNM ${WIFI_NM_WPA_SUPPLICANT_BASE}/wnm_sta.c ) +zephyr_library_sources_ifdef(CONFIG_WIFI_NM_WPA_SUPPLICANT_BGSCAN + ${WIFI_NM_WPA_SUPPLICANT_BASE}/bgscan.c +) +zephyr_library_sources_ifdef(CONFIG_WIFI_NM_WPA_SUPPLICANT_BGSCAN_SIMPLE + ${WIFI_NM_WPA_SUPPLICANT_BASE}/bgscan_simple.c +) +zephyr_library_sources_ifdef(CONFIG_WIFI_NM_WPA_SUPPLICANT_BGSCAN_LEARN + ${WIFI_NM_WPA_SUPPLICANT_BASE}/bgscan_learn.c +) + zephyr_library_sources_ifdef(CONFIG_WPA_CLI src/wpa_cli.c ) diff --git a/modules/hostap/Kconfig b/modules/hostap/Kconfig index 7799239a2840..9eac239daa7d 100644 --- a/modules/hostap/Kconfig +++ b/modules/hostap/Kconfig @@ -451,6 +451,27 @@ config WIFI_NM_WPA_SUPPLICANT_SKIP_DHCP_ON_ROAMING needs to get new IP address after roaming to new AP. Disable this to keep DHCP after roaming. +config WIFI_NM_WPA_SUPPLICANT_BGSCAN + bool "Background scanning (for legacy roaming), recommended if 802.11r is not supported" + depends on WIFI_NM_WPA_SUPPLICANT_WNM + depends on !WIFI_NM_WPA_SUPPLICANT_ROAMING + +if WIFI_NM_WPA_SUPPLICANT_BGSCAN + +config WIFI_NM_WPA_SUPPLICANT_BGSCAN_SIMPLE + bool "Simple background scanning" + default y + help + Periodic background scans based on signal strength. + +config WIFI_NM_WPA_SUPPLICANT_BGSCAN_LEARN + bool "Learning" + help + Learn channels used by the network and try to avoid + background scans on other channels (experimental). + +endif # WIFI_NM_WPA_SUPPLICANT_BGSCAN + # Create hidden config options that are used in hostap. This way we do not need # to mark them as allowed for CI checks, and also someone else cannot use the # same name options. @@ -482,6 +503,15 @@ config NO_RANDOM_POOL config WNM bool +config BGSCAN + bool + +config BGSCAN_SIMPLE + bool + +config BGSCAN_LEARN + bool + config NO_WPA bool default y if WIFI_NM_WPA_SUPPLICANT_CRYPTO_NONE diff --git a/modules/hostap/src/supp_api.c b/modules/hostap/src/supp_api.c index 1b3f19b4c1c1..11c00de06b37 100644 --- a/modules/hostap/src/supp_api.c +++ b/modules/hostap/src/supp_api.c @@ -1916,6 +1916,71 @@ int supplicant_set_bss_max_idle_period(const struct device *dev, return wifi_mgmt_api->set_bss_max_idle_period(dev, bss_max_idle_period); } +#ifdef CONFIG_WIFI_NM_WPA_SUPPLICANT_BGSCAN +int supplicant_set_bgscan(const struct device *dev, struct wifi_bgscan_params *params) +{ + struct wpa_supplicant *wpa_s; + struct wpa_ssid *ssid; + int ret = -1; + + k_mutex_lock(&wpa_supplicant_mutex, K_FOREVER); + + wpa_s = get_wpa_s_handle(dev); + if (wpa_s == NULL) { + wpa_printf(MSG_ERROR, "Interface %s not found", dev->name); + goto out; + } + + ssid = wpa_s->current_ssid; + if (ssid == NULL) { + wpa_printf(MSG_ERROR, "SSID for %s not found", dev->name); + goto out; + } + + switch (params->type) { + case WIFI_BGSCAN_SIMPLE: + if (!IS_ENABLED(CONFIG_WIFI_NM_WPA_SUPPLICANT_BGSCAN_SIMPLE)) { + wpa_printf(MSG_ERROR, "Invalid bgscan type, enable " + "CONFIG_WIFI_NM_WPA_SUPPLICANT_BGSCAN_SIMPLE"); + ret = -ENOTSUP; + goto out; + } + if (!wpa_cli_cmd_v("set_network %d bgscan \"simple:%d:%d:%d:%d\"", ssid->id, + params->short_interval, params->rssi_threshold, + params->long_interval, params->btm_queries)) { + goto out; + } + break; + case WIFI_BGSCAN_LEARN: + if (!IS_ENABLED(CONFIG_WIFI_NM_WPA_SUPPLICANT_BGSCAN_LEARN)) { + wpa_printf(MSG_ERROR, "Invalid bgscan type, enable " + "CONFIG_WIFI_NM_WPA_SUPPLICANT_BGSCAN_LEARN"); + ret = -ENOTSUP; + goto out; + } + if (!wpa_cli_cmd_v("set_network %d bgscan \"learn:%d:%d:%d\"", ssid->id, + params->short_interval, params->rssi_threshold, + params->long_interval)) { + goto out; + } + break; + case WIFI_BGSCAN_NONE: + default: + if (!wpa_cli_cmd_v("set_network %d bgscan \"\"", ssid->id)) { + goto out; + } + break; + } + + ret = 0; + +out: + k_mutex_unlock(&wpa_supplicant_mutex); + + return ret; +} +#endif + #ifdef CONFIG_WIFI_NM_WPA_SUPPLICANT_WNM int supplicant_btm_query(const struct device *dev, uint8_t reason) { diff --git a/modules/hostap/src/supp_api.h b/modules/hostap/src/supp_api.h index 1ef63474f3fb..8ab49f177e70 100644 --- a/modules/hostap/src/supp_api.h +++ b/modules/hostap/src/supp_api.h @@ -321,6 +321,17 @@ int supplicant_wps_config(const struct device *dev, struct wifi_wps_config_param */ int supplicant_set_bss_max_idle_period(const struct device *dev, unsigned short bss_max_idle_period); + +#if defined(CONFIG_WIFI_NM_WPA_SUPPLICANT_BGSCAN) || defined(__DOXYGEN__) +/** @ Set Wi-Fi background scanning parameters + * + * @param dev Wi-Fi interface handle to use + * @param params bgscan parameters + * @return 0 for OK; -1 for ERROR + */ +int supplicant_set_bgscan(const struct device *dev, struct wifi_bgscan_params *params); +#endif + #ifdef CONFIG_AP int set_ap_bandwidth(const struct device *dev, enum wifi_frequency_bandwidths bandwidth); diff --git a/modules/hostap/src/supp_main.c b/modules/hostap/src/supp_main.c index 74d195386b1f..d1f8c360cb50 100644 --- a/modules/hostap/src/supp_main.c +++ b/modules/hostap/src/supp_main.c @@ -85,6 +85,9 @@ static const struct wifi_mgmt_ops mgmt_ops = { .get_conn_params = supplicant_get_wifi_conn_params, .wps_config = supplicant_wps_config, .set_bss_max_idle_period = supplicant_set_bss_max_idle_period, +#ifdef CONFIG_WIFI_NM_WPA_SUPPLICANT_BGSCAN + .set_bgscan = supplicant_set_bgscan, +#endif /* CONFIG_WIFI_NM_WPA_SUPPLICANT_BGSCAN */ #ifdef CONFIG_AP .ap_enable = supplicant_ap_enable, .ap_disable = supplicant_ap_disable, diff --git a/subsys/net/l2/wifi/wifi_mgmt.c b/subsys/net/l2/wifi/wifi_mgmt.c index a962fe317664..6a97f8c0e61c 100644 --- a/subsys/net/l2/wifi/wifi_mgmt.c +++ b/subsys/net/l2/wifi/wifi_mgmt.c @@ -1449,6 +1449,31 @@ static int wifi_set_bss_max_idle_period(uint64_t mgmt_request, struct net_if *if NET_MGMT_REGISTER_REQUEST_HANDLER(NET_REQUEST_WIFI_BSS_MAX_IDLE_PERIOD, wifi_set_bss_max_idle_period); +#ifdef CONFIG_WIFI_NM_WPA_SUPPLICANT_BGSCAN +static int wifi_set_bgscan(uint64_t mgmt_request, struct net_if *iface, void *data, size_t len) +{ + const struct device *dev = net_if_get_device(iface); + const struct wifi_mgmt_ops *const wifi_mgmt_api = get_wifi_api(iface); + struct wifi_bgscan_params *params = data; + + if (wifi_mgmt_api == NULL || wifi_mgmt_api->set_bgscan == NULL) { + return -ENOTSUP; + } + + if (!net_if_is_admin_up(iface)) { + return -ENETDOWN; + } + + if (data == NULL || len != sizeof(*params)) { + return -EINVAL; + } + + return wifi_mgmt_api->set_bgscan(dev, params); +} + +NET_MGMT_REGISTER_REQUEST_HANDLER(NET_REQUEST_WIFI_BGSCAN, wifi_set_bgscan); +#endif + #ifdef CONFIG_WIFI_MGMT_RAW_SCAN_RESULTS void wifi_mgmt_raise_raw_scan_result_event(struct net_if *iface, struct wifi_raw_scan_result *raw_scan_result) From 221597f9012f76f21c81ac8f3057ed7563ab5b3a Mon Sep 17 00:00:00 2001 From: Pieter De Gendt Date: Mon, 8 Sep 2025 16:40:55 +0200 Subject: [PATCH 12/47] [nrf fromtree] net: l2: wifi: shell: Add bgscan command Add a shell command to configure the background scanning. Signed-off-by: Pieter De Gendt (cherry picked from commit 1da7a115cc4ba34e48f36c56871fed37b21a81ba) --- subsys/net/l2/wifi/wifi_shell.c | 120 ++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/subsys/net/l2/wifi/wifi_shell.c b/subsys/net/l2/wifi/wifi_shell.c index 5a90d2ccf4c4..de4d7e9d03cd 100644 --- a/subsys/net/l2/wifi/wifi_shell.c +++ b/subsys/net/l2/wifi/wifi_shell.c @@ -3567,6 +3567,113 @@ static int cmd_wifi_set_bss_max_idle_period(const struct shell *sh, size_t argc, return 0; } +#ifdef CONFIG_WIFI_NM_WPA_SUPPLICANT_BGSCAN +static int wifi_bgscan_args_to_params(const struct shell *sh, size_t argc, char *argv[], + struct wifi_bgscan_params *params) +{ + int err; + int opt; + int opt_index = 0; + struct getopt_state *state; + static const struct option long_options[] = { + {"type", required_argument, 0, 't'}, + {"short-interval", required_argument, 0, 's'}, + {"rss-threshold", required_argument, 0, 'r'}, + {"long-interval", required_argument, 0, 'l'}, + {"btm-queries", required_argument, 0, 'b'}, + {"iface", required_argument, 0, 'i'}, + {0, 0, 0, 0}}; + unsigned long uval; + long val; + + while ((opt = getopt_long(argc, argv, "t:s:r:l:b:i:", long_options, &opt_index)) != -1) { + state = getopt_state_get(); + switch (opt) { + case 't': + if (strcmp("simple", state->optarg) == 0) { + params->type = WIFI_BGSCAN_SIMPLE; + } else if (strcmp("learn", state->optarg) == 0) { + params->type = WIFI_BGSCAN_LEARN; + } else if (strcmp("none", state->optarg) == 0) { + params->type = WIFI_BGSCAN_NONE; + } else { + PR_ERROR("Invalid type %s\n", state->optarg); + shell_help(sh); + return SHELL_CMD_HELP_PRINTED; + } + break; + case 's': + uval = shell_strtoul(state->optarg, 10, &err); + if (err < 0) { + PR_ERROR("Invalid short interval %s\n", state->optarg); + return err; + } + params->short_interval = uval; + break; + case 'l': + uval = shell_strtoul(state->optarg, 10, &err); + if (err < 0) { + PR_ERROR("Invalid long interval %s\n", state->optarg); + return err; + } + params->long_interval = uval; + break; + case 'b': + uval = shell_strtoul(state->optarg, 10, &err); + if (err < 0) { + PR_ERROR("Invalid BTM queries %s\n", state->optarg); + return err; + } + params->btm_queries = uval; + break; + case 'r': + val = shell_strtol(state->optarg, 10, &err); + if (err < 0) { + PR_ERROR("Invalid RSSI threshold %s\n", state->optarg); + return err; + } + params->rssi_threshold = val; + break; + case 'i': + /* Unused, but parsing to avoid unknown option error */ + break; + default: + PR_ERROR("Invalid option %c\n", state->optopt); + shell_help(sh); + return SHELL_CMD_HELP_PRINTED; + } + } + + return 0; +} + +static int cmd_wifi_set_bgscan(const struct shell *sh, size_t argc, char *argv[]) +{ + struct net_if *iface = get_iface(IFACE_TYPE_STA, argc, argv); + struct wifi_bgscan_params bgscan_params = { + .type = WIFI_BGSCAN_NONE, + .short_interval = 30, + .long_interval = 300, + .rssi_threshold = 0, + .btm_queries = 0, + }; + int ret; + + if (wifi_bgscan_args_to_params(sh, argc, argv, &bgscan_params) != 0) { + return -ENOEXEC; + } + + ret = net_mgmt(NET_REQUEST_WIFI_BGSCAN, iface, &bgscan_params, + sizeof(struct wifi_bgscan_params)); + if (ret != 0) { + PR_WARNING("Setting background scanning parameters failed: %s\n", strerror(-ret)); + return -ENOEXEC; + } + + return 0; +} +#endif + static int wifi_config_args_to_params(const struct shell *sh, size_t argc, char *argv[], struct wifi_config_params *params) { @@ -4112,6 +4219,19 @@ SHELL_SUBCMD_ADD((wifi), bss_max_idle_period, NULL, cmd_wifi_set_bss_max_idle_period, 2, 2); +#ifdef CONFIG_WIFI_NM_WPA_SUPPLICANT_BGSCAN +SHELL_SUBCMD_ADD((wifi), bgscan, NULL, + "Configure background scanning.\n" + "<-t, --type simple/learn/none> : The scanning type, use none to disable.\n" + "[-s, --short-interval ] : Short scan interval (default: 30).\n" + "[-l, --long-interval ] : Long scan interval (default: 300).\n" + "[-r, --rssi-threshold ] : Signal strength threshold (default: disabled).\n" + "[-b, --btm-queries ] : BTM queries before scanning (default: disabled).\n" + "[-i, --iface=] : Interface index.\n", + cmd_wifi_set_bgscan, + 2, 6); +#endif /* CONFIG_WIFI_NM_WPA_SUPPLICANT_BGSCAN */ + SHELL_SUBCMD_ADD((wifi), config, NULL, "Configure STA parameters.\n" "-o, --okc=<0/1>: Opportunistic Key Caching. 0: disable, 1: enable\n" From 23ece25ff68453be6363de14cd092ef0fc3885cf Mon Sep 17 00:00:00 2001 From: Chaitanya Tata Date: Tue, 11 Nov 2025 15:07:11 +0530 Subject: [PATCH 13/47] [nrf fromtree] manifest: nrf_wifi: Pull modified API The API now takes type as input. Signed-off-by: Chaitanya Tata (cherry picked from commit a7e6d6f686d1488f0c1e5e75c848a92b49c0942a) --- west.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/west.yml b/west.yml index a00bfc9e1318..d2ddfabf52fd 100644 --- a/west.yml +++ b/west.yml @@ -337,7 +337,7 @@ manifest: revision: 40403f5f2805cca210d2a47c8717d89c4e816cda path: modules/bsim_hw_models/nrf_hw_models - name: nrf_wifi - revision: e269670cd17fb8ccc4b7004544276bc7d9578496 + revision: a39e9b155830461c9d1cf587afb151b894369b91 path: modules/lib/nrf_wifi - name: open-amp revision: c30a6d8b92fcebdb797fc1a7698e8729e250f637 From 26ddde6e9e2cc04b7587a53b6de6ce4e36b8b4d8 Mon Sep 17 00:00:00 2001 From: Jamie McCrae Date: Tue, 4 Nov 2025 09:46:39 +0000 Subject: [PATCH 14/47] [nrf fromtree] doc: releases: release-notes: 4.4: Add note on new settings Kconfigs Lists new Kconfigs added to settings allowing single settings to be saved to NVM without modifying their value Signed-off-by: Jamie McCrae (cherry picked from commit 24e05152065141a215f33095cd4dbfd68838f9f5) --- doc/releases/release-notes-4.4.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/releases/release-notes-4.4.rst b/doc/releases/release-notes-4.4.rst index 79a2bbf802e2..9aa8719c7edc 100644 --- a/doc/releases/release-notes-4.4.rst +++ b/doc/releases/release-notes-4.4.rst @@ -102,6 +102,11 @@ New APIs and options * :dtcompatible:`jedec,mspi-nor` now allows MSPI configuration of read, write and control commands separately via devicetree. +* Settings + + * :kconfig:option:`CONFIG_SETTINGS_SAVE_SINGLE_SUBTREE_WITHOUT_MODIFICATION` + * :kconfig:option:`CONFIG_SETTINGS_SAVE_SINGLE_SUBTREE_WITHOUT_MODIFICATION_VALUE_SIZE` + .. zephyr-keep-sorted-stop New Boards From f9fafd939543f070c75ee3ce91c5f050b1704732 Mon Sep 17 00:00:00 2001 From: Chaitanya Tata Date: Mon, 17 Nov 2025 01:08:42 +0530 Subject: [PATCH 15/47] [nrf fromtree] manifest: hostap: Pull fix for SAE Fix build failure in case a different SAE implementation is used (e.g., PSA). Signed-off-by: Chaitanya Tata (cherry picked from commit e797874fbbb15e7b1570bbfbfc826d5f4476a3b6) --- west.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/west.yml b/west.yml index d2ddfabf52fd..700e3a8978f0 100644 --- a/west.yml +++ b/west.yml @@ -281,7 +281,7 @@ manifest: - hal - name: hostap path: modules/lib/hostap - revision: 6086dea5ee7406e1eede7f2ca6dff1b00b0f04e2 + revision: 51698b0f5fdac2778484f565d4591fcb1dd92bc4 - name: liblc3 revision: 48bbd3eacd36e99a57317a0a4867002e0b09e183 path: modules/lib/liblc3 From af391a95ae26697b616d57020f3227798a8db35d Mon Sep 17 00:00:00 2001 From: Ravi Dondaputi Date: Mon, 8 Sep 2025 18:42:08 +0530 Subject: [PATCH 16/47] [nrf fromlist] drivers: wifi: nrf_wifi: Set SSID for P2P discovery Use `ssids` instead of `filter_ssids` to set the SSID in probe requests. `filter_ssids` are to filter scan results to include only the specified SSIDs. Upstream PR #: 97183 Signed-off-by: Ravi Dondaputi --- drivers/wifi/nrf_wifi/src/wpa_supp_if.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/wifi/nrf_wifi/src/wpa_supp_if.c b/drivers/wifi/nrf_wifi/src/wpa_supp_if.c index 88ea9f9215b4..97c418700431 100644 --- a/drivers/wifi/nrf_wifi/src/wpa_supp_if.c +++ b/drivers/wifi/nrf_wifi/src/wpa_supp_if.c @@ -545,16 +545,16 @@ int nrf_wifi_wpa_supp_scan2(void *if_priv, struct wpa_driver_scan_params *params scan_info->scan_params.num_scan_channels = indx; } - if (params->filter_ssids) { - scan_info->scan_params.num_scan_ssids = params->num_filter_ssids; + if (params->num_ssids) { + scan_info->scan_params.num_scan_ssids = params->num_ssids; - for (indx = 0; indx < params->num_filter_ssids; indx++) { + for (indx = 0; indx < params->num_ssids; indx++) { memcpy(scan_info->scan_params.scan_ssids[indx].nrf_wifi_ssid, - params->filter_ssids[indx].ssid, - params->filter_ssids[indx].ssid_len); + params->ssids[indx].ssid, + params->ssids[indx].ssid_len); scan_info->scan_params.scan_ssids[indx].nrf_wifi_ssid_len = - params->filter_ssids[indx].ssid_len; + params->ssids[indx].ssid_len; } } From e738a18e57a4443b9129964dac6f4ae4d324cc37 Mon Sep 17 00:00:00 2001 From: Ravi Dondaputi Date: Tue, 14 Oct 2025 12:09:51 +0530 Subject: [PATCH 17/47] [nrf fromlist] drivers: wifi: nrf_wifi: Set P2P capability In P2P mode, inform supplicant that the driver is P2P capable. Upstream PR #: 97183 Signed-off-by: Ravi Dondaputi --- drivers/wifi/nrf_wifi/src/wpa_supp_if.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/wifi/nrf_wifi/src/wpa_supp_if.c b/drivers/wifi/nrf_wifi/src/wpa_supp_if.c index 97c418700431..0864a1a00fe3 100644 --- a/drivers/wifi/nrf_wifi/src/wpa_supp_if.c +++ b/drivers/wifi/nrf_wifi/src/wpa_supp_if.c @@ -1798,6 +1798,9 @@ int nrf_wifi_supp_get_capa(void *if_priv, struct wpa_driver_capa *capa) if (IS_ENABLED(CONFIG_NRF70_AP_MODE)) { capa->flags |= WPA_DRIVER_FLAGS_AP; } + if (IS_ENABLED(CONFIG_NRF70_P2P_MODE)) { + capa->flags |= WPA_DRIVER_FLAGS_P2P_CAPABLE; + } capa->enc |= WPA_DRIVER_CAPA_ENC_WEP40 | WPA_DRIVER_CAPA_ENC_WEP104 | From 8e99df2fddb0a428ea8ba8d33857e7bdc492c5ed Mon Sep 17 00:00:00 2001 From: Kapil Bhatt Date: Thu, 9 Oct 2025 17:16:51 +0000 Subject: [PATCH 18/47] [nrf fromlist] net: wifi: Add Wi-Fi direct P2P discovery API support Add supplicant api and mgmt ops support for P2P discovery. Upstream PR #: 97183 Signed-off-by: Kapil Bhatt --- include/zephyr/net/wifi_mgmt.h | 126 ++++++++++++++- modules/hostap/src/supp_api.c | 254 +++++++++++++++++++++++++++++++ modules/hostap/src/supp_api.h | 12 ++ modules/hostap/src/supp_events.c | 54 ++++++- modules/hostap/src/supp_events.h | 7 + modules/hostap/src/supp_main.c | 9 ++ subsys/net/l2/wifi/Kconfig | 9 ++ subsys/net/l2/wifi/wifi_mgmt.c | 29 ++++ 8 files changed, 497 insertions(+), 3 deletions(-) diff --git a/include/zephyr/net/wifi_mgmt.h b/include/zephyr/net/wifi_mgmt.h index 817a4fcd6fb6..ef0a6bac27cf 100644 --- a/include/zephyr/net/wifi_mgmt.h +++ b/include/zephyr/net/wifi_mgmt.h @@ -139,6 +139,8 @@ enum net_request_wifi_cmd { NET_REQUEST_WIFI_CMD_BSS_MAX_IDLE_PERIOD, /** Configure background scanning */ NET_REQUEST_WIFI_CMD_BGSCAN, + /** Wi-Fi Direct (P2P) operations*/ + NET_REQUEST_WIFI_CMD_P2P_OPER, /** @cond INTERNAL_HIDDEN */ NET_REQUEST_WIFI_CMD_MAX /** @endcond */ @@ -339,6 +341,11 @@ NET_MGMT_DEFINE_REQUEST_HANDLER(NET_REQUEST_WIFI_BSS_MAX_IDLE_PERIOD); NET_MGMT_DEFINE_REQUEST_HANDLER(NET_REQUEST_WIFI_BGSCAN); +#define NET_REQUEST_WIFI_P2P_OPER \ + (NET_WIFI_BASE | NET_REQUEST_WIFI_CMD_P2P_OPER) + +NET_MGMT_DEFINE_REQUEST_HANDLER(NET_REQUEST_WIFI_P2P_OPER); + /** @cond INTERNAL_HIDDEN */ enum { @@ -359,7 +366,7 @@ enum { NET_EVENT_WIFI_CMD_AP_STA_CONNECTED_VAL, NET_EVENT_WIFI_CMD_AP_STA_DISCONNECTED_VAL, NET_EVENT_WIFI_CMD_SUPPLICANT_VAL, - + NET_EVENT_WIFI_CMD_P2P_DEVICE_FOUND_VAL, NET_EVENT_WIFI_CMD_MAX, }; @@ -406,6 +413,8 @@ enum net_event_wifi_cmd { NET_MGMT_CMD(NET_EVENT_WIFI_CMD_AP_STA_DISCONNECTED), /** Supplicant specific event */ NET_MGMT_CMD(NET_EVENT_WIFI_CMD_SUPPLICANT), + /** P2P device found */ + NET_MGMT_CMD(NET_EVENT_WIFI_CMD_P2P_DEVICE_FOUND), }; /** Event emitted for Wi-Fi scan result */ @@ -468,6 +477,51 @@ enum net_event_wifi_cmd { #define NET_EVENT_WIFI_AP_STA_DISCONNECTED \ (NET_WIFI_EVENT | NET_EVENT_WIFI_CMD_AP_STA_DISCONNECTED) +/** Event emitted for P2P device found event */ +#define NET_EVENT_WIFI_P2P_DEVICE_FOUND \ + (NET_WIFI_EVENT | NET_EVENT_WIFI_CMD_P2P_DEVICE_FOUND) + +#ifdef CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P +/** Maximum length for P2P device name */ +#define WIFI_P2P_DEVICE_NAME_MAX_LEN 32 +/** Size of P2P primary device type (8 bytes) */ +#define WIFI_P2P_PRI_DEV_TYPE_SIZE 8 +/** Maximum length for P2P primary device type string */ +#define WIFI_P2P_PRI_DEV_TYPE_STR_MAX_LEN 32 +/** Maximum length for P2P WPS configuration methods string */ +#define WIFI_P2P_CONFIG_METHODS_STR_MAX_LEN 16 +/** Maximum length for P2P manufacturer name */ +#define WIFI_P2P_MANUFACTURER_MAX_LEN 64 +/** Maximum length for P2P model name */ +#define WIFI_P2P_MODEL_NAME_MAX_LEN 32 + +/** @brief Wi-Fi P2P device info */ +struct wifi_p2p_device_info { + /** Device MAC address */ + uint8_t mac[WIFI_MAC_ADDR_LEN]; + /** Device name (max 32 chars + null terminator) */ + char device_name[WIFI_P2P_DEVICE_NAME_MAX_LEN + 1]; + /** Primary device type */ + uint8_t pri_dev_type[WIFI_P2P_PRI_DEV_TYPE_SIZE]; + /** Primary device type string */ + char pri_dev_type_str[WIFI_P2P_PRI_DEV_TYPE_STR_MAX_LEN]; + /** Signal strength (RSSI) */ + int8_t rssi; + /** WPS configuration methods supported */ + uint16_t config_methods; + /** WPS configuration methods string */ + char config_methods_str[WIFI_P2P_CONFIG_METHODS_STR_MAX_LEN]; + /** Device capability */ + uint8_t dev_capab; + /** Group capability */ + uint8_t group_capab; + /** Manufacturer (max 64 chars + null terminator) */ + char manufacturer[WIFI_P2P_MANUFACTURER_MAX_LEN + 1]; + /** Model name (max 32 chars + null terminator) */ + char model_name[WIFI_P2P_MODEL_NAME_MAX_LEN + 1]; +}; +#endif /* CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P */ + /** @brief Wi-Fi version */ struct wifi_version { /** Driver version */ @@ -1114,6 +1168,9 @@ union wifi_mgmt_events { #endif /* CONFIG_WIFI_MGMT_RAW_SCAN_RESULTS */ struct wifi_twt_params twt_params; struct wifi_ap_sta_info ap_sta_info; +#ifdef CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P + struct wifi_p2p_device_info p2p_device_info; +#endif }; /** @endcond */ @@ -1397,6 +1454,51 @@ struct wifi_wps_config_params { char pin[WIFI_WPS_PIN_MAX_LEN + 1]; }; +#ifdef CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P +/** Wi-Fi P2P operation */ +enum wifi_p2p_op { + /** P2P find/discovery */ + WIFI_P2P_FIND = 0, + /** P2P stop find/discovery */ + WIFI_P2P_STOP_FIND, + /** P2P query peer info use broadcast MAC (ff:ff:ff:ff:ff:ff) to list all peers, + * or specific MAC address to query a single peer + */ + WIFI_P2P_PEER, +}; + +/** Wi-Fi P2P discovery type */ +enum wifi_p2p_discovery_type { + /** Start with full scan, then only social channels */ + WIFI_P2P_FIND_START_WITH_FULL = 0, + /** Only social channels (1, 6, 11) */ + WIFI_P2P_FIND_ONLY_SOCIAL, + /** Progressive - scan through all channels one at a time */ + WIFI_P2P_FIND_PROGRESSIVE, +}; + +/** Maximum number of P2P peers that can be returned in a single query */ +#define WIFI_P2P_MAX_PEERS CONFIG_WIFI_P2P_MAX_PEERS + +/** Wi-Fi P2P parameters */ +struct wifi_p2p_params { + /** P2P operation */ + enum wifi_p2p_op oper; + /** Discovery type (for find operation) */ + enum wifi_p2p_discovery_type discovery_type; + /** Timeout in seconds (0 = no timeout, run until stopped) */ + uint16_t timeout; + /** Peer device address (for peer operation) */ + uint8_t peer_addr[WIFI_MAC_ADDR_LEN]; + /** Flag to list only discovered peers (for peers operation) */ + bool discovered_only; + /** Pointer to array for peer info results */ + struct wifi_p2p_device_info *peers; + /** Actual number of peers returned */ + uint16_t peer_count; +}; +#endif /* CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P */ + /** Wi-Fi AP status */ enum wifi_sap_iface_state { @@ -1785,6 +1887,17 @@ struct wifi_mgmt_ops { */ int (*set_bgscan)(const struct device *dev, struct wifi_bgscan_params *params); #endif +#ifdef CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P + /** Wi-Fi Direct (P2P) operations for device discovery + * + * @param dev Pointer to the device structure for the driver instance. + * @param params P2P operation parameters including operation type, discovery settings, + * timeout values and peer information retrieval options + * + * @return 0 if ok, < 0 if error + */ + int (*p2p_oper)(const struct device *dev, struct wifi_p2p_params *params); +#endif }; /** Wi-Fi management offload API */ @@ -1916,6 +2029,17 @@ void wifi_mgmt_raise_ap_sta_connected_event(struct net_if *iface, void wifi_mgmt_raise_ap_sta_disconnected_event(struct net_if *iface, struct wifi_ap_sta_info *sta_info); +#ifdef CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P +/** + * @brief Raise P2P device found event + * + * @param iface Network interface + * @param device_info P2P device information + */ +void wifi_mgmt_raise_p2p_device_found_event(struct net_if *iface, + struct wifi_p2p_device_info *peer_info); +#endif /* CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P */ + /** * @} */ diff --git a/modules/hostap/src/supp_api.c b/modules/hostap/src/supp_api.c index 11c00de06b37..8555e91a1cf9 100644 --- a/modules/hostap/src/supp_api.c +++ b/modules/hostap/src/supp_api.c @@ -60,6 +60,14 @@ enum status_thread_state { static struct wifi_enterprise_creds_params enterprise_creds; #endif +#ifdef CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P +#define P2P_CMD_BUF_SIZE 128 +#define P2P_RESP_BUF_SIZE 64 +#define P2P_PEER_INFO_SIZE 512 +#define P2P_ADDR_SIZE 32 +#define P2P_CMD_SIZE 64 +#endif /* CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P */ + K_MUTEX_DEFINE(wpa_supplicant_mutex); extern struct k_work_q *get_workq(void); @@ -2576,3 +2584,249 @@ int supplicant_config_params(const struct device *dev, struct wifi_config_params k_mutex_unlock(&wpa_supplicant_mutex); return ret; } + +#ifdef CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P +static inline void extract_value(const char *src, char *dest, size_t dest_size) +{ + size_t i = 0; + + while (src[i] != '\0' && src[i] != '\n' && i < dest_size - 1) { + dest[i] = src[i]; + i++; + } + dest[i] = '\0'; +} + +static void parse_peer_info_line(const char *line, struct wifi_p2p_device_info *info) +{ + const char *pos; + + if (strncmp(line, "device_name=", 12) == 0) { + extract_value(line + 12, info->device_name, sizeof(info->device_name)); + } else if (strncmp(line, "pri_dev_type=", 13) == 0) { + extract_value(line + 13, info->pri_dev_type_str, sizeof(info->pri_dev_type_str)); + } else if (strncmp(line, "level=", 6) == 0) { + char *endptr; + long val; + + pos = line + 6; + val = strtol(pos, &endptr, 10); + if (endptr != pos && val >= INT8_MIN && val <= INT8_MAX) { + info->rssi = (int8_t)val; + } + } else if (strncmp(line, "config_methods=", 15) == 0) { + char *endptr; + long val; + + pos = line + 15; + extract_value(pos, info->config_methods_str, sizeof(info->config_methods_str)); + + if (pos[0] == '0' && (pos[1] == 'x' || pos[1] == 'X')) { + val = strtol(pos, &endptr, 16); + } else { + val = strtol(pos, &endptr, 10); + } + if (endptr != pos && val >= 0 && val <= UINT16_MAX) { + info->config_methods = (uint16_t)val; + } + } else if (strncmp(line, "manufacturer=", 13) == 0) { + extract_value(line + 13, info->manufacturer, sizeof(info->manufacturer)); + } else if (strncmp(line, "model_name=", 11) == 0) { + extract_value(line + 11, info->model_name, sizeof(info->model_name)); + } +} + +static void parse_peer_info_response(const char *resp, const uint8_t *mac, + struct wifi_p2p_device_info *info) +{ + const char *line = resp; + const char *next_line; + + memset(info, 0, sizeof(*info)); + + if (mac) { + memcpy(info->mac, mac, WIFI_MAC_ADDR_LEN); + } + + while (line && *line) { + if (*line == '\n') { + line++; + continue; + } + next_line = strchr(line, '\n'); + parse_peer_info_line(line, info); + if (next_line) { + line = next_line + 1; + } else { + break; + } + } +} + +int supplicant_p2p_oper(const struct device *dev, struct wifi_p2p_params *params) +{ + struct wpa_supplicant *wpa_s = get_wpa_s_handle(dev); + char cmd_buf[P2P_CMD_BUF_SIZE]; + char resp_buf[P2P_RESP_BUF_SIZE]; + int ret = -1; + const char *discovery_type_str = ""; + + if (!wpa_s || !wpa_s->ctrl_conn) { + wpa_printf(MSG_ERROR, "wpa_supplicant control interface not initialized"); + return -ENOTSUP; + } + + switch (params->oper) { + case WIFI_P2P_FIND: + switch (params->discovery_type) { + case WIFI_P2P_FIND_ONLY_SOCIAL: + discovery_type_str = "type=social"; + break; + case WIFI_P2P_FIND_PROGRESSIVE: + discovery_type_str = "type=progressive"; + break; + case WIFI_P2P_FIND_START_WITH_FULL: + default: + discovery_type_str = ""; + break; + } + + if (params->timeout > 0) { + if (strlen(discovery_type_str) > 0) { + snprintk(cmd_buf, sizeof(cmd_buf), "P2P_FIND %u %s", + params->timeout, discovery_type_str); + } else { + snprintk(cmd_buf, sizeof(cmd_buf), "P2P_FIND %u", + params->timeout); + } + } else { + if (strlen(discovery_type_str) > 0) { + snprintk(cmd_buf, sizeof(cmd_buf), "P2P_FIND %s", + discovery_type_str); + } else { + snprintk(cmd_buf, sizeof(cmd_buf), "P2P_FIND"); + } + } + ret = zephyr_wpa_cli_cmd_resp_noprint(wpa_s->ctrl_conn, cmd_buf, resp_buf); + if (ret < 0) { + wpa_printf(MSG_ERROR, "P2P_FIND command failed: %d", ret); + return -EIO; + } + ret = 0; + break; + + case WIFI_P2P_STOP_FIND: + snprintk(cmd_buf, sizeof(cmd_buf), "P2P_STOP_FIND"); + ret = zephyr_wpa_cli_cmd_resp_noprint(wpa_s->ctrl_conn, cmd_buf, resp_buf); + if (ret < 0) { + wpa_printf(MSG_ERROR, "P2P_STOP_FIND command failed: %d", ret); + return -EIO; + } + ret = 0; + break; + + case WIFI_P2P_PEER: { + char addr[P2P_ADDR_SIZE]; + char cmd[P2P_CMD_SIZE]; + char peer_info[P2P_PEER_INFO_SIZE]; + char *pos; + size_t len; + uint16_t peer_idx = 0; + uint8_t mac[WIFI_MAC_ADDR_LEN]; + struct net_eth_addr peer_mac; + bool query_all_peers; + + if (!params->peers) { + wpa_printf(MSG_ERROR, "Peer info array not provided"); + return -EINVAL; + } + + memcpy(&peer_mac, params->peer_addr, WIFI_MAC_ADDR_LEN); + query_all_peers = net_eth_is_addr_broadcast(&peer_mac); + + if (query_all_peers) { + os_strlcpy(cmd_buf, "P2P_PEER FIRST", sizeof(cmd_buf)); + + while (peer_idx < params->peer_count) { + ret = zephyr_wpa_cli_cmd_resp_noprint(wpa_s->ctrl_conn, + cmd_buf, resp_buf); + + if (ret < 0 || resp_buf[0] == '\0' || + os_strncmp(resp_buf, "FAIL", 4) == 0) { + if (peer_idx == 0) { + wpa_printf(MSG_DEBUG, "No P2P peers found"); + } + break; + } + + len = 0; + pos = resp_buf; + while (*pos != '\0' && *pos != '\n' && len < sizeof(addr) - 1) { + addr[len++] = *pos++; + } + addr[len] = '\0'; + + if (os_strncmp(addr, "00:00:00:00:00:00", 17) != 0 && + sscanf(addr, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + &mac[0], &mac[1], &mac[2], + &mac[3], &mac[4], &mac[5]) == + WIFI_MAC_ADDR_LEN) { + + os_snprintf(cmd, sizeof(cmd), "P2P_PEER %s", addr); + ret = zephyr_wpa_cli_cmd_resp_noprint( + wpa_s->ctrl_conn, cmd, peer_info); + + if (ret >= 0 && + (!params->discovered_only || + os_strstr(peer_info, + "[PROBE_REQ_ONLY]") == NULL)) { + parse_peer_info_response(peer_info, + mac, + ¶ms->peers[peer_idx]); + peer_idx++; + } + } + os_snprintf(cmd_buf, sizeof(cmd_buf), "P2P_PEER NEXT-%s", addr); + } + params->peer_count = peer_idx; + } else { + char addr_str[18]; + + if (params->peer_count < 1) { + wpa_printf(MSG_ERROR, "Peer count must be at least 1"); + return -EINVAL; + } + + snprintk(addr_str, sizeof(addr_str), "%02x:%02x:%02x:%02x:%02x:%02x", + params->peer_addr[0], params->peer_addr[1], params->peer_addr[2], + params->peer_addr[3], params->peer_addr[4], params->peer_addr[5]); + snprintk(cmd_buf, sizeof(cmd_buf), "P2P_PEER %s", addr_str); + + /* Use peer_info buffer for single peer query to avoid large resp_buf */ + ret = zephyr_wpa_cli_cmd_resp_noprint(wpa_s->ctrl_conn, + cmd_buf, peer_info); + if (ret < 0) { + wpa_printf(MSG_ERROR, "P2P_PEER command failed: %d", ret); + return -EIO; + } + if (os_strncmp(peer_info, "FAIL", 4) == 0) { + wpa_printf(MSG_ERROR, "Peer %s not found", addr_str); + return -ENODEV; + } + parse_peer_info_response(peer_info, params->peer_addr, + ¶ms->peers[0]); + params->peer_count = 1; + } + ret = 0; + break; + } + + default: + wpa_printf(MSG_ERROR, "Unknown P2P operation: %d", params->oper); + ret = -EINVAL; + break; + } + + return ret; +} +#endif /* CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P */ diff --git a/modules/hostap/src/supp_api.h b/modules/hostap/src/supp_api.h index 8ab49f177e70..692db61bf277 100644 --- a/modules/hostap/src/supp_api.h +++ b/modules/hostap/src/supp_api.h @@ -386,4 +386,16 @@ int supplicant_dpp_dispatch(const struct device *dev, struct wifi_dpp_params *pa * @return 0 for OK; -1 for ERROR */ int supplicant_config_params(const struct device *dev, struct wifi_config_params *params); + +#ifdef CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P +/** + * @brief P2P operation + * + * @param dev Wi-Fi interface handle to use + * @param params P2P parameters + * @return 0 for OK; -1 for ERROR + */ +int supplicant_p2p_oper(const struct device *dev, struct wifi_p2p_params *params); +#endif /* CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P */ + #endif /* ZEPHYR_SUPP_MGMT_H */ diff --git a/modules/hostap/src/supp_events.c b/modules/hostap/src/supp_events.c index c4175a416bea..ee6a05a568ec 100644 --- a/modules/hostap/src/supp_events.c +++ b/modules/hostap/src/supp_events.c @@ -43,6 +43,9 @@ static const struct wpa_supp_event_info { { "CTRL-EVENT-NETWORK-REMOVED", SUPPLICANT_EVENT_NETWORK_REMOVED }, { "CTRL-EVENT-DSCP-POLICY", SUPPLICANT_EVENT_DSCP_POLICY }, { "CTRL-EVENT-REGDOM-CHANGE", SUPPLICANT_EVENT_REGDOM_CHANGE }, +#ifdef CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P + { "P2P-DEVICE-FOUND", SUPPLICANT_EVENT_P2P_DEVICE_FOUND }, +#endif }; static void copy_mac_addr(const unsigned int *src, uint8_t *dst) @@ -174,6 +177,43 @@ static int supplicant_process_status(struct supplicant_int_event_data *event_dat event_data->data_len = sizeof(data->bss_removed); copy_mac_addr(tmp_mac_addr, data->bss_removed.bssid); break; +#ifdef CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P + case SUPPLICANT_EVENT_P2P_DEVICE_FOUND: + { + char *ptr, *name_start, *name_end; + unsigned int config_methods = 0; + + memset(&data->p2p_device_found, 0, sizeof(data->p2p_device_found)); + ret = sscanf(event_no_prefix, MACSTR, MACADDR2STR(tmp_mac_addr)); + if (ret > 0) { + copy_mac_addr(tmp_mac_addr, data->p2p_device_found.mac); + } + name_start = strstr(event_no_prefix, "name='"); + if (name_start) { + name_start += 6; + name_end = strchr(name_start, '\''); + if (name_end) { + size_t name_len = name_end - name_start; + + if (name_len >= sizeof(data->p2p_device_found.device_name)) { + name_len = sizeof(data->p2p_device_found.device_name) - 1; + } + memcpy(data->p2p_device_found.device_name, name_start, name_len); + data->p2p_device_found.device_name[name_len] = '\0'; + } + } + ptr = strstr(event_no_prefix, "config_methods="); + if (ptr) { + ret = sscanf(ptr, "config_methods=%x", &config_methods); + if (ret > 0) { + data->p2p_device_found.config_methods = config_methods; + } + } + event_data->data_len = sizeof(data->p2p_device_found); + ret = 1; + break; + } +#endif case SUPPLICANT_EVENT_TERMINATING: case SUPPLICANT_EVENT_SCAN_STARTED: case SUPPLICANT_EVENT_SCAN_RESULTS: @@ -386,8 +426,18 @@ int supplicant_send_wifi_mgmt_event(const char *ifname, enum net_event_wifi_cmd case NET_EVENT_WIFI_CMD_SUPPLICANT: event_data.data = &data; if (supplicant_process_status(&event_data, (char *)supplicant_status) > 0) { - net_mgmt_event_notify_with_info(NET_EVENT_SUPPLICANT_INT_EVENT, - iface, &event_data, sizeof(event_data)); +#ifdef CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P + /* Handle P2P events directly */ + if (event_data.event == SUPPLICANT_EVENT_P2P_DEVICE_FOUND) { + wifi_mgmt_raise_p2p_device_found_event(iface, + &data.p2p_device_found); + } else { +#endif + net_mgmt_event_notify_with_info(NET_EVENT_SUPPLICANT_INT_EVENT, + iface, &event_data, sizeof(event_data)); +#ifdef CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P + } +#endif } break; default: diff --git a/modules/hostap/src/supp_events.h b/modules/hostap/src/supp_events.h index 1c74af528492..6ff8812c2fb8 100644 --- a/modules/hostap/src/supp_events.h +++ b/modules/hostap/src/supp_events.h @@ -137,6 +137,10 @@ union supplicant_event_data { unsigned int id; } network_removed; +#ifdef CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P + struct wifi_p2p_device_info p2p_device_found; +#endif + char supplicant_event_str[NM_WIFI_EVENT_STR_LEN]; }; @@ -158,6 +162,9 @@ enum supplicant_event_num { SUPPLICANT_EVENT_NETWORK_REMOVED, SUPPLICANT_EVENT_DSCP_POLICY, SUPPLICANT_EVENT_REGDOM_CHANGE, +#ifdef CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P + SUPPLICANT_EVENT_P2P_DEVICE_FOUND, +#endif }; struct supplicant_int_event_data { diff --git a/modules/hostap/src/supp_main.c b/modules/hostap/src/supp_main.c index d1f8c360cb50..8afdba33b203 100644 --- a/modules/hostap/src/supp_main.c +++ b/modules/hostap/src/supp_main.c @@ -101,6 +101,9 @@ static const struct wifi_mgmt_ops mgmt_ops = { .enterprise_creds = supplicant_add_enterprise_creds, #endif .config_params = supplicant_config_params, +#ifdef CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P + .p2p_oper = supplicant_p2p_oper, +#endif }; DEFINE_WIFI_NM_INSTANCE(wifi_supplicant, &mgmt_ops); @@ -245,6 +248,12 @@ static void zephyr_wpa_supplicant_msg(void *ctx, const char *txt, size_t len) supplicant_send_wifi_mgmt_event(wpa_s->ifname, NET_EVENT_WIFI_CMD_NEIGHBOR_REP_RECEIVED, (void *)txt, len); +#ifdef CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P + } else if (strncmp(txt, "P2P-", 4) == 0) { + supplicant_send_wifi_mgmt_event(wpa_s->ifname, + NET_EVENT_WIFI_CMD_SUPPLICANT, + (void *)txt, len); +#endif } } diff --git a/subsys/net/l2/wifi/Kconfig b/subsys/net/l2/wifi/Kconfig index 63680dba9b30..eddfb7d4c30a 100644 --- a/subsys/net/l2/wifi/Kconfig +++ b/subsys/net/l2/wifi/Kconfig @@ -72,6 +72,15 @@ config WIFI_SHELL_MAX_AP_STA This option defines the maximum number of APs and STAs that can be managed in Wi-Fi shell. +config WIFI_P2P_MAX_PEERS + int "Maximum number of P2P peers that can be returned in a single query" + depends on WIFI_NM_WPA_SUPPLICANT_P2P + default 32 + range 1 256 + help + This option defines the maximum number of P2P peers that can be returned + in a single query operation. + config WIFI_NM bool "Wi-Fi Network manager support" help diff --git a/subsys/net/l2/wifi/wifi_mgmt.c b/subsys/net/l2/wifi/wifi_mgmt.c index 6a97f8c0e61c..58d269e277be 100644 --- a/subsys/net/l2/wifi/wifi_mgmt.c +++ b/subsys/net/l2/wifi/wifi_mgmt.c @@ -1473,6 +1473,35 @@ static int wifi_set_bgscan(uint64_t mgmt_request, struct net_if *iface, void *da NET_MGMT_REGISTER_REQUEST_HANDLER(NET_REQUEST_WIFI_BGSCAN, wifi_set_bgscan); #endif +#ifdef CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P +static int wifi_p2p_oper(uint64_t mgmt_request, struct net_if *iface, + void *data, size_t len) +{ + const struct device *dev = net_if_get_device(iface); + const struct wifi_mgmt_ops *const wifi_mgmt_api = get_wifi_api(iface); + struct wifi_p2p_params *params = data; + + if (wifi_mgmt_api == NULL || wifi_mgmt_api->p2p_oper == NULL) { + return -ENOTSUP; + } + + if (!data || len != sizeof(*params)) { + return -EINVAL; + } + + return wifi_mgmt_api->p2p_oper(dev, params); +} + +NET_MGMT_REGISTER_REQUEST_HANDLER(NET_REQUEST_WIFI_P2P_OPER, wifi_p2p_oper); + +void wifi_mgmt_raise_p2p_device_found_event(struct net_if *iface, + struct wifi_p2p_device_info *peer_info) +{ + net_mgmt_event_notify_with_info(NET_EVENT_WIFI_P2P_DEVICE_FOUND, + iface, peer_info, + sizeof(*peer_info)); +} +#endif /* CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P */ #ifdef CONFIG_WIFI_MGMT_RAW_SCAN_RESULTS void wifi_mgmt_raise_raw_scan_result_event(struct net_if *iface, From 83e27d7588b3dd32958899b0eb655b1dae138042 Mon Sep 17 00:00:00 2001 From: Kapil Bhatt Date: Wed, 29 Oct 2025 13:08:44 +0000 Subject: [PATCH 19/47] [nrf fromlist] net: wifi: Add Wi-Fi direct P2P discovery shell command support Add shell command support for P2P discovery. Upstream PR #: 97183 Signed-off-by: Kapil Bhatt --- subsys/net/l2/wifi/wifi_shell.c | 234 +++++++++++++++++++++++++++++++- 1 file changed, 233 insertions(+), 1 deletion(-) diff --git a/subsys/net/l2/wifi/wifi_shell.c b/subsys/net/l2/wifi/wifi_shell.c index de4d7e9d03cd..91e342f81bd0 100644 --- a/subsys/net/l2/wifi/wifi_shell.c +++ b/subsys/net/l2/wifi/wifi_shell.c @@ -46,7 +46,8 @@ LOG_MODULE_REGISTER(net_wifi_shell, LOG_LEVEL_INF); NET_EVENT_WIFI_AP_STA_CONNECTED |\ NET_EVENT_WIFI_AP_STA_DISCONNECTED|\ NET_EVENT_WIFI_SIGNAL_CHANGE |\ - NET_EVENT_WIFI_NEIGHBOR_REP_COMP) + NET_EVENT_WIFI_NEIGHBOR_REP_COMP |\ + NET_EVENT_WIFI_P2P_DEVICE_FOUND) #ifdef CONFIG_WIFI_MGMT_RAW_SCAN_RESULTS_ONLY #define WIFI_SHELL_SCAN_EVENTS ( \ @@ -519,6 +520,26 @@ static void handle_wifi_neighbor_rep_complete(struct net_mgmt_event_callback *cb } #endif +#ifdef CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P +static void handle_wifi_p2p_device_found(struct net_mgmt_event_callback *cb) +{ + const struct wifi_p2p_device_info *peer_info = + (const struct wifi_p2p_device_info *)cb->info; + const struct shell *sh = context.sh; + + if (!peer_info || peer_info->device_name[0] == '\0') { + return; + } + + PR("Device Name: %-20s MAC Address: %02x:%02x:%02x:%02x:%02x:%02x " + "Config Methods: 0x%x\n", + peer_info->device_name, + peer_info->mac[0], peer_info->mac[1], peer_info->mac[2], + peer_info->mac[3], peer_info->mac[4], peer_info->mac[5], + peer_info->config_methods); +} +#endif /* CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P */ + static void wifi_mgmt_event_handler(struct net_mgmt_event_callback *cb, uint64_t mgmt_event, struct net_if *iface) { @@ -552,6 +573,11 @@ static void wifi_mgmt_event_handler(struct net_mgmt_event_callback *cb, handle_wifi_neighbor_rep_complete(cb, iface); break; #endif +#ifdef CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P + case NET_EVENT_WIFI_P2P_DEVICE_FOUND: + handle_wifi_p2p_device_found(cb); + break; +#endif /* CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P */ default: break; } @@ -3528,6 +3554,180 @@ static int cmd_wifi_dpp_reconfig(const struct shell *sh, size_t argc, char *argv } #endif /* CONFIG_WIFI_NM_WPA_SUPPLICANT_DPP */ + +#ifdef CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P +static void print_peer_info(const struct shell *sh, int index, + const struct wifi_p2p_device_info *peer) +{ + uint8_t mac_string_buf[sizeof("xx:xx:xx:xx:xx:xx")]; + const char *device_name; + const char *device_type; + const char *config_methods; + + device_name = (peer->device_name[0] != '\0') ? + peer->device_name : ""; + device_type = (peer->pri_dev_type_str[0] != '\0') ? + peer->pri_dev_type_str : "-"; + config_methods = (peer->config_methods_str[0] != '\0') ? + peer->config_methods_str : "-"; + + PR("%-4d | %-32s | %-17s | %-4d | %-20s | %s\n", + index, + device_name, + net_sprint_ll_addr_buf(peer->mac, WIFI_MAC_ADDR_LEN, + mac_string_buf, + sizeof(mac_string_buf)), + peer->rssi, + device_type, + config_methods); +} + +static int cmd_wifi_p2p_peer(const struct shell *sh, size_t argc, char *argv[]) +{ + struct net_if *iface = get_iface(IFACE_TYPE_STA, argc, argv); + struct wifi_p2p_params params = {0}; + uint8_t mac_addr[WIFI_MAC_ADDR_LEN]; + static struct wifi_p2p_device_info peers[WIFI_P2P_MAX_PEERS]; + int ret; + int max_peers = (argc < 2) ? WIFI_P2P_MAX_PEERS : 1; + + context.sh = sh; + + memset(peers, 0, sizeof(peers)); + + params.peers = peers; + params.oper = WIFI_P2P_PEER; + params.peer_count = max_peers; + + if (argc >= 2) { + if (sscanf(argv[1], "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + &mac_addr[0], &mac_addr[1], &mac_addr[2], + &mac_addr[3], &mac_addr[4], &mac_addr[5]) != WIFI_MAC_ADDR_LEN) { + PR_ERROR("Invalid MAC address format. Use: XX:XX:XX:XX:XX:XX\n"); + return -EINVAL; + } + memcpy(params.peer_addr, mac_addr, WIFI_MAC_ADDR_LEN); + params.peer_count = 1; + } else { + /* Use broadcast MAC to query all peers */ + memset(params.peer_addr, 0xFF, WIFI_MAC_ADDR_LEN); + } + + ret = net_mgmt(NET_REQUEST_WIFI_P2P_OPER, iface, ¶ms, sizeof(params)); + if (ret) { + PR_WARNING("P2P peer info request failed\n"); + return -ENOEXEC; + } + + if (params.peer_count > 0) { + PR("\n%-4s | %-32s | %-17s | %-4s | %-20s | %s\n", + "Num", "Device Name", "MAC Address", "RSSI", "Device Type", "Config Methods"); + for (int i = 0; i < params.peer_count; i++) { + print_peer_info(sh, i + 1, &peers[i]); + } + } else { + if (argc >= 2) { + shell_print(sh, "No information available for peer %s", argv[1]); + } else { + shell_print(sh, "No P2P peers found"); + } + } + + return 0; +} + + +static int cmd_wifi_p2p_find(const struct shell *sh, size_t argc, char *argv[]) +{ + struct net_if *iface = get_iface(IFACE_TYPE_STA, argc, argv); + struct wifi_p2p_params params = {0}; + + context.sh = sh; + + params.oper = WIFI_P2P_FIND; + params.discovery_type = WIFI_P2P_FIND_START_WITH_FULL; + params.timeout = 10; /* Default 10 second timeout */ + + if (argc > 1) { + int opt; + int opt_index = 0; + struct sys_getopt_state *state; + static const struct sys_getopt_option long_options[] = { + {"timeout", sys_getopt_required_argument, 0, 't'}, + {"type", sys_getopt_required_argument, 0, 'T'}, + {"iface", sys_getopt_required_argument, 0, 'i'}, + {"help", sys_getopt_no_argument, 0, 'h'}, + {0, 0, 0, 0} + }; + long val; + + while ((opt = sys_getopt_long(argc, argv, "t:T:i:h", + long_options, &opt_index)) != -1) { + state = sys_getopt_state_get(); + switch (opt) { + case 't': + if (!parse_number(sh, &val, state->optarg, "timeout", 0, 65535)) { + return -EINVAL; + } + params.timeout = (uint16_t)val; + break; + case 'T': + if (!parse_number(sh, &val, state->optarg, "type", 0, 2)) { + return -EINVAL; + } + switch (val) { + case 0: + params.discovery_type = WIFI_P2P_FIND_ONLY_SOCIAL; + break; + case 1: + params.discovery_type = WIFI_P2P_FIND_PROGRESSIVE; + break; + case 2: + params.discovery_type = WIFI_P2P_FIND_START_WITH_FULL; + break; + default: + return -EINVAL; + } + break; + case 'i': + /* Unused, but parsing to avoid unknown option error */ + break; + case 'h': + shell_help(sh); + return -ENOEXEC; + default: + PR_ERROR("Invalid option %c\n", state->optopt); + return -EINVAL; + } + } + } + + if (net_mgmt(NET_REQUEST_WIFI_P2P_OPER, iface, ¶ms, sizeof(params))) { + PR_WARNING("P2P find request failed\n"); + return -ENOEXEC; + } + PR("P2P find started\n"); + return 0; +} + +static int cmd_wifi_p2p_stop_find(const struct shell *sh, size_t argc, char *argv[]) +{ + struct net_if *iface = get_iface(IFACE_TYPE_STA, argc, argv); + struct wifi_p2p_params params = {0}; + + context.sh = sh; + + params.oper = WIFI_P2P_STOP_FIND; + + if (net_mgmt(NET_REQUEST_WIFI_P2P_OPER, iface, ¶ms, sizeof(params))) { + PR_WARNING("P2P stop find request failed\n"); + return -ENOEXEC; + } + PR("P2P find stopped\n"); + return 0; +} +#endif /* CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P */ + static int cmd_wifi_pmksa_flush(const struct shell *sh, size_t argc, char *argv[]) { struct net_if *iface = get_iface(IFACE_TYPE_STA, argc, argv); @@ -4232,6 +4432,38 @@ SHELL_SUBCMD_ADD((wifi), bgscan, NULL, 2, 6); #endif /* CONFIG_WIFI_NM_WPA_SUPPLICANT_BGSCAN */ +#ifdef CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P +SHELL_STATIC_SUBCMD_SET_CREATE( + wifi_cmd_p2p, + SHELL_CMD_ARG(find, NULL, + "Start P2P device discovery\n" + "[-t, --timeout=]: Discovery timeout\n" + "[-T, --type=<0|1|2>]: Discovery type\n" + " 0: Social channels only (1, 6, 11)\n" + " 1: Progressive scan (all channels)\n" + " 2: Full scan first, then social (default)\n" + "[-i, --iface=]: Interface index\n" + "[-h, --help]: Show help\n", + cmd_wifi_p2p_find, 1, 6), + SHELL_CMD_ARG(stop_find, NULL, + "Stop P2P device discovery\n" + "[-i, --iface=]: Interface index\n", + cmd_wifi_p2p_stop_find, 1, 2), + SHELL_CMD_ARG(peer, NULL, + "Show information about P2P peers\n" + "Usage: peer []\n" + ": Show detailed info for specific peer (format: XX:XX:XX:XX:XX:XX)\n" + "[-i, --iface=]: Interface index\n", + cmd_wifi_p2p_peer, 1, 3), + SHELL_SUBCMD_SET_END +); + +SHELL_SUBCMD_ADD((wifi), p2p, &wifi_cmd_p2p, + "Wi-Fi Direct (P2P) commands.", + NULL, + 0, 0); +#endif /* CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P */ + SHELL_SUBCMD_ADD((wifi), config, NULL, "Configure STA parameters.\n" "-o, --okc=<0/1>: Opportunistic Key Caching. 0: disable, 1: enable\n" From 1bca1dc1f94517eb17acb27f19b775a5d61d573d Mon Sep 17 00:00:00 2001 From: Kapil Bhatt Date: Mon, 3 Nov 2025 12:48:53 +0000 Subject: [PATCH 20/47] [nrf fromlist] manifest: Update nrf_wifi revision Update nrf_wifi revision to include support for P2P feature. Upstream PR #: 97183 Signed-off-by: Kapil Bhatt --- west.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/west.yml b/west.yml index 700e3a8978f0..c85c2f2b2a16 100644 --- a/west.yml +++ b/west.yml @@ -337,7 +337,7 @@ manifest: revision: 40403f5f2805cca210d2a47c8717d89c4e816cda path: modules/bsim_hw_models/nrf_hw_models - name: nrf_wifi - revision: a39e9b155830461c9d1cf587afb151b894369b91 + revision: f0b6706cac522371e651f589d53d3026cc6f97dd path: modules/lib/nrf_wifi - name: open-amp revision: c30a6d8b92fcebdb797fc1a7698e8729e250f637 From b890ba3ba5728ce618122f3776be5045d6602bf8 Mon Sep 17 00:00:00 2001 From: Ravi Dondaputi Date: Thu, 30 Oct 2025 19:48:21 +0530 Subject: [PATCH 21/47] [nrf fromlist] drivers: wifi: nrf_wifi: Add RoC support Add ops for remain-on-channel and cancelling remain-on-channel. Upstream PR #: 97183 Signed-off-by: Ravi Dondaputi --- drivers/wifi/nrf_wifi/inc/wpa_supp_if.h | 8 ++ drivers/wifi/nrf_wifi/src/fmac_main.c | 4 + drivers/wifi/nrf_wifi/src/wpa_supp_if.c | 141 ++++++++++++++++++++++++ 3 files changed, 153 insertions(+) diff --git a/drivers/wifi/nrf_wifi/inc/wpa_supp_if.h b/drivers/wifi/nrf_wifi/inc/wpa_supp_if.h index c543644e3128..28aadae28ebd 100644 --- a/drivers/wifi/nrf_wifi/inc/wpa_supp_if.h +++ b/drivers/wifi/nrf_wifi/inc/wpa_supp_if.h @@ -123,8 +123,16 @@ int nrf_wifi_supp_get_conn_info(void *if_priv, struct wpa_conn_info *info); void nrf_wifi_supp_event_proc_get_conn_info(void *os_vif_ctx, struct nrf_wifi_umac_event_conn_info *info, unsigned int event_len); +void nrf_wifi_supp_event_remain_on_channel(void *os_vif_ctx, + struct nrf_wifi_event_remain_on_channel *info, + unsigned int event_len); +void nrf_wifi_supp_event_roc_cancel_complete(void *os_vif_ctx, + struct nrf_wifi_event_remain_on_channel *info, + unsigned int event_len); int nrf_wifi_supp_set_country(void *if_priv, const char *alpha2); int nrf_wifi_supp_get_country(void *if_priv, char *alpha2); +int nrf_wifi_supp_remain_on_channel(void *if_priv, unsigned int freq, unsigned int duration); +int nrf_wifi_supp_cancel_remain_on_channel(void *if_priv); #endif /* CONFIG_NRF70_STA_MODE */ #ifdef CONFIG_NRF70_AP_MODE diff --git a/drivers/wifi/nrf_wifi/src/fmac_main.c b/drivers/wifi/nrf_wifi/src/fmac_main.c index 47ca53d5a61c..00ea3a873c85 100644 --- a/drivers/wifi/nrf_wifi/src/fmac_main.c +++ b/drivers/wifi/nrf_wifi/src/fmac_main.c @@ -843,6 +843,8 @@ static int nrf_wifi_drv_main_zep(const struct device *dev) callbk_fns.event_get_wiphy = nrf_wifi_wpa_supp_event_get_wiphy; callbk_fns.mgmt_rx_callbk_fn = nrf_wifi_wpa_supp_event_mgmt_rx_callbk_fn; callbk_fns.get_conn_info_callbk_fn = nrf_wifi_supp_event_proc_get_conn_info; + callbk_fns.roc_callbk_fn = nrf_wifi_supp_event_remain_on_channel; + callbk_fns.roc_cancel_callbk_fn = nrf_wifi_supp_event_roc_cancel_complete; #endif /* CONFIG_NRF70_STA_MODE */ /* The OSAL layer needs to be initialized before any other initialization @@ -963,6 +965,8 @@ static const struct zep_wpa_supp_dev_ops wpa_supp_ops = { .get_conn_info = nrf_wifi_supp_get_conn_info, .set_country = nrf_wifi_supp_set_country, .get_country = nrf_wifi_supp_get_country, + .remain_on_channel = nrf_wifi_supp_remain_on_channel, + .cancel_remain_on_channel = nrf_wifi_supp_cancel_remain_on_channel, #ifdef CONFIG_NRF70_AP_MODE .init_ap = nrf_wifi_wpa_supp_init_ap, .start_ap = nrf_wifi_wpa_supp_start_ap, diff --git a/drivers/wifi/nrf_wifi/src/wpa_supp_if.c b/drivers/wifi/nrf_wifi/src/wpa_supp_if.c index 0864a1a00fe3..3caf7f6af526 100644 --- a/drivers/wifi/nrf_wifi/src/wpa_supp_if.c +++ b/drivers/wifi/nrf_wifi/src/wpa_supp_if.c @@ -1986,6 +1986,147 @@ void nrf_wifi_supp_event_proc_get_conn_info(void *if_priv, k_sem_give(&wait_for_event_sem); } +void nrf_wifi_supp_event_remain_on_channel(void *if_priv, + struct nrf_wifi_event_remain_on_channel *roc_complete, + unsigned int event_len) +{ + struct nrf_wifi_vif_ctx_zep *vif_ctx_zep = NULL; + + if (!if_priv) { + LOG_ERR("%s: Missing interface context", __func__); + return; + } + + vif_ctx_zep = if_priv; + + if (!roc_complete) { + LOG_ERR("%s: Missing ROC complete event data", __func__); + return; + } + + LOG_DBG("%s: ROC complete on freq %d, dur %d, vif_idx %d", + __func__, roc_complete->frequency, + roc_complete->dur, vif_ctx_zep->vif_idx); + + if (vif_ctx_zep->supp_drv_if_ctx && vif_ctx_zep->supp_callbk_fns.roc_complete) { + vif_ctx_zep->supp_callbk_fns.roc_complete(vif_ctx_zep->supp_drv_if_ctx, + roc_complete->frequency, + roc_complete->dur); + } +} + +void nrf_wifi_supp_event_roc_cancel_complete(void *if_priv, + struct nrf_wifi_event_remain_on_channel + *roc_cancel_complete, + unsigned int event_len) +{ + struct nrf_wifi_vif_ctx_zep *vif_ctx_zep = NULL; + + if (!if_priv) { + LOG_ERR("%s: Missing interface context", __func__); + return; + } + + vif_ctx_zep = if_priv; + + if (!roc_cancel_complete) { + LOG_ERR("%s: Missing ROC cancel complete event data", __func__); + return; + } + + LOG_DBG("%s: ROC cancel complete on freq %d, vif_idx %d", + __func__, roc_cancel_complete->frequency, + vif_ctx_zep->vif_idx); + + if (vif_ctx_zep->supp_drv_if_ctx && vif_ctx_zep->supp_callbk_fns.roc_cancel_complete) { + vif_ctx_zep->supp_callbk_fns.roc_cancel_complete(vif_ctx_zep->supp_drv_if_ctx, + roc_cancel_complete->frequency); + } +} + +int nrf_wifi_supp_remain_on_channel(void *if_priv, unsigned int freq, + unsigned int duration) +{ + enum nrf_wifi_status status = NRF_WIFI_STATUS_FAIL; +#ifdef NRF70_P2P_MODE + struct nrf_wifi_vif_ctx_zep *vif_ctx_zep = NULL; + struct nrf_wifi_ctx_zep *rpu_ctx_zep = NULL; + struct remain_on_channel_info roc_info; + + if (!if_priv) { + LOG_ERR("%s: Invalid params", __func__); + return -1; + } + + vif_ctx_zep = if_priv; + rpu_ctx_zep = vif_ctx_zep->rpu_ctx_zep; + if (!rpu_ctx_zep) { + LOG_ERR("%s: rpu_ctx_zep is NULL", __func__); + return -1; + } + + k_mutex_lock(&vif_ctx_zep->vif_lock, K_FOREVER); + if (!rpu_ctx_zep->rpu_ctx) { + LOG_DBG("%s: RPU context not initialized", __func__); + goto out; + } + + memset(&roc_info, 0, sizeof(roc_info)); + roc_info.nrf_wifi_freq_params.frequency = freq; + roc_info.nrf_wifi_freq_params.channel_width = NRF_WIFI_CHAN_WIDTH_20; + roc_info.nrf_wifi_freq_params.center_frequency1 = freq; + roc_info.nrf_wifi_freq_params.center_frequency2 = 0; + roc_info.nrf_wifi_freq_params.channel_type = NRF_WIFI_CHAN_HT20; + roc_info.dur = duration; + + status = nrf_wifi_sys_fmac_p2p_roc_start(rpu_ctx_zep->rpu_ctx, vif_ctx_zep->vif_idx, + &roc_info); + if (status != NRF_WIFI_STATUS_SUCCESS) { + LOG_ERR("%s: nrf_wifi_fmac_remain_on_channel failed", __func__); + goto out; + } +out: + k_mutex_unlock(&vif_ctx_zep->vif_lock); +#endif /* NRF70_P2P_MODE */ + return status; +} + +int nrf_wifi_supp_cancel_remain_on_channel(void *if_priv) +{ + enum nrf_wifi_status status = NRF_WIFI_STATUS_FAIL; +#ifdef NRF70_P2P_MODE + struct nrf_wifi_vif_ctx_zep *vif_ctx_zep = NULL; + struct nrf_wifi_ctx_zep *rpu_ctx_zep = NULL; + + if (!if_priv) { + LOG_ERR("%s: Invalid params", __func__); + return -1; + } + + vif_ctx_zep = if_priv; + rpu_ctx_zep = vif_ctx_zep->rpu_ctx_zep; + if (!rpu_ctx_zep) { + LOG_ERR("%s: rpu_ctx_zep is NULL", __func__); + return -1; + } + + k_mutex_lock(&vif_ctx_zep->vif_lock, K_FOREVER); + if (!rpu_ctx_zep->rpu_ctx) { + LOG_DBG("%s: RPU context not initialized", __func__); + goto out; + } + + status = nrf_wifi_sys_fmac_p2p_roc_stop(rpu_ctx_zep->rpu_ctx, vif_ctx_zep->vif_idx, 0); + if (status != NRF_WIFI_STATUS_SUCCESS) { + LOG_ERR("%s: nrf_wifi_fmac_cancel_remain_on_channel failed", __func__); + goto out; + } +out: + k_mutex_unlock(&vif_ctx_zep->vif_lock); +#endif /* NRF70_P2P_MODE */ + return status; +} + #ifdef CONFIG_NRF70_AP_MODE static int nrf_wifi_vif_state_change(struct nrf_wifi_vif_ctx_zep *vif_ctx_zep, enum nrf_wifi_fmac_if_op_state state) From 5792263139ecd97927be603d2590123b8eaf0ca5 Mon Sep 17 00:00:00 2001 From: Ravi Dondaputi Date: Thu, 30 Oct 2025 19:51:45 +0530 Subject: [PATCH 22/47] [nrf fromlist] drivers: wifi: nrf_wifi: Allow off channel TX for probe responses For frames sent down by supplicant in station mode, inform RPU to allow off-channel transmission. This is needed for sending P2P probe responses. Upstream PR #: 97183 Signed-off-by: Ravi Dondaputi --- drivers/wifi/nrf_wifi/src/wpa_supp_if.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/wifi/nrf_wifi/src/wpa_supp_if.c b/drivers/wifi/nrf_wifi/src/wpa_supp_if.c index 3caf7f6af526..a3f562ed138b 100644 --- a/drivers/wifi/nrf_wifi/src/wpa_supp_if.c +++ b/drivers/wifi/nrf_wifi/src/wpa_supp_if.c @@ -1435,6 +1435,10 @@ int nrf_wifi_nl80211_send_mlme(void *if_priv, const u8 *data, goto out; } + if (vif_ctx_zep->if_type == NRF_WIFI_IFTYPE_STATION) { + offchanok = 1; + } + if (offchanok) { mgmt_tx_info->nrf_wifi_flags |= NRF_WIFI_CMD_FRAME_OFFCHANNEL_TX_OK; } From dc40e5882ebac68db1399babe3621aea36d3d430 Mon Sep 17 00:00:00 2001 From: Ravi Dondaputi Date: Thu, 30 Oct 2025 20:04:00 +0530 Subject: [PATCH 23/47] [nrf fromlist] drivers: wifi: nrf_wifi: Register frame without match For frames like Probe Requests, there is no match criterion. Re-arrange the checks to support registering of frames without providing any matching info. Upstream PR #: 97183 Signed-off-by: Ravi Dondaputi --- drivers/wifi/nrf_wifi/src/wpa_supp_if.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/wifi/nrf_wifi/src/wpa_supp_if.c b/drivers/wifi/nrf_wifi/src/wpa_supp_if.c index a3f562ed138b..fabf2df96f87 100644 --- a/drivers/wifi/nrf_wifi/src/wpa_supp_if.c +++ b/drivers/wifi/nrf_wifi/src/wpa_supp_if.c @@ -1702,7 +1702,7 @@ int nrf_wifi_supp_register_frame(void *if_priv, struct nrf_wifi_ctx_zep *rpu_ctx_zep = NULL; struct nrf_wifi_umac_mgmt_frame_info frame_info; - if (!if_priv || !match || !match_len) { + if (!if_priv) { LOG_ERR("%s: Invalid parameters", __func__); return -1; } @@ -1723,8 +1723,14 @@ int nrf_wifi_supp_register_frame(void *if_priv, memset(&frame_info, 0, sizeof(frame_info)); frame_info.frame_type = type; - frame_info.frame_match.frame_match_len = match_len; - memcpy(frame_info.frame_match.frame_match, match, match_len); + if (match_len > 0) { + if (!match) { + LOG_ERR("%s: Invalid match parameters", __func__); + goto out; + } + frame_info.frame_match.frame_match_len = match_len; + memcpy(frame_info.frame_match.frame_match, match, match_len); + } status = nrf_wifi_sys_fmac_register_frame(rpu_ctx_zep->rpu_ctx, vif_ctx_zep->vif_idx, &frame_info); From 1bb6c736f411478b2d7a58cde1b5bbb3bbecb2ce Mon Sep 17 00:00:00 2001 From: Ravi Dondaputi Date: Thu, 30 Oct 2025 20:15:39 +0530 Subject: [PATCH 24/47] [nrf fromlist] modules: hostap: Define heap and stack for P2P support Increase required heap and stack size for P2P. More stack was required during WPS negotiation. Upstream PR #: 97183 Signed-off-by: Ravi Dondaputi --- modules/hostap/Kconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/hostap/Kconfig b/modules/hostap/Kconfig index 9eac239daa7d..ec8263d85a6b 100644 --- a/modules/hostap/Kconfig +++ b/modules/hostap/Kconfig @@ -46,6 +46,7 @@ config HEAP_MEM_POOL_ADD_SIZE_HOSTAP def_int 66560 if WIFI_NM_HOSTAPD_AP def_int 55000 if WIFI_NM_WPA_SUPPLICANT_CRYPTO_ENTERPRISE && WIFI_CREDENTIALS def_int 48000 if WIFI_NM_WPA_SUPPLICANT_CRYPTO_ENTERPRISE + def_int 80000 if WIFI_NM_WPA_SUPPLICANT_P2P def_int 41808 if WIFI_NM_WPA_SUPPLICANT_AP # 30K is mandatory, but might need more for long duration use cases def_int 30000 @@ -54,6 +55,7 @@ endif # WIFI_NM_WPA_SUPPLICANT_GLOBAL_HEAP config WIFI_NM_WPA_SUPPLICANT_THREAD_STACK_SIZE int "Stack size for wpa_supplicant thread" + default 10000 if WIFI_NM_WPA_SUPPLICANT_P2P # TODO: Providing higher stack size for Enterprise mode to fix stack # overflow issues. Need to identify the cause for higher stack usage. default 8192 if WIFI_NM_WPA_SUPPLICANT_CRYPTO_ENTERPRISE From ac14a8bb5a0807b0022669b7f744a0a19918f189 Mon Sep 17 00:00:00 2001 From: Chaitanya Tata Date: Fri, 31 Oct 2025 04:07:05 +0530 Subject: [PATCH 25/47] [nrf fromlist] modules: hostap: Remove obsolete conditional We now support a single MbedTLS shim for hostap, so, this extra check is not needed, we can always use DH5 groups from Mbedtls. Upstream PR #: 97183 Signed-off-by: Chaitanya Tata --- modules/hostap/CMakeLists.txt | 8 -------- 1 file changed, 8 deletions(-) diff --git a/modules/hostap/CMakeLists.txt b/modules/hostap/CMakeLists.txt index 106d31b8cca4..dfa542233eef 100644 --- a/modules/hostap/CMakeLists.txt +++ b/modules/hostap/CMakeLists.txt @@ -385,14 +385,6 @@ zephyr_library_sources_ifdef(CONFIG_WIFI_NM_WPA_SUPPLICANT_WPS ${HOSTAP_SRC_BASE}/crypto/dh_groups.c ) -if(NOT CONFIG_WIFI_NM_WPA_SUPPLICANT_CRYPTO_ALT) -# dh_group5 is only needed if we are not using mbedtls, as mbedtls provides -# its own definition -zephyr_library_sources_ifdef(CONFIG_WIFI_NM_WPA_SUPPLICANT_WPS - ${HOSTAP_SRC_BASE}/crypto/dh_group5.c -) -endif() - zephyr_library_compile_definitions_ifdef(CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P CONFIG_P2P CONFIG_GAS From 28be8f0edb72eea10f62ff1f038e375d18c2bd86 Mon Sep 17 00:00:00 2001 From: Kapil Bhatt Date: Fri, 31 Oct 2025 07:30:40 +0000 Subject: [PATCH 26/47] [nrf fromlist] net: wifi: Add Wi-Fi direct P2P connect API support Add structures and API support for P2P connect. Upstream PR #: 97183 Signed-off-by: Kapil Bhatt --- include/zephyr/net/wifi_mgmt.h | 26 +++++++++++ modules/hostap/src/supp_api.c | 81 ++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) diff --git a/include/zephyr/net/wifi_mgmt.h b/include/zephyr/net/wifi_mgmt.h index ef0a6bac27cf..6cc7039ade66 100644 --- a/include/zephyr/net/wifi_mgmt.h +++ b/include/zephyr/net/wifi_mgmt.h @@ -1465,6 +1465,8 @@ enum wifi_p2p_op { * or specific MAC address to query a single peer */ WIFI_P2P_PEER, + /** P2P connect to peer */ + WIFI_P2P_CONNECT, }; /** Wi-Fi P2P discovery type */ @@ -1477,6 +1479,16 @@ enum wifi_p2p_discovery_type { WIFI_P2P_FIND_PROGRESSIVE, }; +/** Wi-Fi P2P connection method */ +enum wifi_p2p_connection_method { + /** Push Button Configuration */ + WIFI_P2P_METHOD_PBC = 0, + /** Display PIN (device displays PIN for peer to enter) */ + WIFI_P2P_METHOD_DISPLAY, + /** Keypad PIN (user enters PIN on device) */ + WIFI_P2P_METHOD_KEYPAD, +}; + /** Maximum number of P2P peers that can be returned in a single query */ #define WIFI_P2P_MAX_PEERS CONFIG_WIFI_P2P_MAX_PEERS @@ -1496,6 +1508,20 @@ struct wifi_p2p_params { struct wifi_p2p_device_info *peers; /** Actual number of peers returned */ uint16_t peer_count; + /** Connect specific parameters */ + struct { + /** Connection method */ + enum wifi_p2p_connection_method method; + /** PIN for display/keypad methods (8 digits) + * - For DISPLAY: Leave empty, PIN will be generated and returned + * - For KEYPAD: Provide the PIN to use for connection + */ + char pin[WIFI_WPS_PIN_MAX_LEN + 1]; + /** GO intent (0-15, higher values indicate higher willingness to be GO) */ + uint8_t go_intent; + /** Frequency in MHz (0 = not specified, use default) */ + unsigned int freq; + } connect; }; #endif /* CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P */ diff --git a/modules/hostap/src/supp_api.c b/modules/hostap/src/supp_api.c index 8555e91a1cf9..8126b92274e1 100644 --- a/modules/hostap/src/supp_api.c +++ b/modules/hostap/src/supp_api.c @@ -2821,6 +2821,87 @@ int supplicant_p2p_oper(const struct device *dev, struct wifi_p2p_params *params break; } + case WIFI_P2P_CONNECT: { + char addr_str[18]; + const char *method_str = ""; + char freq_str[32] = ""; + + if (!params) { + wpa_printf(MSG_ERROR, "P2P connect params are NULL"); + return -EINVAL; + } + + snprintk(addr_str, sizeof(addr_str), "%02x:%02x:%02x:%02x:%02x:%02x", + params->peer_addr[0], params->peer_addr[1], params->peer_addr[2], + params->peer_addr[3], params->peer_addr[4], params->peer_addr[5]); + + /* Add frequency parameter if specified */ + if (params->connect.freq > 0) { + snprintk(freq_str, sizeof(freq_str), " freq=%u", params->connect.freq); + } + + switch (params->connect.method) { + case WIFI_P2P_METHOD_PBC: + method_str = "pbc"; + snprintk(cmd_buf, sizeof(cmd_buf), "P2P_CONNECT %s %s go_intent=%d%s", + addr_str, method_str, params->connect.go_intent, freq_str); + break; + case WIFI_P2P_METHOD_DISPLAY: + method_str = "pin"; + snprintk(cmd_buf, sizeof(cmd_buf), "P2P_CONNECT %s %s go_intent=%d%s", + addr_str, method_str, params->connect.go_intent, freq_str); + break; + case WIFI_P2P_METHOD_KEYPAD: + method_str = "keypad"; + if (params->connect.pin[0] == '\0') { + wpa_printf(MSG_ERROR, "PIN required for keypad method"); + return -EINVAL; + } + snprintk(cmd_buf, sizeof(cmd_buf), "P2P_CONNECT %s %s %s go_intent=%d%s", + addr_str, method_str, params->connect.pin, + params->connect.go_intent, freq_str); + break; + default: + wpa_printf(MSG_ERROR, "Unknown P2P connection method: %d", + params->connect.method); + return -EINVAL; + } + + ret = zephyr_wpa_cli_cmd_resp_noprint(wpa_s->ctrl_conn, cmd_buf, resp_buf); + if (ret < 0) { + wpa_printf(MSG_ERROR, "P2P_CONNECT command failed: %d", ret); + return -EIO; + } + if (os_strncmp(resp_buf, "FAIL", 4) == 0) { + wpa_printf(MSG_ERROR, "P2P connect failed: %s", resp_buf); + return -ENODEV; + } + + /* For DISPLAY method, capture the generated PIN from response */ + if (params->connect.method == WIFI_P2P_METHOD_DISPLAY) { + size_t len = 0; + char *pos = resp_buf; + + while (*pos == ' ' || *pos == '\t' || *pos == '\n') { + pos++; + } + + while (*pos != '\0' && *pos != '\n' && *pos != ' ' && + len < WIFI_WPS_PIN_MAX_LEN) { + params->connect.pin[len++] = *pos++; + } + params->connect.pin[len] = '\0'; + + if (params->connect.pin[0] == '\0') { + wpa_printf(MSG_ERROR, "P2P connect: No PIN returned"); + return -ENODEV; + } + } + + ret = 0; + break; + } + default: wpa_printf(MSG_ERROR, "Unknown P2P operation: %d", params->oper); ret = -EINVAL; From 717762d023160682950cff360cfd3bbc98012420 Mon Sep 17 00:00:00 2001 From: Kapil Bhatt Date: Wed, 12 Nov 2025 09:53:18 +0000 Subject: [PATCH 27/47] [nrf fromlist] net: wifi: Add Wi-Fi direct P2P connect shell command support Add shell command support for P2P connect. Upstream PR #: 97183 Signed-off-by: Kapil Bhatt --- subsys/net/l2/wifi/wifi_shell.c | 117 ++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/subsys/net/l2/wifi/wifi_shell.c b/subsys/net/l2/wifi/wifi_shell.c index 91e342f81bd0..b468212589ed 100644 --- a/subsys/net/l2/wifi/wifi_shell.c +++ b/subsys/net/l2/wifi/wifi_shell.c @@ -3726,6 +3726,106 @@ static int cmd_wifi_p2p_stop_find(const struct shell *sh, size_t argc, char *arg PR("P2P find stopped\n"); return 0; } + +static int cmd_wifi_p2p_connect(const struct shell *sh, size_t argc, char *argv[]) +{ + struct net_if *iface = get_iface(IFACE_TYPE_STA, argc, argv); + struct wifi_p2p_params params = {0}; + uint8_t mac_addr[WIFI_MAC_ADDR_LEN]; + const char *method_arg = NULL; + int opt; + int opt_index = 0; + struct sys_getopt_state *state; + static const struct sys_getopt_option long_options[] = { + {"go-intent", sys_getopt_required_argument, 0, 'g'}, + {"freq", sys_getopt_required_argument, 0, 'f'}, + {"iface", sys_getopt_required_argument, 0, 'i'}, + {"help", sys_getopt_no_argument, 0, 'h'}, + {0, 0, 0, 0} + }; + long val; + + context.sh = sh; + + if (argc < 3) { + PR_ERROR("Usage: wifi p2p connect [PIN] " + "[--go-intent=<0-15>] [--freq=]\n"); + return -EINVAL; + } + + /* Parse MAC address */ + if (sscanf(argv[1], "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + &mac_addr[0], &mac_addr[1], &mac_addr[2], + &mac_addr[3], &mac_addr[4], &mac_addr[5]) != WIFI_MAC_ADDR_LEN) { + PR_ERROR("Invalid MAC address format. Use: XX:XX:XX:XX:XX:XX\n"); + return -EINVAL; + } + memcpy(params.peer_addr, mac_addr, WIFI_MAC_ADDR_LEN); + + method_arg = argv[2]; + if (strcmp(method_arg, "pbc") == 0) { + params.connect.method = WIFI_P2P_METHOD_PBC; + } else if (strcmp(method_arg, "pin") == 0) { + if (argc > 3 && argv[3][0] != '-') { + params.connect.method = WIFI_P2P_METHOD_KEYPAD; + strncpy(params.connect.pin, argv[3], WIFI_WPS_PIN_MAX_LEN); + params.connect.pin[WIFI_WPS_PIN_MAX_LEN] = '\0'; + } else { + params.connect.method = WIFI_P2P_METHOD_DISPLAY; + params.connect.pin[0] = '\0'; + } + } else { + PR_ERROR("Invalid connection method. Use: pbc or pin\n"); + return -EINVAL; + } + + /* Set default GO intent */ + params.connect.go_intent = 0; + /* Set default frequency to 2462 MHz (channel 11, 2.4 GHz) */ + params.connect.freq = 2462; + + while ((opt = sys_getopt_long(argc, argv, "g:f:i:h", long_options, &opt_index)) != -1) { + state = sys_getopt_state_get(); + switch (opt) { + case 'g': + if (!parse_number(sh, &val, state->optarg, "go-intent", 0, 15)) { + return -EINVAL; + } + params.connect.go_intent = (uint8_t)val; + break; + case 'f': + if (!parse_number(sh, &val, state->optarg, "freq", 0, 6000)) { + return -EINVAL; + } + params.connect.freq = (unsigned int)val; + break; + case 'i': + /* Unused, but parsing to avoid unknown option error */ + break; + case 'h': + shell_help(sh); + return -ENOEXEC; + default: + PR_ERROR("Invalid option %c\n", state->optopt); + return -EINVAL; + } + } + + params.oper = WIFI_P2P_CONNECT; + + if (net_mgmt(NET_REQUEST_WIFI_P2P_OPER, iface, ¶ms, sizeof(params))) { + PR_WARNING("P2P connect request failed\n"); + return -ENOEXEC; + } + + /* Display the generated PIN for DISPLAY method */ + if (params.connect.method == WIFI_P2P_METHOD_DISPLAY && params.connect.pin[0] != '\0') { + PR("%s\n", params.connect.pin); + } else { + PR("P2P connection initiated\n"); + } + return 0; +} #endif /* CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P */ static int cmd_wifi_pmksa_flush(const struct shell *sh, size_t argc, char *argv[]) @@ -4455,6 +4555,23 @@ SHELL_STATIC_SUBCMD_SET_CREATE( ": Show detailed info for specific peer (format: XX:XX:XX:XX:XX:XX)\n" "[-i, --iface=]: Interface index\n", cmd_wifi_p2p_peer, 1, 3), + SHELL_CMD_ARG(connect, NULL, + "Connect to a P2P peer\n" + "Usage: connect [PIN] [options]\n" + ": Peer device MAC address (format: XX:XX:XX:XX:XX:XX)\n" + ": Use Push Button Configuration\n" + ": Use PIN method\n" + " - Without PIN: Device displays generated PIN for peer to enter\n" + " - With PIN: Device uses provided PIN to connect\n" + "[PIN]: 8-digit PIN (optional, generates if omitted)\n" + "[-g, --go-intent=<0-15>]: GO intent (0=client, 15=GO, default: 0)\n" + "[-f, --freq=]: Frequency in MHz (default: 2462)\n" + "[-i, --iface=]: Interface index\n" + "[-h, --help]: Show help\n" + "Examples:\n" + " wifi p2p connect 9c:b1:50:e3:81:96 pin -g 0 (displays PIN)\n" + " wifi p2p connect 9c:b1:50:e3:81:96 pin 12345670 -g 0 (uses PIN)\n", + cmd_wifi_p2p_connect, 3, 5), SHELL_SUBCMD_SET_END ); From 1c97878c11cfec0c2c5a5820d6832ef1b5f58c9d Mon Sep 17 00:00:00 2001 From: Kapil Bhatt Date: Fri, 7 Nov 2025 08:33:54 +0000 Subject: [PATCH 28/47] [nrf fromlist] drivers: nrf_wifi: Add default value to p2p mode Kconfig The Kconfig NRF70_P2P_MODE should be enabled when WIFI_NM_WPA_SUPPLICANT_P2P is enabled. Upstream PR #: 97183 Signed-off-by: Kapil Bhatt --- drivers/wifi/nrf_wifi/Kconfig.nrfwifi | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/wifi/nrf_wifi/Kconfig.nrfwifi b/drivers/wifi/nrf_wifi/Kconfig.nrfwifi index 2021d25580ae..be0ea854b2e2 100644 --- a/drivers/wifi/nrf_wifi/Kconfig.nrfwifi +++ b/drivers/wifi/nrf_wifi/Kconfig.nrfwifi @@ -119,6 +119,7 @@ config NRF70_ENABLE_DUAL_VIF config NRF70_P2P_MODE bool "P2P support in driver" + default y if WIFI_NM_WPA_SUPPLICANT_P2P config NRF70_SYSTEM_WITH_RAW_MODES bool "nRF70 system mode with raw modes" From 7a2c9a020670b421dc1dd13d4521c58718f12ae4 Mon Sep 17 00:00:00 2001 From: Ravi Dondaputi Date: Wed, 12 Nov 2025 12:32:52 +0530 Subject: [PATCH 29/47] [nrf fromlist] modules: hostap: Add support for P2P GO mode ops Enable build time configs required for supporting P2P GO mode. Upstream PR #: 97183 Signed-off-by: Ravi Dondaputi --- modules/hostap/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/hostap/CMakeLists.txt b/modules/hostap/CMakeLists.txt index dfa542233eef..42ccd291fb10 100644 --- a/modules/hostap/CMakeLists.txt +++ b/modules/hostap/CMakeLists.txt @@ -383,6 +383,9 @@ zephyr_library_sources_ifdef(CONFIG_WIFI_NM_WPA_SUPPLICANT_WPS ${HOSTAP_SRC_BASE}/wps/wps_enrollee.c ${HOSTAP_SRC_BASE}/wps/wps_registrar.c ${HOSTAP_SRC_BASE}/crypto/dh_groups.c + ${HOSTAP_SRC_BASE}/eap_server/eap_server_wsc.c + ${HOSTAP_SRC_BASE}/eap_server/eap_server.c + ${HOSTAP_SRC_BASE}/eap_server/eap_server_methods.c ) zephyr_library_compile_definitions_ifdef(CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P @@ -394,6 +397,7 @@ zephyr_library_compile_definitions_ifdef(CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P zephyr_library_compile_definitions_ifdef(CONFIG_WIFI_NM_WPA_SUPPLICANT_WPS CONFIG_WPS EAP_WSC + EAP_SERVER_WSC ) zephyr_library_sources_ifdef(CONFIG_WIFI_NM_HOSTAPD_WPS From ab1201ef66b732de9ea72f8335dad1fd8d6ce108 Mon Sep 17 00:00:00 2001 From: Ravi Dondaputi Date: Wed, 12 Nov 2025 13:00:08 +0530 Subject: [PATCH 30/47] [nrf fromlist] drivers: wifi: nrf_wifi: Add per-peer authorized flag Add per-peer authorized parameter. Port authorization command from supplicant will set this flag and will be used by driver to allow or nor allow data traffic. Upstream PR #: 97183 Signed-off-by: Ravi Dondaputi --- drivers/wifi/nrf_wifi/inc/fmac_main.h | 3 --- drivers/wifi/nrf_wifi/src/net_if.c | 15 +++++++++-- drivers/wifi/nrf_wifi/src/wifi_mgmt.c | 32 ++++++++++++++++++------ drivers/wifi/nrf_wifi/src/wpa_supp_if.c | 33 +++++++++++++++++++++++-- 4 files changed, 69 insertions(+), 14 deletions(-) diff --git a/drivers/wifi/nrf_wifi/inc/fmac_main.h b/drivers/wifi/nrf_wifi/inc/fmac_main.h index 799beff1fbaf..be8c9d763679 100644 --- a/drivers/wifi/nrf_wifi/inc/fmac_main.h +++ b/drivers/wifi/nrf_wifi/inc/fmac_main.h @@ -75,9 +75,6 @@ struct nrf_wifi_vif_ctx_zep { #endif /* CONFIG_NET_STATISTICS_ETHERNET_VENDOR */ struct net_stats_eth eth_stats; #endif /* CONFIG_NET_STATISTICS_ETHERNET */ -#if defined(CONFIG_NRF70_STA_MODE) || defined(CONFIG_NRF70_RAW_DATA_RX) - bool authorized; -#endif #ifdef CONFIG_NRF70_STA_MODE unsigned int assoc_freq; enum nrf_wifi_fmac_if_carr_state if_carr_state; diff --git a/drivers/wifi/nrf_wifi/src/net_if.c b/drivers/wifi/nrf_wifi/src/net_if.c index 0662c6c80d09..1e85c2e77626 100644 --- a/drivers/wifi/nrf_wifi/src/net_if.c +++ b/drivers/wifi/nrf_wifi/src/net_if.c @@ -25,6 +25,7 @@ LOG_MODULE_DECLARE(wifi_nrf, CONFIG_WIFI_NRF70_LOG_LEVEL); #include "util.h" #include "common/fmac_util.h" +#include "system/fmac_peer.h" #include "shim.h" #include "fmac_main.h" #include "wpa_supp_if.h" @@ -388,6 +389,8 @@ int nrf_wifi_if_send(const struct device *dev, struct rpu_host_stats *host_stats = NULL; void *nbuf = NULL; bool locked = false; + unsigned char *ra = NULL; + int peer_id = -1; if (!dev || !pkt) { LOG_ERR("%s: vif_ctx_zep is NULL", __func__); @@ -436,12 +439,20 @@ int nrf_wifi_if_send(const struct device *dev, nbuf); } else { #endif /* CONFIG_NRF70_RAW_DATA_TX */ + + ra = nrf_wifi_util_get_ra(sys_dev_ctx->vif_ctx[vif_ctx_zep->vif_idx], nbuf); + peer_id = nrf_wifi_fmac_peer_get_id(rpu_ctx_zep->rpu_ctx, ra); + if (peer_id == -1) { + nrf_wifi_osal_log_dbg("%s: Invalid peer", + __func__); + goto out; + } + if ((vif_ctx_zep->if_carr_state != NRF_WIFI_FMAC_IF_CARR_STATE_ON) || - (!vif_ctx_zep->authorized && !is_eapol(pkt))) { + (!sys_dev_ctx->tx_config.peers[peer_id].authorized && !is_eapol(pkt))) { ret = -EPERM; goto drop; } - ret = nrf_wifi_fmac_start_xmit(rpu_ctx_zep->rpu_ctx, vif_ctx_zep->vif_idx, nbuf); diff --git a/drivers/wifi/nrf_wifi/src/wifi_mgmt.c b/drivers/wifi/nrf_wifi/src/wifi_mgmt.c index 6bb31241b295..4c98ef0d8aa0 100644 --- a/drivers/wifi/nrf_wifi/src/wifi_mgmt.c +++ b/drivers/wifi/nrf_wifi/src/wifi_mgmt.c @@ -18,6 +18,7 @@ #include "system/fmac_api.h" #include "system/fmac_tx.h" #include "common/fmac_util.h" +#include "common/fmac_structs_common.h" #include "fmac_main.h" #include "wifi_mgmt.h" @@ -757,6 +758,8 @@ int nrf_wifi_mode(const struct device *dev, struct nrf_wifi_vif_ctx_zep *vif_ctx_zep = NULL; struct nrf_wifi_fmac_dev_ctx *fmac_dev_ctx = NULL; struct nrf_wifi_sys_fmac_dev_ctx *sys_dev_ctx = NULL; + struct peers_info *peer = NULL; + int i = 0; int ret = -1; if (!dev || !mode) { @@ -798,10 +801,16 @@ int nrf_wifi_mode(const struct device *dev, goto out; } - if (vif_ctx_zep->authorized && (mode->mode == NRF_WIFI_MONITOR_MODE)) { - LOG_ERR("%s: Cannot set monitor mode when station is connected", - __func__); - goto out; + for (i = 0; i < MAX_PEERS; i++) { + peer = &sys_dev_ctx->tx_config.peers[i]; + if (peer->peer_id == -1) { + continue; + } + if (peer->authorized && (mode->mode == NRF_WIFI_MONITOR_MODE)) { + LOG_ERR("%s: Cannot set monitor mode when station is connected", + __func__); + goto out; + } } /** @@ -851,6 +860,8 @@ int nrf_wifi_channel(const struct device *dev, struct nrf_wifi_vif_ctx_zep *vif_ctx_zep = NULL; struct nrf_wifi_sys_fmac_dev_ctx *sys_dev_ctx = NULL; struct nrf_wifi_fmac_dev_ctx *fmac_dev_ctx = NULL; + struct peers_info *peer = NULL; + int i = 0; int ret = -1; if (!dev || !channel) { @@ -864,9 +875,16 @@ int nrf_wifi_channel(const struct device *dev, return ret; } - if (vif_ctx_zep->authorized) { - LOG_ERR("%s: Cannot change channel when in station connected mode", __func__); - return ret; + for (i = 0; i < MAX_PEERS; i++) { + peer = &sys_dev_ctx->tx_config.peers[i]; + if (peer->peer_id == -1) { + continue; + } + if (peer->authorized) { + LOG_ERR("%s: Cannot change channel when in station connected mode", + __func__); + return ret; + } } rpu_ctx_zep = vif_ctx_zep->rpu_ctx_zep; diff --git a/drivers/wifi/nrf_wifi/src/wpa_supp_if.c b/drivers/wifi/nrf_wifi/src/wpa_supp_if.c index fabf2df96f87..a15d66276c7a 100644 --- a/drivers/wifi/nrf_wifi/src/wpa_supp_if.c +++ b/drivers/wifi/nrf_wifi/src/wpa_supp_if.c @@ -17,6 +17,7 @@ #include "common/fmac_util.h" #include "wifi_mgmt.h" #include "wpa_supp_if.h" +#include LOG_MODULE_DECLARE(wifi_nrf, CONFIG_WIFI_NRF70_LOG_LEVEL); @@ -1103,7 +1104,9 @@ int nrf_wifi_wpa_set_supp_port(void *if_priv, int authorized, char *bssid) struct nrf_wifi_vif_ctx_zep *vif_ctx_zep = NULL; struct nrf_wifi_umac_chg_sta_info chg_sta_info; struct nrf_wifi_ctx_zep *rpu_ctx_zep = NULL; + struct nrf_wifi_sys_fmac_dev_ctx *sys_dev_ctx = NULL; enum nrf_wifi_status status = NRF_WIFI_STATUS_FAIL; + int peer_id = -1; int ret = -1; if (!if_priv || !bssid) { @@ -1134,8 +1137,6 @@ int nrf_wifi_wpa_set_supp_port(void *if_priv, int authorized, char *bssid) memcpy(chg_sta_info.mac_addr, bssid, ETH_ALEN); - vif_ctx_zep->authorized = authorized; - if (authorized) { /* BIT(NL80211_STA_FLAG_AUTHORIZED) */ chg_sta_info.sta_flags2.nrf_wifi_mask = 1 << 1; @@ -1153,6 +1154,19 @@ int nrf_wifi_wpa_set_supp_port(void *if_priv, int authorized, char *bssid) goto out; } + sys_dev_ctx = wifi_dev_priv(rpu_ctx_zep->rpu_ctx); + + peer_id = nrf_wifi_fmac_peer_get_id(rpu_ctx_zep->rpu_ctx, chg_sta_info.mac_addr); + if (peer_id == -1) { + nrf_wifi_osal_log_err("%s: Invalid peer", + __func__); + goto out; + } + + if (chg_sta_info.sta_flags2.nrf_wifi_set & NRF_WIFI_STA_FLAG_AUTHORIZED) { + sys_dev_ctx->tx_config.peers[peer_id].authorized = true; + } + ret = 0; out: k_mutex_unlock(&vif_ctx_zep->vif_lock); @@ -2939,7 +2953,9 @@ int nrf_wifi_wpa_supp_sta_set_flags(void *if_priv, const u8 *addr, struct nrf_wifi_vif_ctx_zep *vif_ctx_zep = NULL; struct nrf_wifi_umac_chg_sta_info chg_sta = {0}; struct nrf_wifi_ctx_zep *rpu_ctx_zep = NULL; + struct nrf_wifi_sys_fmac_dev_ctx *sys_dev_ctx = NULL; enum nrf_wifi_status status = NRF_WIFI_STATUS_FAIL; + int peer_id = -1; int ret = -1; if (!if_priv || !addr) { @@ -2974,6 +2990,19 @@ int nrf_wifi_wpa_supp_sta_set_flags(void *if_priv, const u8 *addr, goto out; } + sys_dev_ctx = wifi_dev_priv(rpu_ctx_zep->rpu_ctx); + + peer_id = nrf_wifi_fmac_peer_get_id(rpu_ctx_zep->rpu_ctx, chg_sta.mac_addr); + if (peer_id == -1) { + nrf_wifi_osal_log_err("%s: Invalid peer", + __func__); + goto out; + } + + if (chg_sta.sta_flags2.nrf_wifi_set & NRF_WIFI_STA_FLAG_AUTHORIZED) { + sys_dev_ctx->tx_config.peers[peer_id].authorized = true; + } + ret = 0; out: From b596326b6e04ee0659b43d394621bd0ada3caa1a Mon Sep 17 00:00:00 2001 From: Kapil Bhatt Date: Thu, 13 Nov 2025 09:09:07 +0000 Subject: [PATCH 31/47] [nrf fromlist] net: wifi: Add API support for P2P GO mode Add structures and API support for P2P Go mode. Upstream PR #: 97183 Signed-off-by: Kapil Bhatt --- include/zephyr/net/wifi_mgmt.h | 50 ++++++++++++ modules/hostap/src/supp_api.c | 141 +++++++++++++++++++++++++++++++++ 2 files changed, 191 insertions(+) diff --git a/include/zephyr/net/wifi_mgmt.h b/include/zephyr/net/wifi_mgmt.h index 6cc7039ade66..389ab70d7e7e 100644 --- a/include/zephyr/net/wifi_mgmt.h +++ b/include/zephyr/net/wifi_mgmt.h @@ -1467,6 +1467,12 @@ enum wifi_p2p_op { WIFI_P2P_PEER, /** P2P connect to peer */ WIFI_P2P_CONNECT, + /** P2P group add */ + WIFI_P2P_GROUP_ADD, + /** P2P group remove */ + WIFI_P2P_GROUP_REMOVE, + /** P2P invite */ + WIFI_P2P_INVITE, }; /** Wi-Fi P2P discovery type */ @@ -1522,6 +1528,50 @@ struct wifi_p2p_params { /** Frequency in MHz (0 = not specified, use default) */ unsigned int freq; } connect; + /** Group add specific parameters */ + struct { + /** Frequency in MHz (0 = auto) */ + int freq; + /** Persistent group ID (-1 = not persistent) */ + int persistent; + /** Enable HT40 */ + bool ht40; + /** Enable VHT */ + bool vht; + /** Enable HE */ + bool he; + /** Enable EDMG */ + bool edmg; + /** GO BSSID (NULL = auto) */ + uint8_t go_bssid[WIFI_MAC_ADDR_LEN]; + /** GO BSSID length */ + uint8_t go_bssid_length; + } group_add; + /** Group remove specific parameters */ + struct { + /** Interface name (e.g., "wlan0") */ + char ifname[CONFIG_NET_INTERFACE_NAME_LEN + 1]; + } group_remove; + /** Invite specific parameters */ + struct { + /** Invite type: persistent or group */ + enum { + WIFI_P2P_INVITE_PERSISTENT = 0, + WIFI_P2P_INVITE_GROUP, + } type; + /** Persistent group ID (for persistent type) */ + int persistent_id; + /** Group interface name (for group type) */ + char group_ifname[CONFIG_NET_INTERFACE_NAME_LEN + 1]; + /** Peer MAC address */ + uint8_t peer_addr[WIFI_MAC_ADDR_LEN]; + /** Frequency in MHz (0 = auto) */ + int freq; + /** GO device address (for group type, NULL = auto) */ + uint8_t go_dev_addr[WIFI_MAC_ADDR_LEN]; + /** GO device address length */ + uint8_t go_dev_addr_length; + } invite; }; #endif /* CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P */ diff --git a/modules/hostap/src/supp_api.c b/modules/hostap/src/supp_api.c index 8126b92274e1..2532a11f865e 100644 --- a/modules/hostap/src/supp_api.c +++ b/modules/hostap/src/supp_api.c @@ -2902,6 +2902,147 @@ int supplicant_p2p_oper(const struct device *dev, struct wifi_p2p_params *params break; } + case WIFI_P2P_GROUP_ADD: { + int len = 0; + + if (!params) { + wpa_printf(MSG_ERROR, "P2P group add params are NULL"); + return -EINVAL; + } + + len = snprintk(cmd_buf, sizeof(cmd_buf), "P2P_GROUP_ADD"); + + if (params->group_add.freq > 0) { + len += snprintk(cmd_buf + len, sizeof(cmd_buf) - len, " freq=%d", + params->group_add.freq); + } + + if (params->group_add.persistent >= 0) { + len += snprintk(cmd_buf + len, sizeof(cmd_buf) - len, " persistent=%d", + params->group_add.persistent); + } + + if (params->group_add.ht40) { + len += snprintk(cmd_buf + len, sizeof(cmd_buf) - len, " ht40"); + } + + if (params->group_add.vht) { + len += snprintk(cmd_buf + len, sizeof(cmd_buf) - len, " vht"); + } + + if (params->group_add.he) { + len += snprintk(cmd_buf + len, sizeof(cmd_buf) - len, " he"); + } + + if (params->group_add.edmg) { + len += snprintk(cmd_buf + len, sizeof(cmd_buf) - len, " edmg"); + } + + if (params->group_add.go_bssid_length == WIFI_MAC_ADDR_LEN) { + len += snprintk(cmd_buf + len, sizeof(cmd_buf) - len, + " go_bssid=%02x:%02x:%02x:%02x:%02x:%02x", + params->group_add.go_bssid[0], + params->group_add.go_bssid[1], + params->group_add.go_bssid[2], + params->group_add.go_bssid[3], + params->group_add.go_bssid[4], + params->group_add.go_bssid[5]); + } + + ret = zephyr_wpa_cli_cmd_resp_noprint(wpa_s->ctrl_conn, cmd_buf, resp_buf); + if (ret < 0) { + wpa_printf(MSG_ERROR, "P2P_GROUP_ADD command failed: %d", ret); + return -EIO; + } + ret = 0; + break; + } + + case WIFI_P2P_GROUP_REMOVE: + if (!params) { + wpa_printf(MSG_ERROR, "P2P group remove params are NULL"); + return -EINVAL; + } + + if (params->group_remove.ifname[0] == '\0') { + wpa_printf(MSG_ERROR, "Interface name required for P2P_GROUP_REMOVE"); + return -EINVAL; + } + + snprintk(cmd_buf, sizeof(cmd_buf), "P2P_GROUP_REMOVE %s", + params->group_remove.ifname); + + ret = zephyr_wpa_cli_cmd_resp_noprint(wpa_s->ctrl_conn, cmd_buf, resp_buf); + if (ret < 0) { + wpa_printf(MSG_ERROR, "P2P_GROUP_REMOVE command failed: %d", ret); + return -EIO; + } + ret = 0; + break; + + case WIFI_P2P_INVITE: { + char addr_str[18]; + int len = 0; + + if (!params) { + wpa_printf(MSG_ERROR, "P2P invite params are NULL"); + return -EINVAL; + } + + snprintk(addr_str, sizeof(addr_str), "%02x:%02x:%02x:%02x:%02x:%02x", + params->invite.peer_addr[0], params->invite.peer_addr[1], + params->invite.peer_addr[2], params->invite.peer_addr[3], + params->invite.peer_addr[4], params->invite.peer_addr[5]); + + if (params->invite.type == WIFI_P2P_INVITE_PERSISTENT) { + if (params->invite.persistent_id < 0) { + wpa_printf(MSG_ERROR, "Persistent group ID required"); + return -EINVAL; + } + len = snprintk(cmd_buf, sizeof(cmd_buf), "P2P_INVITE persistent=%d peer=%s", + params->invite.persistent_id, addr_str); + + if (params->invite.freq > 0) { + len += snprintk(cmd_buf + len, sizeof(cmd_buf) - len, " freq=%d", + params->invite.freq); + } + } else if (params->invite.type == WIFI_P2P_INVITE_GROUP) { + if (params->invite.group_ifname[0] == '\0') { + wpa_printf(MSG_ERROR, "Group interface name required"); + return -EINVAL; + } + len = snprintk(cmd_buf, sizeof(cmd_buf), "P2P_INVITE group=%s peer=%s", + params->invite.group_ifname, addr_str); + + if (params->invite.freq > 0) { + len += snprintk(cmd_buf + len, sizeof(cmd_buf) - len, " freq=%d", + params->invite.freq); + } + + if (params->invite.go_dev_addr_length == WIFI_MAC_ADDR_LEN) { + len += snprintk(cmd_buf + len, sizeof(cmd_buf) - len, + " go_dev_addr=%02x:%02x:%02x:%02x:%02x:%02x", + params->invite.go_dev_addr[0], + params->invite.go_dev_addr[1], + params->invite.go_dev_addr[2], + params->invite.go_dev_addr[3], + params->invite.go_dev_addr[4], + params->invite.go_dev_addr[5]); + } + } else { + wpa_printf(MSG_ERROR, "Invalid invite type: %d", params->invite.type); + return -EINVAL; + } + + ret = zephyr_wpa_cli_cmd_resp_noprint(wpa_s->ctrl_conn, cmd_buf, resp_buf); + if (ret < 0) { + wpa_printf(MSG_ERROR, "P2P_INVITE command failed: %d", ret); + return -EIO; + } + ret = 0; + break; + } + default: wpa_printf(MSG_ERROR, "Unknown P2P operation: %d", params->oper); ret = -EINVAL; From 81c7aee45e81bafb5ca3d0ccf50fa7bbaa74be35 Mon Sep 17 00:00:00 2001 From: Kapil Bhatt Date: Thu, 13 Nov 2025 09:10:42 +0000 Subject: [PATCH 32/47] [nrf fromlist] net: wifi: Add Wi-Fi direct P2P GO mode shell command Add shell commands support for P2P GO mode. Upstream PR #: 97183 Signed-off-by: Kapil Bhatt --- subsys/net/l2/wifi/wifi_shell.c | 279 ++++++++++++++++++++++++++++++++ 1 file changed, 279 insertions(+) diff --git a/subsys/net/l2/wifi/wifi_shell.c b/subsys/net/l2/wifi/wifi_shell.c index b468212589ed..71ff72eff5dc 100644 --- a/subsys/net/l2/wifi/wifi_shell.c +++ b/subsys/net/l2/wifi/wifi_shell.c @@ -3826,6 +3826,256 @@ static int cmd_wifi_p2p_connect(const struct shell *sh, size_t argc, char *argv[ } return 0; } + +static int cmd_wifi_p2p_group_add(const struct shell *sh, size_t argc, char *argv[]) +{ + struct net_if *iface = get_iface(IFACE_TYPE_STA, argc, argv); + struct wifi_p2p_params params = {0}; + int opt; + int opt_index = 0; + struct sys_getopt_state *state; + static const struct sys_getopt_option long_options[] = { + {"freq", sys_getopt_required_argument, 0, 'f'}, + {"persistent", sys_getopt_required_argument, 0, 'p'}, + {"ht40", sys_getopt_no_argument, 0, 'h'}, + {"vht", sys_getopt_no_argument, 0, 'v'}, + {"he", sys_getopt_no_argument, 0, 'H'}, + {"edmg", sys_getopt_no_argument, 0, 'e'}, + {"go-bssid", sys_getopt_required_argument, 0, 'b'}, + {"iface", sys_getopt_required_argument, 0, 'i'}, + {"help", sys_getopt_no_argument, 0, '?'}, + {0, 0, 0, 0} + }; + long val; + uint8_t mac_addr[WIFI_MAC_ADDR_LEN]; + + context.sh = sh; + + params.oper = WIFI_P2P_GROUP_ADD; + params.group_add.freq = 0; + params.group_add.persistent = -1; + params.group_add.ht40 = false; + params.group_add.vht = false; + params.group_add.he = false; + params.group_add.edmg = false; + params.group_add.go_bssid_length = 0; + + while ((opt = sys_getopt_long(argc, argv, "f:p:hvHeb:i:?", long_options, + &opt_index)) != -1) { + state = sys_getopt_state_get(); + switch (opt) { + case 'f': + if (!parse_number(sh, &val, state->optarg, "freq", 0, 6000)) { + return -EINVAL; + } + params.group_add.freq = (int)val; + break; + case 'p': + if (!parse_number(sh, &val, state->optarg, "persistent", -1, 255)) { + return -EINVAL; + } + params.group_add.persistent = (int)val; + break; + case 'h': + params.group_add.ht40 = true; + break; + case 'v': + params.group_add.vht = true; + params.group_add.ht40 = true; + break; + case 'H': + params.group_add.he = true; + break; + case 'e': + params.group_add.edmg = true; + break; + case 'b': + if (sscanf(state->optarg, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + &mac_addr[0], &mac_addr[1], &mac_addr[2], &mac_addr[3], + &mac_addr[4], &mac_addr[5]) != WIFI_MAC_ADDR_LEN) { + PR_ERROR("Invalid GO BSSID format. Use: XX:XX:XX:XX:XX:XX\n"); + return -EINVAL; + } + memcpy(params.group_add.go_bssid, mac_addr, WIFI_MAC_ADDR_LEN); + params.group_add.go_bssid_length = WIFI_MAC_ADDR_LEN; + break; + case 'i': + /* Unused, but parsing to avoid unknown option error */ + break; + case '?': + shell_help(sh); + return -ENOEXEC; + default: + PR_ERROR("Invalid option %c\n", state->optopt); + return -EINVAL; + } + } + + if (net_mgmt(NET_REQUEST_WIFI_P2P_OPER, iface, ¶ms, sizeof(params))) { + PR_WARNING("P2P group add request failed\n"); + return -ENOEXEC; + } + PR("P2P group add initiated\n"); + return 0; +} + +static int cmd_wifi_p2p_group_remove(const struct shell *sh, size_t argc, char *argv[]) +{ + struct net_if *iface = get_iface(IFACE_TYPE_STA, argc, argv); + struct wifi_p2p_params params = {0}; + + context.sh = sh; + + if (argc < 2) { + PR_ERROR("Interface name required. Usage: wifi p2p group_remove \n"); + return -EINVAL; + } + + params.oper = WIFI_P2P_GROUP_REMOVE; + strncpy(params.group_remove.ifname, argv[1], + CONFIG_NET_INTERFACE_NAME_LEN); + params.group_remove.ifname[CONFIG_NET_INTERFACE_NAME_LEN] = '\0'; + + if (net_mgmt(NET_REQUEST_WIFI_P2P_OPER, iface, ¶ms, sizeof(params))) { + PR_WARNING("P2P group remove request failed\n"); + return -ENOEXEC; + } + PR("P2P group remove initiated\n"); + return 0; +} + +static int cmd_wifi_p2p_invite(const struct shell *sh, size_t argc, char *argv[]) +{ + struct net_if *iface = get_iface(IFACE_TYPE_STA, argc, argv); + struct wifi_p2p_params params = {0}; + uint8_t mac_addr[WIFI_MAC_ADDR_LEN]; + int opt; + int opt_index = 0; + struct sys_getopt_state *state; + static const struct sys_getopt_option long_options[] = { + {"persistent", sys_getopt_required_argument, 0, 'p'}, + {"group", sys_getopt_required_argument, 0, 'g'}, + {"peer", sys_getopt_required_argument, 0, 'P'}, + {"freq", sys_getopt_required_argument, 0, 'f'}, + {"go-dev-addr", sys_getopt_required_argument, 0, 'd'}, + {"iface", sys_getopt_required_argument, 0, 'i'}, + {"help", sys_getopt_no_argument, 0, 'h'}, + {0, 0, 0, 0} + }; + long val; + + context.sh = sh; + + params.oper = WIFI_P2P_INVITE; + params.invite.type = WIFI_P2P_INVITE_PERSISTENT; + params.invite.persistent_id = -1; + params.invite.group_ifname[0] = '\0'; + params.invite.freq = 0; + params.invite.go_dev_addr_length = 0; + memset(params.invite.peer_addr, 0, WIFI_MAC_ADDR_LEN); + + if (argc < 2) { + PR_ERROR("Usage: wifi p2p invite --persistent= OR " + "wifi p2p invite --group= --peer= [options]\n"); + return -EINVAL; + } + + while ((opt = sys_getopt_long(argc, argv, "p:g:P:f:d:i:h", long_options, + &opt_index)) != -1) { + state = sys_getopt_state_get(); + switch (opt) { + case 'p': + if (!parse_number(sh, &val, state->optarg, "persistent", 0, 255)) { + return -EINVAL; + } + params.invite.type = WIFI_P2P_INVITE_PERSISTENT; + params.invite.persistent_id = (int)val; + break; + case 'g': + params.invite.type = WIFI_P2P_INVITE_GROUP; + strncpy(params.invite.group_ifname, state->optarg, + CONFIG_NET_INTERFACE_NAME_LEN); + params.invite.group_ifname[CONFIG_NET_INTERFACE_NAME_LEN] = '\0'; + break; + case 'P': + if (sscanf(state->optarg, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + &mac_addr[0], &mac_addr[1], &mac_addr[2], &mac_addr[3], + &mac_addr[4], &mac_addr[5]) != WIFI_MAC_ADDR_LEN) { + PR_ERROR("Invalid peer MAC address format\n"); + return -EINVAL; + } + memcpy(params.invite.peer_addr, mac_addr, WIFI_MAC_ADDR_LEN); + break; + case 'f': + if (!parse_number(sh, &val, state->optarg, "freq", 0, 6000)) { + return -EINVAL; + } + params.invite.freq = (int)val; + break; + case 'd': + if (sscanf(state->optarg, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + &mac_addr[0], &mac_addr[1], &mac_addr[2], &mac_addr[3], + &mac_addr[4], &mac_addr[5]) != WIFI_MAC_ADDR_LEN) { + PR_ERROR("Invalid GO device address format\n"); + return -EINVAL; + } + memcpy(params.invite.go_dev_addr, mac_addr, WIFI_MAC_ADDR_LEN); + params.invite.go_dev_addr_length = WIFI_MAC_ADDR_LEN; + break; + case 'i': + /* Unused, but parsing to avoid unknown option error */ + break; + case 'h': + shell_help(sh); + return -ENOEXEC; + default: + PR_ERROR("Invalid option %c\n", state->optopt); + return -EINVAL; + } + } + + state = sys_getopt_state_get(); + + if (params.invite.type == WIFI_P2P_INVITE_PERSISTENT && + params.invite.persistent_id >= 0 && + params.invite.peer_addr[0] == 0 && params.invite.peer_addr[1] == 0 && + argc > state->optind && argv[state->optind][0] != '-') { + if (sscanf(argv[state->optind], "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + &mac_addr[0], &mac_addr[1], &mac_addr[2], &mac_addr[3], + &mac_addr[4], &mac_addr[5]) != WIFI_MAC_ADDR_LEN) { + PR_ERROR("Invalid peer MAC address format\n"); + return -EINVAL; + } + memcpy(params.invite.peer_addr, mac_addr, WIFI_MAC_ADDR_LEN); + } + + if (params.invite.type == WIFI_P2P_INVITE_PERSISTENT) { + if (params.invite.persistent_id < 0) { + PR_ERROR("Persistent group ID required. Use --persistent=\n"); + return -EINVAL; + } + if (params.invite.peer_addr[0] == 0 && params.invite.peer_addr[1] == 0) { + PR_ERROR("Peer MAC address required\n"); + return -EINVAL; + } + } else if (params.invite.type == WIFI_P2P_INVITE_GROUP) { + if (params.invite.group_ifname[0] == '\0') { + PR_ERROR("Group interface name required. Use --group=\n"); + return -EINVAL; + } + if (params.invite.peer_addr[0] == 0 && params.invite.peer_addr[1] == 0) { + PR_ERROR("Peer MAC address required. Use --peer=\n"); + return -EINVAL; + } + } + + if (net_mgmt(NET_REQUEST_WIFI_P2P_OPER, iface, ¶ms, sizeof(params))) { + PR_WARNING("P2P invite request failed\n"); + return -ENOEXEC; + } + PR("P2P invite initiated\n"); + return 0; +} #endif /* CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P */ static int cmd_wifi_pmksa_flush(const struct shell *sh, size_t argc, char *argv[]) @@ -4572,6 +4822,35 @@ SHELL_STATIC_SUBCMD_SET_CREATE( " wifi p2p connect 9c:b1:50:e3:81:96 pin -g 0 (displays PIN)\n" " wifi p2p connect 9c:b1:50:e3:81:96 pin 12345670 -g 0 (uses PIN)\n", cmd_wifi_p2p_connect, 3, 5), + SHELL_CMD_ARG(group_add, NULL, + "Add a P2P group (start as GO)\n" + "Usage: group_add [options]\n" + "[-f, --freq=]: Frequency in MHz (0 = auto)\n" + "[-p, --persistent=]: Persistent group ID (-1 = not persistent)\n" + "[-h, --ht40]: Enable HT40\n" + "[-v, --vht]: Enable VHT (also enables HT40)\n" + "[-H, --he]: Enable HE\n" + "[-e, --edmg]: Enable EDMG\n" + "[-b, --go-bssid=]: GO BSSID (format: XX:XX:XX:XX:XX:XX)\n" + "[-i, --iface=]: Interface index\n", + cmd_wifi_p2p_group_add, 1, 10), + SHELL_CMD_ARG(group_remove, NULL, + "Remove a P2P group\n" + "Usage: group_remove \n" + ": Interface name (e.g., wlan0)\n" + "[-i, --iface=]: Interface index\n", + cmd_wifi_p2p_group_remove, 2, 3), + SHELL_CMD_ARG(invite, NULL, + "Invite a peer to a P2P group\n" + "Usage: invite --persistent= OR\n" + " invite --group= --peer= [options]\n" + "[-p, --persistent=]: Persistent group ID\n" + "[-g, --group=]: Group interface name\n" + "[-P, --peer=]: Peer MAC address (format: XX:XX:XX:XX:XX:XX)\n" + "[-f, --freq=]: Frequency in MHz (0 = auto)\n" + "[-d, --go-dev-addr=]: GO device address (for group type)\n" + "[-i, --iface=]: Interface index\n", + cmd_wifi_p2p_invite, 2, 8), SHELL_SUBCMD_SET_END ); From 7bae0bf2eca46dba08edcb0bfd15b50cfa70f52b Mon Sep 17 00:00:00 2001 From: Ravi Dondaputi Date: Mon, 17 Nov 2025 23:32:15 +0530 Subject: [PATCH 33/47] [nrf fromlist] drivers: wifi: nrf_wifi: Add P2P powersave support Add ops to handle P2P powersave configuration. Upstream PR #: 97183 Signed-off-by: Ravi Dondaputi --- drivers/wifi/nrf_wifi/inc/wpa_supp_if.h | 1 + drivers/wifi/nrf_wifi/src/fmac_main.c | 1 + drivers/wifi/nrf_wifi/src/wpa_supp_if.c | 45 +++++++++++++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/drivers/wifi/nrf_wifi/inc/wpa_supp_if.h b/drivers/wifi/nrf_wifi/inc/wpa_supp_if.h index 28aadae28ebd..b4ec7545be49 100644 --- a/drivers/wifi/nrf_wifi/inc/wpa_supp_if.h +++ b/drivers/wifi/nrf_wifi/inc/wpa_supp_if.h @@ -133,6 +133,7 @@ int nrf_wifi_supp_set_country(void *if_priv, const char *alpha2); int nrf_wifi_supp_get_country(void *if_priv, char *alpha2); int nrf_wifi_supp_remain_on_channel(void *if_priv, unsigned int freq, unsigned int duration); int nrf_wifi_supp_cancel_remain_on_channel(void *if_priv); +int nrf_wifi_supp_set_p2p_powersave(void *if_priv, int legacy_ps, int opp_ps, int ctwindow); #endif /* CONFIG_NRF70_STA_MODE */ #ifdef CONFIG_NRF70_AP_MODE diff --git a/drivers/wifi/nrf_wifi/src/fmac_main.c b/drivers/wifi/nrf_wifi/src/fmac_main.c index 00ea3a873c85..8aaac5c2b964 100644 --- a/drivers/wifi/nrf_wifi/src/fmac_main.c +++ b/drivers/wifi/nrf_wifi/src/fmac_main.c @@ -967,6 +967,7 @@ static const struct zep_wpa_supp_dev_ops wpa_supp_ops = { .get_country = nrf_wifi_supp_get_country, .remain_on_channel = nrf_wifi_supp_remain_on_channel, .cancel_remain_on_channel = nrf_wifi_supp_cancel_remain_on_channel, + .set_p2p_powersave = nrf_wifi_supp_set_p2p_powersave, #ifdef CONFIG_NRF70_AP_MODE .init_ap = nrf_wifi_wpa_supp_init_ap, .start_ap = nrf_wifi_wpa_supp_start_ap, diff --git a/drivers/wifi/nrf_wifi/src/wpa_supp_if.c b/drivers/wifi/nrf_wifi/src/wpa_supp_if.c index a15d66276c7a..1106f3c05808 100644 --- a/drivers/wifi/nrf_wifi/src/wpa_supp_if.c +++ b/drivers/wifi/nrf_wifi/src/wpa_supp_if.c @@ -2151,6 +2151,51 @@ int nrf_wifi_supp_cancel_remain_on_channel(void *if_priv) return status; } +int nrf_wifi_supp_set_p2p_powersave(void *if_priv, int legacy_ps, int opp_ps, int ctwindow) +{ + enum nrf_wifi_status status = NRF_WIFI_STATUS_FAIL; +#ifdef NRF70_P2P_MODE + struct nrf_wifi_vif_ctx_zep *vif_ctx_zep = NULL; + struct nrf_wifi_ctx_zep *rpu_ctx_zep = NULL; + + if (!if_priv) { + LOG_ERR("%s: Invalid params", __func__); + return -1; + } + vif_ctx_zep = if_priv; + rpu_ctx_zep = vif_ctx_zep->rpu_ctx_zep; + if (!rpu_ctx_zep) { + LOG_ERR("%s: rpu_ctx_zep is NULL", __func__); + return -1; + } + k_mutex_lock(&vif_ctx_zep->vif_lock, K_FOREVER); + if (!rpu_ctx_zep->rpu_ctx) { + LOG_DBG("%s: RPU context not initialized", __func__); + goto out; + } + + if (legacy_ps == -1) { + status = 0; + goto out; + } + + if (legacy_ps != 0 && legacy_ps != 1) { + LOG_ERR("%s: Invalid legacy_ps value: %d", __func__, legacy_ps); + goto out; + } + + status = nrf_wifi_sys_fmac_set_power_save(rpu_ctx_zep->rpu_ctx, vif_ctx_zep->vif_idx, + legacy_ps); + if (status != NRF_WIFI_STATUS_SUCCESS) { + LOG_ERR("%s: nrf_wifi_fmac_set_p2p_powersave failed", __func__); + goto out; + } +out: + k_mutex_unlock(&vif_ctx_zep->vif_lock); +#endif /* NRF70_P2P_MODE */ + return status; +} + #ifdef CONFIG_NRF70_AP_MODE static int nrf_wifi_vif_state_change(struct nrf_wifi_vif_ctx_zep *vif_ctx_zep, enum nrf_wifi_fmac_if_op_state state) From 34649e6475db036802c2a6e863f0e685a9d3a584 Mon Sep 17 00:00:00 2001 From: Ravi Dondaputi Date: Mon, 17 Nov 2025 23:33:01 +0530 Subject: [PATCH 34/47] [nrf fromlist] drivers: wifi: nrf_wifi: Add cookie handling support Add cookie event callbacks to track RoC and cancel-RoC requests and its responses from firmware. Upstream PR #: 97183 Signed-off-by: Ravi Dondaputi --- drivers/wifi/nrf_wifi/inc/wpa_supp_if.h | 5 +++-- drivers/wifi/nrf_wifi/src/fmac_main.c | 5 +++++ drivers/wifi/nrf_wifi/src/wpa_supp_if.c | 11 ++++++----- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/drivers/wifi/nrf_wifi/inc/wpa_supp_if.h b/drivers/wifi/nrf_wifi/inc/wpa_supp_if.h index b4ec7545be49..321705308d1a 100644 --- a/drivers/wifi/nrf_wifi/inc/wpa_supp_if.h +++ b/drivers/wifi/nrf_wifi/inc/wpa_supp_if.h @@ -131,8 +131,9 @@ void nrf_wifi_supp_event_roc_cancel_complete(void *os_vif_ctx, unsigned int event_len); int nrf_wifi_supp_set_country(void *if_priv, const char *alpha2); int nrf_wifi_supp_get_country(void *if_priv, char *alpha2); -int nrf_wifi_supp_remain_on_channel(void *if_priv, unsigned int freq, unsigned int duration); -int nrf_wifi_supp_cancel_remain_on_channel(void *if_priv); +int nrf_wifi_supp_remain_on_channel(void *if_priv, unsigned int freq, + unsigned int duration, u64 host_cookie); +int nrf_wifi_supp_cancel_remain_on_channel(void *if_priv, u64 rpu_cookie); int nrf_wifi_supp_set_p2p_powersave(void *if_priv, int legacy_ps, int opp_ps, int ctwindow); #endif /* CONFIG_NRF70_STA_MODE */ diff --git a/drivers/wifi/nrf_wifi/src/fmac_main.c b/drivers/wifi/nrf_wifi/src/fmac_main.c index 8aaac5c2b964..9e8ae2aafb93 100644 --- a/drivers/wifi/nrf_wifi/src/fmac_main.c +++ b/drivers/wifi/nrf_wifi/src/fmac_main.c @@ -452,6 +452,11 @@ void nrf_wifi_event_proc_cookie_rsp(void *vif_ctx, /* TODO: When supp_callbk_fns.mgmt_tx_status is implemented, add logic * here to use the cookie and host_cookie to map requests to responses. */ + if (vif_ctx_zep->supp_drv_if_ctx && + vif_ctx_zep->supp_callbk_fns.cookie_event) { + vif_ctx_zep->supp_callbk_fns.cookie_event(vif_ctx_zep->supp_drv_if_ctx, + cookie_rsp_event->host_cookie, cookie_rsp_event->cookie); + } } #endif /* CONFIG_NRF70_STA_MODE */ diff --git a/drivers/wifi/nrf_wifi/src/wpa_supp_if.c b/drivers/wifi/nrf_wifi/src/wpa_supp_if.c index 1106f3c05808..0a9ab6c93998 100644 --- a/drivers/wifi/nrf_wifi/src/wpa_supp_if.c +++ b/drivers/wifi/nrf_wifi/src/wpa_supp_if.c @@ -2035,7 +2035,7 @@ void nrf_wifi_supp_event_remain_on_channel(void *if_priv, if (vif_ctx_zep->supp_drv_if_ctx && vif_ctx_zep->supp_callbk_fns.roc_complete) { vif_ctx_zep->supp_callbk_fns.roc_complete(vif_ctx_zep->supp_drv_if_ctx, roc_complete->frequency, - roc_complete->dur); + roc_complete->dur, roc_complete->cookie); } } @@ -2064,12 +2064,12 @@ void nrf_wifi_supp_event_roc_cancel_complete(void *if_priv, if (vif_ctx_zep->supp_drv_if_ctx && vif_ctx_zep->supp_callbk_fns.roc_cancel_complete) { vif_ctx_zep->supp_callbk_fns.roc_cancel_complete(vif_ctx_zep->supp_drv_if_ctx, - roc_cancel_complete->frequency); + roc_cancel_complete->frequency, roc_cancel_complete->cookie); } } int nrf_wifi_supp_remain_on_channel(void *if_priv, unsigned int freq, - unsigned int duration) + unsigned int duration, u64 host_cookie) { enum nrf_wifi_status status = NRF_WIFI_STATUS_FAIL; #ifdef NRF70_P2P_MODE @@ -2102,6 +2102,7 @@ int nrf_wifi_supp_remain_on_channel(void *if_priv, unsigned int freq, roc_info.nrf_wifi_freq_params.center_frequency2 = 0; roc_info.nrf_wifi_freq_params.channel_type = NRF_WIFI_CHAN_HT20; roc_info.dur = duration; + roc_info.host_cookie = host_cookie; status = nrf_wifi_sys_fmac_p2p_roc_start(rpu_ctx_zep->rpu_ctx, vif_ctx_zep->vif_idx, &roc_info); @@ -2115,7 +2116,7 @@ int nrf_wifi_supp_remain_on_channel(void *if_priv, unsigned int freq, return status; } -int nrf_wifi_supp_cancel_remain_on_channel(void *if_priv) +int nrf_wifi_supp_cancel_remain_on_channel(void *if_priv, u64 cookie) { enum nrf_wifi_status status = NRF_WIFI_STATUS_FAIL; #ifdef NRF70_P2P_MODE @@ -2140,7 +2141,7 @@ int nrf_wifi_supp_cancel_remain_on_channel(void *if_priv) goto out; } - status = nrf_wifi_sys_fmac_p2p_roc_stop(rpu_ctx_zep->rpu_ctx, vif_ctx_zep->vif_idx, 0); + status = nrf_wifi_sys_fmac_p2p_roc_stop(rpu_ctx_zep->rpu_ctx, vif_ctx_zep->vif_idx, cookie); if (status != NRF_WIFI_STATUS_SUCCESS) { LOG_ERR("%s: nrf_wifi_fmac_cancel_remain_on_channel failed", __func__); goto out; From 581a16460a4a6cb5dfa6a73d5f18cfc64d2b70d3 Mon Sep 17 00:00:00 2001 From: Ravi Dondaputi Date: Mon, 17 Nov 2025 23:37:14 +0530 Subject: [PATCH 35/47] [nrf fromlist] manifest: hostap: Pull in P2P powersave support Add P2P power save and cookie handling support. Upstream PR #: 97183 Signed-off-by: Ravi Dondaputi --- west.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/west.yml b/west.yml index c85c2f2b2a16..8eb988998a78 100644 --- a/west.yml +++ b/west.yml @@ -281,7 +281,7 @@ manifest: - hal - name: hostap path: modules/lib/hostap - revision: 51698b0f5fdac2778484f565d4591fcb1dd92bc4 + revision: 528eb2d95e35c7c9b187a15d2fb7f6e5bb983181 - name: liblc3 revision: 48bbd3eacd36e99a57317a0a4867002e0b09e183 path: modules/lib/liblc3 From af83bf9475c59cdda4726a582e56b25e19cc3f55 Mon Sep 17 00:00:00 2001 From: Kapil Bhatt Date: Tue, 18 Nov 2025 12:39:11 +0000 Subject: [PATCH 36/47] [nrf fromlist] net: wifi: Add API support for P2P power save Add API support for P2P power save. Upstream PR #: 97183 Signed-off-by: Kapil Bhatt --- include/zephyr/net/wifi_mgmt.h | 4 ++++ modules/hostap/src/supp_api.c | 14 ++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/include/zephyr/net/wifi_mgmt.h b/include/zephyr/net/wifi_mgmt.h index 389ab70d7e7e..434e27941965 100644 --- a/include/zephyr/net/wifi_mgmt.h +++ b/include/zephyr/net/wifi_mgmt.h @@ -1473,6 +1473,8 @@ enum wifi_p2p_op { WIFI_P2P_GROUP_REMOVE, /** P2P invite */ WIFI_P2P_INVITE, + /** P2P power save */ + WIFI_P2P_POWER_SAVE, }; /** Wi-Fi P2P discovery type */ @@ -1514,6 +1516,8 @@ struct wifi_p2p_params { struct wifi_p2p_device_info *peers; /** Actual number of peers returned */ uint16_t peer_count; + /** Power save enabled (for power save operation) */ + bool power_save; /** Connect specific parameters */ struct { /** Connection method */ diff --git a/modules/hostap/src/supp_api.c b/modules/hostap/src/supp_api.c index 2532a11f865e..04c9b7d81e01 100644 --- a/modules/hostap/src/supp_api.c +++ b/modules/hostap/src/supp_api.c @@ -3043,6 +3043,20 @@ int supplicant_p2p_oper(const struct device *dev, struct wifi_p2p_params *params break; } + case WIFI_P2P_POWER_SAVE: + snprintk(cmd_buf, sizeof(cmd_buf), "p2p_set ps %d", params->power_save ? 1 : 0); + ret = zephyr_wpa_cli_cmd_resp_noprint(wpa_s->ctrl_conn, cmd_buf, resp_buf); + if (ret < 0) { + wpa_printf(MSG_ERROR, "p2p_set ps command failed: %d", ret); + return -EIO; + } + if (os_strncmp(resp_buf, "FAIL", 4) == 0) { + wpa_printf(MSG_ERROR, "p2p_set ps command returned FAIL"); + return -EIO; + } + ret = 0; + break; + default: wpa_printf(MSG_ERROR, "Unknown P2P operation: %d", params->oper); ret = -EINVAL; From b5152120acbd93303beba4b3d537a6f24ff12d23 Mon Sep 17 00:00:00 2001 From: Kapil Bhatt Date: Tue, 18 Nov 2025 12:40:59 +0000 Subject: [PATCH 37/47] [nrf fromlist] net: wifi: Add P2P power save shell command support Add shell command support for P2P power save. Upstream PR #: 97183 Signed-off-by: Kapil Bhatt --- subsys/net/l2/wifi/wifi_shell.c | 41 +++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/subsys/net/l2/wifi/wifi_shell.c b/subsys/net/l2/wifi/wifi_shell.c index 71ff72eff5dc..220b8bee7633 100644 --- a/subsys/net/l2/wifi/wifi_shell.c +++ b/subsys/net/l2/wifi/wifi_shell.c @@ -4076,6 +4076,40 @@ static int cmd_wifi_p2p_invite(const struct shell *sh, size_t argc, char *argv[] PR("P2P invite initiated\n"); return 0; } + +static int cmd_wifi_p2p_power_save(const struct shell *sh, size_t argc, char *argv[]) +{ + struct net_if *iface = get_iface(IFACE_TYPE_STA, argc, argv); + struct wifi_p2p_params params = {0}; + bool power_save_enable = false; + + context.sh = sh; + + if (argc < 2) { + PR_ERROR("Usage: wifi p2p power_save \n"); + return -EINVAL; + } + + if (strcmp(argv[1], "on") == 0) { + power_save_enable = true; + } else if (strcmp(argv[1], "off") == 0) { + power_save_enable = false; + } else { + PR_ERROR("Invalid argument. Use 'on' or 'off'\n"); + return -EINVAL; + } + + params.oper = WIFI_P2P_POWER_SAVE; + params.power_save = power_save_enable; + + if (net_mgmt(NET_REQUEST_WIFI_P2P_OPER, iface, ¶ms, sizeof(params))) { + PR_WARNING("P2P power save request failed\n"); + return -ENOEXEC; + } + + PR("P2P power save %s\n", power_save_enable ? "enabled" : "disabled"); + return 0; +} #endif /* CONFIG_WIFI_NM_WPA_SUPPLICANT_P2P */ static int cmd_wifi_pmksa_flush(const struct shell *sh, size_t argc, char *argv[]) @@ -4851,6 +4885,13 @@ SHELL_STATIC_SUBCMD_SET_CREATE( "[-d, --go-dev-addr=]: GO device address (for group type)\n" "[-i, --iface=]: Interface index\n", cmd_wifi_p2p_invite, 2, 8), + SHELL_CMD_ARG(power_save, NULL, + "Set P2P power save mode\n" + "Usage: power_save \n" + ": Enable P2P power save\n" + ": Disable P2P power save\n" + "[-i, --iface=]: Interface index\n", + cmd_wifi_p2p_power_save, 2, 3), SHELL_SUBCMD_SET_END ); From 3cb4be2dc3b1acc7ccabfed84f345ce3df02b91a Mon Sep 17 00:00:00 2001 From: Ravi Dondaputi Date: Mon, 24 Nov 2025 19:26:58 +0530 Subject: [PATCH 38/47] [nrf fromlist] drivers: wifi: nrf_wifi: Suppress 11b rates in P2P scan Add an identifier to P2P scan request. RPU can use this to differentiate it from regular scan requests and suppress 11b rates. Upstream PR #: 97183 Signed-off-by: Ravi Dondaputi --- drivers/wifi/nrf_wifi/src/wpa_supp_if.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/wifi/nrf_wifi/src/wpa_supp_if.c b/drivers/wifi/nrf_wifi/src/wpa_supp_if.c index 0a9ab6c93998..26893f2214e2 100644 --- a/drivers/wifi/nrf_wifi/src/wpa_supp_if.c +++ b/drivers/wifi/nrf_wifi/src/wpa_supp_if.c @@ -559,6 +559,10 @@ int nrf_wifi_wpa_supp_scan2(void *if_priv, struct wpa_driver_scan_params *params } } + if (params->p2p_probe) { + scan_info->scan_params.no_cck = 1; + } + scan_info->scan_reason = SCAN_CONNECT; /* Copy extra_ies */ From 50f8814b838eff94b63b1eebefed01a7e96fc6cb Mon Sep 17 00:00:00 2001 From: Chaitanya Tata Date: Tue, 25 Nov 2025 03:56:28 +0530 Subject: [PATCH 39/47] [nrf noup] net: l2: wifi: Fix getopt handling gettopt now has its own namespace in upstream, but the full POSIX namespacing is not pulled to NCS and is deemed too risky, so, this is a "noup" patch to partially rever the gettop commit [1] and reinstate old behaviour for Wi-Fi shell. [1] - https://github.com/zephyrproject-rtos/zephyr/commit/22f9ef0a3386e02bdcd9779d0eb68a3008af5bc6 Signed-off-by: Chaitanya Tata --- subsys/net/l2/wifi/wifi_shell.c | 82 ++++++++++++++++----------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/subsys/net/l2/wifi/wifi_shell.c b/subsys/net/l2/wifi/wifi_shell.c index 220b8bee7633..f27fe5a6ffcd 100644 --- a/subsys/net/l2/wifi/wifi_shell.c +++ b/subsys/net/l2/wifi/wifi_shell.c @@ -3651,19 +3651,19 @@ static int cmd_wifi_p2p_find(const struct shell *sh, size_t argc, char *argv[]) if (argc > 1) { int opt; int opt_index = 0; - struct sys_getopt_state *state; - static const struct sys_getopt_option long_options[] = { - {"timeout", sys_getopt_required_argument, 0, 't'}, - {"type", sys_getopt_required_argument, 0, 'T'}, - {"iface", sys_getopt_required_argument, 0, 'i'}, - {"help", sys_getopt_no_argument, 0, 'h'}, + struct getopt_state *state; + static const struct option long_options[] = { + {"timeout", required_argument, 0, 't'}, + {"type", required_argument, 0, 'T'}, + {"iface", required_argument, 0, 'i'}, + {"help", no_argument, 0, 'h'}, {0, 0, 0, 0} }; long val; - while ((opt = sys_getopt_long(argc, argv, "t:T:i:h", + while ((opt = getopt_long(argc, argv, "t:T:i:h", long_options, &opt_index)) != -1) { - state = sys_getopt_state_get(); + state = getopt_state_get(); switch (opt) { case 't': if (!parse_number(sh, &val, state->optarg, "timeout", 0, 65535)) { @@ -3735,12 +3735,12 @@ static int cmd_wifi_p2p_connect(const struct shell *sh, size_t argc, char *argv[ const char *method_arg = NULL; int opt; int opt_index = 0; - struct sys_getopt_state *state; - static const struct sys_getopt_option long_options[] = { - {"go-intent", sys_getopt_required_argument, 0, 'g'}, - {"freq", sys_getopt_required_argument, 0, 'f'}, - {"iface", sys_getopt_required_argument, 0, 'i'}, - {"help", sys_getopt_no_argument, 0, 'h'}, + struct getopt_state *state; + static const struct option long_options[] = { + {"go-intent", required_argument, 0, 'g'}, + {"freq", required_argument, 0, 'f'}, + {"iface", required_argument, 0, 'i'}, + {"help", no_argument, 0, 'h'}, {0, 0, 0, 0} }; long val; @@ -3784,8 +3784,8 @@ static int cmd_wifi_p2p_connect(const struct shell *sh, size_t argc, char *argv[ /* Set default frequency to 2462 MHz (channel 11, 2.4 GHz) */ params.connect.freq = 2462; - while ((opt = sys_getopt_long(argc, argv, "g:f:i:h", long_options, &opt_index)) != -1) { - state = sys_getopt_state_get(); + while ((opt = getopt_long(argc, argv, "g:f:i:h", long_options, &opt_index)) != -1) { + state = getopt_state_get(); switch (opt) { case 'g': if (!parse_number(sh, &val, state->optarg, "go-intent", 0, 15)) { @@ -3833,17 +3833,17 @@ static int cmd_wifi_p2p_group_add(const struct shell *sh, size_t argc, char *arg struct wifi_p2p_params params = {0}; int opt; int opt_index = 0; - struct sys_getopt_state *state; - static const struct sys_getopt_option long_options[] = { - {"freq", sys_getopt_required_argument, 0, 'f'}, - {"persistent", sys_getopt_required_argument, 0, 'p'}, - {"ht40", sys_getopt_no_argument, 0, 'h'}, - {"vht", sys_getopt_no_argument, 0, 'v'}, - {"he", sys_getopt_no_argument, 0, 'H'}, - {"edmg", sys_getopt_no_argument, 0, 'e'}, - {"go-bssid", sys_getopt_required_argument, 0, 'b'}, - {"iface", sys_getopt_required_argument, 0, 'i'}, - {"help", sys_getopt_no_argument, 0, '?'}, + struct getopt_state *state; + static const struct option long_options[] = { + {"freq", required_argument, 0, 'f'}, + {"persistent", required_argument, 0, 'p'}, + {"ht40", no_argument, 0, 'h'}, + {"vht", no_argument, 0, 'v'}, + {"he", no_argument, 0, 'H'}, + {"edmg", no_argument, 0, 'e'}, + {"go-bssid", required_argument, 0, 'b'}, + {"iface", required_argument, 0, 'i'}, + {"help", no_argument, 0, '?'}, {0, 0, 0, 0} }; long val; @@ -3860,9 +3860,9 @@ static int cmd_wifi_p2p_group_add(const struct shell *sh, size_t argc, char *arg params.group_add.edmg = false; params.group_add.go_bssid_length = 0; - while ((opt = sys_getopt_long(argc, argv, "f:p:hvHeb:i:?", long_options, + while ((opt = getopt_long(argc, argv, "f:p:hvHeb:i:?", long_options, &opt_index)) != -1) { - state = sys_getopt_state_get(); + state = getopt_state_get(); switch (opt) { case 'f': if (!parse_number(sh, &val, state->optarg, "freq", 0, 6000)) { @@ -3951,15 +3951,15 @@ static int cmd_wifi_p2p_invite(const struct shell *sh, size_t argc, char *argv[] uint8_t mac_addr[WIFI_MAC_ADDR_LEN]; int opt; int opt_index = 0; - struct sys_getopt_state *state; - static const struct sys_getopt_option long_options[] = { - {"persistent", sys_getopt_required_argument, 0, 'p'}, - {"group", sys_getopt_required_argument, 0, 'g'}, - {"peer", sys_getopt_required_argument, 0, 'P'}, - {"freq", sys_getopt_required_argument, 0, 'f'}, - {"go-dev-addr", sys_getopt_required_argument, 0, 'd'}, - {"iface", sys_getopt_required_argument, 0, 'i'}, - {"help", sys_getopt_no_argument, 0, 'h'}, + struct getopt_state *state; + static const struct option long_options[] = { + {"persistent", required_argument, 0, 'p'}, + {"group", required_argument, 0, 'g'}, + {"peer", required_argument, 0, 'P'}, + {"freq", required_argument, 0, 'f'}, + {"go-dev-addr", required_argument, 0, 'd'}, + {"iface", required_argument, 0, 'i'}, + {"help", no_argument, 0, 'h'}, {0, 0, 0, 0} }; long val; @@ -3980,9 +3980,9 @@ static int cmd_wifi_p2p_invite(const struct shell *sh, size_t argc, char *argv[] return -EINVAL; } - while ((opt = sys_getopt_long(argc, argv, "p:g:P:f:d:i:h", long_options, + while ((opt = getopt_long(argc, argv, "p:g:P:f:d:i:h", long_options, &opt_index)) != -1) { - state = sys_getopt_state_get(); + state = getopt_state_get(); switch (opt) { case 'p': if (!parse_number(sh, &val, state->optarg, "persistent", 0, 255)) { @@ -4034,7 +4034,7 @@ static int cmd_wifi_p2p_invite(const struct shell *sh, size_t argc, char *argv[] } } - state = sys_getopt_state_get(); + state = getopt_state_get(); if (params.invite.type == WIFI_P2P_INVITE_PERSISTENT && params.invite.persistent_id >= 0 && From 71e98e71aaea7ef3c3178d6b2ece57e1f9d941fd Mon Sep 17 00:00:00 2001 From: Pieter De Gendt Date: Fri, 14 Nov 2025 08:36:22 +0100 Subject: [PATCH 40/47] [nrf fromtree] doc: releases: 4.4: Add NVMEM entry for flash device support Add an entry with Kconfig options for NVMEM on flash devices. Signed-off-by: Pieter De Gendt (cherry picked from commit 8439d0d9171fd96cd5122405ee9fa72ec2a95ad9) --- doc/releases/release-notes-4.4.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/releases/release-notes-4.4.rst b/doc/releases/release-notes-4.4.rst index 9aa8719c7edc..a5e1e49dd9c3 100644 --- a/doc/releases/release-notes-4.4.rst +++ b/doc/releases/release-notes-4.4.rst @@ -102,6 +102,13 @@ New APIs and options * :dtcompatible:`jedec,mspi-nor` now allows MSPI configuration of read, write and control commands separately via devicetree. +* NVMEM + + * Flash device support + + * :kconfig:option:`CONFIG_NVMEM_FLASH` + * :kconfig:option:`CONFIG_NVMEM_FLASH_WRITE` + * Settings * :kconfig:option:`CONFIG_SETTINGS_SAVE_SINGLE_SUBTREE_WITHOUT_MODIFICATION` From 6c41de4643c0a373c11b031c8fb3571f7cb4dfe4 Mon Sep 17 00:00:00 2001 From: Kapil Bhatt Date: Fri, 7 Nov 2025 09:08:30 +0000 Subject: [PATCH 41/47] [nrf fromlist] doc: networking: Add Wi-Fi P2P info Add Wi-Fi P2P mode build command and info. Upstream PR #: 97183 Signed-off-by: Kapil Bhatt --- doc/connectivity/networking/api/wifi.rst | 14 ++++++++++++++ doc/releases/release-notes-4.4.rst | 6 ++++++ 2 files changed, 20 insertions(+) diff --git a/doc/connectivity/networking/api/wifi.rst b/doc/connectivity/networking/api/wifi.rst index e0be44835443..e50a1f9fc74b 100644 --- a/doc/connectivity/networking/api/wifi.rst +++ b/doc/connectivity/networking/api/wifi.rst @@ -10,6 +10,7 @@ The Wi-Fi management API is used to manage Wi-Fi networks. It supports below mod * IEEE802.11 Station (STA) * IEEE802.11 Access Point (AP) +* IEEE802.11 P2P (Wi-Fi Direct) Only personal mode security is supported with below types: @@ -215,6 +216,19 @@ The test certificates in ``samples/net/wifi/test_certs/rsa2k`` are generated usi .. note:: These certificates are for testing only and should not be used in production. +Wi-Fi P2P (Wi-Fi Direct) +************************ + +Wi-Fi P2P or Wi-Fi Direct enables devices to communicate directly with each other without requiring +a traditional access point. This feature is particularly useful for device-to-device communication +scenarios. + +To enable and build with Wi-Fi P2P support: + +.. code-block:: bash + + $ west build -p -b samples/net/wifi/shell -- -DCONFIG_WIFI_NM_WPA_SUPPLICANT_P2P=y + API Reference ************* diff --git a/doc/releases/release-notes-4.4.rst b/doc/releases/release-notes-4.4.rst index a5e1e49dd9c3..b74d1670107e 100644 --- a/doc/releases/release-notes-4.4.rst +++ b/doc/releases/release-notes-4.4.rst @@ -109,6 +109,12 @@ New APIs and options * :kconfig:option:`CONFIG_NVMEM_FLASH` * :kconfig:option:`CONFIG_NVMEM_FLASH_WRITE` +* Networking + + * Wi-Fi + + * Add support for Wi-Fi Direct (P2P) mode. + * Settings * :kconfig:option:`CONFIG_SETTINGS_SAVE_SINGLE_SUBTREE_WITHOUT_MODIFICATION` From 07cbdf572c70a40b7d0326ff5fec031f40352342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Mo=C5=84?= Date: Fri, 24 Oct 2025 10:22:18 +0200 Subject: [PATCH 42/47] [nrf fromtree] usb: device_next: msc: stall endpoints on enqueue error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When endpoint enqueue fails the device has no reliable means of recovery other than a reset. Implement 6.6.2 Internal Device Error handling on failed enqueue. Signed-off-by: Tomasz Moń (cherry picked from commit 78291d4fc2ed82f64a70d478417b285ec8ea8876) --- subsys/usb/device_next/class/usbd_msc.c | 56 +++++++++++++++---------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/subsys/usb/device_next/class/usbd_msc.c b/subsys/usb/device_next/class/usbd_msc.c index 6e6c75f671e9..ee5784ae00ca 100644 --- a/subsys/usb/device_next/class/usbd_msc.c +++ b/subsys/usb/device_next/class/usbd_msc.c @@ -214,6 +214,31 @@ static uint8_t msc_get_bulk_out(struct usbd_class_data *const c_data) return desc->if0_out_ep.bEndpointAddress; } +static void msc_stall_bulk_out_ep(struct usbd_class_data *const c_data) +{ + uint8_t ep; + + ep = msc_get_bulk_out(c_data); + usbd_ep_set_halt(usbd_class_get_ctx(c_data), ep); +} + +static void msc_stall_bulk_in_ep(struct usbd_class_data *const c_data) +{ + uint8_t ep; + + ep = msc_get_bulk_in(c_data); + usbd_ep_set_halt(usbd_class_get_ctx(c_data), ep); +} + +static void msc_stall_and_wait_for_recovery(struct msc_bot_ctx *ctx) +{ + atomic_set_bit(&ctx->bits, MSC_BULK_IN_WEDGED); + atomic_set_bit(&ctx->bits, MSC_BULK_OUT_WEDGED); + msc_stall_bulk_in_ep(ctx->class_node); + msc_stall_bulk_out_ep(ctx->class_node); + ctx->state = MSC_BBB_WAIT_FOR_RESET_RECOVERY; +} + static void msc_queue_bulk_out_ep(struct usbd_class_data *const c_data, bool data) { struct msc_bot_ctx *ctx = usbd_class_get_private(c_data); @@ -246,25 +271,11 @@ static void msc_queue_bulk_out_ep(struct usbd_class_data *const c_data, bool dat LOG_ERR("Failed to enqueue net_buf for 0x%02x", ep); net_buf_unref(buf); atomic_clear_bit(&ctx->bits, MSC_BULK_OUT_QUEUED); + /* 6.6.2 Internal Device Error */ + msc_stall_and_wait_for_recovery(ctx); } } -static void msc_stall_bulk_out_ep(struct usbd_class_data *const c_data) -{ - uint8_t ep; - - ep = msc_get_bulk_out(c_data); - usbd_ep_set_halt(usbd_class_get_ctx(c_data), ep); -} - -static void msc_stall_bulk_in_ep(struct usbd_class_data *const c_data) -{ - uint8_t ep; - - ep = msc_get_bulk_in(c_data); - usbd_ep_set_halt(usbd_class_get_ctx(c_data), ep); -} - static void msc_reset_handler(struct usbd_class_data *c_data) { struct msc_bot_ctx *ctx = usbd_class_get_private(c_data); @@ -343,6 +354,8 @@ static void msc_process_read(struct msc_bot_ctx *ctx) LOG_ERR("Failed to enqueue net_buf for 0x%02x", ep); net_buf_unref(buf); atomic_clear_bit(&ctx->bits, MSC_BULK_IN_QUEUED); + /* 6.6.2 Internal Device Error */ + msc_stall_and_wait_for_recovery(ctx); } } @@ -500,11 +513,7 @@ static void msc_handle_bulk_out(struct msc_bot_ctx *ctx, } else { /* 6.6.1 CBW Not Valid */ LOG_INF("Invalid CBW"); - atomic_set_bit(&ctx->bits, MSC_BULK_IN_WEDGED); - atomic_set_bit(&ctx->bits, MSC_BULK_OUT_WEDGED); - msc_stall_bulk_in_ep(ctx->class_node); - msc_stall_bulk_out_ep(ctx->class_node); - ctx->state = MSC_BBB_WAIT_FOR_RESET_RECOVERY; + msc_stall_and_wait_for_recovery(ctx); } } else if (ctx->state == MSC_BBB_PROCESS_WRITE) { msc_process_write(ctx, buf, len); @@ -567,8 +576,11 @@ static void msc_send_csw(struct msc_bot_ctx *ctx) LOG_ERR("Failed to enqueue net_buf for 0x%02x", ep); net_buf_unref(buf); atomic_clear_bit(&ctx->bits, MSC_BULK_IN_QUEUED); + /* 6.6.2 Internal Device Error */ + msc_stall_and_wait_for_recovery(ctx); + } else { + ctx->state = MSC_BBB_WAIT_FOR_CSW_SENT; } - ctx->state = MSC_BBB_WAIT_FOR_CSW_SENT; } static void usbd_msc_handle_request(struct usbd_class_data *c_data, From 00c190ccf218a03bd8d80d474ef53c05eeb20a01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Mo=C5=84?= Date: Mon, 20 Oct 2025 10:04:49 +0200 Subject: [PATCH 43/47] [nrf fromtree] usb: device_next: msc: Implement double buffering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Double buffering make it possible to significantly increase the transfer rates by avoiding idle states. With two SCSI buffers, one buffer can be used by disk subsystem while the other is being used by UDC (ideally with DMA). Signed-off-by: Tomasz Moń (cherry picked from commit 1243aba8e5a9565e761923a8f51c9a7273b32b6b) --- subsys/usb/device_next/class/Kconfig.msc | 7 + subsys/usb/device_next/class/usbd_msc.c | 239 +++++++++++++++++------ 2 files changed, 186 insertions(+), 60 deletions(-) diff --git a/subsys/usb/device_next/class/Kconfig.msc b/subsys/usb/device_next/class/Kconfig.msc index d85d0561cbf4..d4ff01cf17b1 100644 --- a/subsys/usb/device_next/class/Kconfig.msc +++ b/subsys/usb/device_next/class/Kconfig.msc @@ -30,6 +30,13 @@ config USBD_MSC_SCSI_BUFFER_SIZE Buffer size must be able to hold at least one sector. All LUNs within single instance share the SCSI buffer. +config USBD_MSC_DOUBLE_BUFFERING + bool "SCSI double buffering" + default y + help + Allocate two SCSI buffers instead of one to increase throughput by + using one buffer by disk subsystem and one by USB at the same time. + module = USBD_MSC module-str = usbd msc default-count = 1 diff --git a/subsys/usb/device_next/class/usbd_msc.c b/subsys/usb/device_next/class/usbd_msc.c index ee5784ae00ca..79e81cc01382 100644 --- a/subsys/usb/device_next/class/usbd_msc.c +++ b/subsys/usb/device_next/class/usbd_msc.c @@ -60,9 +60,10 @@ struct CSW { /* Single instance is likely enough because it can support multiple LUNs */ #define MSC_NUM_INSTANCES CONFIG_USBD_MSC_INSTANCES_COUNT -UDC_BUF_POOL_DEFINE(msc_ep_pool, - MSC_NUM_INSTANCES * 2, USBD_MAX_BULK_MPS, - sizeof(struct udc_buf_info), NULL); +#define MSC_NUM_BUFFERS UTIL_INC(IS_ENABLED(CONFIG_USBD_MSC_DOUBLE_BUFFERING)) + +UDC_BUF_POOL_DEFINE(msc_ep_pool, MSC_NUM_INSTANCES * (1 + MSC_NUM_BUFFERS), + USBD_MAX_BULK_MPS, sizeof(struct udc_buf_info), NULL); struct msc_event { struct usbd_class_data *c_data; @@ -91,8 +92,6 @@ struct msc_bot_desc { enum { MSC_CLASS_ENABLED, - MSC_BULK_OUT_QUEUED, - MSC_BULK_IN_QUEUED, MSC_BULK_IN_WEDGED, MSC_BULK_OUT_WEDGED, }; @@ -112,13 +111,16 @@ struct msc_bot_ctx { struct msc_bot_desc *const desc; const struct usb_desc_header **const fs_desc; const struct usb_desc_header **const hs_desc; + uint8_t *scsi_bufs[MSC_NUM_BUFFERS]; atomic_t bits; enum msc_bot_state state; + uint8_t scsi_bufs_used; + uint8_t num_in_queued; + uint8_t num_out_queued; uint8_t registered_luns; struct scsi_ctx luns[CONFIG_USBD_MSC_LUNS_PER_INSTANCE]; struct CBW cbw; struct CSW csw; - uint8_t *scsi_buf; uint32_t transferred_data; size_t scsi_bytes; }; @@ -160,13 +162,34 @@ static struct net_buf *msc_buf_alloc_data(const uint8_t ep, uint8_t *data, size_ return buf; } -static size_t msc_next_transfer_length(struct usbd_class_data *const c_data) +static uint8_t *msc_alloc_scsi_buf(struct msc_bot_ctx *ctx) { - struct usbd_context *uds_ctx = usbd_class_get_ctx(c_data); - struct msc_bot_ctx *ctx = usbd_class_get_private(c_data); - struct scsi_ctx *lun = &ctx->luns[ctx->cbw.bCBWLUN]; - size_t len = scsi_cmd_remaining_data_len(lun); + for (int i = 0; i < MSC_NUM_BUFFERS; i++) { + if (!(ctx->scsi_bufs_used & BIT(i))) { + ctx->scsi_bufs_used |= BIT(i); + return ctx->scsi_bufs[i]; + } + } + + /* Code must not attempt to queue more than MSC_NUM_BUFFERS at once */ + __ASSERT(false, "MSC ran out of SCSI buffers"); + return NULL; +} + +void msc_free_scsi_buf(struct msc_bot_ctx *ctx, uint8_t *buf) +{ + for (int i = 0; i < MSC_NUM_BUFFERS; i++) { + if (buf == ctx->scsi_bufs[i]) { + ctx->scsi_bufs_used &= ~BIT(i); + return; + } + } +} +static size_t clamp_transfer_length(struct usbd_context *uds_ctx, + struct scsi_ctx *lun, + size_t len) +{ len = MIN(CONFIG_USBD_MSC_SCSI_BUFFER_SIZE, len); /* Limit transfer to bulk endpoint wMaxPacketSize multiple */ @@ -186,6 +209,39 @@ static size_t msc_next_transfer_length(struct usbd_class_data *const c_data) return len; } +static size_t msc_next_in_transfer_length(struct usbd_class_data *const c_data) +{ + struct usbd_context *uds_ctx = usbd_class_get_ctx(c_data); + struct msc_bot_ctx *ctx = usbd_class_get_private(c_data); + struct scsi_ctx *lun = &ctx->luns[ctx->cbw.bCBWLUN]; + size_t len = scsi_cmd_remaining_data_len(lun); + + return clamp_transfer_length(uds_ctx, lun, len); +} + +static size_t msc_next_out_transfer_length(struct usbd_class_data *const c_data) +{ + struct usbd_context *uds_ctx = usbd_class_get_ctx(c_data); + struct msc_bot_ctx *ctx = usbd_class_get_private(c_data); + struct scsi_ctx *lun = &ctx->luns[ctx->cbw.bCBWLUN]; + size_t remaining = scsi_cmd_remaining_data_len(lun); + size_t len = clamp_transfer_length(uds_ctx, lun, remaining); + + /* This function can only estimate one more transfer after the current + * one. Queueing more buffers is not supported. + */ + __ASSERT_NO_MSG(ctx->num_out_queued < 2); + + if (ctx->num_out_queued == 0) { + return len; + } + + /* MSC BOT specification requires host to send all the data it intends + * to send. Therefore it should be safe to use "remaining - len" here. + */ + return clamp_transfer_length(uds_ctx, lun, remaining - len); +} + static uint8_t msc_get_bulk_in(struct usbd_class_data *const c_data) { struct usbd_context *uds_ctx = usbd_class_get_ctx(c_data); @@ -239,27 +295,56 @@ static void msc_stall_and_wait_for_recovery(struct msc_bot_ctx *ctx) ctx->state = MSC_BBB_WAIT_FOR_RESET_RECOVERY; } -static void msc_queue_bulk_out_ep(struct usbd_class_data *const c_data, bool data) +static void msc_queue_write(struct msc_bot_ctx *ctx) +{ + struct net_buf *buf; + uint8_t *scsi_buf; + uint8_t ep; + size_t len; + int ret; + + ep = msc_get_bulk_out(ctx->class_node); + + /* Ensure there are as many OUT transfers queued as possible */ + while ((ctx->num_out_queued < MSC_NUM_BUFFERS) && + (len = msc_next_out_transfer_length(ctx->class_node))) { + scsi_buf = msc_alloc_scsi_buf(ctx); + buf = msc_buf_alloc_data(ep, scsi_buf, len); + + /* The pool is large enough to support all allocations. Failing + * alloc indicates either a memory leak or logic error. + */ + __ASSERT_NO_MSG(buf); + + ret = usbd_ep_enqueue(ctx->class_node, buf); + if (ret) { + LOG_ERR("Failed to enqueue net_buf for 0x%02x", ep); + net_buf_unref(buf); + msc_free_scsi_buf(ctx, scsi_buf); + /* 6.6.2 Internal Device Error */ + msc_stall_and_wait_for_recovery(ctx); + return; + } + + ctx->num_out_queued++; + } +} + +static void msc_queue_cbw(struct usbd_class_data *const c_data) { struct msc_bot_ctx *ctx = usbd_class_get_private(c_data); struct net_buf *buf; - uint8_t *scsi_buf = ctx->scsi_buf; uint8_t ep; int ret; - if (atomic_test_and_set_bit(&ctx->bits, MSC_BULK_OUT_QUEUED)) { + if (ctx->num_out_queued) { /* Already queued */ return; } LOG_DBG("Queuing OUT"); ep = msc_get_bulk_out(c_data); - - if (data) { - buf = msc_buf_alloc_data(ep, scsi_buf, msc_next_transfer_length(c_data)); - } else { - buf = msc_buf_alloc(ep); - } + buf = msc_buf_alloc(ep); /* The pool is large enough to support all allocations. Failing alloc * indicates either a memory leak or logic error. @@ -270,9 +355,10 @@ static void msc_queue_bulk_out_ep(struct usbd_class_data *const c_data, bool dat if (ret) { LOG_ERR("Failed to enqueue net_buf for 0x%02x", ep); net_buf_unref(buf); - atomic_clear_bit(&ctx->bits, MSC_BULK_OUT_QUEUED); /* 6.6.2 Internal Device Error */ msc_stall_and_wait_for_recovery(ctx); + } else { + ctx->num_out_queued++; } } @@ -311,51 +397,58 @@ static bool is_cbw_meaningful(struct msc_bot_ctx *const ctx) return true; } -static void msc_process_read(struct msc_bot_ctx *ctx) +static void msc_queue_bulk_in_ep(struct msc_bot_ctx *ctx, uint8_t *data, int len) { - struct scsi_ctx *lun = &ctx->luns[ctx->cbw.bCBWLUN]; - int bytes_queued = 0; struct net_buf *buf; - uint8_t *scsi_buf = ctx->scsi_buf; uint8_t ep; int ret; - /* Fill SCSI Data IN buffer if there is no data available */ - if (ctx->scsi_bytes == 0) { - size_t len = msc_next_transfer_length(ctx->class_node); - - bytes_queued = scsi_read_data(lun, scsi_buf, len); - } else { - bytes_queued = ctx->scsi_bytes; - } - - /* All data is submitted in one go. Any potential new data will - * have to be retrieved using scsi_read_data() on next call. - */ - ctx->scsi_bytes = 0; - - if (atomic_test_and_set_bit(&ctx->bits, MSC_BULK_IN_QUEUED)) { - __ASSERT_NO_MSG(false); - LOG_ERR("IN already queued"); - return; - } - ep = msc_get_bulk_in(ctx->class_node); - buf = msc_buf_alloc_data(ep, scsi_buf, bytes_queued); + buf = msc_buf_alloc_data(ep, data, len); /* The pool is large enough to support all allocations. Failing alloc * indicates either a memory leak or logic error. */ __ASSERT_NO_MSG(buf); /* Either the net buf is full or there is no more SCSI data */ - ctx->csw.dCSWDataResidue -= bytes_queued; + ctx->csw.dCSWDataResidue -= len; ret = usbd_ep_enqueue(ctx->class_node, buf); if (ret) { LOG_ERR("Failed to enqueue net_buf for 0x%02x", ep); + msc_free_scsi_buf(ctx, data); net_buf_unref(buf); - atomic_clear_bit(&ctx->bits, MSC_BULK_IN_QUEUED); /* 6.6.2 Internal Device Error */ msc_stall_and_wait_for_recovery(ctx); + } else { + ctx->num_in_queued++; + } +} + +static void msc_process_read(struct msc_bot_ctx *ctx) +{ + struct scsi_ctx *lun = &ctx->luns[ctx->cbw.bCBWLUN]; + int bytes_queued = 0; + uint8_t *scsi_buf; + size_t len; + + /* Data can be already in scsi_buf0 only on first call after CBW */ + if (ctx->scsi_bytes) { + __ASSERT_NO_MSG(ctx->scsi_bufs_used == 0); + ctx->scsi_bufs_used = BIT(0); + msc_queue_bulk_in_ep(ctx, ctx->scsi_bufs[0], ctx->scsi_bytes); + /* All data is submitted in one go. Any potential new data will + * have to be retrieved using scsi_read_data() later. + */ + ctx->scsi_bytes = 0; + } + + /* Fill SCSI Data IN buffer if there is avaialble buffer and data */ + while ((ctx->num_in_queued < MSC_NUM_BUFFERS) && + (ctx->state == MSC_BBB_PROCESS_READ) && + (len = msc_next_in_transfer_length(ctx->class_node))) { + scsi_buf = msc_alloc_scsi_buf(ctx); + bytes_queued = scsi_read_data(lun, scsi_buf, len); + msc_queue_bulk_in_ep(ctx, scsi_buf, bytes_queued); } } @@ -366,8 +459,11 @@ static void msc_process_cbw(struct msc_bot_ctx *ctx) size_t data_len; int cb_len; + /* All SCSI buffers must be available */ + __ASSERT_NO_MSG(ctx->scsi_bufs_used == 0); + cb_len = scsi_usb_boot_cmd_len(ctx->cbw.CBWCB, ctx->cbw.bCBWCBLength); - data_len = scsi_cmd(lun, ctx->cbw.CBWCB, cb_len, ctx->scsi_buf); + data_len = scsi_cmd(lun, ctx->cbw.CBWCB, cb_len, ctx->scsi_bufs[0]); ctx->scsi_bytes = data_len; cmd_is_data_read = scsi_cmd_is_data_read(lun); cmd_is_data_write = scsi_cmd_is_data_write(lun); @@ -530,7 +626,7 @@ static void msc_handle_bulk_in(struct msc_bot_ctx *ctx, struct scsi_ctx *lun = &ctx->luns[ctx->cbw.bCBWLUN]; ctx->transferred_data += len; - if (msc_next_transfer_length(ctx->class_node) == 0) { + if (msc_next_in_transfer_length(ctx->class_node) == 0) { if (ctx->csw.dCSWDataResidue > 0) { /* Case (5) Hi > Di * While we may have sent short packet, device @@ -555,7 +651,7 @@ static void msc_send_csw(struct msc_bot_ctx *ctx) uint8_t ep; int ret; - if (atomic_test_and_set_bit(&ctx->bits, MSC_BULK_IN_QUEUED)) { + if (ctx->num_in_queued) { __ASSERT_NO_MSG(false); LOG_ERR("IN already queued"); return; @@ -575,10 +671,10 @@ static void msc_send_csw(struct msc_bot_ctx *ctx) if (ret) { LOG_ERR("Failed to enqueue net_buf for 0x%02x", ep); net_buf_unref(buf); - atomic_clear_bit(&ctx->bits, MSC_BULK_IN_QUEUED); /* 6.6.2 Internal Device Error */ msc_stall_and_wait_for_recovery(ctx); } else { + ctx->num_in_queued++; ctx->state = MSC_BBB_WAIT_FOR_CSW_SENT; } } @@ -611,10 +707,17 @@ static void usbd_msc_handle_request(struct usbd_class_data *c_data, ep_request_error: if (bi->ep == msc_get_bulk_out(c_data)) { - atomic_clear_bit(&ctx->bits, MSC_BULK_OUT_QUEUED); + ctx->num_out_queued--; + if (buf->frags) { + ctx->num_out_queued--; + } } else if (bi->ep == msc_get_bulk_in(c_data)) { - atomic_clear_bit(&ctx->bits, MSC_BULK_IN_QUEUED); + ctx->num_in_queued--; + if (buf->frags) { + ctx->num_in_queued--; + } } + msc_free_scsi_buf(ctx, buf->__buf); usbd_ep_buf_free(uds_ctx, buf); } @@ -642,11 +745,14 @@ static void usbd_msc_thread(void *arg1, void *arg2, void *arg3) switch (ctx->state) { case MSC_BBB_EXPECT_CBW: - msc_queue_bulk_out_ep(evt.c_data, false); + msc_queue_cbw(evt.c_data); break; case MSC_BBB_PROCESS_WRITE: /* Ensure we can accept next OUT packet */ - msc_queue_bulk_out_ep(evt.c_data, true); + msc_queue_write(ctx); + break; + case MSC_BBB_PROCESS_READ: + msc_process_read(ctx); break; default: break; @@ -655,7 +761,7 @@ static void usbd_msc_thread(void *arg1, void *arg2, void *arg3) /* Skip (potentially) response generating code if there is * IN data already available for the host to pick up. */ - if (atomic_test_bit(&ctx->bits, MSC_BULK_IN_QUEUED)) { + if (ctx->num_in_queued) { continue; } @@ -666,7 +772,7 @@ static void usbd_msc_thread(void *arg1, void *arg2, void *arg3) if (ctx->state == MSC_BBB_PROCESS_READ) { msc_process_read(ctx); } else if (ctx->state == MSC_BBB_PROCESS_WRITE) { - msc_queue_bulk_out_ep(evt.c_data, true); + msc_queue_write(ctx); } else if (ctx->state == MSC_BBB_SEND_CSW) { msc_send_csw(ctx); } @@ -885,14 +991,27 @@ struct usbd_class_api msc_bot_api = { .init = msc_bot_init, }; +#define BUF_NAME(x, i) scsi_buf##i##_##x + +#define DEFINE_SCSI_BUF(x, i) \ + UDC_STATIC_BUF_DEFINE(BUF_NAME(x, i), CONFIG_USBD_MSC_SCSI_BUFFER_SIZE); + +#define DEFINE_SCSI_BUFS(x) \ + DEFINE_SCSI_BUF(x, 0) \ + IF_ENABLED(CONFIG_USBD_MSC_DOUBLE_BUFFERING, (DEFINE_SCSI_BUF(x, 1))) + +#define NAME_SCSI_BUFS(x) \ + BUF_NAME(x, 0) \ + IF_ENABLED(CONFIG_USBD_MSC_DOUBLE_BUFFERING, (, BUF_NAME(x, 1))) + #define DEFINE_MSC_BOT_CLASS_DATA(x, _) \ - UDC_STATIC_BUF_DEFINE(scsi_buf_##x, CONFIG_USBD_MSC_SCSI_BUFFER_SIZE); \ + DEFINE_SCSI_BUFS(x) \ \ static struct msc_bot_ctx msc_bot_ctx_##x = { \ .desc = &msc_bot_desc_##x, \ .fs_desc = msc_bot_fs_desc_##x, \ .hs_desc = msc_bot_hs_desc_##x, \ - .scsi_buf = scsi_buf_##x \ + .scsi_bufs = { NAME_SCSI_BUFS(x) }, \ }; \ \ USBD_DEFINE_CLASS(msc_##x, &msc_bot_api, &msc_bot_ctx_##x, \ From 480ee0b0d15038f5066f5d43516368e5d9db6551 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Mo=C5=84?= Date: Fri, 24 Oct 2025 13:31:19 +0200 Subject: [PATCH 44/47] [nrf fromtree] usb: device_next: msc: Reduce memory usage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MSC BOT can work with just one buffer because buffer to receive CBW is never queued at the same time as CSW. SCSI buffer needs to be multiple of bulk endpoint wMaxPacketSize and therefore is suitable for handling both CBW and CSW. Take advantage of this to reduce memory usage. Signed-off-by: Tomasz Moń (cherry picked from commit 7e11bc58172e0ae5804220e7f6e07a9c292c07df) --- subsys/usb/device_next/class/usbd_msc.c | 40 ++++++++++++------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/subsys/usb/device_next/class/usbd_msc.c b/subsys/usb/device_next/class/usbd_msc.c index 79e81cc01382..c631376b61f6 100644 --- a/subsys/usb/device_next/class/usbd_msc.c +++ b/subsys/usb/device_next/class/usbd_msc.c @@ -62,8 +62,12 @@ struct CSW { #define MSC_NUM_BUFFERS UTIL_INC(IS_ENABLED(CONFIG_USBD_MSC_DOUBLE_BUFFERING)) +#if USBD_MAX_BULK_MPS > CONFIG_USBD_MSC_SCSI_BUFFER_SIZE +#error "SCSI buffer must be at least USB bulk endpoint wMaxPacketSize" +#endif + UDC_BUF_POOL_DEFINE(msc_ep_pool, MSC_NUM_INSTANCES * (1 + MSC_NUM_BUFFERS), - USBD_MAX_BULK_MPS, sizeof(struct udc_buf_info), NULL); + 0, sizeof(struct udc_buf_info), NULL); struct msc_event { struct usbd_class_data *c_data; @@ -125,22 +129,6 @@ struct msc_bot_ctx { size_t scsi_bytes; }; -static struct net_buf *msc_buf_alloc(const uint8_t ep) -{ - struct net_buf *buf = NULL; - struct udc_buf_info *bi; - - buf = net_buf_alloc(&msc_ep_pool, K_NO_WAIT); - if (!buf) { - return NULL; - } - - bi = udc_get_buf_info(buf); - bi->ep = ep; - - return buf; -} - static struct net_buf *msc_buf_alloc_data(const uint8_t ep, uint8_t *data, size_t len) { struct net_buf *buf = NULL; @@ -334,6 +322,7 @@ static void msc_queue_cbw(struct usbd_class_data *const c_data) { struct msc_bot_ctx *ctx = usbd_class_get_private(c_data); struct net_buf *buf; + uint8_t *scsi_buf; uint8_t ep; int ret; @@ -342,9 +331,13 @@ static void msc_queue_cbw(struct usbd_class_data *const c_data) return; } + __ASSERT(ctx->scsi_bufs_used == 0, + "CBW can only be queued when SCSI buffers are free"); + LOG_DBG("Queuing OUT"); ep = msc_get_bulk_out(c_data); - buf = msc_buf_alloc(ep); + scsi_buf = msc_alloc_scsi_buf(ctx); + buf = msc_buf_alloc_data(ep, scsi_buf, USBD_MAX_BULK_MPS); /* The pool is large enough to support all allocations. Failing alloc * indicates either a memory leak or logic error. @@ -355,6 +348,7 @@ static void msc_queue_cbw(struct usbd_class_data *const c_data) if (ret) { LOG_ERR("Failed to enqueue net_buf for 0x%02x", ep); net_buf_unref(buf); + msc_free_scsi_buf(ctx, scsi_buf); /* 6.6.2 Internal Device Error */ msc_stall_and_wait_for_recovery(ctx); } else { @@ -648,6 +642,7 @@ static void msc_handle_bulk_in(struct msc_bot_ctx *ctx, static void msc_send_csw(struct msc_bot_ctx *ctx) { struct net_buf *buf; + uint8_t *scsi_buf; uint8_t ep; int ret; @@ -657,20 +652,25 @@ static void msc_send_csw(struct msc_bot_ctx *ctx) return; } + __ASSERT(ctx->scsi_bufs_used == 0, + "CSW can be sent only if SCSI buffers are free"); + /* Convert dCSWDataResidue to LE, other fields are already set */ ctx->csw.dCSWDataResidue = sys_cpu_to_le32(ctx->csw.dCSWDataResidue); ep = msc_get_bulk_in(ctx->class_node); - buf = msc_buf_alloc(ep); + scsi_buf = msc_alloc_scsi_buf(ctx); + memcpy(scsi_buf, &ctx->csw, sizeof(ctx->csw)); + buf = msc_buf_alloc_data(ep, scsi_buf, sizeof(ctx->csw)); /* The pool is large enough to support all allocations. Failing alloc * indicates either a memory leak or logic error. */ __ASSERT_NO_MSG(buf); - net_buf_add_mem(buf, &ctx->csw, sizeof(ctx->csw)); ret = usbd_ep_enqueue(ctx->class_node, buf); if (ret) { LOG_ERR("Failed to enqueue net_buf for 0x%02x", ep); net_buf_unref(buf); + msc_free_scsi_buf(ctx, scsi_buf); /* 6.6.2 Internal Device Error */ msc_stall_and_wait_for_recovery(ctx); } else { From 6f676c855d21a88664be42ab934d4182dc4de979 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Mo=C5=84?= Date: Mon, 24 Nov 2025 14:36:24 +0100 Subject: [PATCH 45/47] [nrf fromtree] usb: device_next: msc: Do not leak SCSI buffer on dequeue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Multiple submitted requests are getting merged to single cancelled net_buf on endpoint dequeue. While MSC class was correctly decrementing the usage counters, it was not freeing SCSI buffer pointed to by frags. Signed-off-by: Tomasz Moń (cherry picked from commit 152844a7e0f9edeb143cc8f865a23da7648968c0) --- subsys/usb/device_next/class/usbd_msc.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/subsys/usb/device_next/class/usbd_msc.c b/subsys/usb/device_next/class/usbd_msc.c index c631376b61f6..6aefb1aefb64 100644 --- a/subsys/usb/device_next/class/usbd_msc.c +++ b/subsys/usb/device_next/class/usbd_msc.c @@ -718,6 +718,9 @@ static void usbd_msc_handle_request(struct usbd_class_data *c_data, } } msc_free_scsi_buf(ctx, buf->__buf); + if (buf->frags) { + msc_free_scsi_buf(ctx, buf->frags->__buf); + } usbd_ep_buf_free(uds_ctx, buf); } From 6d272cd7eb2c6f2c3fbf07a5477fab7d182cc683 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Mo=C5=84?= Date: Thu, 20 Nov 2025 12:30:34 +0100 Subject: [PATCH 46/47] [nrf fromtree] drivers: usb: dwc2: Do cache operations on ownership change MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Perform cache operations in thread context when the buffer ownership changes between USB stack and UDC driver. This offers clearly measurable throughput increase on Mass Storage when double buffering is enabled. When endpoint operations are not double buffered the difference is negligible. The positive side effect is reducing number of operations performed in interrupt context. Signed-off-by: Tomasz Moń (cherry picked from commit 614dd5738e703b87888e58cc3d7ee9854477db5b) --- drivers/usb/udc/udc_dwc2.c | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/drivers/usb/udc/udc_dwc2.c b/drivers/usb/udc/udc_dwc2.c index eb7368e1178f..e27444f7bf11 100644 --- a/drivers/usb/udc/udc_dwc2.c +++ b/drivers/usb/udc/udc_dwc2.c @@ -419,6 +419,11 @@ static int dwc2_ctrl_feed_dout(const struct device *dev, const size_t length) return -ENOMEM; } + if (dwc2_in_buffer_dma_mode(dev)) { + /* Get rid of all dirty cache lines */ + sys_cache_data_invd_range(buf->data, net_buf_tailroom(buf)); + } + udc_buf_put(ep_cfg, buf); atomic_set_bit(&priv->xfer_new, 16); k_event_post(&priv->drv_evt, BIT(DWC2_DRV_EVT_XFER)); @@ -589,8 +594,6 @@ static int dwc2_tx_fifo_write(const struct device *dev, sys_write32((uint32_t)buf->data, (mem_addr_t)&base->in_ep[ep_idx].diepdma); - - sys_cache_data_flush_range(buf->data, len); } diepctl = sys_read32(diepctl_reg); @@ -790,8 +793,6 @@ static void dwc2_prep_rx(const struct device *dev, struct net_buf *buf, sys_write32((uint32_t)data, (mem_addr_t)&base->out_ep[ep_idx].doepdma); - - sys_cache_data_invd_range(data, xfersize); } sys_write32(doepctl, doepctl_reg); @@ -968,6 +969,10 @@ static inline int dwc2_handle_evt_dout(const struct device *dev, return -ENODATA; } + if (dwc2_in_buffer_dma_mode(dev)) { + sys_cache_data_invd_range(buf->data, buf->len); + } + udc_ep_set_busy(cfg, false); if (cfg->addr == USB_CONTROL_EP_OUT) { @@ -1835,6 +1840,17 @@ static int udc_dwc2_ep_enqueue(const struct device *dev, struct udc_dwc2_data *const priv = udc_get_private(dev); LOG_DBG("%p enqueue %x %p", dev, cfg->addr, buf); + + if (dwc2_in_buffer_dma_mode(dev)) { + if (USB_EP_DIR_IS_IN(cfg->addr)) { + /* Write all dirty cache lines to memory */ + sys_cache_data_flush_range(buf->data, buf->len); + } else { + /* Get rid of all dirty cache lines */ + sys_cache_data_invd_range(buf->data, net_buf_tailroom(buf)); + } + } + udc_buf_put(cfg, buf); if (!cfg->stat.halted) { @@ -1861,6 +1877,13 @@ static int udc_dwc2_ep_dequeue(const struct device *dev, udc_dwc2_ep_disable(dev, cfg, false, true); buf = udc_buf_get_all(cfg); + + if (dwc2_in_buffer_dma_mode(dev) && USB_EP_DIR_IS_OUT(cfg->addr)) { + for (struct net_buf *iter = buf; iter; iter = iter->frags) { + sys_cache_data_invd_range(iter->data, iter->len); + } + } + if (buf) { udc_submit_ep_event(dev, buf, -ECONNABORTED); } @@ -2820,7 +2843,9 @@ static inline void dwc2_handle_out_xfercompl(const struct device *dev, } if (dwc2_in_buffer_dma_mode(dev) && bcnt) { - sys_cache_data_invd_range(net_buf_tail(buf), bcnt); + /* Update just the length, cache will be invalidated in thread + * context after transfer if finished or cancelled. + */ net_buf_add(buf, bcnt); } From 22895318114e7a0a478e3c66eb8daed64ea850fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Mo=C5=84?= Date: Tue, 25 Nov 2025 11:09:24 +0100 Subject: [PATCH 47/47] [nrf fromtree] drivers: udc_dwc2: Avoid endpoint disable timeouts on bus reset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DWC2 core automatically clears USBActEP (for all endpoints other than endpoint 0) on bus reset. While core is deactivating the endpoint, it does not disarm it. On bus reset USB stack first calls ep_disable API and then ep_dequeue. This was leading to endpoint is not active warning followed by endpoint disable timeout. Disable timeout was effectively caused by waiting for EPDisbld interrupt on endpoint with disabled interrupts. Solve the issue by unconditionally disarming endpoint in ep_disable API handler. Remove the false warning because USBActEP cannot really be used for sanity checking as it is not only the driver that clears it. Signed-off-by: Tomasz Moń (cherry picked from commit 6f0a40090f91c84e57eb070fd65ab878e817bf2e) --- drivers/usb/udc/udc_dwc2.c | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/drivers/usb/udc/udc_dwc2.c b/drivers/usb/udc/udc_dwc2.c index e27444f7bf11..7d38aa69f7ec 100644 --- a/drivers/usb/udc/udc_dwc2.c +++ b/drivers/usb/udc/udc_dwc2.c @@ -1748,20 +1748,11 @@ static int udc_dwc2_ep_deactivate(const struct device *dev, dxepctl_reg = dwc2_get_dxepctl_reg(dev, cfg->addr); } - dxepctl = sys_read32(dxepctl_reg); - - if (dxepctl & USB_DWC2_DEPCTL_USBACTEP) { - LOG_DBG("Disable ep 0x%02x DxEPCTL%u %x", - cfg->addr, ep_idx, dxepctl); - - udc_dwc2_ep_disable(dev, cfg, false, true); + udc_dwc2_ep_disable(dev, cfg, false, true); - dxepctl = sys_read32(dxepctl_reg); - dxepctl &= ~USB_DWC2_DEPCTL_USBACTEP; - } else { - LOG_WRN("ep 0x%02x is not active DxEPCTL%u %x", - cfg->addr, ep_idx, dxepctl); - } + dxepctl = sys_read32(dxepctl_reg); + LOG_DBG("Disable ep 0x%02x DxEPCTL%u %x", cfg->addr, ep_idx, dxepctl); + dxepctl &= ~USB_DWC2_DEPCTL_USBACTEP; if (USB_EP_DIR_IS_IN(cfg->addr) && udc_mps_ep_size(cfg) != 0U && ep_idx != 0U) {