Linux wireless drivers development
 help / color / mirror / Atom feed
* Re: [PATCH v5 8/9] dt-bindings: net: wireless: brcm: Add compatible for bcm43752
From: Ronald Claveau @ 2026-03-26 14:03 UTC (permalink / raw)
  To: Neil Armstrong
  Cc: linux-arm-kernel, linux-amlogic, devicetree, linux-kernel,
	linux-mmc, linux-wireless, Conor Dooley, Kevin Hilman,
	Jerome Brunet, Martin Blumenstingl, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Ulf Hansson, Johannes Berg,
	van Spriel
In-Reply-To: <9bc23f1e-1cf3-43d1-935a-c4738d576c29@linaro.org>

On 3/26/26 1:55 PM, Neil Armstrong wrote:
> On 3/26/26 10:59, Ronald Claveau wrote:
>> Add bcm43752 compatible with its bcm4329 compatible fallback.
>>
>> Acked-by: Conor Dooley <conor.dooley@microchip.com>
>> Signed-off-by: Ronald Claveau <linux-kernel-dev@aliel.fr>
>> ---
>>   Documentation/devicetree/bindings/net/wireless/brcm,bcm4329-
>> fmac.yaml | 1 +
>>   1 file changed, 1 insertion(+)
>>
>> diff --git a/Documentation/devicetree/bindings/net/wireless/
>> brcm,bcm4329-fmac.yaml b/Documentation/devicetree/bindings/net/
>> wireless/brcm,bcm4329-fmac.yaml
>> index 3be7576787644..81fd3e37452a6 100644
>> --- a/Documentation/devicetree/bindings/net/wireless/brcm,bcm4329-
>> fmac.yaml
>> +++ b/Documentation/devicetree/bindings/net/wireless/brcm,bcm4329-
>> fmac.yaml
>> @@ -42,6 +42,7 @@ properties:
>>                 - brcm,bcm4356-fmac
>>                 - brcm,bcm4359-fmac
>>                 - brcm,bcm4366-fmac
>> +              - brcm,bcm43752-fmac
>>                 - cypress,cyw4373-fmac
>>                 - cypress,cyw43012-fmac
>>                 - infineon,cyw43439-fmac
>>
> 
> I'll apply all the other DT patches, please send this one alone with the
> [PATCH net-next] prefix so it gets picked by the wireless/net people.
> 
> Thanks,
> Neil

I'm on it, thank you.

-- 
Best regards,
Ronald

^ permalink raw reply

* Re: (subset) [PATCH v5 0/9] arm64: dts: amlogic: Add MMC/SD/SDIO support for Khadas VIM4 (Amlogic T7)
From: Neil Armstrong @ 2026-03-26 13:10 UTC (permalink / raw)
  To: Kevin Hilman, Jerome Brunet, Martin Blumenstingl, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Ulf Hansson, Johannes Berg,
	van Spriel, Ronald Claveau
  Cc: linux-arm-kernel, linux-amlogic, devicetree, linux-kernel,
	linux-mmc, linux-wireless, Conor Dooley, Xianwei Zhao, Nick Xie
In-Reply-To: <177453048347.439230.1647215482099213609.b4-ty@linaro.org>

On 3/26/26 14:08, Neil Armstrong wrote:
> Hi,
> 
> On Thu, 26 Mar 2026 10:59:11 +0100, Ronald Claveau wrote:
>> This patch series depends on Jian's SCMI clock patches yet to merge
>> https://lore.kernel.org/all/20260313070022.700437-1-jian.hu@amlogic.com/
>>
>> This series adds device tree support for the MMC, SD card and SDIO
>> interfaces on the Amlogic T7 SoC and the Khadas VIM4 board.
>>
>> The first patches add the necessary building blocks in the T7 SoC
>> DTSI: pinctrl nodes for pin muxing, PWM controller nodes, and MMC
>> controller nodes. The amlogic,t7-mmc and amlogic,t7-pwm compatible
>> strings are introduced with fallbacks to existing drivers, avoiding
>> the need for new driver code.
>>
>> [...]
> 
> Thanks, Applied to https://git.kernel.org/pub/scm/linux/kernel/git/amlogic/linux.git (v7.1/arm64-dt)
> 
> [1/9] arm64: dts: amlogic: t7: Add eMMC, SD card and SDIO pinctrl nodes
>        https://git.kernel.org/amlogic/c/fb69fa2cabc68da247bcc0bc99a14dc857b16842
> [4/9] arm64: dts: amlogic: t7: Add PWM pinctrl nodes
>        https://git.kernel.org/amlogic/c/b1e49f6c1ac15b2c947bdb1d22a82b823de22d27
> [6/9] arm64: dts: amlogic: t7: khadas-vim4: Add power regulators
>        https://git.kernel.org/amlogic/c/60eff75ac67bbf5445bdbd2842b0109ac591441c


I'll pick patch 3,5,7 & 9 once patches 1 & 2 from [1] are merged.

[1] https://lore.kernel.org/all/20260326092645.1053261-1-jian.hu@amlogic.com/

Thanks,
Neil

> 
> These changes has been applied on the intermediate git tree [1].
> 
> The v7.1/arm64-dt branch will then be sent via a formal Pull Request to the Linux SoC maintainers
> for inclusion in their intermediate git branches in order to be sent to Linus during
> the next merge window, or sooner if it's a set of fixes.
> 
> In the cases of fixes, those will be merged in the current release candidate
> kernel and as soon they appear on the Linux master branch they will be
> backported to the previous Stable and Long-Stable kernels [2].
> 
> The intermediate git branches are merged daily in the linux-next tree [3],
> people are encouraged testing these pre-release kernels and report issues on the
> relevant mailing-lists.
> 
> If problems are discovered on those changes, please submit a signed-off-by revert
> patch followed by a corrective changeset.
> 
> [1] https://git.kernel.org/pub/scm/linux/kernel/git/amlogic/linux.git
> [2] https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
> [3] https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git
> 


^ permalink raw reply

* Re: (subset) [PATCH v5 0/9] arm64: dts: amlogic: Add MMC/SD/SDIO support for Khadas VIM4 (Amlogic T7)
From: Neil Armstrong @ 2026-03-26 13:08 UTC (permalink / raw)
  To: Kevin Hilman, Jerome Brunet, Martin Blumenstingl, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Ulf Hansson, Johannes Berg,
	van Spriel, Ronald Claveau
  Cc: linux-arm-kernel, linux-amlogic, devicetree, linux-kernel,
	linux-mmc, linux-wireless, Conor Dooley, Xianwei Zhao, Nick Xie
In-Reply-To: <20260326-add-emmc-t7-vim4-v5-0-d3f182b48e9d@aliel.fr>

Hi,

On Thu, 26 Mar 2026 10:59:11 +0100, Ronald Claveau wrote:
> This patch series depends on Jian's SCMI clock patches yet to merge
> https://lore.kernel.org/all/20260313070022.700437-1-jian.hu@amlogic.com/
> 
> This series adds device tree support for the MMC, SD card and SDIO
> interfaces on the Amlogic T7 SoC and the Khadas VIM4 board.
> 
> The first patches add the necessary building blocks in the T7 SoC
> DTSI: pinctrl nodes for pin muxing, PWM controller nodes, and MMC
> controller nodes. The amlogic,t7-mmc and amlogic,t7-pwm compatible
> strings are introduced with fallbacks to existing drivers, avoiding
> the need for new driver code.
> 
> [...]

Thanks, Applied to https://git.kernel.org/pub/scm/linux/kernel/git/amlogic/linux.git (v7.1/arm64-dt)

[1/9] arm64: dts: amlogic: t7: Add eMMC, SD card and SDIO pinctrl nodes
      https://git.kernel.org/amlogic/c/fb69fa2cabc68da247bcc0bc99a14dc857b16842
[4/9] arm64: dts: amlogic: t7: Add PWM pinctrl nodes
      https://git.kernel.org/amlogic/c/b1e49f6c1ac15b2c947bdb1d22a82b823de22d27
[6/9] arm64: dts: amlogic: t7: khadas-vim4: Add power regulators
      https://git.kernel.org/amlogic/c/60eff75ac67bbf5445bdbd2842b0109ac591441c

These changes has been applied on the intermediate git tree [1].

The v7.1/arm64-dt branch will then be sent via a formal Pull Request to the Linux SoC maintainers
for inclusion in their intermediate git branches in order to be sent to Linus during
the next merge window, or sooner if it's a set of fixes.

In the cases of fixes, those will be merged in the current release candidate
kernel and as soon they appear on the Linux master branch they will be
backported to the previous Stable and Long-Stable kernels [2].

The intermediate git branches are merged daily in the linux-next tree [3],
people are encouraged testing these pre-release kernels and report issues on the
relevant mailing-lists.

If problems are discovered on those changes, please submit a signed-off-by revert
patch followed by a corrective changeset.

[1] https://git.kernel.org/pub/scm/linux/kernel/git/amlogic/linux.git
[2] https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
[3] https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git

-- 
Neil


^ permalink raw reply

* Re: [PATCH v5 3/9] arm64: dts: amlogic: t7: Add MMC controller nodes
From: Neil Armstrong @ 2026-03-26 12:56 UTC (permalink / raw)
  To: Ronald Claveau, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Ulf Hansson,
	Johannes Berg, van Spriel
  Cc: linux-arm-kernel, linux-amlogic, devicetree, linux-kernel,
	linux-mmc, linux-wireless
In-Reply-To: <20260326-add-emmc-t7-vim4-v5-3-d3f182b48e9d@aliel.fr>

On 3/26/26 10:59, Ronald Claveau wrote:
> Add device tree nodes for the three MMC controllers available
> on the Amlogic T7 SoC, using amlogic,meson-axg-mmc as fallback compatible.
> All nodes are disabled by default and should be
> enabled in the board-specific DTS file.
> 
> Signed-off-by: Ronald Claveau <linux-kernel-dev@aliel.fr>
> ---
>   arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi | 39 +++++++++++++++++++++++++++++
>   1 file changed, 39 insertions(+)
> 
> diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi
> index 36d13371f56ba..fe1ced0a58967 100644
> --- a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi
> +++ b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi
> @@ -374,6 +374,45 @@ sec_ao: ao-secure@10220 {
>   				reg = <0x0 0x10220 0x0 0x140>;
>   				amlogic,has-chip-id;
>   			};
> +
> +			sd_emmc_a: mmc@88000 {
> +				compatible = "amlogic,t7-mmc", "amlogic,meson-axg-mmc";
> +				reg = <0x0 0x88000 0x0 0x800>;
> +				interrupts = <GIC_SPI 176 IRQ_TYPE_LEVEL_HIGH>;
> +				clocks = <&clkc_periphs CLKID_SYS_SD_EMMC_A>,
> +					 <&clkc_periphs CLKID_SD_EMMC_A>,
> +					 <&scmi_clk CLKID_FCLK_DIV2>;
> +				clock-names = "core", "clkin0", "clkin1";
> +				assigned-clocks = <&clkc_periphs CLKID_SD_EMMC_A_SEL>;
> +				assigned-clock-parents = <&xtal>;
> +				status = "disabled";
> +			};
> +
> +			sd_emmc_b: mmc@8a000 {
> +				compatible = "amlogic,t7-mmc", "amlogic,meson-axg-mmc";
> +				reg = <0x0 0x8a000 0x0 0x800>;
> +				interrupts = <GIC_SPI 177 IRQ_TYPE_EDGE_RISING>;
> +				clocks = <&clkc_periphs CLKID_SYS_SD_EMMC_B>,
> +					 <&clkc_periphs CLKID_SD_EMMC_B>,
> +					 <&scmi_clk CLKID_FCLK_DIV2>;
> +				clock-names = "core", "clkin0", "clkin1";
> +				assigned-clocks = <&clkc_periphs CLKID_SD_EMMC_B_SEL>;
> +				assigned-clock-parents = <&xtal>;
> +				status = "disabled";
> +			};
> +
> +			sd_emmc_c: mmc@8c000 {
> +				compatible = "amlogic,t7-mmc", "amlogic,meson-axg-mmc";
> +				reg = <0x0 0x8c000 0x0 0x800>;
> +				interrupts = <GIC_SPI 178 IRQ_TYPE_EDGE_RISING>;
> +				clocks = <&clkc_periphs CLKID_SYS_SD_EMMC_C>,
> +					 <&clkc_periphs CLKID_SD_EMMC_C>,
> +					 <&scmi_clk CLKID_FCLK_DIV2>;
> +				clock-names = "core", "clkin0", "clkin1";
> +				assigned-clocks = <&clkc_periphs CLKID_SD_EMMC_C_SEL>;
> +				assigned-clock-parents = <&xtal>;
> +				status = "disabled";
> +			};
>   		};
>   
>   	};
> 

Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org>

Thanks,
Neil

^ permalink raw reply

* Re: [PATCH v5 8/9] dt-bindings: net: wireless: brcm: Add compatible for bcm43752
From: Neil Armstrong @ 2026-03-26 12:55 UTC (permalink / raw)
  To: Ronald Claveau, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Ulf Hansson,
	Johannes Berg, van Spriel
  Cc: linux-arm-kernel, linux-amlogic, devicetree, linux-kernel,
	linux-mmc, linux-wireless, Conor Dooley
In-Reply-To: <20260326-add-emmc-t7-vim4-v5-8-d3f182b48e9d@aliel.fr>

On 3/26/26 10:59, Ronald Claveau wrote:
> Add bcm43752 compatible with its bcm4329 compatible fallback.
> 
> Acked-by: Conor Dooley <conor.dooley@microchip.com>
> Signed-off-by: Ronald Claveau <linux-kernel-dev@aliel.fr>
> ---
>   Documentation/devicetree/bindings/net/wireless/brcm,bcm4329-fmac.yaml | 1 +
>   1 file changed, 1 insertion(+)
> 
> diff --git a/Documentation/devicetree/bindings/net/wireless/brcm,bcm4329-fmac.yaml b/Documentation/devicetree/bindings/net/wireless/brcm,bcm4329-fmac.yaml
> index 3be7576787644..81fd3e37452a6 100644
> --- a/Documentation/devicetree/bindings/net/wireless/brcm,bcm4329-fmac.yaml
> +++ b/Documentation/devicetree/bindings/net/wireless/brcm,bcm4329-fmac.yaml
> @@ -42,6 +42,7 @@ properties:
>                 - brcm,bcm4356-fmac
>                 - brcm,bcm4359-fmac
>                 - brcm,bcm4366-fmac
> +              - brcm,bcm43752-fmac
>                 - cypress,cyw4373-fmac
>                 - cypress,cyw43012-fmac
>                 - infineon,cyw43439-fmac
> 

I'll apply all the other DT patches, please send this one alone with the [PATCH net-next] prefix so it gets picked by the wireless/net people.

Thanks,
Neil

^ permalink raw reply

* Re: [PATCH v5 1/9] arm64: dts: amlogic: t7: Add eMMC, SD card and SDIO pinctrl nodes
From: Neil Armstrong @ 2026-03-26 12:53 UTC (permalink / raw)
  To: Ronald Claveau, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Ulf Hansson,
	Johannes Berg, van Spriel
  Cc: linux-arm-kernel, linux-amlogic, devicetree, linux-kernel,
	linux-mmc, linux-wireless
In-Reply-To: <20260326-add-emmc-t7-vim4-v5-1-d3f182b48e9d@aliel.fr>

On 3/26/26 10:59, Ronald Claveau wrote:
> These pinctrl nodes are required by the eMMC, SD card and SDIO drivers
> to configure pin muxing at runtime.
> 
> - eMMC: control, 4-bit/8-bit data, data strobe and clock gate pins
> - SD card: data, clock, command and clock gate pins
> - SDIO: data, clock, command and clock gate pins
> 
> Signed-off-by: Ronald Claveau <linux-kernel-dev@aliel.fr>
> ---
>   arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi | 98 +++++++++++++++++++++++++++++
>   1 file changed, 98 insertions(+)
> 
> diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi
> index 6510068bcff92..36d13371f56ba 100644
> --- a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi
> +++ b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi
> @@ -250,6 +250,104 @@ gpio: bank@4000 {
>   					#gpio-cells = <2>;
>   					gpio-ranges = <&periphs_pinctrl 0 0 157>;
>   				};
> +
> +				emmc_ctrl_pins: emmc-ctrl {
> +					mux-0 {
> +						groups = "emmc_cmd";
> +						function = "emmc";
> +						bias-pull-up;
> +					};
> +
> +					mux-1 {
> +						groups = "emmc_clk";
> +						function = "emmc";
> +						bias-disable;
> +					};
> +				};
> +
> +				emmc_data_4b_pins: emmc-data-4b {
> +					mux {
> +						groups = "emmc_nand_d0",
> +							 "emmc_nand_d1",
> +							 "emmc_nand_d2",
> +							 "emmc_nand_d3";
> +						function = "emmc";
> +						bias-pull-up;
> +					};
> +				};
> +
> +				emmc_data_8b_pins: emmc-data-8b {
> +					mux {
> +						groups = "emmc_nand_d0",
> +							 "emmc_nand_d1",
> +							 "emmc_nand_d2",
> +							 "emmc_nand_d3",
> +							 "emmc_nand_d4",
> +							 "emmc_nand_d5",
> +							 "emmc_nand_d6",
> +							 "emmc_nand_d7";
> +						function = "emmc";
> +						bias-pull-up;
> +					};
> +				};
> +
> +				emmc_ds_pins: emmc-ds {
> +					mux {
> +						groups = "emmc_nand_ds";
> +						function = "emmc";
> +						bias-pull-down;
> +					};
> +				};
> +
> +				emmc_clk_gate_pins: emmc-clk-gate {
> +					mux {
> +						groups = "GPIOB_8";
> +						function = "gpio_periphs";
> +						bias-pull-down;
> +					};
> +				};
> +
> +				sdcard_pins: sdcard {
> +					mux {
> +						groups = "sdcard_d0",
> +							 "sdcard_d1",
> +							 "sdcard_d2",
> +							 "sdcard_d3",
> +							 "sdcard_clk",
> +							 "sdcard_cmd";
> +						function = "sdcard";
> +						bias-pull-up;
> +					};
> +				};
> +
> +				sdcard_clk_gate_pins: sdcard-clk-gate {
> +					mux {
> +						groups = "GPIOC_4";
> +						function = "gpio_periphs";
> +						bias-pull-down;
> +					};
> +				};
> +
> +				sdio_pins: sdio {
> +					mux {
> +						groups = "sdio_d0",
> +							 "sdio_d1",
> +							 "sdio_d2",
> +							 "sdio_d3",
> +							 "sdio_clk",
> +							 "sdio_cmd";
> +						function = "sdio";
> +						bias-pull-up;
> +					};
> +				};
> +
> +				sdio_clk_gate_pins: sdio-clk-gate {
> +					mux {
> +						groups = "GPIOX_4";
> +						function = "gpio_periphs";
> +						bias-pull-up;
> +					};
> +				};
>   			};
>   
>   			gpio_intc: interrupt-controller@4080 {
> 

Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org>

Thanks,
Neil

^ permalink raw reply

* Re: [PATCH v5 2/9] dt-bindings: mmc: amlogic: Add compatible for T7 mmc
From: Ulf Hansson @ 2026-03-26 12:33 UTC (permalink / raw)
  To: Ronald Claveau
  Cc: Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Johannes Berg,
	van Spriel, linux-arm-kernel, linux-amlogic, devicetree,
	linux-kernel, linux-mmc, linux-wireless, Conor Dooley,
	Xianwei Zhao
In-Reply-To: <20260326-add-emmc-t7-vim4-v5-2-d3f182b48e9d@aliel.fr>

On Thu, 26 Mar 2026 at 11:01, Ronald Claveau <linux-kernel-dev@aliel.fr> wrote:
>
> Add amlogic,t7-mmc compatible string, falling back to amlogic,meson-axg-mmc
> as the T7 MMC controller is compatible with the AXG implementation.
>
> Acked-by: Conor Dooley <conor.dooley@microchip.com>
> Acked-by: Rob Herring (Arm) <robh@kernel.org>
> Reviewed-by: Xianwei Zhao <xianwei.zhao@amlogic.com>
> Signed-off-by: Ronald Claveau <linux-kernel-dev@aliel.fr>

Applied for next, thanks!

Kind regards
Uffe


> ---
>  Documentation/devicetree/bindings/mmc/amlogic,meson-gx-mmc.yaml | 4 ++++
>  1 file changed, 4 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/mmc/amlogic,meson-gx-mmc.yaml b/Documentation/devicetree/bindings/mmc/amlogic,meson-gx-mmc.yaml
> index 57646575a13f8..976f36de2091c 100644
> --- a/Documentation/devicetree/bindings/mmc/amlogic,meson-gx-mmc.yaml
> +++ b/Documentation/devicetree/bindings/mmc/amlogic,meson-gx-mmc.yaml
> @@ -19,6 +19,10 @@ allOf:
>  properties:
>    compatible:
>      oneOf:
> +      - items:
> +          - enum:
> +              - amlogic,t7-mmc
> +          - const: amlogic,meson-axg-mmc
>        - const: amlogic,meson-axg-mmc
>        - items:
>            - const: amlogic,meson-gx-mmc
>
> --
> 2.49.0
>

^ permalink raw reply

* [PATCH v2 4/4] net/rds: Use special gfp_t format specifier
From: Brendan Jackman @ 2026-03-26 12:32 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Stanislaw Gruszka, Alexander Potapenko,
	Marco Elver, Dmitry Vyukov, Andrew Morton, Allison Henderson,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman
  Cc: dri-devel, linux-kernel, linux-wireless, kasan-dev, linux-mm,
	netdev, linux-rdma, rds-devel, Brendan Jackman
In-Reply-To: <20260326-gfp64-v2-0-d916021cecdf@google.com>

%pGg produces nice readable output and decouples the format string from
the size of gfp_t.

Signed-off-by: Brendan Jackman <jackmanb@google.com>
---
 net/rds/tcp_recv.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/net/rds/tcp_recv.c b/net/rds/tcp_recv.c
index 49f96ee0c40f6..ffe843ca219c7 100644
--- a/net/rds/tcp_recv.c
+++ b/net/rds/tcp_recv.c
@@ -275,7 +275,7 @@ static int rds_tcp_read_sock(struct rds_conn_path *cp, gfp_t gfp)
 	desc.count = 1; /* give more than one skb per call */
 
 	tcp_read_sock(sock->sk, &desc, rds_tcp_data_recv);
-	rdsdebug("tcp_read_sock for tc %p gfp 0x%x returned %d\n", tc, gfp,
+	rdsdebug("tcp_read_sock for tc %p gfp %pGg returned %d\n", tc, &gfp,
 		 desc.error);
 
 	if (skb_queue_empty_lockless(&sock->sk->sk_receive_queue) &&

-- 
2.51.2


^ permalink raw reply related

* [PATCH v2 3/4] mm/kfence: Use special gfp_t format specifier
From: Brendan Jackman @ 2026-03-26 12:31 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Stanislaw Gruszka, Alexander Potapenko,
	Marco Elver, Dmitry Vyukov, Andrew Morton, Allison Henderson,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman
  Cc: dri-devel, linux-kernel, linux-wireless, kasan-dev, linux-mm,
	netdev, linux-rdma, rds-devel, Brendan Jackman
In-Reply-To: <20260326-gfp64-v2-0-d916021cecdf@google.com>

%pGg produces nice readable output and decouples the format string from
the size of gfp_t.

Signed-off-by: Brendan Jackman <jackmanb@google.com>
---
 mm/kfence/kfence_test.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/mm/kfence/kfence_test.c b/mm/kfence/kfence_test.c
index 5725a367246d9..10424cd25e5a6 100644
--- a/mm/kfence/kfence_test.c
+++ b/mm/kfence/kfence_test.c
@@ -263,7 +263,7 @@ static void *test_alloc(struct kunit *test, size_t size, gfp_t gfp, enum allocat
 		break;
 	}
 
-	kunit_info(test, "%s: size=%zu, gfp=%x, policy=%s, cache=%i\n", __func__, size, gfp,
+	kunit_info(test, "%s: size=%zu, gfp=%pGg, policy=%s, cache=%i\n", __func__, size, &gfp,
 		   policy_name, !!test_cache);
 
 	/*

-- 
2.51.2


^ permalink raw reply related

* [PATCH v2 2/4] iwlegacy: 3945-mac: Fixup allocation failure log
From: Brendan Jackman @ 2026-03-26 12:31 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Stanislaw Gruszka, Alexander Potapenko,
	Marco Elver, Dmitry Vyukov, Andrew Morton, Allison Henderson,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman
  Cc: dri-devel, linux-kernel, linux-wireless, kasan-dev, linux-mm,
	netdev, linux-rdma, rds-devel, Brendan Jackman
In-Reply-To: <20260326-gfp64-v2-0-d916021cecdf@google.com>

Fix 2 issues spotted by AI[0]:

1. Missing space after the full stop.

2. Wrong GFP flags are printed.

And also switch to %pGg for the GFP flags. This produces nice readable
output and decouples the format string from the size of gfp_t.

[0] https://sashiko.dev/#/patchset/20260319-gfp64-v1-0-2c73b8d42b7f%40google.com

Signed-off-by: Brendan Jackman <jackmanb@google.com>
---
 drivers/net/wireless/intel/iwlegacy/3945-mac.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/net/wireless/intel/iwlegacy/3945-mac.c b/drivers/net/wireless/intel/iwlegacy/3945-mac.c
index c148654aa9533..88b31e0b9568c 100644
--- a/drivers/net/wireless/intel/iwlegacy/3945-mac.c
+++ b/drivers/net/wireless/intel/iwlegacy/3945-mac.c
@@ -1002,9 +1002,9 @@ il3945_rx_allocate(struct il_priv *il, gfp_t priority)
 				D_INFO("Failed to allocate SKB buffer.\n");
 			if (rxq->free_count <= RX_LOW_WATERMARK &&
 			    net_ratelimit())
-				IL_ERR("Failed to allocate SKB buffer with %0x."
+				IL_ERR("Failed to allocate SKB buffer with %pGg. "
 				       "Only %u free buffers remaining.\n",
-				       priority, rxq->free_count);
+				       &gfp_mask, rxq->free_count);
 			/* We don't reschedule replenish work here -- we will
 			 * call the restock method and if it still needs
 			 * more buffers it will schedule replenish */

-- 
2.51.2


^ permalink raw reply related

* [PATCH v2 1/4] drm/managed: Use special gfp_t format specifier
From: Brendan Jackman @ 2026-03-26 12:31 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Stanislaw Gruszka, Alexander Potapenko,
	Marco Elver, Dmitry Vyukov, Andrew Morton, Allison Henderson,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman
  Cc: dri-devel, linux-kernel, linux-wireless, kasan-dev, linux-mm,
	netdev, linux-rdma, rds-devel, Brendan Jackman
In-Reply-To: <20260326-gfp64-v2-0-d916021cecdf@google.com>

%pGg produces nice readable output and decouples the format string from
the size of gfp_t.

Signed-off-by: Brendan Jackman <jackmanb@google.com>
---
 drivers/gpu/drm/drm_managed.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/drm_managed.c b/drivers/gpu/drm/drm_managed.c
index 247f468731de0..a9da94319b052 100644
--- a/drivers/gpu/drm/drm_managed.c
+++ b/drivers/gpu/drm/drm_managed.c
@@ -232,8 +232,8 @@ void *drmm_kmalloc(struct drm_device *dev, size_t size, gfp_t gfp)
 
 	dr = alloc_dr(NULL, size, gfp, dev_to_node(dev->dev));
 	if (!dr) {
-		drm_dbg_drmres(dev, "failed to allocate %zu bytes, %u flags\n",
-			       size, gfp);
+		drm_dbg_drmres(dev, "failed to allocate %zu bytes, %pGg\n",
+			       size, &gfp);
 		return NULL;
 	}
 	dr->node.name = kstrdup_const("kmalloc", gfp);

-- 
2.51.2


^ permalink raw reply related

* [PATCH v2 0/4] treewide: fixup gfp_t printks
From: Brendan Jackman @ 2026-03-26 12:31 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Stanislaw Gruszka, Alexander Potapenko,
	Marco Elver, Dmitry Vyukov, Andrew Morton, Allison Henderson,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman
  Cc: dri-devel, linux-kernel, linux-wireless, kasan-dev, linux-mm,
	netdev, linux-rdma, rds-devel, Brendan Jackman

This patchset used to be about switching gfp_t to unsigned long. That is
probably not gonna happen any more but while writing it I found these
cleanups that seem worthwhile regardless.

Signed-off-by: Brendan Jackman <jackmanb@google.com>
---
Changes in v2:
- Drop gfp_t changes
- Add correct CCs
- Add minor fixups to preexisting code spotted by AI review
- Link to v1: https://lore.kernel.org/r/20260319-gfp64-v1-0-2c73b8d42b7f@google.com

---
Brendan Jackman (4):
      drm/managed: Use special gfp_t format specifier
      iwlegacy: 3945-mac: Fixup allocation failure log
      mm/kfence: Use special gfp_t format specifier
      net/rds: Use special gfp_t format specifier

 drivers/gpu/drm/drm_managed.c                  | 4 ++--
 drivers/net/wireless/intel/iwlegacy/3945-mac.c | 4 ++--
 mm/kfence/kfence_test.c                        | 2 +-
 net/rds/tcp_recv.c                             | 2 +-
 4 files changed, 6 insertions(+), 6 deletions(-)
---
base-commit: c369299895a591d96745d6492d4888259b004a9e
change-id: 20260319-gfp64-7a970a80ba4e

Best regards,
-- 
Brendan Jackman <jackmanb@google.com>


^ permalink raw reply

* Re: [PATCH v2] wifi: mac80211: fix the issue of NULL pointer access when deleting the virtual interface
From: Óscar Alfonso Díaz @ 2026-03-26 12:16 UTC (permalink / raw)
  To: 傅继晗; +Cc: johannes, linux-kernel, linux-wireless, stable
In-Reply-To: <20260326013719.1662-1-fjhhz1997@gmail.com>

Hi, in response to the three points:

1. VMware

2. This is the output of the lsusb command: "Bus 004 Device 002: ID
0e8d:7961 MediaTek Inc. Wireless_Device". The adapter is very cheap,
it’s a Fenvi AX1800 (MT7921U), this one:
https://s.click.aliexpress.com/e/_okxhxNl . But as I said, the bug
also happens when using the Alfa AWUS036AXML (MT7921AUN).

3. I’m not sure about this right now. I’d say everything dies. I’ll
test that to see if SSH is still available (I don’t think so, but I’m
not 100% sure at the moment).

Give me a few days. I’ll test this again over the weekend. I’ll also
run a test on bare metal (not in a VM). That said, like me, many
people use VMs for pentesting. So even if it works on bare metal,
which I’ll test this weekend, I think it would still be worth
investigating whether it can be fixed for VMs, since many people,
myself included, use them for work. If it works with other WiFi
adapters, it would be a big drawback if it didn’t work with MediaTek
adapters.

I’ll also reply with a similar message in the thread.

Thanks and regards.
--
Oscar

OpenPGP Key: DA9C60E9 ||
https://pgp.mit.edu/pks/lookup?op=get&search=0x79B17260DA9C60E9
4F74 B302 354D 817D DE38 0A43 79B1 7260 DA9C 60E9
--

El jue, 26 mar 2026 a las 2:37, 傅继晗 (<fjhhz1997@gmail.com>) escribió:
>
> Hi Óscar,
>
> Lucid-Duck spent some time trying to reproduce your crash and wasn't able
> to trigger it. Here's a summary of what was tested:
>
> - Kali 2025.4 (kernel 6.18.12+kali-amd64) VM on QEMU/KVM, with my v2
>   patch applied
> - MT7921AU USB adapter, passthrough to VM
> - Full airgeddon evil twin flow: monitor VIF + hostapd AP + continuous
>   deauth via aireplay-ng
> - Also tested on bare metal Fedora 6.19.8 with the same adapter
>
> All tests were stable -- no crash, no dmesg errors, load stayed low. The
> deauth frames were confirmed sending for 30+ seconds under the v2 patch
> without issues.
>
> The one variable that couldn't be matched was the VM hypervisor.
> Lucid-Duck used QEMU/KVM, which handles USB passthrough at the kernel
> level (xHCI). If you're using VirtualBox or VMware, the USB passthrough
> path is quite different (userspace proxy), and that could potentially
> explain a total VM freeze that isn't a kernel panic.
>
> Could you please reply to Lucid-Duck directly on GitHub with the
> following information? Here's the link:
> https://github.com/morrownr/USB-WiFi/issues/682#issuecomment-4129198757
>
> 1. Which hypervisor are you using? (VirtualBox, VMware, QEMU/KVM, etc.)
> 2. Your exact USB adapter model and ID? (0e8d:7961 covers several
>    MT7921 variants)
> 3. If possible, try SSHing into the VM from the host while the display
>    is frozen -- if SSH still works, the issue is at the hypervisor/display
>    level, not the kernel.
>
> Thanks,
> 傅继晗

^ permalink raw reply

* Re: [PATCH rtw-next] wifi: rtw89: Fill fw_version member of struct wiphy
From: Bitterblue Smith @ 2026-03-26 12:04 UTC (permalink / raw)
  To: Ping-Ke Shih, linux-wireless@vger.kernel.org
In-Reply-To: <9c25a5eee4194356b4d0210b2bace5f2@realtek.com>

On 26/03/2026 03:55, Ping-Ke Shih wrote:
> Bitterblue Smith <rtl8821cerfe2@gmail.com> wrote:
>> Let userspace tools like lshw show the firmware version by filling the
>> fw_version member of struct wiphy.
>>
>> Before:
>>
>> configuration: broadcast=yes driver=rtw89_8852au
>> driverversion=6.19.6-arch1-1 firmware=N/A link=no multicast=yes
>> wireless=IEEE 802.11
>>
>> After:
>>
>> configuration: broadcast=yes driver=rtw89_8852au
>> driverversion=6.19.6-arch1-1 firmware=0.13.36.2 link=no multicast=yes
>> wireless=IEEE 802.11
>>
>> Signed-off-by: Bitterblue Smith <rtl8821cerfe2@gmail.com>
>> ---
>>  drivers/net/wireless/realtek/rtw89/fw.c | 8 ++++++++
>>  1 file changed, 8 insertions(+)
>>
>> diff --git a/drivers/net/wireless/realtek/rtw89/fw.c
>> b/drivers/net/wireless/realtek/rtw89/fw.c
>> index 45d8c5e70084..55aee6eb8478 100644
>> --- a/drivers/net/wireless/realtek/rtw89/fw.c
>> +++ b/drivers/net/wireless/realtek/rtw89/fw.c
>> @@ -755,6 +755,14 @@ static int rtw89_fw_update_ver(struct rtw89_dev *rtwdev,
>>                    fw_suit->major_ver, fw_suit->minor_ver, fw_suit->sub_ver,
>>                    fw_suit->sub_idex, fw_suit->commitid, fw_suit->cmd_ver,
>> type);
>>
>> +       if (type == RTW89_FW_NORMAL || type == RTW89_FW_NORMAL_CE ||
>> +           type == RTW89_FW_NORMAL_B)
>> +               snprintf(rtwdev->hw->wiphy->fw_version,
>> +                        sizeof(rtwdev->hw->wiphy->fw_version),
> 
> nit: how about defining a local variable wiphy? as well as rtw88.
> 

Okay, I will do that.

>> +                        "%u.%u.%u.%u",
>> +                        fw_suit->major_ver, fw_suit->minor_ver,
>> +                        fw_suit->sub_ver, fw_suit->sub_idex);
>> +
>>         return 0;
>>  }
>>
>> --
>> 2.53.0
> 


^ permalink raw reply

* [PATCH] ath11k: workaround firmware bug where peer_id=0
From: Matthew Leach @ 2026-03-26 10:53 UTC (permalink / raw)
  To: Jeff Johnson; +Cc: linux-wireless, ath11k, linux-kernel, kernel, Matthew Leach

It has been observed that under certain conditions the ath11k firmware
sets the peer_id=0 for RX'd frames. For standard MPDUs this is fine as
ath11k_dp_rx_h_find_peer() has a fallback case where it locates the peer
based upon the source mac address.

However, on an aggregated link, reception of an A-MSDU results in the
peer not being resolved for the second (any any subsequent) sub-MSDUs.
This causes the encryption type of the frame to be set to an incorrect
value, resulting in the sub-MSDUs being dropped by ieee80211. Notice how
the flags differ in:

ath11k_pci 0000:03:00.0: data rx skb 000000002f4b704d len 1534 peer xx:xx:xx:xx:xx:xx 0 ucast sn 3063 he160 rate_idx 9 vht_nss 2 freq 5240 band 1 flag 0x40d1a fcs-err 0 mic-err 0 amsdu-more 0 peer_id 0 first_msdu 1 last_msdu 0
ath11k_pci 0000:03:00.0: data rx skb 0000000038acd580 len 1534 peer (null) 0 ucast sn 3063 he160 rate_idx 9 vht_nss 2 freq 5240 band 1 flag 0x40d00 fcs-err 0 mic-err 0 amsdu-more 0 peer_id 0 first_msdu 0 last_msdu 1

This patch caches the peer enctype during the MSDU processing loop,
caching it on the first AMSDU sub-frame (is_first_msdu=1 is_last_msdu=0)
and setting the correct enctype for any subsequent sub-MSDUs.

Signed-off-by: Matthew Leach <matthew.leach@collabora.com>
---
 drivers/net/wireless/ath/ath11k/dp_rx.c | 35 ++++++++++++++++++++++++++++-----
 1 file changed, 30 insertions(+), 5 deletions(-)

diff --git a/drivers/net/wireless/ath/ath11k/dp_rx.c b/drivers/net/wireless/ath/ath11k/dp_rx.c
index 49d959b2e148..f5c2a8085a1b 100644
--- a/drivers/net/wireless/ath/ath11k/dp_rx.c
+++ b/drivers/net/wireless/ath/ath11k/dp_rx.c
@@ -21,6 +21,12 @@
 
 #define ATH11K_DP_RX_FRAGMENT_TIMEOUT_MS (2 * HZ)
 
+struct cached_peer_info {
+	enum hal_encrypt_type enctype;
+	u16 seq_no;
+	bool valid;
+};
+
 static inline
 u8 *ath11k_dp_rx_h_80211_hdr(struct ath11k_base *ab, struct hal_rx_desc *desc)
 {
@@ -2232,7 +2238,8 @@ ath11k_dp_rx_h_find_peer(struct ath11k_base *ab, struct sk_buff *msdu)
 static void ath11k_dp_rx_h_mpdu(struct ath11k *ar,
 				struct sk_buff *msdu,
 				struct hal_rx_desc *rx_desc,
-				struct ieee80211_rx_status *rx_status)
+				struct ieee80211_rx_status *rx_status,
+				struct cached_peer_info *peer_cache)
 {
 	bool  fill_crypto_hdr;
 	enum hal_encrypt_type enctype;
@@ -2265,6 +2272,21 @@ static void ath11k_dp_rx_h_mpdu(struct ath11k *ar,
 	}
 	spin_unlock_bh(&ar->ab->base_lock);
 
+	if (!rxcb->peer_id && rxcb->is_first_msdu && !rxcb->is_last_msdu) {
+		peer_cache->enctype = enctype;
+		peer_cache->seq_no = rxcb->seq_no;
+		peer_cache->valid = true;
+	}
+
+	if (!rxcb->peer_id && !rxcb->is_first_msdu && peer_cache->valid) {
+		if (rxcb->seq_no == peer_cache->seq_no)
+			enctype = peer_cache->enctype;
+		else
+			ath11k_dbg(ar->ab, ATH11K_DBG_DATA,
+				   "null peer_id workaround failed. cached seq_no=%d, msdu seq_no=%d",
+				   peer_cache->seq_no, rxcb->seq_no);
+	}
+
 	rx_attention = ath11k_dp_rx_get_attention(ar->ab, rx_desc);
 	err_bitmap = ath11k_dp_rx_h_attn_mpdu_err(rx_attention);
 	if (enctype != HAL_ENCRYPT_TYPE_OPEN && !err_bitmap)
@@ -2506,7 +2528,8 @@ static void ath11k_dp_rx_deliver_msdu(struct ath11k *ar, struct napi_struct *nap
 static int ath11k_dp_rx_process_msdu(struct ath11k *ar,
 				     struct sk_buff *msdu,
 				     struct sk_buff_head *msdu_list,
-				     struct ieee80211_rx_status *rx_status)
+				     struct ieee80211_rx_status *rx_status,
+				     struct cached_peer_info *peer_cache)
 {
 	struct ath11k_base *ab = ar->ab;
 	struct hal_rx_desc *rx_desc, *lrx_desc;
@@ -2574,7 +2597,7 @@ static int ath11k_dp_rx_process_msdu(struct ath11k *ar,
 	}
 
 	ath11k_dp_rx_h_ppdu(ar, rx_desc, rx_status);
-	ath11k_dp_rx_h_mpdu(ar, msdu, rx_desc, rx_status);
+	ath11k_dp_rx_h_mpdu(ar, msdu, rx_desc, rx_status, peer_cache);
 
 	rx_status->flag |= RX_FLAG_SKIP_MONITOR | RX_FLAG_DUP_VALIDATED;
 
@@ -2592,6 +2615,7 @@ static void ath11k_dp_rx_process_received_packets(struct ath11k_base *ab,
 	struct sk_buff *msdu;
 	struct ath11k *ar;
 	struct ieee80211_rx_status rx_status = {};
+	struct cached_peer_info peer_cache = {};
 	int ret;
 
 	if (skb_queue_empty(msdu_list))
@@ -2609,7 +2633,7 @@ static void ath11k_dp_rx_process_received_packets(struct ath11k_base *ab,
 	}
 
 	while ((msdu = __skb_dequeue(msdu_list))) {
-		ret = ath11k_dp_rx_process_msdu(ar, msdu, msdu_list, &rx_status);
+		ret = ath11k_dp_rx_process_msdu(ar, msdu, msdu_list, &rx_status, &peer_cache);
 		if (unlikely(ret)) {
 			ath11k_dbg(ab, ATH11K_DBG_DATA,
 				   "Unable to process msdu %d", ret);
@@ -3959,6 +3983,7 @@ static int ath11k_dp_rx_h_null_q_desc(struct ath11k *ar, struct sk_buff *msdu,
 	u8 l3pad_bytes;
 	struct ath11k_skb_rxcb *rxcb = ATH11K_SKB_RXCB(msdu);
 	u32 hal_rx_desc_sz = ar->ab->hw_params.hal_desc_sz;
+	struct cached_peer_info peer_cache = {};
 
 	msdu_len = ath11k_dp_rx_h_msdu_start_msdu_len(ar->ab, desc);
 
@@ -4002,7 +4027,7 @@ static int ath11k_dp_rx_h_null_q_desc(struct ath11k *ar, struct sk_buff *msdu,
 	}
 	ath11k_dp_rx_h_ppdu(ar, desc, status);
 
-	ath11k_dp_rx_h_mpdu(ar, msdu, desc, status);
+	ath11k_dp_rx_h_mpdu(ar, msdu, desc, status, &peer_cache);
 
 	rxcb->tid = ath11k_dp_rx_h_mpdu_start_tid(ar->ab, desc);
 

---
base-commit: f338e77383789c0cae23ca3d48adcc5e9e137e3c
change-id: 20260326-ath11k-null-peerid-workaround-625a129781b1

Best regards,
--  
Matt


^ permalink raw reply related

* Re: [PATCH rtw-next] wifi: rtw89: Fill fw_version member of struct wiphy
From: kernel test robot @ 2026-03-26 10:31 UTC (permalink / raw)
  To: Bitterblue Smith, linux-wireless@vger.kernel.org
  Cc: llvm, oe-kbuild-all, Ping-Ke Shih
In-Reply-To: <60bdb2f8-d5dd-46a3-8679-5b4a5fd0604b@gmail.com>

Hi Bitterblue,

kernel test robot noticed the following build errors:

[auto build test ERROR on wireless-next/main]
[also build test ERROR on wireless/main linus/master v7.0-rc5 next-20260325]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Bitterblue-Smith/wifi-rtw89-Fill-fw_version-member-of-struct-wiphy/20260326-082721
base:   https://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless-next.git main
patch link:    https://lore.kernel.org/r/60bdb2f8-d5dd-46a3-8679-5b4a5fd0604b%40gmail.com
patch subject: [PATCH rtw-next] wifi: rtw89: Fill fw_version member of struct wiphy
config: loongarch-defconfig (https://download.01.org/0day-ci/archive/20260326/202603261814.fyYqPTNZ-lkp@intel.com/config)
compiler: clang version 19.1.7 (https://github.com/llvm/llvm-project cd708029e0b2869e80abe31ddb175f7c35361f90)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260326/202603261814.fyYqPTNZ-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202603261814.fyYqPTNZ-lkp@intel.com/

All errors (new ones prefixed by >>):

>> drivers/net/wireless/realtek/rtw89/fw.c:759:14: error: use of undeclared identifier 'RTW89_FW_NORMAL_B'
     759 |             type == RTW89_FW_NORMAL_B)
         |                     ^
   1 error generated.


vim +/RTW89_FW_NORMAL_B +759 drivers/net/wireless/realtek/rtw89/fw.c

   726	
   727	static int rtw89_fw_update_ver(struct rtw89_dev *rtwdev,
   728				       enum rtw89_fw_type type,
   729				       struct rtw89_fw_suit *fw_suit)
   730	{
   731		const struct rtw89_fw_hdr *v0 = (const struct rtw89_fw_hdr *)fw_suit->data;
   732		const struct rtw89_fw_hdr_v1 *v1 = (const struct rtw89_fw_hdr_v1 *)fw_suit->data;
   733	
   734		if (type == RTW89_FW_LOGFMT)
   735			return 0;
   736	
   737		fw_suit->type = type;
   738		fw_suit->hdr_ver = le32_get_bits(v0->w3, FW_HDR_W3_HDR_VER);
   739	
   740		switch (fw_suit->hdr_ver) {
   741		case 0:
   742			rtw89_fw_update_ver_v0(rtwdev, fw_suit, v0);
   743			break;
   744		case 1:
   745			rtw89_fw_update_ver_v1(rtwdev, fw_suit, v1);
   746			break;
   747		default:
   748			rtw89_err(rtwdev, "Unknown firmware header version %u\n",
   749				  fw_suit->hdr_ver);
   750			return -ENOENT;
   751		}
   752	
   753		rtw89_info(rtwdev,
   754			   "Firmware version %u.%u.%u.%u (%08x), cmd version %u, type %u\n",
   755			   fw_suit->major_ver, fw_suit->minor_ver, fw_suit->sub_ver,
   756			   fw_suit->sub_idex, fw_suit->commitid, fw_suit->cmd_ver, type);
   757	
   758		if (type == RTW89_FW_NORMAL || type == RTW89_FW_NORMAL_CE ||
 > 759		    type == RTW89_FW_NORMAL_B)
   760			snprintf(rtwdev->hw->wiphy->fw_version,
   761				 sizeof(rtwdev->hw->wiphy->fw_version),
   762				 "%u.%u.%u.%u",
   763				 fw_suit->major_ver, fw_suit->minor_ver,
   764				 fw_suit->sub_ver, fw_suit->sub_idex);
   765	
   766		return 0;
   767	}
   768	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply

* [PATCH v2 wireless-next 15/15] wifi: mac80211: allow add_key on NAN interfaces
From: Miri Korenblit @ 2026-03-26 10:14 UTC (permalink / raw)
  To: linux-wireless; +Cc: Avraham Stern, Ilan Peer
In-Reply-To: <20260326101445.1443198-1-miriam.rachel.korenblit@intel.com>

From: Avraham Stern <avraham.stern@intel.com>

Keys may be added to the NAN interfaces to protect
NAN management frames and data, allow that.

Signed-off-by: Avraham Stern <avraham.stern@intel.com>
Reviewed-by: Ilan Peer <ilan.peer@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 net/mac80211/cfg.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 8cdbefac1bee..210f0030f228 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -702,6 +702,8 @@ static int ieee80211_add_key(struct wiphy *wiphy, struct wireless_dev *wdev,
 		break;
 	case NL80211_IFTYPE_AP:
 	case NL80211_IFTYPE_AP_VLAN:
+	case NL80211_IFTYPE_NAN:
+	case NL80211_IFTYPE_NAN_DATA:
 		/* Keys without a station are used for TX only */
 		if (sta && test_sta_flag(sta, WLAN_STA_MFP))
 			key->conf.flags |= IEEE80211_KEY_FLAG_RX_MGMT;
@@ -718,13 +720,11 @@ static int ieee80211_add_key(struct wiphy *wiphy, struct wireless_dev *wdev,
 	case NL80211_IFTYPE_WDS:
 	case NL80211_IFTYPE_MONITOR:
 	case NL80211_IFTYPE_P2P_DEVICE:
-	case NL80211_IFTYPE_NAN:
 	case NL80211_IFTYPE_UNSPECIFIED:
 	case NUM_NL80211_IFTYPES:
 	case NL80211_IFTYPE_P2P_CLIENT:
 	case NL80211_IFTYPE_P2P_GO:
 	case NL80211_IFTYPE_OCB:
-	case NL80211_IFTYPE_NAN_DATA:
 		/* shouldn't happen */
 		WARN_ON_ONCE(1);
 		break;
-- 
2.34.1


^ permalink raw reply related

* [PATCH v2 wireless-next 14/15] wifi: mac80211: report and drop spurious NAN Data frames
From: Miri Korenblit @ 2026-03-26 10:14 UTC (permalink / raw)
  To: linux-wireless
In-Reply-To: <20260326101445.1443198-1-miriam.rachel.korenblit@intel.com>

According to Wi-Fi Aware (TM) 4.0 specification 6.2.5, in case a frame
is recevied from an address that doesn't belong to any active NDP, the
frame should be dropped and a NAN Data Path Termination should be sent
to the transmitter.
Do it by dropping the frame and calling cfg80211_rx_spurious_frame in
that case.

Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 net/mac80211/rx.c | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index 979ac26d1173..3e5d1c47a5b0 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -1589,6 +1589,25 @@ ieee80211_rx_h_check(struct ieee80211_rx_data *rx)
 	if (ieee80211_vif_is_mesh(&rx->sdata->vif))
 		return ieee80211_rx_mesh_check(rx);
 
+	/*
+	 * Wi-Fi Aware (TM) 4.0 specification 6.2.5:
+	 * For NAN_DATA, unicast data frames must have A2 (source)
+	 * assigned to an active NDP. If not the frame must be dropped
+	 * and NAN Data Path termination frame should be sent. Notify
+	 * user space so it can do so.
+	 */
+	if (rx->sdata->vif.type == NL80211_IFTYPE_NAN_DATA) {
+		if (ieee80211_is_data(hdr->frame_control) &&
+		    !is_multicast_ether_addr(hdr->addr1) &&
+		    (!rx->sta || !test_sta_flag(rx->sta, WLAN_STA_ASSOC))) {
+			if (cfg80211_rx_spurious_frame(rx->sdata->dev, hdr->addr2,
+						       rx->link_id, GFP_ATOMIC))
+				return RX_DROP_U_SPURIOUS_NOTIF;
+			return RX_DROP_U_SPURIOUS;
+		}
+		return RX_CONTINUE;
+	}
+
 	if (unlikely((ieee80211_is_data(hdr->frame_control) ||
 		      ieee80211_is_pspoll(hdr->frame_control)) &&
 		     rx->sdata->vif.type != NL80211_IFTYPE_ADHOC &&
-- 
2.34.1


^ permalink raw reply related

* [PATCH v2 wireless-next 13/15] wifi: mac80211: allow block ack agreements in NAN Data
From: Miri Korenblit @ 2026-03-26 10:14 UTC (permalink / raw)
  To: linux-wireless
In-Reply-To: <20260326101445.1443198-1-miriam.rachel.korenblit@intel.com>

Allow receiving and sending Add Block Ack action frames for NAN Data

Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 net/mac80211/agg-tx.c | 3 ++-
 net/mac80211/rx.c     | 3 ++-
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/net/mac80211/agg-tx.c b/net/mac80211/agg-tx.c
index 01d927b88264..4833b46770b6 100644
--- a/net/mac80211/agg-tx.c
+++ b/net/mac80211/agg-tx.c
@@ -641,7 +641,8 @@ int ieee80211_start_tx_ba_session(struct ieee80211_sta *pubsta, u16 tid,
 	    sdata->vif.type != NL80211_IFTYPE_MESH_POINT &&
 	    sdata->vif.type != NL80211_IFTYPE_AP_VLAN &&
 	    sdata->vif.type != NL80211_IFTYPE_AP &&
-	    sdata->vif.type != NL80211_IFTYPE_ADHOC)
+	    sdata->vif.type != NL80211_IFTYPE_ADHOC &&
+	    sdata->vif.type != NL80211_IFTYPE_NAN_DATA)
 		return -EINVAL;
 
 	if (test_sta_flag(sta, WLAN_STA_BLOCK_BA)) {
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index a00b73420929..979ac26d1173 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -3748,7 +3748,8 @@ ieee80211_rx_h_action(struct ieee80211_rx_data *rx)
 		    sdata->vif.type != NL80211_IFTYPE_MESH_POINT &&
 		    sdata->vif.type != NL80211_IFTYPE_AP_VLAN &&
 		    sdata->vif.type != NL80211_IFTYPE_AP &&
-		    sdata->vif.type != NL80211_IFTYPE_ADHOC)
+		    sdata->vif.type != NL80211_IFTYPE_ADHOC &&
+		    sdata->vif.type != NL80211_IFTYPE_NAN_DATA)
 			break;
 
 		/* verify action_code is present */
-- 
2.34.1


^ permalink raw reply related

* [PATCH v2 wireless-next 11/15] wifi: mac80211: add support for TX over NAN_DATA interfaces
From: Miri Korenblit @ 2026-03-26 10:14 UTC (permalink / raw)
  To: linux-wireless
In-Reply-To: <20260326101445.1443198-1-miriam.rachel.korenblit@intel.com>

Add support for TXing frames over NAN_DATA interfaces:
- find the NDI station
- populoate the addresses fields
- use NUM_NL80211_BANDS for the band, similar to NAN interfaces.

Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 net/mac80211/tx.c | 32 +++++++++++++++++++++++++++++---
 1 file changed, 29 insertions(+), 3 deletions(-)

diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c
index 730c208c3bdf..b0eacab6763d 100644
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -2541,6 +2541,13 @@ int ieee80211_lookup_ra_sta(struct ieee80211_sub_if_data *sdata,
 		if (!sta)
 			return -ENOLINK;
 		break;
+	case NL80211_IFTYPE_NAN_DATA:
+		if (is_multicast_ether_addr(skb->data)) {
+			*sta_out = ERR_PTR(-ENOENT);
+			return 0;
+		}
+		sta = sta_info_get(sdata, skb->data);
+		break;
 	default:
 		return -EINVAL;
 	}
@@ -2834,18 +2841,37 @@ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata,
 		memcpy(hdr.addr3, sdata->u.ibss.bssid, ETH_ALEN);
 		hdrlen = 24;
 		break;
+	case NL80211_IFTYPE_NAN_DATA: {
+		struct ieee80211_sub_if_data *nmi;
+
+		/* DA SA Cluster ID */
+		memcpy(hdr.addr1, skb->data, ETH_ALEN);
+		memcpy(hdr.addr2, skb->data + ETH_ALEN, ETH_ALEN);
+		nmi = rcu_dereference(sdata->u.nan_data.nmi);
+		if (!nmi) {
+			ret = -ENOTCONN;
+			goto free;
+		}
+		memcpy(hdr.addr3, nmi->wdev.u.nan.cluster_id, ETH_ALEN);
+		hdrlen = 24;
+		break;
+	}
 	default:
 		ret = -EINVAL;
 		goto free;
 	}
 
 	if (!chanctx_conf) {
-		if (!ieee80211_vif_is_mld(&sdata->vif)) {
+		if (sdata->vif.type == NL80211_IFTYPE_NAN_DATA) {
+			 /* NAN operates on multiple bands */
+			band = NUM_NL80211_BANDS;
+		} else if (!ieee80211_vif_is_mld(&sdata->vif)) {
 			ret = -ENOTCONN;
 			goto free;
+		} else {
+			/* MLD transmissions must not rely on the band */
+			band = 0;
 		}
-		/* MLD transmissions must not rely on the band */
-		band = 0;
 	} else {
 		band = chanctx_conf->def.chan->band;
 	}
-- 
2.34.1


^ permalink raw reply related

* [PATCH v2 wireless-next 12/15] wifi: mac80211: Accept frames on NAN DATA interfaces
From: Miri Korenblit @ 2026-03-26 10:14 UTC (permalink / raw)
  To: linux-wireless
In-Reply-To: <20260326101445.1443198-1-miriam.rachel.korenblit@intel.com>

Accept frames there were received on NAN DATA interfaces:

- Data frames, both multicast or unicast
- Non-Public action frames, both multicast or unicast
- Unicast secure management frames
- FromDS and ToDS are 0.

While at it, check FromDS/ToDS also for NAN management frames.

Accept only data frames from devices that are part of the NAN
cluster.

Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 net/mac80211/rx.c | 37 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 36 insertions(+), 1 deletion(-)

diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index dbdd67c181d8..a00b73420929 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -4469,6 +4469,9 @@ static bool ieee80211_accept_frame(struct ieee80211_rx_data *rx)
 	u8 *bssid = ieee80211_get_bssid(hdr, skb->len, sdata->vif.type);
 	bool multicast = is_multicast_ether_addr(hdr->addr1) ||
 			 ieee80211_is_s1g_beacon(hdr->frame_control);
+	static const u8 nan_network_id[ETH_ALEN] __aligned(2) = {
+		0x51, 0x6F, 0x9A, 0x01, 0x00, 0x00
+	};
 
 	switch (sdata->vif.type) {
 	case NL80211_IFTYPE_STATION:
@@ -4597,6 +4600,10 @@ static bool ieee80211_accept_frame(struct ieee80211_rx_data *rx)
 		       (ieee80211_is_auth(hdr->frame_control) &&
 			ether_addr_equal(sdata->vif.addr, hdr->addr1));
 	case NL80211_IFTYPE_NAN:
+		if (ieee80211_has_tods(hdr->frame_control) ||
+		    ieee80211_has_fromds(hdr->frame_control))
+			return false;
+
 		/* Accept only frames that are addressed to the NAN cluster
 		 * (based on the Cluster ID). From these frames, accept only
 		 * action frames or authentication frames that are addressed to
@@ -4608,7 +4615,35 @@ static bool ieee80211_accept_frame(struct ieee80211_rx_data *rx)
 			 (ieee80211_is_auth(hdr->frame_control) &&
 			  ether_addr_equal(sdata->vif.addr, hdr->addr1)));
 	case NL80211_IFTYPE_NAN_DATA:
-		return false;
+		if (ieee80211_has_tods(hdr->frame_control) ||
+		    ieee80211_has_fromds(hdr->frame_control))
+			return false;
+
+		if (ieee80211_is_data(hdr->frame_control)) {
+			struct ieee80211_sub_if_data *nmi;
+
+			nmi = rcu_dereference(sdata->u.nan_data.nmi);
+			if (!nmi)
+				return false;
+
+			if (!ether_addr_equal(nmi->wdev.u.nan.cluster_id,
+					      hdr->addr3))
+				return false;
+
+			return multicast ||
+			       ether_addr_equal(sdata->vif.addr, hdr->addr1);
+		}
+
+		/* Non-public action frames (unicast or multicast) */
+		if (ieee80211_is_action(hdr->frame_control) &&
+		    !ieee80211_is_public_action(hdr, skb->len) &&
+		    (ether_addr_equal(nan_network_id, hdr->addr1) ||
+		     ether_addr_equal(sdata->vif.addr, hdr->addr1)))
+			return true;
+
+		/* Unicast secure management frames */
+		return ether_addr_equal(sdata->vif.addr, hdr->addr1) &&
+		       ieee80211_is_unicast_robust_mgmt_frame(skb);
 	default:
 		break;
 	}
-- 
2.34.1


^ permalink raw reply related

* [PATCH v2 wireless-next 10/15] wifi: mac80211: update NAN data path state on schedule changes
From: Miri Korenblit @ 2026-03-26 10:14 UTC (permalink / raw)
  To: linux-wireless
In-Reply-To: <20260326101445.1443198-1-miriam.rachel.korenblit@intel.com>

A carrier of an NDI interface is turned on when there is at least one NDI
station that: (1) correlates to this interface (2) is authorized (3) the
NAN peer to which this station belongs has at least one common slot with
the local schedule. Otherwise, it is turned off.
(common slots are slots where both schedules are active on compatible
 channels.)

Implement the calculation of the carrier state and trigger it when
needed.

Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 net/mac80211/cfg.c         |  10 +++-
 net/mac80211/ieee80211_i.h |   1 +
 net/mac80211/nan.c         | 108 +++++++++++++++++++++++++++++++++++++
 net/mac80211/sta_info.c    |   4 ++
 4 files changed, 122 insertions(+), 1 deletion(-)

diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 493f7b5cfd6f..8cdbefac1bee 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -2502,7 +2502,15 @@ static int ieee80211_add_station(struct wiphy *wiphy, struct wireless_dev *wdev,
 	    test_sta_flag(sta, WLAN_STA_ASSOC))
 		rate_control_rate_init_all_links(sta);
 
-	return sta_info_insert(sta);
+	err = sta_info_insert(sta);
+
+	/*
+	 * ieee80211_nan_update_ndi_carrier was called from sta_apply_parameters,
+	 * but then we did not have the STA in the list.
+	 */
+	if (!err && sdata->vif.type == NL80211_IFTYPE_NAN_DATA)
+		ieee80211_nan_update_ndi_carrier(sta->sdata);
+	return err;
 }
 
 static int ieee80211_del_station(struct wiphy *wiphy, struct wireless_dev *wdev,
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 23bf00472915..2a693406294b 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -2045,6 +2045,7 @@ int ieee80211_nan_set_local_sched(struct ieee80211_sub_if_data *sdata,
 int ieee80211_nan_set_peer_sched(struct ieee80211_sub_if_data *sdata,
 				 struct cfg80211_nan_peer_sched *sched);
 void ieee80211_nan_free_peer_sched(struct ieee80211_nan_peer_sched *sched);
+void ieee80211_nan_update_ndi_carrier(struct ieee80211_sub_if_data *ndi_sdata);
 
 /* scan/BSS handling */
 void ieee80211_scan_work(struct wiphy *wiphy, struct wiphy_work *work);
diff --git a/net/mac80211/nan.c b/net/mac80211/nan.c
index 5e1f9bb7c49d..4e262b624521 100644
--- a/net/mac80211/nan.c
+++ b/net/mac80211/nan.c
@@ -220,6 +220,23 @@ ieee80211_nan_remove_channel(struct ieee80211_sub_if_data *sdata,
 		ieee80211_free_chanctx(sdata->local, ctx, false);
 }
 
+static void
+ieee80211_nan_update_all_ndi_carriers(struct ieee80211_local *local)
+{
+	struct ieee80211_sub_if_data *sdata;
+
+	lockdep_assert_wiphy(local->hw.wiphy);
+
+	/* Iterate all interfaces and update carrier for NDI interfaces */
+	list_for_each_entry(sdata, &local->interfaces, list) {
+		if (!ieee80211_sdata_running(sdata) ||
+		    sdata->vif.type != NL80211_IFTYPE_NAN_DATA)
+			continue;
+
+		ieee80211_nan_update_ndi_carrier(sdata);
+	}
+}
+
 static struct ieee80211_nan_channel *
 ieee80211_nan_find_free_channel(struct ieee80211_nan_sched_cfg *sched_cfg)
 {
@@ -347,6 +364,7 @@ int ieee80211_nan_set_local_sched(struct ieee80211_sub_if_data *sdata,
 	if (sched_cfg->deferred)
 		return 0;
 
+	ieee80211_nan_update_all_ndi_carriers(sdata->local);
 	bitmap_zero(sdata->u.nan.removed_channels, IEEE80211_NAN_MAX_CHANNELS);
 
 	return 0;
@@ -409,6 +427,7 @@ int ieee80211_nan_set_local_sched(struct ieee80211_sub_if_data *sdata,
 	bitmap_zero(sdata->u.nan.removed_channels, IEEE80211_NAN_MAX_CHANNELS);
 
 	drv_vif_cfg_changed(sdata->local, sdata, BSS_CHANGED_NAN_LOCAL_SCHED);
+	ieee80211_nan_update_all_ndi_carriers(sdata->local);
 	return ret;
 }
 
@@ -423,6 +442,8 @@ void ieee80211_nan_sched_update_done(struct ieee80211_vif *vif)
 	if (WARN_ON(!sched_cfg->deferred))
 		return;
 
+	ieee80211_nan_update_all_ndi_carriers(sdata->local);
+
 	/*
 	 * Clear the deferred flag before removing channels. Removing channels
 	 * will trigger another schedule update to the driver, and there is no
@@ -531,6 +552,91 @@ ieee80211_nan_init_peer_map(struct ieee80211_nan_peer_sched *peer_sched,
 	}
 }
 
+/*
+ * Check if the local schedule and a peer schedule have at least one common
+ * slot - a slot where both schedules are active on compatible channels.
+ */
+static bool
+ieee80211_nan_has_common_slots(struct ieee80211_sub_if_data *sdata,
+			       struct ieee80211_nan_peer_sched *peer_sched)
+{
+	for (int slot = 0; slot < CFG80211_NAN_SCHED_NUM_TIME_SLOTS; slot++) {
+		struct ieee80211_nan_channel *local_chan =
+			sdata->vif.cfg.nan_sched.schedule[slot];
+
+		if (!local_chan || !local_chan->chanctx_conf)
+			continue;
+
+		/* Check all peer maps for this slot */
+		for (int m = 0; m < CFG80211_NAN_MAX_PEER_MAPS; m++) {
+			struct ieee80211_nan_peer_map *map = &peer_sched->maps[m];
+			struct ieee80211_nan_channel *peer_chan;
+
+			if (map->map_id == CFG80211_NAN_INVALID_MAP_ID)
+				continue;
+
+			peer_chan = map->slots[slot];
+			if (!peer_chan)
+				continue;
+
+			if (local_chan->chanctx_conf == peer_chan->chanctx_conf)
+				return true;
+		}
+	}
+
+	return false;
+}
+
+void ieee80211_nan_update_ndi_carrier(struct ieee80211_sub_if_data *ndi_sdata)
+{
+	struct ieee80211_local *local = ndi_sdata->local;
+	struct ieee80211_sub_if_data *nmi_sdata;
+	struct sta_info *sta;
+
+	lockdep_assert_wiphy(local->hw.wiphy);
+
+	if (WARN_ON(ndi_sdata->vif.type != NL80211_IFTYPE_NAN_DATA ||
+		    !ndi_sdata->dev) || !ieee80211_sdata_running(ndi_sdata))
+		return;
+
+	nmi_sdata = wiphy_dereference(local->hw.wiphy, ndi_sdata->u.nan_data.nmi);
+	if (WARN_ON(!nmi_sdata))
+		return;
+
+	list_for_each_entry(sta, &local->sta_list, list) {
+		struct ieee80211_sta *nmi_sta;
+
+		if (sta->sdata != ndi_sdata ||
+		    !test_sta_flag(sta, WLAN_STA_AUTHORIZED))
+			continue;
+
+		nmi_sta = wiphy_dereference(local->hw.wiphy, sta->sta.nmi);
+		if (WARN_ON(!nmi_sta) || !nmi_sta->nan_sched)
+			continue;
+
+		if (ieee80211_nan_has_common_slots(nmi_sdata, nmi_sta->nan_sched)) {
+			netif_carrier_on(ndi_sdata->dev);
+			return;
+		}
+	}
+
+	netif_carrier_off(ndi_sdata->dev);
+}
+
+static void
+ieee80211_nan_update_peer_ndis_carrier(struct ieee80211_local *local,
+				       struct sta_info *nmi_sta)
+{
+	struct sta_info *sta;
+
+	lockdep_assert_wiphy(local->hw.wiphy);
+
+	list_for_each_entry(sta, &local->sta_list, list) {
+		if (rcu_access_pointer(sta->sta.nmi) == &nmi_sta->sta)
+			ieee80211_nan_update_ndi_carrier(sta->sdata);
+	}
+}
+
 int ieee80211_nan_set_peer_sched(struct ieee80211_sub_if_data *sdata,
 				 struct cfg80211_nan_peer_sched *sched)
 {
@@ -592,6 +698,8 @@ int ieee80211_nan_set_peer_sched(struct ieee80211_sub_if_data *sdata,
 		goto out;
 	}
 
+	ieee80211_nan_update_peer_ndis_carrier(sdata->local, sta);
+
 	/* Success - free old schedule */
 	to_free = old_sched;
 	ret = 0;
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index dd0d92abf60d..de85a7c68bec 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -1454,6 +1454,8 @@ static int _sta_info_move_state(struct sta_info *sta,
 		} else if (sta->sta_state == IEEE80211_STA_AUTHORIZED) {
 			ieee80211_vif_dec_num_mcast(sta->sdata);
 			clear_bit(WLAN_STA_AUTHORIZED, &sta->_flags);
+			if (sta->sdata->vif.type == NL80211_IFTYPE_NAN_DATA)
+				ieee80211_nan_update_ndi_carrier(sta->sdata);
 
 			/*
 			 * If we have encryption offload, flush (station) queues
@@ -1482,6 +1484,8 @@ static int _sta_info_move_state(struct sta_info *sta,
 			set_bit(WLAN_STA_AUTHORIZED, &sta->_flags);
 			ieee80211_check_fast_xmit(sta);
 			ieee80211_check_fast_rx(sta);
+			if (sta->sdata->vif.type == NL80211_IFTYPE_NAN_DATA)
+				ieee80211_nan_update_ndi_carrier(sta->sdata);
 		}
 		if (sta->sdata->vif.type == NL80211_IFTYPE_AP_VLAN ||
 		    sta->sdata->vif.type == NL80211_IFTYPE_AP)
-- 
2.34.1


^ permalink raw reply related

* [PATCH v2 wireless-next 09/15] wifi: mac80211: add NAN peer schedule support
From: Miri Korenblit @ 2026-03-26 10:14 UTC (permalink / raw)
  To: linux-wireless
In-Reply-To: <20260326101445.1443198-1-miriam.rachel.korenblit@intel.com>

Peer schedules specify which channels the peer is available on and when.
Add support for configuring peer NAN schedules:
- build and store the schedule and maps
- for each channel, make sure that it fits into the capabilities, and
  take the minimum between it and the local compatible nan channel.
- configure the driver

Note that the removal of a peer schedule should be done by the driver
upon NMI station removal.

Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 include/net/mac80211.h     |  62 +++++++++-
 net/mac80211/cfg.c         |  13 +++
 net/mac80211/driver-ops.h  |  21 ++++
 net/mac80211/ieee80211_i.h |   3 +
 net/mac80211/nan.c         | 226 ++++++++++++++++++++++++++++++++++++-
 net/mac80211/sta_info.c    |   4 +
 net/mac80211/trace.h       |  31 +++++
 net/mac80211/util.c        |   8 ++
 8 files changed, 365 insertions(+), 3 deletions(-)

diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 8656e2cc2b75..783757f14b61 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -877,8 +877,11 @@ struct ieee80211_bss_conf {
  *	is irrelevant for NAN, still store it for convenience - some functions
  *	require it as an argument.
  * @needed_rx_chains: number of RX chains needed for this NAN channel
- * @chanctx_conf: chanctx_conf assigned to this NAN channel. Will be %NULL
- *	if the channel is ULWed.
+ * @chanctx_conf: chanctx_conf assigned to this NAN channel.
+ *	If a local channel is being ULWed (because we needed this chanctx for
+ *	something else), the local NAN channel that used this chanctx,
+ *	will have this pointer set to %NULL.
+ *	A peer NAN channel should never have this pointer set to %NULL.
  * @channel_entry: the Channel Entry blob as defined in Wi-Fi Aware
  *	(TM) 4.0 specification Table 100 (Channel Entry format for the NAN
  *	Availability attribute).
@@ -890,6 +893,49 @@ struct ieee80211_nan_channel {
 	u8 channel_entry[6];
 };
 
+/**
+ * struct ieee80211_nan_peer_map - NAN peer schedule map
+ *
+ * This stores a single map from a peer's schedule. Each peer can have
+ * multiple maps.
+ *
+ * @map_id: the map ID from the peer schedule, %CFG80211_NAN_INVALID_MAP_ID
+ *	if unused
+ * @slots: mapping of time slots to channel configurations in the schedule's
+ *	channels array
+ */
+struct ieee80211_nan_peer_map {
+	u8 map_id;
+	struct ieee80211_nan_channel *slots[CFG80211_NAN_SCHED_NUM_TIME_SLOTS];
+};
+
+/**
+ * struct ieee80211_nan_peer_sched - NAN peer schedule
+ *
+ * This stores the complete schedule from a peer. Contains peer-level
+ * parameters and an array of schedule maps.
+ *
+ * @seq_id: the sequence ID from the peer schedule
+ * @committed_dw: committed DW as published by the peer
+ * @max_chan_switch: maximum channel switch time in microseconds
+ * @init_ulw: initial ULWs as published by the peer (copied)
+ * @ulw_size: number of bytes in @init_ulw
+ * @maps: array of peer schedule maps. Invalid slots have map_id set to
+ *	%CFG80211_NAN_INVALID_MAP_ID.
+ * @n_channels: number of valid channel entries in @channels
+ * @channels: flexible array of negotiated peer channels for this schedule
+ */
+struct ieee80211_nan_peer_sched {
+	u8 seq_id;
+	u16 committed_dw;
+	u16 max_chan_switch;
+	const u8 *init_ulw;
+	u16 ulw_size;
+	struct ieee80211_nan_peer_map maps[CFG80211_NAN_MAX_PEER_MAPS];
+	u8 n_channels;
+	struct ieee80211_nan_channel channels[] __counted_by(n_channels);
+};
+
 /**
  * enum mac80211_tx_info_flags - flags to describe transmission information/status
  *
@@ -2625,6 +2671,7 @@ struct ieee80211_link_sta {
  * @spp_amsdu: indicates whether the STA uses SPP A-MSDU or not.
  * @epp_peer: indicates that the peer is an EPP peer.
  * @nmi: For NDI stations, pointer to the NMI station of the peer.
+ * @nan_sched: NAN peer schedule for this station. Valid only for NMI stations.
  */
 struct ieee80211_sta {
 	u8 addr[ETH_ALEN] __aligned(2);
@@ -2655,6 +2702,9 @@ struct ieee80211_sta {
 
 	struct ieee80211_sta __rcu *nmi;
 
+	/* should only be accessed with the wiphy mutex held */
+	struct ieee80211_nan_peer_sched *nan_sched;
+
 	/* must be last */
 	u8 drv_priv[] __aligned(sizeof(void *));
 };
@@ -4556,6 +4606,12 @@ struct ieee80211_prep_tx_info {
  * @del_nan_func: Remove a NAN function. The driver must call
  *	ieee80211_nan_func_terminated() with
  *	NL80211_NAN_FUNC_TERM_REASON_USER_REQUEST reason code upon removal.
+ * @nan_peer_sched_changed: Notifies the driver that the peer NAN schedule
+ *	has changed. The new schedule is available via sta->nan_sched.
+ *	Note that the channel_entry blob might not match the actual chandef
+ *	since the bandwidth of the chandef is the minimum of the local and peer
+ *	bandwidth. It is the driver responsibility to remove the peer schedule
+ *	when the NMI station is removed.
  * @can_aggregate_in_amsdu: Called in order to determine if HW supports
  *	aggregating two specific frames in the same A-MSDU. The relation
  *	between the skbs should be symmetric and transitive. Note that while
@@ -4961,6 +5017,8 @@ struct ieee80211_ops {
 	void (*del_nan_func)(struct ieee80211_hw *hw,
 			    struct ieee80211_vif *vif,
 			    u8 instance_id);
+	int (*nan_peer_sched_changed)(struct ieee80211_hw *hw,
+				      struct ieee80211_sta *sta);
 	bool (*can_aggregate_in_amsdu)(struct ieee80211_hw *hw,
 				       struct sk_buff *head,
 				       struct sk_buff *skb);
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index a1089e3964bd..493f7b5cfd6f 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -5689,6 +5689,18 @@ ieee80211_set_local_nan_sched(struct wiphy *wiphy,
 	return ieee80211_nan_set_local_sched(sdata, sched);
 }
 
+static int
+ieee80211_set_peer_nan_sched(struct wiphy *wiphy,
+			     struct wireless_dev *wdev,
+			     struct cfg80211_nan_peer_sched *sched)
+{
+	struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev);
+
+	lockdep_assert_wiphy(sdata->local->hw.wiphy);
+
+	return ieee80211_nan_set_peer_sched(sdata, sched);
+}
+
 const struct cfg80211_ops mac80211_config_ops = {
 	.add_virtual_intf = ieee80211_add_iface,
 	.del_virtual_intf = ieee80211_del_iface,
@@ -5806,4 +5818,5 @@ const struct cfg80211_ops mac80211_config_ops = {
 	.assoc_ml_reconf = ieee80211_assoc_ml_reconf,
 	.set_epcs = ieee80211_set_epcs,
 	.nan_set_local_sched = ieee80211_set_local_nan_sched,
+	.nan_set_peer_sched = ieee80211_set_peer_nan_sched,
 };
diff --git a/net/mac80211/driver-ops.h b/net/mac80211/driver-ops.h
index 51bf3c7822a7..f1c0b87fddd5 100644
--- a/net/mac80211/driver-ops.h
+++ b/net/mac80211/driver-ops.h
@@ -1793,4 +1793,25 @@ static inline int drv_set_eml_op_mode(struct ieee80211_sub_if_data *sdata,
 	return ret;
 }
 
+static inline int
+drv_nan_peer_sched_changed(struct ieee80211_local *local,
+			   struct ieee80211_sub_if_data *sdata,
+			   struct sta_info *sta)
+{
+	int ret;
+
+	might_sleep();
+	lockdep_assert_wiphy(local->hw.wiphy);
+	check_sdata_in_driver(sdata);
+
+	if (!local->ops->nan_peer_sched_changed)
+		return -EOPNOTSUPP;
+
+	trace_drv_nan_peer_sched_changed(local, sdata, &sta->sta);
+	ret = local->ops->nan_peer_sched_changed(&local->hw, &sta->sta);
+	trace_drv_return_int(local, ret);
+
+	return ret;
+}
+
 #endif /* __MAC80211_DRIVER_OPS */
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index e3a051beba6a..23bf00472915 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -2042,6 +2042,9 @@ int ieee80211_mesh_finish_csa(struct ieee80211_sub_if_data *sdata,
 /* NAN code */
 int ieee80211_nan_set_local_sched(struct ieee80211_sub_if_data *sdata,
 				  struct cfg80211_nan_local_sched *sched);
+int ieee80211_nan_set_peer_sched(struct ieee80211_sub_if_data *sdata,
+				 struct cfg80211_nan_peer_sched *sched);
+void ieee80211_nan_free_peer_sched(struct ieee80211_nan_peer_sched *sched);
 
 /* scan/BSS handling */
 void ieee80211_scan_work(struct wiphy *wiphy, struct wiphy_work *work);
diff --git a/net/mac80211/nan.c b/net/mac80211/nan.c
index 2fa55e9a9dab..5e1f9bb7c49d 100644
--- a/net/mac80211/nan.c
+++ b/net/mac80211/nan.c
@@ -1,12 +1,13 @@
 // SPDX-License-Identifier: GPL-2.0-only
 /*
  * NAN mode implementation
- * Copyright(c) 2025 Intel Corporation
+ * Copyright(c) 2025-2026 Intel Corporation
  */
 #include <net/mac80211.h>
 
 #include "ieee80211_i.h"
 #include "driver-ops.h"
+#include "sta_info.h"
 
 static void
 ieee80211_nan_init_channel(struct ieee80211_nan_channel *nan_channel,
@@ -96,6 +97,82 @@ ieee80211_nan_use_chanctx(struct ieee80211_sub_if_data *sdata,
 	return 0;
 }
 
+static void
+ieee80211_nan_update_peer_channels(struct ieee80211_sub_if_data *sdata,
+				   struct ieee80211_chanctx_conf *removed_conf)
+{
+	struct ieee80211_local *local = sdata->local;
+	struct sta_info *sta;
+
+	lockdep_assert_wiphy(local->hw.wiphy);
+
+	list_for_each_entry(sta, &local->sta_list, list) {
+		struct ieee80211_nan_peer_sched *peer_sched;
+		int write_idx = 0;
+		bool updated = false;
+
+		if (sta->sdata != sdata)
+			continue;
+
+		peer_sched = sta->sta.nan_sched;
+		if (!peer_sched)
+			continue;
+
+		/* NULL out map slots for channels being removed */
+		for (int i = 0; i < peer_sched->n_channels; i++) {
+			if (peer_sched->channels[i].chanctx_conf != removed_conf)
+				continue;
+
+			for (int m = 0; m < CFG80211_NAN_MAX_PEER_MAPS; m++) {
+				struct ieee80211_nan_peer_map *map =
+					&peer_sched->maps[m];
+
+				if (map->map_id == CFG80211_NAN_INVALID_MAP_ID)
+					continue;
+
+				for (int s = 0; s < ARRAY_SIZE(map->slots); s++)
+					if (map->slots[s] == &peer_sched->channels[i])
+						map->slots[s] = NULL;
+			}
+		}
+
+		/* Compact channels array, removing those with removed_conf */
+		for (int i = 0; i < peer_sched->n_channels; i++) {
+			if (peer_sched->channels[i].chanctx_conf == removed_conf) {
+				updated = true;
+				continue;
+			}
+
+			if (write_idx != i) {
+				/* Update map pointers before moving */
+				for (int m = 0; m < CFG80211_NAN_MAX_PEER_MAPS; m++) {
+					struct ieee80211_nan_peer_map *map =
+						&peer_sched->maps[m];
+
+					if (map->map_id == CFG80211_NAN_INVALID_MAP_ID)
+						continue;
+
+					for (int s = 0; s < ARRAY_SIZE(map->slots); s++)
+						if (map->slots[s] == &peer_sched->channels[i])
+							map->slots[s] = &peer_sched->channels[write_idx];
+				}
+
+				peer_sched->channels[write_idx] = peer_sched->channels[i];
+			}
+			write_idx++;
+		}
+
+		/* Clear any remaining entries at the end */
+		for (int i = write_idx; i < peer_sched->n_channels; i++)
+			memset(&peer_sched->channels[i], 0, sizeof(peer_sched->channels[i]));
+
+		peer_sched->n_channels = write_idx;
+
+		if (updated)
+			drv_nan_peer_sched_changed(local, sdata, sta);
+	}
+}
+
 static void
 ieee80211_nan_remove_channel(struct ieee80211_sub_if_data *sdata,
 			     struct ieee80211_nan_channel *nan_channel)
@@ -118,6 +195,10 @@ ieee80211_nan_remove_channel(struct ieee80211_sub_if_data *sdata,
 
 	conf = nan_channel->chanctx_conf;
 
+	/* If any peer nan schedule uses this chanctx, update them */
+	if (conf)
+		ieee80211_nan_update_peer_channels(sdata, conf);
+
 	memset(nan_channel, 0, sizeof(*nan_channel));
 
 	/* Update the driver before (possibly) releasing the channel context */
@@ -376,3 +457,146 @@ void ieee80211_nan_sched_update_done(struct ieee80211_vif *vif)
 				       GFP_KERNEL);
 }
 EXPORT_SYMBOL(ieee80211_nan_sched_update_done);
+
+void ieee80211_nan_free_peer_sched(struct ieee80211_nan_peer_sched *sched)
+{
+	if (!sched)
+		return;
+
+	kfree(sched->init_ulw);
+	kfree(sched);
+}
+
+static int
+ieee80211_nan_init_peer_channel(struct ieee80211_sub_if_data *sdata,
+				const struct sta_info *sta,
+				const struct cfg80211_nan_channel *cfg_chan,
+				struct ieee80211_nan_channel *new_chan)
+{
+	struct ieee80211_nan_sched_cfg *sched_cfg = &sdata->vif.cfg.nan_sched;
+
+	/* Find compatible local channel */
+	for (int j = 0; j < ARRAY_SIZE(sched_cfg->channels); j++) {
+		struct ieee80211_nan_channel *local_chan =
+			&sched_cfg->channels[j];
+		const struct cfg80211_chan_def *compat;
+
+		if (!local_chan->chanreq.oper.chan)
+			continue;
+
+		compat = cfg80211_chandef_compatible(&local_chan->chanreq.oper,
+						     &cfg_chan->chandef);
+		if (!compat)
+			continue;
+
+		/* compat is the wider chandef, and we want the narrower one */
+		new_chan->chanreq.oper = compat == &local_chan->chanreq.oper ?
+					 cfg_chan->chandef : local_chan->chanreq.oper;
+		new_chan->needed_rx_chains = min(local_chan->needed_rx_chains,
+						 cfg_chan->rx_nss);
+		new_chan->chanctx_conf = local_chan->chanctx_conf;
+
+		break;
+	}
+
+	/*
+	 * nl80211 already validated that each peer channel is compatible
+	 * with at least one local channel, so this should never happen.
+	 */
+	if (WARN_ON(!new_chan->chanreq.oper.chan))
+		return -EINVAL;
+
+	memcpy(new_chan->channel_entry, cfg_chan->channel_entry,
+	       sizeof(new_chan->channel_entry));
+
+	return 0;
+}
+
+static void
+ieee80211_nan_init_peer_map(struct ieee80211_nan_peer_sched *peer_sched,
+			    const struct cfg80211_nan_peer_map *cfg_map,
+			    struct ieee80211_nan_peer_map *new_map)
+{
+	new_map->map_id = cfg_map->map_id;
+
+	if (new_map->map_id == CFG80211_NAN_INVALID_MAP_ID)
+		return;
+
+	/* Set up the slots array */
+	for (int slot = 0; slot < ARRAY_SIZE(new_map->slots); slot++) {
+		u8 chan_idx = cfg_map->schedule[slot];
+
+		if (chan_idx < peer_sched->n_channels)
+			new_map->slots[slot] = &peer_sched->channels[chan_idx];
+	}
+}
+
+int ieee80211_nan_set_peer_sched(struct ieee80211_sub_if_data *sdata,
+				 struct cfg80211_nan_peer_sched *sched)
+{
+	struct ieee80211_nan_peer_sched *new_sched, *old_sched, *to_free;
+	struct sta_info *sta;
+	int ret;
+
+	lockdep_assert_wiphy(sdata->local->hw.wiphy);
+
+	if (!sdata->u.nan.started)
+		return -EINVAL;
+
+	sta = sta_info_get(sdata, sched->peer_addr);
+	if (!sta)
+		return -ENOENT;
+
+	new_sched = kzalloc(struct_size(new_sched, channels, sched->n_channels),
+			    GFP_KERNEL);
+	if (!new_sched)
+		return -ENOMEM;
+
+	to_free = new_sched;
+
+	new_sched->seq_id = sched->seq_id;
+	new_sched->committed_dw = sched->committed_dw;
+	new_sched->max_chan_switch = sched->max_chan_switch;
+	new_sched->n_channels = sched->n_channels;
+
+	if (sched->ulw_size && sched->init_ulw) {
+		new_sched->init_ulw = kmemdup(sched->init_ulw, sched->ulw_size,
+					      GFP_KERNEL);
+		if (!new_sched->init_ulw) {
+			ret = -ENOMEM;
+			goto out;
+		}
+		new_sched->ulw_size = sched->ulw_size;
+	}
+
+	for (int i = 0; i < sched->n_channels; i++) {
+		ret = ieee80211_nan_init_peer_channel(sdata, sta,
+						      &sched->nan_channels[i],
+						      &new_sched->channels[i]);
+		if (ret)
+			goto out;
+	}
+
+	for (int m = 0; m < ARRAY_SIZE(sched->maps); m++)
+		ieee80211_nan_init_peer_map(new_sched, &sched->maps[m],
+					    &new_sched->maps[m]);
+
+	/* Install the new schedule before calling the driver */
+	old_sched = sta->sta.nan_sched;
+	sta->sta.nan_sched = new_sched;
+
+	ret = drv_nan_peer_sched_changed(sdata->local, sdata, sta);
+	if (ret) {
+		/* Revert to old schedule */
+		sta->sta.nan_sched = old_sched;
+		goto out;
+	}
+
+	/* Success - free old schedule */
+	to_free = old_sched;
+	ret = 0;
+
+out:
+	ieee80211_nan_free_peer_sched(to_free);
+	return ret;
+}
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index 017d91365920..dd0d92abf60d 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -1309,6 +1309,10 @@ static int __must_check __sta_info_destroy_part1(struct sta_info *sta)
 				continue;
 			sta_info_destroy_addr(sta_iter->sdata, sta_iter->addr);
 		}
+
+		/* Free and clear the local peer schedule */
+		ieee80211_nan_free_peer_sched(sta->sta.nan_sched);
+		sta->sta.nan_sched = NULL;
 	}
 
 	/*
diff --git a/net/mac80211/trace.h b/net/mac80211/trace.h
index e5968d754f8b..71cf88039bd4 100644
--- a/net/mac80211/trace.h
+++ b/net/mac80211/trace.h
@@ -3366,6 +3366,37 @@ TRACE_EVENT(drv_set_eml_op_mode,
 	)
 );
 
+TRACE_EVENT(drv_nan_peer_sched_changed,
+	TP_PROTO(struct ieee80211_local *local,
+		 struct ieee80211_sub_if_data *sdata,
+		 struct ieee80211_sta *sta),
+
+	TP_ARGS(local, sdata, sta),
+	TP_STRUCT__entry(
+		LOCAL_ENTRY
+		VIF_ENTRY
+		STA_ENTRY
+		__array(u8, map_ids, CFG80211_NAN_MAX_PEER_MAPS)
+	),
+
+	TP_fast_assign(
+		LOCAL_ASSIGN;
+		VIF_ASSIGN;
+		STA_ASSIGN;
+		for (int i = 0; i < CFG80211_NAN_MAX_PEER_MAPS; i++)
+			__entry->map_ids[i] = sta->nan_sched ?
+					      sta->nan_sched->maps[i].map_id :
+					      0xff;
+	),
+
+	TP_printk(
+		LOCAL_PR_FMT  VIF_PR_FMT  STA_PR_FMT
+		" map_ids=[%u, %u]",
+		LOCAL_PR_ARG, VIF_PR_ARG, STA_PR_ARG,
+		__entry->map_ids[0], __entry->map_ids[1]
+	)
+);
+
 #endif /* !__MAC80211_DRIVER_TRACE || TRACE_HEADER_MULTI_READ */
 
 #undef TRACE_INCLUDE_PATH
diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index a352f73a7ec4..b093bc203c81 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -1817,6 +1817,14 @@ static int ieee80211_reconfig_nan(struct ieee80211_sub_if_data *sdata)
 			if (WARN_ON(res))
 				return res;
 		}
+
+		/* Add peer schedules for NMI stations that have them */
+		if (!sta->sta.nan_sched)
+			continue;
+
+		res = drv_nan_peer_sched_changed(local, sdata, sta);
+		if (WARN_ON(res))
+			return res;
 	}
 
 	/* Add NDI stations (stations on NAN_DATA interfaces) */
-- 
2.34.1


^ permalink raw reply related

* [PATCH v2 wireless-next 08/15] wifi: mac80211: support NAN stations
From: Miri Korenblit @ 2026-03-26 10:14 UTC (permalink / raw)
  To: linux-wireless
In-Reply-To: <20260326101445.1443198-1-miriam.rachel.korenblit@intel.com>

Add support for both NMI and NDI stations.

The NDI station will be linked to the NMI station of the NAN peer for
which the NDI station is added.

A peer can choose to reuse its NMI address as the NDI address.
Since different keys might be in use for NAN management and for data
frames, we will have 2 different stations, even if they'll have the same
address.

Even though there are no links in NAN, sta->deflink will still be used
to store the one set of capabilities and SMPS mode.

Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 include/net/mac80211.h  |  13 ++++-
 net/mac80211/cfg.c      | 103 ++++++++++++++++++++++++++++++++++++----
 net/mac80211/he.c       |   7 ++-
 net/mac80211/ht.c       |  19 ++++++--
 net/mac80211/iface.c    |  10 ++--
 net/mac80211/sta_info.c |  21 +++++++-
 net/mac80211/sta_info.h |   3 +-
 net/mac80211/util.c     |  41 ++++++++++++++++
 net/mac80211/vht.c      |  16 ++++++-
 9 files changed, 209 insertions(+), 24 deletions(-)

diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 24685f106757..8656e2cc2b75 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -2534,11 +2534,15 @@ struct ieee80211_sta_aggregates {
  * @uhr_cap: UHR capabilities of this STA
  * @s1g_cap: S1G capabilities of this STA
  * @agg: per-link data for multi-link aggregation
- * @bandwidth: current bandwidth the station can receive with
+ * @bandwidth: current bandwidth the station can receive with.
+ *	This is the minimum between the peer's capabilities and our own
+ *	operating channel width; Invalid for NAN since that is operating on
+ *	multiple channels.
  * @rx_nss: in HT/VHT, the maximum number of spatial streams the
  *	station can receive at the moment, changed by operating mode
  *	notifications and capabilities. The value is only valid after
- *	the station moves to associated state.
+ *	the station moves to associated state. Invalid for NAN since it
+ *	operates on multiple configurations of rx_nss.
  * @txpwr: the station tx power configuration
  *
  */
@@ -2620,6 +2624,7 @@ struct ieee80211_link_sta {
  * @valid_links: bitmap of valid links, or 0 for non-MLO
  * @spp_amsdu: indicates whether the STA uses SPP A-MSDU or not.
  * @epp_peer: indicates that the peer is an EPP peer.
+ * @nmi: For NDI stations, pointer to the NMI station of the peer.
  */
 struct ieee80211_sta {
 	u8 addr[ETH_ALEN] __aligned(2);
@@ -2648,6 +2653,8 @@ struct ieee80211_sta {
 	struct ieee80211_link_sta deflink;
 	struct ieee80211_link_sta __rcu *link[IEEE80211_MLD_MAX_NUM_LINKS];
 
+	struct ieee80211_sta __rcu *nmi;
+
 	/* must be last */
 	u8 drv_priv[] __aligned(sizeof(void *));
 };
@@ -2881,6 +2888,8 @@ struct ieee80211_txq {
  *	station has a unique address, i.e. each station entry can be identified
  *	by just its MAC address; this prevents, for example, the same station
  *	from connecting to two virtual AP interfaces at the same time.
+ *	Note that this doesn't apply for NAN, in which the peer's NMI address
+ *	can be equal to its NDI address.
  *
  * @IEEE80211_HW_SUPPORTS_REORDERING_BUFFER: Hardware (or driver) manages the
  *	reordering buffer internally, guaranteeing mac80211 receives frames in
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 701b111db7df..a1089e3964bd 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -2077,7 +2077,7 @@ static int sta_link_apply_parameters(struct ieee80211_local *local,
 				     enum sta_link_apply_mode mode,
 				     struct link_station_parameters *params)
 {
-	struct ieee80211_supported_band *sband;
+	struct ieee80211_supported_band *sband = NULL;
 	struct ieee80211_sub_if_data *sdata = sta->sdata;
 	u32 link_id = params->link_id < 0 ? 0 : params->link_id;
 	struct ieee80211_link_data *link =
@@ -2085,6 +2085,9 @@ static int sta_link_apply_parameters(struct ieee80211_local *local,
 	struct link_sta_info *link_sta =
 		rcu_dereference_protected(sta->link[link_id],
 					  lockdep_is_held(&local->hw.wiphy->mtx));
+	const struct ieee80211_sta_ht_cap *own_ht_cap;
+	const struct ieee80211_sta_vht_cap *own_vht_cap;
+	const struct ieee80211_sta_he_cap *own_he_cap;
 	bool changes = params->link_mac ||
 		       params->txpwr_set ||
 		       params->supported_rates_len ||
@@ -2114,10 +2117,27 @@ static int sta_link_apply_parameters(struct ieee80211_local *local,
 	if (!link || !link_sta)
 		return -EINVAL;
 
-	sband = ieee80211_get_link_sband(link);
-	if (!sband)
+	/*
+	 * We should not have any changes in NDI station, its capabilities are
+	 * copied from the NMI sta
+	 */
+	if (WARN_ON(sdata->vif.type == NL80211_IFTYPE_NAN_DATA))
 		return -EINVAL;
 
+	if (sdata->vif.type == NL80211_IFTYPE_NAN) {
+		own_ht_cap = &local->hw.wiphy->nan_capa.phy.ht;
+		own_vht_cap = &local->hw.wiphy->nan_capa.phy.vht;
+		own_he_cap = &local->hw.wiphy->nan_capa.phy.he;
+	} else {
+		sband = ieee80211_get_link_sband(link);
+		if (!sband)
+			return -EINVAL;
+
+		own_ht_cap = &sband->ht_cap;
+		own_vht_cap = &sband->vht_cap;
+		own_he_cap = ieee80211_get_he_iftype_cap_vif(sband, &sdata->vif);
+	}
+
 	if (params->link_mac) {
 		if (mode == STA_LINK_MODE_NEW) {
 			memcpy(link_sta->addr, params->link_mac, ETH_ALEN);
@@ -2139,6 +2159,27 @@ static int sta_link_apply_parameters(struct ieee80211_local *local,
 			return ret;
 	}
 
+	if (sdata->vif.type == NL80211_IFTYPE_NAN) {
+		static const u8 all_ofdm_rates[] = {
+			0x0c, 0x12, 0x18, 0x24, 0x30, 0x48, 0x60, 0x6c
+		};
+
+		/* Set the same supported_rates for all bands */
+		for (int i = 0; i < NUM_NL80211_BANDS; i++) {
+			struct ieee80211_supported_band *tmp =
+				sdata->local->hw.wiphy->bands[i];
+
+			if ((i != NL80211_BAND_2GHZ && i != NL80211_BAND_5GHZ) ||
+			    !tmp)
+				continue;
+
+			if (!ieee80211_parse_bitrates(tmp, all_ofdm_rates,
+						      sizeof(all_ofdm_rates),
+						      &link_sta->pub->supp_rates[i]))
+				return -EINVAL;
+		}
+	}
+
 	if (params->supported_rates &&
 	    params->supported_rates_len &&
 	    !ieee80211_parse_bitrates(sband, params->supported_rates,
@@ -2147,22 +2188,24 @@ static int sta_link_apply_parameters(struct ieee80211_local *local,
 		return -EINVAL;
 
 	if (params->ht_capa)
-		ieee80211_ht_cap_ie_to_sta_ht_cap(sdata, &sband->ht_cap,
+		ieee80211_ht_cap_ie_to_sta_ht_cap(sdata, own_ht_cap,
 						  params->ht_capa, link_sta);
 
 	/* VHT can override some HT caps such as the A-MSDU max length */
 	if (params->vht_capa)
 		ieee80211_vht_cap_ie_to_sta_vht_cap(sdata, sband,
-						    &sband->vht_cap,
+						    own_vht_cap,
 						    params->vht_capa, NULL,
 						    link_sta);
 
 	if (params->he_capa)
-		ieee80211_he_cap_ie_to_sta_he_cap(sdata, sband,
-						  (void *)params->he_capa,
-						  params->he_capa_len,
-						  (void *)params->he_6ghz_capa,
-						  link_sta);
+		_ieee80211_he_cap_ie_to_sta_he_cap(sdata,
+						   own_he_cap,
+						   (void *)params->he_capa,
+						   params->he_capa_len,
+						   (sband && sband->band == NL80211_BAND_6GHZ) ?
+						   (void *)params->he_6ghz_capa : NULL,
+						   link_sta);
 
 	if (params->he_capa && params->eht_capa)
 		ieee80211_eht_cap_ie_to_sta_eht_cap(sdata, sband,
@@ -2349,6 +2392,32 @@ static int sta_apply_parameters(struct ieee80211_local *local,
 	if (params->airtime_weight)
 		sta->airtime_weight = params->airtime_weight;
 
+	if (params->nmi_mac) {
+		struct ieee80211_sub_if_data *nmi =
+			rcu_dereference_wiphy(local->hw.wiphy,
+					      sdata->u.nan_data.nmi);
+		struct sta_info *nmi_sta;
+
+		if (WARN_ON(!nmi))
+			return -EINVAL;
+
+		nmi_sta = sta_info_get(nmi, params->nmi_mac);
+		if (!nmi_sta)
+			return -ENOENT;
+		rcu_assign_pointer(sta->sta.nmi, &nmi_sta->sta);
+
+		/* For NAN_DATA stations, copy capabilities from the NMI station */
+		if (!nmi_sta->deflink.pub->ht_cap.ht_supported)
+			return -EINVAL;
+
+		sta->deflink.pub->ht_cap = nmi_sta->deflink.pub->ht_cap;
+		sta->deflink.pub->vht_cap = nmi_sta->deflink.pub->vht_cap;
+		sta->deflink.pub->he_cap = nmi_sta->deflink.pub->he_cap;
+		memcpy(&sta->deflink.pub->supp_rates,
+		       &nmi_sta->deflink.pub->supp_rates,
+		       sizeof(sta->deflink.pub->supp_rates));
+	}
+
 	/* set the STA state after all sta info from usermode has been set */
 	if (test_sta_flag(sta, WLAN_STA_TDLS_PEER) ||
 	    set & BIT(NL80211_STA_FLAG_ASSOCIATED)) {
@@ -2494,6 +2563,12 @@ static int ieee80211_change_station(struct wiphy *wiphy,
 		else
 			statype = CFG80211_STA_AP_CLIENT_UNASSOC;
 		break;
+	case NL80211_IFTYPE_NAN:
+		statype = CFG80211_STA_NAN_MGMT;
+		break;
+	case NL80211_IFTYPE_NAN_DATA:
+		statype = CFG80211_STA_NAN_DATA;
+		break;
 	default:
 		return -EOPNOTSUPP;
 	}
@@ -2532,6 +2607,14 @@ static int ieee80211_change_station(struct wiphy *wiphy,
 		}
 	}
 
+	/* NAN capabilties should not change */
+	if (statype == CFG80211_STA_NAN_DATA &&
+	    sta->deflink.pub->ht_cap.ht_supported &&
+	    (params->link_sta_params.ht_capa ||
+	     params->link_sta_params.vht_capa ||
+	     params->link_sta_params.he_capa))
+		return -EINVAL;
+
 	err = sta_apply_parameters(local, sta, params);
 	if (err)
 		return err;
diff --git a/net/mac80211/he.c b/net/mac80211/he.c
index 93e0342cff4f..a3e16a5bec22 100644
--- a/net/mac80211/he.c
+++ b/net/mac80211/he.c
@@ -127,6 +127,10 @@ _ieee80211_he_cap_ie_to_sta_he_cap(struct ieee80211_sub_if_data *sdata,
 	if (!he_cap_ie || !own_he_cap_ptr || !own_he_cap_ptr->has_he)
 		return;
 
+	/* NDI station are using the capabilities from the NMI station */
+	if (WARN_ON_ONCE(sdata->vif.type == NL80211_IFTYPE_NAN_DATA))
+		return;
+
 	own_he_cap = *own_he_cap_ptr;
 
 	/* Make sure size is OK */
@@ -156,7 +160,8 @@ _ieee80211_he_cap_ie_to_sta_he_cap(struct ieee80211_sub_if_data *sdata,
 	he_cap->has_he = true;
 
 	link_sta->cur_max_bandwidth = ieee80211_sta_cap_rx_bw(link_sta);
-	link_sta->pub->bandwidth = ieee80211_sta_cur_vht_bw(link_sta);
+	if (sdata->vif.type != NL80211_IFTYPE_NAN)
+		link_sta->pub->bandwidth = ieee80211_sta_cur_vht_bw(link_sta);
 
 	if (he_6ghz_capa)
 		ieee80211_update_from_he_6ghz_capa(he_6ghz_capa, link_sta);
diff --git a/net/mac80211/ht.c b/net/mac80211/ht.c
index 410e2354f33a..97719298e038 100644
--- a/net/mac80211/ht.c
+++ b/net/mac80211/ht.c
@@ -154,6 +154,10 @@ bool ieee80211_ht_cap_ie_to_sta_ht_cap(struct ieee80211_sub_if_data *sdata,
 	if (!ht_cap_ie || !own_cap_ptr->ht_supported)
 		goto apply;
 
+	/* NDI station are using the capabilities from the NMI station */
+	if (WARN_ON_ONCE(sdata->vif.type == NL80211_IFTYPE_NAN_DATA))
+		return 0;
+
 	ht_cap.ht_supported = true;
 
 	own_cap = *own_cap_ptr;
@@ -254,10 +258,17 @@ bool ieee80211_ht_cap_ie_to_sta_ht_cap(struct ieee80211_sub_if_data *sdata,
 
 	rcu_read_lock();
 	link_conf = rcu_dereference(sdata->vif.link_conf[link_sta->link_id]);
-	if (WARN_ON(!link_conf))
+	if (WARN_ON(!link_conf)) {
 		width = NL80211_CHAN_WIDTH_20_NOHT;
-	else
+	} else if (sdata->vif.type == NL80211_IFTYPE_NAN ||
+		   sdata->vif.type == NL80211_IFTYPE_NAN_DATA) {
+		/* In NAN, link_sta->bandwidth is invalid since NAN operates on
+		 * multiple channels. Just take the maximum.
+		 */
+		width = NL80211_CHAN_WIDTH_320;
+	} else {
 		width = link_conf->chanreq.oper.width;
+	}
 
 	switch (width) {
 	default:
@@ -285,7 +296,9 @@ bool ieee80211_ht_cap_ie_to_sta_ht_cap(struct ieee80211_sub_if_data *sdata,
 				IEEE80211_STA_RX_BW_40 : IEEE80211_STA_RX_BW_20;
 
 	if (sta->sdata->vif.type == NL80211_IFTYPE_AP ||
-	    sta->sdata->vif.type == NL80211_IFTYPE_AP_VLAN) {
+	    sta->sdata->vif.type == NL80211_IFTYPE_AP_VLAN ||
+	    sta->sdata->vif.type == NL80211_IFTYPE_NAN ||
+	    sta->sdata->vif.type == NL80211_IFTYPE_NAN_DATA) {
 		enum ieee80211_smps_mode smps_mode;
 
 		switch ((ht_cap.cap & IEEE80211_HT_CAP_SM_PS)
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c
index 507c5e016ec8..f1ab85ff326d 100644
--- a/net/mac80211/iface.c
+++ b/net/mac80211/iface.c
@@ -535,12 +535,14 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata, bool going_do
 	 * (because if we remove a STA after ops->remove_interface()
 	 * the driver will have removed the vif info already!)
 	 *
-	 * For AP_VLANs stations may exist since there's nothing else that
-	 * would have removed them, but in other modes there shouldn't
-	 * be any stations.
+	 * For AP_VLANs, NAN and NAN_DATA stations may exist since there's
+	 * nothing else that would have removed them, but in other modes there
+	 * shouldn't be any stations.
 	 */
 	flushed = sta_info_flush(sdata, -1);
-	WARN_ON_ONCE(sdata->vif.type != NL80211_IFTYPE_AP_VLAN && flushed > 0);
+	WARN_ON_ONCE(sdata->vif.type != NL80211_IFTYPE_AP_VLAN &&
+		     sdata->vif.type != NL80211_IFTYPE_NAN &&
+		     sdata->vif.type != NL80211_IFTYPE_NAN_DATA && flushed > 0);
 
 	/* don't count this interface for allmulti while it is down */
 	if (sdata->flags & IEEE80211_SDATA_ALLMULTI)
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index 4259e9c13ed7..017d91365920 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -795,6 +795,7 @@ struct sta_info *sta_info_alloc_with_link(struct ieee80211_sub_if_data *sdata,
 static int sta_info_insert_check(struct sta_info *sta)
 {
 	struct ieee80211_sub_if_data *sdata = sta->sdata;
+	struct ieee80211_sta *same_addr_sta;
 
 	lockdep_assert_wiphy(sdata->local->hw.wiphy);
 
@@ -810,13 +811,18 @@ static int sta_info_insert_check(struct sta_info *sta)
 		    !is_valid_ether_addr(sta->sta.addr)))
 		return -EINVAL;
 
+	if (!ieee80211_hw_check(&sdata->local->hw, NEEDS_UNIQUE_STA_ADDR))
+		return 0;
+
 	/* The RCU read lock is required by rhashtable due to
 	 * asynchronous resize/rehash.  We also require the mutex
 	 * for correctness.
 	 */
 	rcu_read_lock();
-	if (ieee80211_hw_check(&sdata->local->hw, NEEDS_UNIQUE_STA_ADDR) &&
-	    ieee80211_find_sta_by_ifaddr(&sdata->local->hw, sta->addr, NULL)) {
+	same_addr_sta = ieee80211_find_sta_by_ifaddr(&sdata->local->hw,
+						     sta->addr, NULL);
+	/* For NAN, a peer can re-use */
+	if (same_addr_sta && same_addr_sta != rcu_access_pointer(sta->sta.nmi)) {
 		rcu_read_unlock();
 		return -ENOTUNIQ;
 	}
@@ -1294,6 +1300,17 @@ static int __must_check __sta_info_destroy_part1(struct sta_info *sta)
 
 	lockdep_assert_wiphy(local->hw.wiphy);
 
+	if (sdata->vif.type == NL80211_IFTYPE_NAN) {
+		struct sta_info *sta_iter, *tmp;
+
+		/* Remove all NDI stations associated with this NMI STA */
+		list_for_each_entry_safe(sta_iter, tmp, &local->sta_list, list) {
+			if (rcu_access_pointer(sta_iter->sta.nmi) != &sta->sta)
+				continue;
+			sta_info_destroy_addr(sta_iter->sdata, sta_iter->addr);
+		}
+	}
+
 	/*
 	 * Before removing the station from the driver and
 	 * rate control, it might still start new aggregation
diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h
index 58ccbea7f6f6..3e5d003bd31f 100644
--- a/net/mac80211/sta_info.h
+++ b/net/mac80211/sta_info.h
@@ -505,7 +505,8 @@ struct ieee80211_fragment_cache {
  * @status_stats.ack_signal_filled: last ACK signal validity
  * @status_stats.avg_ack_signal: average ACK signal
  * @cur_max_bandwidth: maximum bandwidth to use for TX to the station,
- *	taken from HT/VHT capabilities or VHT operating mode notification
+ *	taken from HT/VHT capabilities or VHT operating mode notification.
+ *	Invalid for NAN since that is operating on multiple bands.
  * @rx_omi_bw_rx: RX OMI bandwidth restriction to apply for RX
  * @rx_omi_bw_tx: RX OMI bandwidth restriction to apply for TX
  * @rx_omi_bw_staging: RX OMI bandwidth restriction to apply later
diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index 925a09246ad9..a352f73a7ec4 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -1782,6 +1782,7 @@ static int ieee80211_reconfig_nan(struct ieee80211_sub_if_data *sdata)
 {
 	struct ieee80211_local *local = sdata->local;
 	struct ieee80211_sub_if_data *ndi_sdata;
+	struct sta_info *sta;
 	int res;
 
 	res = drv_start_nan(local, sdata, &sdata->u.nan.conf);
@@ -1802,6 +1803,42 @@ static int ieee80211_reconfig_nan(struct ieee80211_sub_if_data *sdata)
 		}
 	}
 
+	/* Add NMI stations (stations on the NAN interface) */
+	list_for_each_entry(sta, &local->sta_list, list) {
+		enum ieee80211_sta_state state;
+
+		if (!sta->uploaded || sta->sdata != sdata)
+			continue;
+
+		for (state = IEEE80211_STA_NOTEXIST; state < sta->sta_state;
+		     state++) {
+			res = drv_sta_state(local, sdata, sta, state,
+					    state + 1);
+			if (WARN_ON(res))
+				return res;
+		}
+	}
+
+	/* Add NDI stations (stations on NAN_DATA interfaces) */
+	list_for_each_entry(sta, &local->sta_list, list) {
+		enum ieee80211_sta_state state;
+
+		if (!sta->uploaded ||
+		    sta->sdata->vif.type != NL80211_IFTYPE_NAN_DATA)
+			continue;
+
+		if (WARN_ON(!sta->sta.nmi))
+			continue;
+
+		for (state = IEEE80211_STA_NOTEXIST; state < sta->sta_state;
+		     state++) {
+			res = drv_sta_state(local, sta->sdata, sta, state,
+					    state + 1);
+			if (WARN_ON(res))
+				return res;
+		}
+	}
+
 	return 0;
 }
 
@@ -2060,6 +2097,10 @@ int ieee80211_reconfig(struct ieee80211_local *local)
 		case NL80211_IFTYPE_AP_VLAN:
 		case NL80211_IFTYPE_MONITOR:
 			break;
+		case NL80211_IFTYPE_NAN:
+		case NL80211_IFTYPE_NAN_DATA:
+			/* NAN stations are handled later */
+			break;
 		case NL80211_IFTYPE_ADHOC:
 			if (sdata->vif.cfg.ibss_joined)
 				WARN_ON(drv_join_ibss(local, sdata));
diff --git a/net/mac80211/vht.c b/net/mac80211/vht.c
index a6570781740a..f3bb5a561a38 100644
--- a/net/mac80211/vht.c
+++ b/net/mac80211/vht.c
@@ -133,6 +133,10 @@ ieee80211_vht_cap_ie_to_sta_vht_cap(struct ieee80211_sub_if_data *sdata,
 	if (!vht_cap_ie || !own_vht_cap->vht_supported)
 		return;
 
+	/* NDI station are using the capabilities from the NMI station */
+	if (WARN_ON_ONCE(sdata->vif.type == NL80211_IFTYPE_NAN_DATA))
+		return;
+
 	if (sband) {
 		/* Allow VHT if at least one channel on the sband supports 80 MHz */
 		bool have_80mhz = false;
@@ -320,7 +324,8 @@ ieee80211_vht_cap_ie_to_sta_vht_cap(struct ieee80211_sub_if_data *sdata,
 				IEEE80211_STA_RX_BW_160;
 	}
 
-	link_sta->pub->bandwidth = ieee80211_sta_cur_vht_bw(link_sta);
+	if (sdata->vif.type != NL80211_IFTYPE_NAN)
+		link_sta->pub->bandwidth = ieee80211_sta_cur_vht_bw(link_sta);
 
 	/*
 	 * Work around the Cisco 9115 FW 17.3 bug by taking the min of
@@ -373,6 +378,10 @@ __ieee80211_sta_cap_rx_bw(struct link_sta_info *link_sta,
 		} else {
 			struct ieee80211_bss_conf *link_conf;
 
+			if (WARN_ON_ONCE(sdata->vif.type == NL80211_IFTYPE_NAN_DATA ||
+					 sdata->vif.type == NL80211_IFTYPE_NAN))
+				return IEEE80211_STA_RX_BW_20;
+
 			rcu_read_lock();
 			link_conf = rcu_dereference(sdata->vif.link_conf[link_id]);
 			band = link_conf->chanreq.oper.chan->band;
@@ -518,6 +527,11 @@ _ieee80211_sta_cur_vht_bw(struct link_sta_info *link_sta,
 	} else {
 		struct ieee80211_bss_conf *link_conf;
 
+		/* NAN operates on multiple channels so a chandef must be given */
+		if (WARN_ON_ONCE(sta->sdata->vif.type == NL80211_IFTYPE_NAN ||
+				 sta->sdata->vif.type == NL80211_IFTYPE_NAN_DATA))
+			return IEEE80211_STA_RX_BW_20;
+
 		rcu_read_lock();
 		link_conf = rcu_dereference(sta->sdata->vif.link_conf[link_sta->link_id]);
 		if (WARN_ON_ONCE(!link_conf)) {
-- 
2.34.1


^ permalink raw reply related

* [PATCH v2 wireless-next 05/15] wifi: mac80211: add NAN local schedule support
From: Miri Korenblit @ 2026-03-26 10:14 UTC (permalink / raw)
  To: linux-wireless; +Cc: Avraham Stern
In-Reply-To: <20260326101445.1443198-1-miriam.rachel.korenblit@intel.com>

A NAN local schedule consist of a list of NAN channels, and an array
that maps time slots to the channel it is scheduled to (or NULL to indicate
unscheduled).

A NAN channel is the configuration of a channel which is used for NAN
operations. It is a new type of chanctx user (before, the only user is a
link). A NAN channel may not have a chanctx assigned if it is ULWed out.

A NAN channel may or may not be scheduled (for example, user space
may want to prepare the resources before the actual schedule is
configured).

Add management of the NAN local schedule.

Since we introduce a new chanctx user, also adjust the different
for_each_chanctx_user_* macros to visit also the NAN channels and take
those into account.

Co-developed-by: Avraham Stern <avraham.stern@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 include/net/mac80211.h     |  66 +++++++
 net/mac80211/Makefile      |   2 +-
 net/mac80211/cfg.c         |  13 ++
 net/mac80211/chan.c        | 132 ++++++++++---
 net/mac80211/ieee80211_i.h |  20 +-
 net/mac80211/iface.c       |   8 +
 net/mac80211/nan.c         | 378 +++++++++++++++++++++++++++++++++++++
 net/mac80211/util.c        |  28 ++-
 8 files changed, 609 insertions(+), 38 deletions(-)
 create mode 100644 net/mac80211/nan.c

diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index ff862d54eb77..24685f106757 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -365,6 +365,7 @@ struct ieee80211_vif_chanctx_switch {
  * @BSS_CHANGED_MLD_VALID_LINKS: MLD valid links status changed.
  * @BSS_CHANGED_MLD_TTLM: negotiated TID to link mapping was changed
  * @BSS_CHANGED_TPE: transmit power envelope changed
+ * @BSS_CHANGED_NAN_LOCAL_SCHED: NAN local schedule changed (NAN mode only)
  */
 enum ieee80211_bss_change {
 	BSS_CHANGED_ASSOC		= 1<<0,
@@ -402,6 +403,7 @@ enum ieee80211_bss_change {
 	BSS_CHANGED_MLD_VALID_LINKS	= BIT_ULL(33),
 	BSS_CHANGED_MLD_TTLM		= BIT_ULL(34),
 	BSS_CHANGED_TPE			= BIT_ULL(35),
+	BSS_CHANGED_NAN_LOCAL_SCHED	= BIT_ULL(36),
 
 	/* when adding here, make sure to change ieee80211_reconfig */
 };
@@ -866,6 +868,28 @@ struct ieee80211_bss_conf {
 	u8 s1g_long_beacon_period;
 };
 
+#define IEEE80211_NAN_MAX_CHANNELS 3
+
+/**
+ * struct ieee80211_nan_channel - NAN channel information
+ *
+ * @chanreq: channel request for this NAN channel. Even though this chanreq::ap
+ *	is irrelevant for NAN, still store it for convenience - some functions
+ *	require it as an argument.
+ * @needed_rx_chains: number of RX chains needed for this NAN channel
+ * @chanctx_conf: chanctx_conf assigned to this NAN channel. Will be %NULL
+ *	if the channel is ULWed.
+ * @channel_entry: the Channel Entry blob as defined in Wi-Fi Aware
+ *	(TM) 4.0 specification Table 100 (Channel Entry format for the NAN
+ *	Availability attribute).
+ */
+struct ieee80211_nan_channel {
+	struct ieee80211_chan_req chanreq;
+	u8 needed_rx_chains;
+	struct ieee80211_chanctx_conf *chanctx_conf;
+	u8 channel_entry[6];
+};
+
 /**
  * enum mac80211_tx_info_flags - flags to describe transmission information/status
  *
@@ -1917,6 +1941,8 @@ enum ieee80211_offload_flags {
 	IEEE80211_OFFLOAD_DECAP_ENABLED		= BIT(2),
 };
 
+#define IEEE80211_NAN_AVAIL_BLOB_MAX_LEN	54
+
 /**
  * struct ieee80211_eml_params - EHT Operating mode notification parameters
  *
@@ -1942,6 +1968,32 @@ struct ieee80211_eml_params {
 	u8 emlmr_mcs_map_bw[9];
 };
 
+/**
+ * struct ieee80211_nan_sched_cfg - NAN schedule configuration
+ * @channels: array of NAN channels. A channel entry is in use if
+ *	channels[i].chanreq.oper.chan is not NULL.
+ * @schedule: NAN local schedule - mapping of each 16TU time slot to
+ *	the NAN channel on which the radio will operate. NULL if unscheduled.
+ * @avail_blob: NAN Availability attribute blob.
+ * @avail_blob_len: length of the @avail_blob in bytes.
+ * @deferred: indicates that the driver should notify peers before applying the
+ *	new NAN schedule, and apply the new schedule the second NAN Slot
+ *	boundary after it notified the peers, as defined in Wi-Fi Aware (TM) 4.0
+ *	specification, section 5.2.2.
+ *	The driver must call ieee80211_nan_sched_update_done() after the
+ *	schedule has been applied.
+ *	If a HW restart happened while a deferred schedule update was pending,
+ *	mac80211 will reconfigure the deferred schedule (and wait for the driver
+ *	to notify that the schedule has been applied).
+ */
+struct ieee80211_nan_sched_cfg {
+	struct ieee80211_nan_channel channels[IEEE80211_NAN_MAX_CHANNELS];
+	struct ieee80211_nan_channel *schedule[CFG80211_NAN_SCHED_NUM_TIME_SLOTS];
+	u8 avail_blob[IEEE80211_NAN_AVAIL_BLOB_MAX_LEN];
+	u16 avail_blob_len;
+	bool deferred;
+};
+
 /**
  * struct ieee80211_vif_cfg - interface configuration
  * @assoc: association status
@@ -1970,6 +2022,7 @@ struct ieee80211_eml_params {
  *	your driver/device needs to do.
  * @ap_addr: AP MLD address, or BSSID for non-MLO connections
  *	(station mode only)
+ * @nan_sched: NAN schedule parameters. &struct ieee80211_nan_sched_cfg
  */
 struct ieee80211_vif_cfg {
 	/* association related data */
@@ -1988,6 +2041,8 @@ struct ieee80211_vif_cfg {
 	bool s1g;
 	bool idle;
 	u8 ap_addr[ETH_ALEN] __aligned(2);
+	/* Protected by the wiphy mutex */
+	struct ieee80211_nan_sched_cfg nan_sched;
 };
 
 #define IEEE80211_TTLM_NUM_TIDS 8
@@ -7754,6 +7809,17 @@ void ieee80211_nan_func_match(struct ieee80211_vif *vif,
 			      struct cfg80211_nan_match_params *match,
 			      gfp_t gfp);
 
+/**
+ * ieee80211_nan_sched_update_done - notify that NAN schedule update is done
+ *
+ * This function is called by the driver to notify mac80211 that the NAN
+ * schedule update has been applied.
+ * Must be called with wiphy mutex held. May sleep.
+ *
+ * @vif: &struct ieee80211_vif pointer from the add_interface callback.
+ */
+void ieee80211_nan_sched_update_done(struct ieee80211_vif *vif);
+
 /**
  * ieee80211_calc_rx_airtime - calculate estimated transmission airtime for RX.
  *
diff --git a/net/mac80211/Makefile b/net/mac80211/Makefile
index b0e392eb7753..abf46c951299 100644
--- a/net/mac80211/Makefile
+++ b/net/mac80211/Makefile
@@ -36,7 +36,7 @@ mac80211-y := \
 	tdls.o \
 	ocb.o \
 	airtime.o \
-	eht.o uhr.o
+	eht.o uhr.o nan.o
 
 mac80211-$(CONFIG_MAC80211_LEDS) += led.o
 mac80211-$(CONFIG_MAC80211_DEBUGFS) += \
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index e851668f4ef3..701b111db7df 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -5594,6 +5594,18 @@ ieee80211_set_epcs(struct wiphy *wiphy, struct net_device *dev, bool enable)
 	return ieee80211_mgd_set_epcs(sdata, enable);
 }
 
+static int
+ieee80211_set_local_nan_sched(struct wiphy *wiphy,
+			      struct wireless_dev *wdev,
+			      struct cfg80211_nan_local_sched *sched)
+{
+	struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev);
+
+	lockdep_assert_wiphy(wiphy);
+
+	return ieee80211_nan_set_local_sched(sdata, sched);
+}
+
 const struct cfg80211_ops mac80211_config_ops = {
 	.add_virtual_intf = ieee80211_add_iface,
 	.del_virtual_intf = ieee80211_del_iface,
@@ -5710,4 +5722,5 @@ const struct cfg80211_ops mac80211_config_ops = {
 	.get_radio_mask = ieee80211_get_radio_mask,
 	.assoc_ml_reconf = ieee80211_assoc_ml_reconf,
 	.set_epcs = ieee80211_set_epcs,
+	.nan_set_local_sched = ieee80211_set_local_nan_sched,
 };
diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
index 1e4bfcd25697..eca3fed1edbe 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -16,6 +16,8 @@ struct ieee80211_chanctx_user_iter {
 	struct ieee80211_chan_req *chanreq;
 	struct ieee80211_sub_if_data *sdata;
 	struct ieee80211_link_data *link;
+	struct ieee80211_nan_channel *nan_channel;
+	int nan_channel_next_idx;
 	enum nl80211_iftype iftype;
 	bool reserved, radar_required, done;
 	enum {
@@ -31,20 +33,38 @@ enum ieee80211_chanctx_iter_type {
 	CHANCTX_ITER_ASSIGNED,
 };
 
-static void ieee80211_chanctx_user_iter_next(struct ieee80211_local *local,
-					     struct ieee80211_chanctx *ctx,
-					     struct ieee80211_chanctx_user_iter *iter,
-					     enum ieee80211_chanctx_iter_type type,
-					     bool start)
+static bool
+ieee80211_chanctx_user_iter_next_nan_channel(struct ieee80211_chanctx *ctx,
+					     struct ieee80211_chanctx_user_iter *iter)
 {
-	lockdep_assert_wiphy(local->hw.wiphy);
+	/* Start from the next index after current position */
+	for (int i = iter->nan_channel_next_idx;
+	     i < ARRAY_SIZE(iter->sdata->vif.cfg.nan_sched.channels); i++) {
+		struct ieee80211_nan_channel *nan_channel =
+			&iter->sdata->vif.cfg.nan_sched.channels[i];
 
-	if (start) {
-		memset(iter, 0, sizeof(*iter));
-		goto next_interface;
+		if (!nan_channel->chanreq.oper.chan)
+			continue;
+
+		if (nan_channel->chanctx_conf != &ctx->conf)
+			continue;
+
+		iter->nan_channel = nan_channel;
+		iter->nan_channel_next_idx = i + 1;
+		iter->chanreq = &nan_channel->chanreq;
+		iter->link = NULL;
+		iter->reserved = false;
+		iter->radar_required = false;
+		return true;
 	}
+	return false;
+}
 
-next_link:
+static bool
+ieee80211_chanctx_user_iter_next_link(struct ieee80211_chanctx *ctx,
+				      struct ieee80211_chanctx_user_iter *iter,
+				      enum ieee80211_chanctx_iter_type type)
+{
 	for (int link_id = iter->link ? iter->link->link_id : 0;
 	     link_id < ARRAY_SIZE(iter->sdata->link);
 	     link_id++) {
@@ -64,7 +84,7 @@ static void ieee80211_chanctx_user_iter_next(struct ieee80211_local *local,
 				iter->reserved = false;
 				iter->radar_required = link->radar_required;
 				iter->chanreq = &link->conf->chanreq;
-				return;
+				return true;
 			}
 			fallthrough;
 		case CHANCTX_ITER_POS_RESERVED:
@@ -77,7 +97,7 @@ static void ieee80211_chanctx_user_iter_next(struct ieee80211_local *local,
 					link->reserved_radar_required;
 
 				iter->chanreq = &link->reserved;
-				return;
+				return true;
 			}
 			fallthrough;
 		case CHANCTX_ITER_POS_DONE:
@@ -85,6 +105,33 @@ static void ieee80211_chanctx_user_iter_next(struct ieee80211_local *local,
 			continue;
 		}
 	}
+	return false;
+}
+
+static void
+ieee80211_chanctx_user_iter_next(struct ieee80211_local *local,
+				 struct ieee80211_chanctx *ctx,
+				 struct ieee80211_chanctx_user_iter *iter,
+				 enum ieee80211_chanctx_iter_type type,
+				 bool start)
+{
+	bool found;
+
+	lockdep_assert_wiphy(local->hw.wiphy);
+
+	if (start) {
+		memset(iter, 0, sizeof(*iter));
+		goto next_interface;
+	}
+
+next_user:
+	if (iter->iftype == NL80211_IFTYPE_NAN)
+		found = ieee80211_chanctx_user_iter_next_nan_channel(ctx, iter);
+	else
+		found = ieee80211_chanctx_user_iter_next_link(ctx, iter, type);
+
+	if (found)
+		return;
 
 next_interface:
 	/* next (or first) interface */
@@ -97,10 +144,18 @@ static void ieee80211_chanctx_user_iter_next(struct ieee80211_local *local,
 		if (iter->sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
 			continue;
 
+		/* NAN channels don't reserve channel context */
+		if (iter->sdata->vif.type == NL80211_IFTYPE_NAN &&
+		    type == CHANCTX_ITER_RESERVED)
+			continue;
+
+		iter->nan_channel = NULL;
 		iter->link = NULL;
-		iter->per_link = CHANCTX_ITER_POS_ASSIGNED;
 		iter->iftype = iter->sdata->vif.type;
-		goto next_link;
+		iter->chanreq = NULL;
+		iter->per_link = CHANCTX_ITER_POS_ASSIGNED;
+		iter->nan_channel_next_idx = 0;
+		goto next_user;
 	}
 
 	iter->done = true;
@@ -133,8 +188,8 @@ static void ieee80211_chanctx_user_iter_next(struct ieee80211_local *local,
 					      CHANCTX_ITER_ALL,		\
 					      false))
 
-static int ieee80211_chanctx_num_assigned(struct ieee80211_local *local,
-					  struct ieee80211_chanctx *ctx)
+int ieee80211_chanctx_num_assigned(struct ieee80211_local *local,
+				   struct ieee80211_chanctx *ctx)
 {
 	struct ieee80211_chanctx_user_iter iter;
 	int num = 0;
@@ -321,7 +376,7 @@ ieee80211_chanctx_non_reserved_chandef(struct ieee80211_local *local,
 	lockdep_assert_wiphy(local->hw.wiphy);
 
 	for_each_chanctx_user_assigned(local, ctx, &iter) {
-		if (iter.link->reserved_chanctx)
+		if (iter.link && iter.link->reserved_chanctx)
 			continue;
 
 		comp_def = ieee80211_chanreq_compatible(iter.chanreq,
@@ -480,7 +535,6 @@ ieee80211_get_width_of_link(struct ieee80211_link_data *link)
 	case NL80211_IFTYPE_AP_VLAN:
 		return ieee80211_get_max_required_bw(link);
 	case NL80211_IFTYPE_P2P_DEVICE:
-	case NL80211_IFTYPE_NAN:
 		break;
 	case NL80211_IFTYPE_MONITOR:
 		WARN_ON_ONCE(!ieee80211_hw_check(&local->hw,
@@ -495,6 +549,7 @@ ieee80211_get_width_of_link(struct ieee80211_link_data *link)
 	case NUM_NL80211_IFTYPES:
 	case NL80211_IFTYPE_P2P_CLIENT:
 	case NL80211_IFTYPE_P2P_GO:
+	case NL80211_IFTYPE_NAN:
 	case NL80211_IFTYPE_NAN_DATA:
 		WARN_ON_ONCE(1);
 		break;
@@ -504,6 +559,18 @@ ieee80211_get_width_of_link(struct ieee80211_link_data *link)
 	return NL80211_CHAN_WIDTH_20_NOHT;
 }
 
+static enum nl80211_chan_width
+ieee80211_get_width_of_chanctx_user(struct ieee80211_chanctx_user_iter *iter)
+{
+	if (iter->link)
+		return ieee80211_get_width_of_link(iter->link);
+
+	if (WARN_ON_ONCE(!iter->nan_channel || iter->reserved))
+		return NL80211_CHAN_WIDTH_20_NOHT;
+
+	return iter->nan_channel->chanreq.oper.width;
+}
+
 static enum nl80211_chan_width
 ieee80211_get_chanctx_max_required_bw(struct ieee80211_local *local,
 				      struct ieee80211_chanctx *ctx,
@@ -521,7 +588,7 @@ ieee80211_get_chanctx_max_required_bw(struct ieee80211_local *local,
 	/* When this is true we only care about the reserving links */
 	if (check_reserved) {
 		for_each_chanctx_user_reserved(local, ctx, &iter) {
-			width = ieee80211_get_width_of_link(iter.link);
+			width = ieee80211_get_width_of_chanctx_user(&iter);
 			max_bw = max(max_bw, width);
 		}
 		goto check_monitor;
@@ -529,7 +596,7 @@ ieee80211_get_chanctx_max_required_bw(struct ieee80211_local *local,
 
 	/* Consider all assigned links */
 	for_each_chanctx_user_assigned(local, ctx, &iter) {
-		width = ieee80211_get_width_of_link(iter.link);
+		width = ieee80211_get_width_of_chanctx_user(&iter);
 		max_bw = max(max_bw, width);
 	}
 
@@ -941,7 +1008,10 @@ ieee80211_new_chanctx(struct ieee80211_local *local,
 		kfree(ctx);
 		return ERR_PTR(err);
 	}
-	/* We ignored a driver error, see _ieee80211_set_active_links */
+	/*
+	 * We ignored a driver error, see _ieee80211_set_active_links and/or
+	 * ieee80211_nan_set_local_sched
+	 */
 	WARN_ON_ONCE(err && !local->in_reconfig);
 
 	list_add_rcu(&ctx->list, &local->chanctx_list);
@@ -962,9 +1032,9 @@ static void ieee80211_del_chanctx(struct ieee80211_local *local,
 	ieee80211_remove_wbrf(local, &ctx->conf.def);
 }
 
-static void ieee80211_free_chanctx(struct ieee80211_local *local,
-				   struct ieee80211_chanctx *ctx,
-				   bool skip_idle_recalc)
+void ieee80211_free_chanctx(struct ieee80211_local *local,
+			    struct ieee80211_chanctx *ctx,
+			    bool skip_idle_recalc)
 {
 	lockdep_assert_wiphy(local->hw.wiphy);
 
@@ -1159,6 +1229,7 @@ void ieee80211_recalc_smps_chanctx(struct ieee80211_local *local,
 		case NL80211_IFTYPE_ADHOC:
 		case NL80211_IFTYPE_MESH_POINT:
 		case NL80211_IFTYPE_OCB:
+		case NL80211_IFTYPE_NAN:
 			break;
 		default:
 			continue;
@@ -1169,6 +1240,15 @@ void ieee80211_recalc_smps_chanctx(struct ieee80211_local *local,
 			break;
 		}
 
+		if (iter.nan_channel) {
+			rx_chains_dynamic = rx_chains_static =
+				iter.nan_channel->needed_rx_chains;
+			break;
+		}
+
+		if (!iter.link)
+			continue;
+
 		switch (iter.link->smps_mode) {
 		default:
 			WARN_ONCE(1, "Invalid SMPS mode %d\n",
@@ -1777,7 +1857,7 @@ static int ieee80211_vif_use_reserved_switch(struct ieee80211_local *local)
 
 		for_each_chanctx_user_assigned(local, ctx->replace_ctx, &iter) {
 			n_assigned++;
-			if (iter.link->reserved_chanctx) {
+			if (iter.link && iter.link->reserved_chanctx) {
 				n_reserved++;
 				if (iter.link->reserved_ready)
 					n_ready++;
@@ -2033,7 +2113,7 @@ void __ieee80211_link_release_channel(struct ieee80211_link_data *link,
 		ieee80211_vif_use_reserved_switch(local);
 }
 
-static struct ieee80211_chanctx *
+struct ieee80211_chanctx *
 ieee80211_find_or_create_chanctx(struct ieee80211_sub_if_data *sdata,
 				 const struct ieee80211_chan_req *chanreq,
 				 enum ieee80211_chanctx_mode mode,
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 8d5f9a725fdf..92ea8de8a6db 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -990,6 +990,8 @@ struct ieee80211_if_mntr {
  * @de: Discovery Engine state (only valid if !WIPHY_NAN_FLAGS_USERSPACE_DE)
  * @de.func_lock: lock for @de.function_inst_ids
  * @de.function_inst_ids: a bitmap of available instance_id's
+ * @removed_channels: bitmap of channels that should be removed from the NAN
+ *	schedule once the deferred schedule update is completed.
  */
 struct ieee80211_if_nan {
 	struct cfg80211_nan_conf conf;
@@ -1000,6 +1002,8 @@ struct ieee80211_if_nan {
 		spinlock_t func_lock;
 		struct idr function_inst_ids;
 	} de;
+
+	DECLARE_BITMAP(removed_channels, IEEE80211_NAN_MAX_CHANNELS);
 };
 
 struct ieee80211_link_data_managed {
@@ -2024,6 +2028,10 @@ int ieee80211_mesh_csa_beacon(struct ieee80211_sub_if_data *sdata,
 int ieee80211_mesh_finish_csa(struct ieee80211_sub_if_data *sdata,
 			      u64 *changed);
 
+/* NAN code */
+int ieee80211_nan_set_local_sched(struct ieee80211_sub_if_data *sdata,
+				  struct cfg80211_nan_local_sched *sched);
+
 /* scan/BSS handling */
 void ieee80211_scan_work(struct wiphy *wiphy, struct wiphy_work *work);
 int ieee80211_request_ibss_scan(struct ieee80211_sub_if_data *sdata,
@@ -2812,7 +2820,17 @@ int ieee80211_max_num_channels(struct ieee80211_local *local, int radio_idx);
 u32 ieee80211_get_radio_mask(struct wiphy *wiphy, struct net_device *dev);
 void ieee80211_recalc_chanctx_chantype(struct ieee80211_local *local,
 				       struct ieee80211_chanctx *ctx);
-
+struct ieee80211_chanctx *
+ieee80211_find_or_create_chanctx(struct ieee80211_sub_if_data *sdata,
+				 const struct ieee80211_chan_req *chanreq,
+				 enum ieee80211_chanctx_mode mode,
+				 bool assign_on_failure,
+				 bool *reused_ctx);
+void ieee80211_free_chanctx(struct ieee80211_local *local,
+			    struct ieee80211_chanctx *ctx,
+			    bool skip_idle_recalc);
+int ieee80211_chanctx_num_assigned(struct ieee80211_local *local,
+				   struct ieee80211_chanctx *ctx);
 /* TDLS */
 int ieee80211_tdls_mgmt(struct wiphy *wiphy, struct net_device *dev,
 			const u8 *peer, int link_id,
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c
index f0a5a675c5a5..0f3e49cdbb39 100644
--- a/net/mac80211/iface.c
+++ b/net/mac80211/iface.c
@@ -886,6 +886,14 @@ static void ieee80211_teardown_sdata(struct ieee80211_sub_if_data *sdata)
 
 	ieee80211_vif_clear_links(sdata);
 	ieee80211_link_stop(&sdata->deflink);
+
+	if (sdata->vif.type == NL80211_IFTYPE_NAN) {
+		struct ieee80211_nan_sched_cfg *nan_sched =
+			&sdata->vif.cfg.nan_sched;
+
+		for (int i = 0; i < ARRAY_SIZE(nan_sched->channels); i++)
+			WARN_ON(nan_sched->channels[i].chanreq.oper.chan);
+	}
 }
 
 static void ieee80211_uninit(struct net_device *dev)
diff --git a/net/mac80211/nan.c b/net/mac80211/nan.c
new file mode 100644
index 000000000000..2fa55e9a9dab
--- /dev/null
+++ b/net/mac80211/nan.c
@@ -0,0 +1,378 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * NAN mode implementation
+ * Copyright(c) 2025 Intel Corporation
+ */
+#include <net/mac80211.h>
+
+#include "ieee80211_i.h"
+#include "driver-ops.h"
+
+static void
+ieee80211_nan_init_channel(struct ieee80211_nan_channel *nan_channel,
+			   struct cfg80211_nan_channel *cfg_nan_channel)
+{
+	memset(nan_channel, 0, sizeof(*nan_channel));
+
+	nan_channel->chanreq.oper = cfg_nan_channel->chandef;
+	memcpy(nan_channel->channel_entry, cfg_nan_channel->channel_entry,
+	       sizeof(nan_channel->channel_entry));
+	nan_channel->needed_rx_chains = cfg_nan_channel->rx_nss;
+}
+
+static void
+ieee80211_nan_update_channel(struct ieee80211_local *local,
+			     struct ieee80211_nan_channel *nan_channel,
+			     struct cfg80211_nan_channel *cfg_nan_channel,
+			     bool deferred)
+{
+	struct ieee80211_chanctx_conf *conf;
+	bool reducing_nss;
+
+	if (WARN_ON(!cfg80211_chandef_identical(&nan_channel->chanreq.oper,
+						&cfg_nan_channel->chandef)))
+		return;
+
+	if (WARN_ON(memcmp(nan_channel->channel_entry,
+			   cfg_nan_channel->channel_entry,
+			   sizeof(nan_channel->channel_entry))))
+		return;
+
+	if (nan_channel->needed_rx_chains == cfg_nan_channel->rx_nss)
+		return;
+
+	reducing_nss = nan_channel->needed_rx_chains > cfg_nan_channel->rx_nss;
+	nan_channel->needed_rx_chains = cfg_nan_channel->rx_nss;
+
+	conf = nan_channel->chanctx_conf;
+
+	/*
+	 * If we are adding NSSs, we need to be ready before notifying the peer,
+	 * if we are reducing NSSs, we need to wait until the peer is notified.
+	 */
+	if (!conf || (deferred && reducing_nss))
+		return;
+
+	ieee80211_recalc_smps_chanctx(local, container_of(conf,
+							  struct ieee80211_chanctx,
+							  conf));
+}
+
+static int
+ieee80211_nan_use_chanctx(struct ieee80211_sub_if_data *sdata,
+			  struct ieee80211_nan_channel *nan_channel,
+			  bool assign_on_failure)
+{
+	struct ieee80211_chanctx *ctx;
+	bool reused_ctx;
+
+	if (!nan_channel->chanreq.oper.chan)
+		return -EINVAL;
+
+	if (ieee80211_check_combinations(sdata, &nan_channel->chanreq.oper,
+					 IEEE80211_CHANCTX_SHARED, 0, -1))
+		return -EBUSY;
+
+	ctx = ieee80211_find_or_create_chanctx(sdata, &nan_channel->chanreq,
+					       IEEE80211_CHANCTX_SHARED,
+					       assign_on_failure,
+					       &reused_ctx);
+	if (IS_ERR(ctx))
+		return PTR_ERR(ctx);
+
+	nan_channel->chanctx_conf = &ctx->conf;
+
+	/*
+	 * In case an existing channel context is being used, we marked it as
+	 * will_be_used, now that it is assigned - clear this indication
+	 */
+	if (reused_ctx) {
+		WARN_ON(!ctx->will_be_used);
+		ctx->will_be_used = false;
+	}
+	ieee80211_recalc_chanctx_min_def(sdata->local, ctx);
+	ieee80211_recalc_smps_chanctx(sdata->local, ctx);
+
+	return 0;
+}
+
+static void
+ieee80211_nan_remove_channel(struct ieee80211_sub_if_data *sdata,
+			     struct ieee80211_nan_channel *nan_channel)
+{
+	struct ieee80211_chanctx_conf *conf;
+	struct ieee80211_chanctx *ctx;
+	struct ieee80211_nan_sched_cfg *sched_cfg = &sdata->vif.cfg.nan_sched;
+
+	if (WARN_ON(!nan_channel))
+		return;
+
+	lockdep_assert_wiphy(sdata->local->hw.wiphy);
+
+	if (!nan_channel->chanreq.oper.chan)
+		return;
+
+	for (int slot = 0; slot < ARRAY_SIZE(sched_cfg->schedule); slot++)
+		if (sched_cfg->schedule[slot] == nan_channel)
+			sched_cfg->schedule[slot] = NULL;
+
+	conf = nan_channel->chanctx_conf;
+
+	memset(nan_channel, 0, sizeof(*nan_channel));
+
+	/* Update the driver before (possibly) releasing the channel context */
+	drv_vif_cfg_changed(sdata->local, sdata, BSS_CHANGED_NAN_LOCAL_SCHED);
+
+	/* Channel might not have a chanctx if it was ULWed */
+	if (!conf)
+		return;
+
+	ctx = container_of(conf, struct ieee80211_chanctx, conf);
+
+	if (ieee80211_chanctx_num_assigned(sdata->local, ctx) > 0) {
+		ieee80211_recalc_chanctx_chantype(sdata->local, ctx);
+		ieee80211_recalc_smps_chanctx(sdata->local, ctx);
+		ieee80211_recalc_chanctx_min_def(sdata->local, ctx);
+	}
+
+	if (ieee80211_chanctx_refcount(sdata->local, ctx) == 0)
+		ieee80211_free_chanctx(sdata->local, ctx, false);
+}
+
+static struct ieee80211_nan_channel *
+ieee80211_nan_find_free_channel(struct ieee80211_nan_sched_cfg *sched_cfg)
+{
+	for (int i = 0; i < ARRAY_SIZE(sched_cfg->channels); i++) {
+		if (!sched_cfg->channels[i].chanreq.oper.chan)
+			return &sched_cfg->channels[i];
+	}
+
+	return NULL;
+}
+
+int ieee80211_nan_set_local_sched(struct ieee80211_sub_if_data *sdata,
+				  struct cfg80211_nan_local_sched *sched)
+{
+	struct ieee80211_nan_channel *sched_idx_to_chan[IEEE80211_NAN_MAX_CHANNELS] = {};
+	struct ieee80211_nan_sched_cfg *sched_cfg = &sdata->vif.cfg.nan_sched;
+	struct ieee80211_nan_sched_cfg backup_sched;
+	int ret;
+
+	if (sched->n_channels > IEEE80211_NAN_MAX_CHANNELS)
+		return -EOPNOTSUPP;
+
+	if (sched->nan_avail_blob_len > IEEE80211_NAN_AVAIL_BLOB_MAX_LEN)
+		return -EINVAL;
+
+	/*
+	 * If a deferred schedule update is pending completion, new updates are
+	 * not allowed. Only allow to configure an empty schedule so NAN can be
+	 * stopped in the middle of a deferred update. This is fine because
+	 * empty schedule means the local NAN device will not be available for
+	 * peers anymore so there is no need to update peers about a new
+	 * schedule.
+	 */
+	if (WARN_ON(sched_cfg->deferred && sched->n_channels))
+		return -EBUSY;
+
+	bitmap_zero(sdata->u.nan.removed_channels, IEEE80211_NAN_MAX_CHANNELS);
+
+	memcpy(backup_sched.schedule, sched_cfg->schedule,
+	       sizeof(backup_sched.schedule));
+	memcpy(backup_sched.channels, sched_cfg->channels,
+	       sizeof(backup_sched.channels));
+	memcpy(backup_sched.avail_blob, sched_cfg->avail_blob,
+	       sizeof(backup_sched.avail_blob));
+	backup_sched.avail_blob_len = sched_cfg->avail_blob_len;
+
+	memcpy(sched_cfg->avail_blob, sched->nan_avail_blob,
+	       sched->nan_avail_blob_len);
+	sched_cfg->avail_blob_len = sched->nan_avail_blob_len;
+
+	/*
+	 * Remove channels that are no longer in the new schedule to free up
+	 * resources before adding new channels. For deferred schedule, channels
+	 * will be removed when the schedule is applied.
+	 * Create a mapping from sched index to sched_cfg channel
+	 */
+	for (int i = 0; i < ARRAY_SIZE(sched_cfg->channels); i++) {
+		bool still_needed = false;
+
+		if (!sched_cfg->channels[i].chanreq.oper.chan)
+			continue;
+
+		for (int j = 0; j < sched->n_channels; j++) {
+			if (cfg80211_chandef_identical(&sched_cfg->channels[i].chanreq.oper,
+						       &sched->nan_channels[j].chandef)) {
+				sched_idx_to_chan[j] =
+					&sched_cfg->channels[i];
+				still_needed = true;
+				break;
+			}
+		}
+
+		if (!still_needed) {
+			__set_bit(i, sdata->u.nan.removed_channels);
+			if (!sched->deferred)
+				ieee80211_nan_remove_channel(sdata,
+							     &sched_cfg->channels[i]);
+		}
+	}
+
+	for (int i = 0; i < sched->n_channels; i++) {
+		struct ieee80211_nan_channel *chan = sched_idx_to_chan[i];
+
+		if (chan) {
+			ieee80211_nan_update_channel(sdata->local, chan,
+						     &sched->nan_channels[i],
+						     sched->deferred);
+		} else {
+			chan = ieee80211_nan_find_free_channel(sched_cfg);
+			if (WARN_ON(!chan)) {
+				ret = -EINVAL;
+				goto err;
+			}
+
+			sched_idx_to_chan[i] = chan;
+			ieee80211_nan_init_channel(chan,
+						   &sched->nan_channels[i]);
+
+			ret = ieee80211_nan_use_chanctx(sdata, chan, false);
+			if (ret) {
+				memset(chan, 0, sizeof(*chan));
+				goto err;
+			}
+		}
+	}
+
+	for (int s = 0; s < ARRAY_SIZE(sched_cfg->schedule); s++) {
+		if (sched->schedule[s] < ARRAY_SIZE(sched_idx_to_chan))
+			sched_cfg->schedule[s] =
+				sched_idx_to_chan[sched->schedule[s]];
+		else
+			sched_cfg->schedule[s] = NULL;
+	}
+
+	sched_cfg->deferred = sched->deferred;
+
+	drv_vif_cfg_changed(sdata->local, sdata, BSS_CHANGED_NAN_LOCAL_SCHED);
+
+	/*
+	 * For deferred update, don't update NDI carriers yet as the new
+	 * schedule is not yet applied so common slots don't change. The NDI
+	 * carrier will be updated once the driver notifies the new schedule is
+	 * applied.
+	 */
+	if (sched_cfg->deferred)
+		return 0;
+
+	bitmap_zero(sdata->u.nan.removed_channels, IEEE80211_NAN_MAX_CHANNELS);
+
+	return 0;
+err:
+	/* Remove newly added channels */
+	for (int i = 0; i < ARRAY_SIZE(sched_cfg->channels); i++) {
+		struct cfg80211_chan_def *chan_def =
+			&sched_cfg->channels[i].chanreq.oper;
+
+		if (!chan_def->chan)
+			continue;
+
+		if (!cfg80211_chandef_identical(&backup_sched.channels[i].chanreq.oper,
+						chan_def))
+			ieee80211_nan_remove_channel(sdata,
+						     &sched_cfg->channels[i]);
+	}
+
+	/* Re-add all backed up channels */
+	for (int i = 0; i < ARRAY_SIZE(backup_sched.channels); i++) {
+		struct ieee80211_nan_channel *chan = &sched_cfg->channels[i];
+
+		*chan = backup_sched.channels[i];
+
+		/*
+		 * For deferred update, no channels were removed and the channel
+		 * context didn't change, so nothing else to do.
+		 */
+		if (!chan->chanctx_conf || sched->deferred)
+			continue;
+
+		if (test_bit(i, sdata->u.nan.removed_channels)) {
+			/* Clear the stale chanctx pointer */
+			chan->chanctx_conf = NULL;
+			/*
+			 * We removed the newly added channels so we don't lack
+			 * resources. So the only reason that this would fail
+			 * is a FW error which we ignore. Therefore, this
+			 * should never fail.
+			 */
+			WARN_ON(ieee80211_nan_use_chanctx(sdata, chan, true));
+		} else {
+			struct ieee80211_chanctx_conf *conf = chan->chanctx_conf;
+
+			/* FIXME: detect no-op? */
+			/* Channel was not removed but may have been updated */
+			ieee80211_recalc_smps_chanctx(sdata->local,
+						     container_of(conf,
+								  struct ieee80211_chanctx,
+								  conf));
+		}
+	}
+
+	memcpy(sched_cfg->schedule, backup_sched.schedule,
+	       sizeof(backup_sched.schedule));
+	memcpy(sched_cfg->avail_blob, backup_sched.avail_blob,
+	       sizeof(backup_sched.avail_blob));
+	sched_cfg->avail_blob_len = backup_sched.avail_blob_len;
+	sched_cfg->deferred = false;
+	bitmap_zero(sdata->u.nan.removed_channels, IEEE80211_NAN_MAX_CHANNELS);
+
+	drv_vif_cfg_changed(sdata->local, sdata, BSS_CHANGED_NAN_LOCAL_SCHED);
+	return ret;
+}
+
+void ieee80211_nan_sched_update_done(struct ieee80211_vif *vif)
+{
+	struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
+	struct ieee80211_nan_sched_cfg *sched_cfg = &vif->cfg.nan_sched;
+	unsigned int i;
+
+	lockdep_assert_wiphy(sdata->local->hw.wiphy);
+
+	if (WARN_ON(!sched_cfg->deferred))
+		return;
+
+	/*
+	 * Clear the deferred flag before removing channels. Removing channels
+	 * will trigger another schedule update to the driver, and there is no
+	 * need for this update to be deferred since removed channels are not
+	 * part of the schedule anymore, so no need to notify peers about
+	 * removing them.
+	 */
+	sched_cfg->deferred = false;
+
+	for (i = 0; i < ARRAY_SIZE(sched_cfg->channels); i++) {
+		struct ieee80211_nan_channel *chan = &sched_cfg->channels[i];
+		struct ieee80211_chanctx_conf *conf = chan->chanctx_conf;
+
+		if (!chan->chanreq.oper.chan)
+			continue;
+
+		if (test_bit(i, sdata->u.nan.removed_channels))
+			ieee80211_nan_remove_channel(sdata, chan);
+		else if (conf)
+			/*
+			 * We might have called this already for some channels,
+			 * but this knows to handle a no-op.
+			 */
+			ieee80211_recalc_smps_chanctx(sdata->local,
+						      container_of(conf,
+								   struct ieee80211_chanctx,
+								   conf));
+	}
+
+	bitmap_zero(sdata->u.nan.removed_channels, IEEE80211_NAN_MAX_CHANNELS);
+	cfg80211_nan_sched_update_done(ieee80211_vif_to_wdev(vif), true,
+				       GFP_KERNEL);
+}
+EXPORT_SYMBOL(ieee80211_nan_sched_update_done);
diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index 36795529ff82..19ac778b704d 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -1744,20 +1744,12 @@ static void ieee80211_reconfig_stations(struct ieee80211_sub_if_data *sdata)
 	}
 }
 
-static int ieee80211_reconfig_nan(struct ieee80211_sub_if_data *sdata)
+static int
+ieee80211_reconfig_nan_offload_de(struct ieee80211_sub_if_data *sdata)
 {
 	struct cfg80211_nan_func *func, **funcs;
 	int res, id, i = 0;
 
-	res = drv_start_nan(sdata->local, sdata,
-			    &sdata->u.nan.conf);
-	if (WARN_ON(res))
-		return res;
-
-	if (sdata->local->hw.wiphy->nan_capa.flags &
-	    WIPHY_NAN_FLAGS_USERSPACE_DE)
-		return 0;
-
 	funcs = kzalloc_objs(*funcs, sdata->local->hw.max_nan_de_entries + 1);
 	if (!funcs)
 		return -ENOMEM;
@@ -1783,6 +1775,22 @@ static int ieee80211_reconfig_nan(struct ieee80211_sub_if_data *sdata)
 	}
 
 	kfree(funcs);
+	return res;
+}
+
+static int ieee80211_reconfig_nan(struct ieee80211_sub_if_data *sdata)
+{
+	int res;
+
+	res = drv_start_nan(sdata->local, sdata,
+			    &sdata->u.nan.conf);
+	if (WARN_ON(res))
+		return res;
+
+	if (!(sdata->local->hw.wiphy->nan_capa.flags & WIPHY_NAN_FLAGS_USERSPACE_DE))
+		return ieee80211_reconfig_nan_offload_de(sdata);
+
+	drv_vif_cfg_changed(sdata->local, sdata, BSS_CHANGED_NAN_LOCAL_SCHED);
 
 	return 0;
 }
-- 
2.34.1


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox